文件的上传&预览&下载学习(一)

注:主要是说明后端逻辑和数据库表设计

1.当前主流的几种文件上传&预览&下载方式

  • 把文件直接存储在服务器
  • 分布式存储OSS,比如阿里OSS、Minio

2.数据库表设计

  1. 由于文件都是跟业务关联的,比如评论里面掺杂评论图片,常规的设计就是在'评论表'添加上传'图片名称'字段和'图片相对路径',在上传成功后返回给前端
    1.1 如果是加入多个文件的话,这种设计就不太合适:10个文件在评论表就需要加20个字段
    1.2 针对'1.1'进行改进,建一种'文件信息表',设计字段'外键 fk_id'用来存储评论id(如果后续有商品图片也可以用这个存),这时候有需要考虑一个问题,id是根据什么生成的,是否唯一,如果是雪花ID就无需考虑这个问题,但如果是递增那极可能重复(商品id和评论id同时为1的情况);这时就还需要在'文件信息表'添加一个'外键类型 fk_type'作区分,比如评论就是comment,商品就是goods。然后把'图片名称'字段和'图片相对路径'放到'文件信息表'来(预览路径和下载路径可能不同,下载路径是绝对路径,预览路径为了防止用户直接访问服务器其他位置,做一层虚拟映射nginx)

1.3 可能还需要加一个'文件下载名称',由于文件可能是'数字+字母(可能是md5)'的形式(如果是手机拍摄),然后要按照一定的规则比如'订单编号-产品名称-用户昵称'生成

3.后端实现

1.直接使用Java下载到指定目录(现根据外键查询出绝对路径,在下载)

@Override
public void downloadByOrderId(HttpServletResponse response, Long orderId, String fkType) throws IOException {
	String fileLocation = null;
	try{
		// 一个订单只有一个报告pdf
		QueryWrapper<FileUpload> fileUploadQueryWrapper = new QueryWrapper<>();
		fileUploadQueryWrapper.eq("is_deleted","0");
		fileUploadQueryWrapper.eq("fk_id",orderId);
		fileUploadQueryWrapper.eq("fk_type",fkType);
		FileUpload uploadEntity = uploadMapper.selectOne(fileUploadQueryWrapper);

		response.setHeader("content-type", "application/octet-stream");
		response.setContentType("application/octet-stream");        //这边可以设置文件下载时的名字,我这边用的是文件原本的名字,可以根据实际场景设置
		response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(uploadEntity.getOldName(), "UTF-8"));

		if(StrUtil.isNotBlank(uploadEntity.getLocation())){
			fileLocation = uploadEntity.getLocation();
		}
		logger.info("fileLocation: "+fileLocation);

		FileUtils.writeBytes(uploadEntity.getLocation(), response.getOutputStream());
	}catch (Exception e){
		logger.error("download error orderId: {} fkType: {} && fileLocation: {} msg: {}",orderId,fkType,fileLocation,e.getMessage());
	}
}

public static void writeBytes(String filePath, OutputStream os) {
	FileInputStream fi = null;
	try {
		File file = new File(filePath);
		if (!file.exists()) {
			throw new FileNotFoundException(filePath);
		}
		fi = new FileInputStream(file);
		//TODO 扩大缓冲,多线程(请求的节点过多超时)
		byte[] b = new byte[8192];
		int length;
		while ((length = fi.read(b,0,8192)) > 0) {
			os.write(b, 0, length);
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		if(os != null) {
			try {
				os.close();
			}catch (IOException e) {
				e.printStackTrace();
			}
		}
		if(fi != null) {
			try {
				fi.close();
			}catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

2.分布式存储Minio,参考博客:https://www.freesion.com/article/5069848586/ (下载和上面的方式相同 根据文件名先去Minio查询,获取输入流,然后下载)

posted @ 2021-11-26 09:53  LHX2018  阅读(244)  评论(0编辑  收藏  举报