文件的上传&预览&下载学习(一)
注:主要是说明后端逻辑和数据库表设计
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查询,获取输入流,然后下载)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了