文件的上传&预览&下载学习(一)
注:主要是说明后端逻辑和数据库表设计
1.当前主流的几种文件上传&预览&下载方式
- 把文件直接存储在服务器
- 分布式存储OSS,比如阿里OSS、Minio
2.数据库表设计
- 由于文件都是跟业务关联的,比如评论里面掺杂评论图片,常规的设计就是在'评论表'添加上传'图片名称'字段和'图片相对路径',在上传成功后返回给前端
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查询,获取输入流,然后下载)