文件 - 04 下载文件: 根据文件下载链接下载文件

article/2025/8/24 8:21:32

文章目录

        • 1. 根据下载链接下载文件
        • 2. 根据下载链接下载文件
          • 1. 根据token获取存放在redis中的fileId
          • 2. 根据fileId获得文件信息
          • 3. 确定用户拥有操作这个fileId的权限
          • 4. 获取文件在服务器的存储路径
          • 5. 如果用户指定了下载时的文件名则处理文件名
          • 6. 验证文件路径是否存在
          • 7. 下载文件
          • 8. 更新文件操作时间

系列文章:

文件 - 01 上传附件到服务器并保存信息到MySQL
文件 - 02 上传临时文件到服务器
文件 - 03 根据文件id获取下载链接

整体思路:
在这里插入图片描述

附件表:

CREATE TABLE `t_upload_file` (`id` int(11) NOT NULL AUTO_INCREMENT,`fileId` varchar(50) NOT NULL COMMENT '文件id',`fileName` varchar(50) NOT NULL COMMENT '文件名',`fileType` varchar(100) NOT NULL COMMENT '文件类型',`fileSize` int(10) NOT NULL COMMENT '文件大小bytes',`filePathSuffix` varchar(60) NOT NULL COMMENT '文件存储路径',`fileUploader` varchar(32) DEFAULT NULL,`uploadTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '文件上传时间',`lastUsedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '文件最近使用时间',`neverExpire` tinyint(1) NOT NULL DEFAULT '1' COMMENT '文件是否永不过期,当为1时,则忽略expireTime',`expireTime` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '文件过期时间',PRIMARY KEY (`id`),UNIQUE KEY `fileId` (`fileId`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

1. 根据下载链接下载文件

@RestController
@ResponseBody
@ResponseResult
@Slf4j
@RequestMapping("/ngsoc/PORTAL/api/v1")
@Api(tags = "File")
public class FileController {@ApiOperation(value = "根据下载链接下载文件", notes = "根据输入的token查询redis,如果存在/尚未过期,则下载文件", httpMethod = "GET")@RequestMapping(value = "/files/fetch/{token}", method = RequestMethod.GET)public ResponseEntity<StreamingResponseBody> downloadFileByToken(@ApiParam(value = "下载链接token", required = true) @PathVariable("token") String downloadToken,@ApiParam(value = "用户输入的文件名", required = false) @RequestParam(value = "inputFileName", required = false, defaultValue = "") String inputFileName) throws UploadDownloadFileException {return fileService.downloadFileByToken(downloadToken, inputFileName);}
}

2. 根据下载链接下载文件

public interface IFileService {/*** 根据下载链接下载文件** @param downloadToken 下载链接token* @param inputFileName 用户传入的文件名* @return* @throws UploadDownloadFileException*/ResponseEntity<StreamingResponseBody> downloadFileByToken(String downloadToken, String inputFileName) throws UploadDownloadFileException;
}
@Controller
@Slf4j
public class FileServiceImpl implements IFileService {@Autowiredprivate IFileMapper fileDao;@Autowiredprivate FileConfig fileConfig;@Autowiredprivate RedisTemplate<String, String> stringRedisTemplate;@Setter(onMethod_ = @Autowired)private FileIdAuthorityServiceImpl fileIdAuthorityServiceImpl;/*** 根据token值 下载文件** @param downloadToken 返回的下载链接中的token* @return 下载文件*/@Overridepublic ResponseEntity<StreamingResponseBody> downloadFileByToken(String downloadToken, String inputFileName) throws UploadDownloadFileException {// 根据token获取存放在redis中的fileIdString fileId = this.stringRedisTemplate.opsForValue().get(downloadToken);if (StringUtils.isEmpty(fileId)) {log.error("Download link expired.");throw new UploadDownloadFileException(I18nUtils.i18n("exception.file.redis.expire"));}return downloadFileById(fileId, inputFileName);}/*** 下載文件* @param fileId        文件id* @param inputFileName 用户传入的文件名* @return* @throws UploadDownloadFileException*/@Overridepublic ResponseEntity<StreamingResponseBody> downloadFileById(String fileId, String inputFileName)throws UploadDownloadFileException {// 根据fileId --> path , name --> 更新文件最近使用时间 --> returnFileInfo fileInfo = fileDao.getFileNameTypePathByFileId(fileId);fileIdAuthorityServiceImpl.assertThisUserHaveAuthority(fileInfo);if (ObjectUtils.isEmpty(fileInfo)) {throw new UploadDownloadFileException(I18nUtils.i18n("exception.file.mysql.absent"));}String storePath = getStorePath(fileInfo);// 如果用户指定了下载时的文件名String fileName;if (StringUtils.isEmpty(inputFileName)) {fileName = fileInfo.getFileName();} else {fileName = inputFileName.concat(getDotSuffix(fileInfo.getFileName()));}// 下载时,验证文件路径是否存在if (!FileUtil.exist(storePath)) {log.error("File not exist.");throw new UploadDownloadFileException(I18nUtils.i18n("exception.file.download"));}// 下载的时候 直接下载,不用验证类型 调用downloadFile即可ResponseEntity<StreamingResponseBody> responseBodyResponseEntity = downloadFile(storePath, fileName);// 更新 mysql中文件的最后过期时间fileDao.updateLastUsedTime(fileId, new Timestamp(System.currentTimeMillis()));return responseBodyResponseEntity;}}
1. 根据token获取存放在redis中的fileId
String fileId = this.stringRedisTemplate.opsForValue().get(downloadToken);
2. 根据fileId获得文件信息

在这里插入图片描述

@Mapper
@Repository
public interface IFileMapper {/*** 根据id获得文件名, 在文件下载时需要文件的初始名展示给用户** @param fileId 文件id* @return*/FileInfo getFileNameTypePathByFileId(@Param("fileId") String fileId);
}
<select id="getFileNameTypePathByFileId" parameterType="String"resultType="portal.model.entity.FileInfo">select fileName, fileType, filePathSuffix from t_upload_filewhere fileId = #{fileId} and <include refid="unexpiredFilter"/>
</select><!-- 大于号 &gt;-->
<!-- 永不过期 neverExpire = 1,或者未过期的,即过期时间 >= 当前时间 -->
<sql id="unexpiredFilter">(neverExpire = 1 or expireTime &gt;= current_timestamp())
</sql>
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = "文件信息", description = "文件基本信息表")
public class FileInfo {@ApiModelProperty(value = "文件Id", required = true, example = "b70c68b5833f41b6a8f506698541b7141604318375398")private String fileId;@ApiModelProperty(value = "文件名", required = true, example = "xxx.png")private String fileName;@ApiModelProperty(value = "文件类型", required = true, example = "image/png")private String fileType;@ApiModelProperty(value = "文件大小bytes", required = true, example = "1211")private Long fileSize;@ApiModelProperty(value = "文件存储路径后缀", required = true, example = "as\\da\\6d3a8c3629fb48cea1c22c5e74b4801e")private String filePathSuffix;@ApiModelProperty(value = "文件上传者", required = true, example = "1211")private String fileUploader;@ApiModelProperty(value = "文件是否永不过期", required = true, example = "1")private Integer neverExpire;@ApiModelProperty(value = "文件过期时间", example = "2020-11-03 17:20:00")private Timestamp expireTime;@ApiModelProperty(value = "文件信息上传时间", required = true, example = "2020-11-03 17:12:58")private Timestamp uploadTime;@ApiModelProperty(value = "文件信息更新时间", required = true, example = "2020-11-03 17:13:14")private Timestamp lastUsedTime;/*** 是否所有人都可以获取&操作该文件** 如果为null,则视为与true等同处理*/@ApiModelProperty(value = "是否所有人都可以获取&操作该文件", required = true, example = "1")private Integer share;
}
3. 确定用户拥有操作这个fileId的权限
public interface FileIdAuthorityService {/*** 确定用户拥有操作这个fileId的权限* <p>* 否则报错** @param fileInfo fileInfo* @return FileInfo* @throws FileIdAuthorityException 当用户无权限操作该文件时*/@NullableFileInfo assertThisUserHaveAuthority(@Nullable FileInfo fileInfo);
}
@Slf4j
@Service
public class FileIdAuthorityServiceImpl implements FileIdAuthorityService {@Setter(onMethod_ = @Autowired)private IFileMapper fileDao;@Nullable@Overridepublic FileInfo assertThisUserHaveAuthority(@Nullable FileInfo fileInfo) {if (fileInfo == null) {return null;}String userName = SpringSecurityUtil.getCurrentUserNameFromSpringSecurity();return this.assertThisUserHaveAuthority(fileInfo, userName);}@Nullable@Overridepublic FileInfo assertThisUserHaveAuthority(@Nullable FileInfo fileInfo, @Nullable String userName) {log.debug("assertThisUserHaveAuthority : fileInfo : {} , userName : {}", fileInfo, userName);if (fileInfo == null) {return null;}if (fileInfo.getShare() == null || fileInfo.getShare() == FileInfoShareEnum.SHARE_TO_ALL.getValue()) {return fileInfo;}if (StringUtils.equalsAny(userName,"transfer",fileInfo.getFileUploader())) {// 按照当前云soc权限设计,凡是API调用都是transfer账户return fileInfo;}throw new FileIdAuthorityException(fileInfo.getFileId(),userName);}
}

从SpringSecurity获取当前用户名称

/*** 用于从SpringSecurity中获取当前用户的用户名*/
public class SpringSecurityUtil {/*** 静态工具类,禁止创建实例*/private SpringSecurityUtil() {}/*** 从SpringSecurity获取当前用户名称** @return 当前用户名称*/@Nullablepublic static String getCurrentUserNameFromSpringSecurity() {return Optional.ofNullable(UserInfoShareHolder.getUserInfo()).map(UserInfo::getName).orElse(null);}
}
/*** 用户信息共享的ThreadLocal类*/
public class UserInfoShareHolder {private static final ThreadLocal<UserInfo> USER_INFO_THREAD_LOCAL = new TransmittableThreadLocal<>();/*** 存储用户信息** @param userInfo 用户信息* @return*/public static void setUserInfo(UserInfo userInfo) {USER_INFO_THREAD_LOCAL.set(userInfo);}/*** 获取用户相关信息** @return*/public static UserInfo getUserInfo() {return USER_INFO_THREAD_LOCAL.get();}/*** 清除ThreadLocal信息*/public static void remove() {USER_INFO_THREAD_LOCAL.remove();}
}
4. 获取文件在服务器的存储路径
String storePath = getStorePath(fileInfo);
// /opt/ngsoc/data/local/upload/12/29/6dd633cdfba7489f8ecc1054366ce274
private String getStorePath(FileInfo fileInfo) {return Paths.get(fileConfig.getSavePath(), fileInfo.getFilePathSuffix()).toString();
}
5. 如果用户指定了下载时的文件名则处理文件名
String fileName;
if (StringUtils.isEmpty(inputFileName)) {fileName = fileInfo.getFileName();
} else {fileName = inputFileName.concat(getDotSuffix(fileInfo.getFileName()));
}
private String getDotSuffix(String fileName) {return fileName.substring(fileName.lastIndexOf("."));
}
6. 验证文件路径是否存在
// 下载时,验证文件路径是否存在
if (!FileUtil.exist(storePath)) {log.error("File not exist.");throw new UploadDownloadFileException(I18nUtils.i18n("exception.file.download"));
}
7. 下载文件
ResponseEntity<StreamingResponseBody> responseBodyResponseEntity = downloadFile(storePath, fileName);
@Override
public ResponseEntity<StreamingResponseBody> downloadFile(String filePath, String fileName) {HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);headers.setContentDisposition(ContentDisposition.parse("attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8)));StreamingResponseBody streamingResponseBody = outputStream -> {Files.copy(Paths.get(filePath), outputStream);};return new ResponseEntity<>(streamingResponseBody, headers, HttpStatus.OK);
}
8. 更新文件操作时间
fileDao.updateLastUsedTime(fileId, new Timestamp(System.currentTimeMillis()));
@Mapper
@Repository
public interface IFileMapper {/*** 文件被下载时 修改文件的最后使用时间** @param lastUsedTime 当前时间* @param fileId       文件id*/void updateLastUsedTime(@Param("fileId") String fileId, @Param("lastUsedTime") Timestamp lastUsedTime);
}
<update id="updateLastUsedTime">update t_upload_file set lastUsedTime = #{lastUsedTime}where fileId = #{fileId} and <include refid="unexpiredFilter"/>
</update><!-- 大于号 &gt;-->
<!-- 永不过期 neverExpire = 1,或者未过期的,即过期时间 >= 当前时间 -->
<sql id="unexpiredFilter">(neverExpire = 1 or expireTime &gt;= current_timestamp())
</sql>

http://chatgpt.dhexx.cn/article/Ie7EwJ2v.shtml

相关文章

文件上传、下载

在Web应用系统开发中&#xff0c;文件上传和下载功能是非常常用的功能&#xff0c;今天来讲一下JavaWeb中的文件上传和下载功能的实现。 对于文件上传&#xff0c;浏览器在上传的过程中是将文件以流的形式提交到服务器端的&#xff0c;如果直接使用Servlet获取上传文件的输入流…

文件下载相关

下载文件的时候&#xff0c;返回报文里缺失响应码&#xff0c;原因&#xff1a;报文头在报文体前面&#xff0c;报文体一旦确定&#xff0c;响应头信息就不能被修改或添加了&#xff0c;但是如果文件流字节数小于等于8192*2的话&#xff0c;文件流内容会被默认加载在缓存中&…

Linux 如何从网上下载文件

镜像下载、域名解析、时间同步请点击 阿里云开源镜像站 将网络上的文件下载到使用 Linux 操作系统的计算机上&#xff0c;需要用到 wget 指令&#xff0c;使用该指令可能会面临两个问题。 首先&#xff0c;如何获取文件的下载 url&#xff1f;这需要你在浏览器上找到要下载文…

github怎么下载文件

在浏览器上打开github主站&#xff0c;点击搜索框。输入关键字搜索自己需要的代码文件&#xff0c;搜索到后点击打开文件库的主分支。 点击右边的code按钮。点击下拉菜单的下载按钮就可以下载文件了。

前端下载文件的几种方式

一、a标签download下载 后端返回一个可下载的url文件&#xff0c;或者前端本地保存的文件&#xff0c;通过路径引入下载。 &#xff08;1&#xff09;将资源放入前端本地保存&#xff0c;打包后随一起上传自服务器 // 本地资源下载&#xff0c;引入路径即可,这里的路径指的是…

这里有11种方法,供你用Python下载文件

今天我们一起学习如何使用不同的Python模块从web下载文件。此外&#xff0c;你将下载常规文件、web页面、Amazon S3和其他资源。 最后&#xff0c;你将学习如何克服可能遇到的各种挑战&#xff0c;例如下载重定向的文件、下载大型文件、完成一个多线程下载以及其他策略。 1、使…

15种下载文件的方法

转载&#xff1a;https://blog.netspi.com/15-ways-to-download-a-file/ 瑞恩甘德鲁德 原创时间&#xff1a;2014年6月16日 Pentesters经常将文件上传到受感染的盒子以帮助进行权限提升&#xff0c;或者保持在计算机上的存在。本博客将介绍将文件从您的计算机移动到受感染系统的…

【基础知识】---概率密度函数和似然函数的区别

这个是quora上的一个回答 What is the difference between probability and likelihood? 在评论中这位老师将概率密度函数和似然函数之间的关系&#xff0c;类比成 2b 和 a2 之间的关系。详细翻译如下&#xff1a; 2我们可以做一个类比&#xff0c;假设一个函数为 ab&#x…

概率论考点之多维随机变量及密度函数

如题&#xff1a;2019年10月 分析&#xff1a;概率论是最初要考的一个科目&#xff0c;看了好几遍了吧&#xff0c;总还是没印象。可见别人讲得再天花乱坠&#xff0c;自己不懂&#xff0c;一点用都没有&#xff0c;白白浪费时间。知识&#xff0c;要靠自己去掌握。 还是借此顺…

最大最小次序统计量密度函数的推导

今天在推导最小统计量时出现了一些错误。及时分享出来&#xff0c;和朋友们一起反思进步。 我的错误是&#xff1a;分布函数的定义搞错了。我一心想着让所有样本都大于x&#xff08;1&#xff09;所以在原本是小于等于的位置写成了大于&#xff0c;推导最后多出一个负号。 反思…

矩阵分析与应用-06-概率密度函数01

概率密度函数 实随机向量的概率密度函数 一个含有m 个随机变量的实值向量 称为mx1实随机向量&#xff0c;或简称随机向量(当维数无关紧要时)。其中&#xff0c;表示样本点&#xff0c;可以是时间&#xff0c;频率&#xff0c;或者位置等。 一个随机向量由它的联合累积分布函…

概论_第3章_二维随机变量_已知概念密度函数求分布函数

首先看定义 一 定义 设二维随机变量(X, Y)的分布函数为F(x, y), 若存在非负可积函数f(x, y), 使得对任意的实数 x, y, 有 ,则称(X, Y)为二维连续型随机变量&#xff0c; 称f(x, y)为(X, Y)的概率密度或X与Y的联合密度函数。 二 例题 题 1 设二维随机变量(X, Y)的概率密度为 , 求…

概论_第3章_已知联合密度求边缘密度

一 例题 设二维随机变量(X, Y)的概率密度为 &#xff0c; 求边缘概率密度。 解&#xff1a; 先画图 所以, X的边缘密度为 Y的边缘密度为 总结: 求X的边缘密度 是对y求积分&#xff0c; 会带上dy, 因此要确定y的上下限&#xff0c; 注意确定上下限 时作一个垂直于x轴的箭头线…

联合密度分布

最近参与翻译的一本书&#xff0c;以下是我翻译的其中一章&#xff0c;其余可以阅读 https://github.com/apachecn/prob140-textbook-zh 英文原文&#xff1a;https://nbviewer.jupyter.org/github/prob140/textbook/tree/gh-pages/notebooks/ 17. 联合密度 我们现在开始研…

连续型随机变量+分布函数+密度函数+联合分布函数

1.先验知识 连续型随机变量&#xff1a; 随机变量 X 取值无法全部列举。 数学定义&#xff1a; 随机变量 X&#xff0c;存在一个非负可积函数 f(t) ,使得对任意 x ,有 则称 X 为连续型随机变量。其中 f(t) 为 X 的概率密度函数。 概率分布函数&#xff08;简称&#xff1a;分布…

联合密度函数求期望_联合密度函数的数学期望怎么求

展开全部 数学期望是试验中每次可能结果的概率636f707962616964757a686964616f31333433623039乘以其结果的总和。 计算公式&#xff1a; 1、离散型&#xff1a; 离散型随机变量X的取值为X1、X2、X3……Xn&#xff0c;p(X1)、p(X2)、p(X3)……p(Xn)、为X对应取值的概率&#xff…

概率论

概率论 概率的性质 减法公式 A-BAB的逆 应用 画图更简单 abc至少有一个发生表示 P(A并B并C) P&#xff08;ABC&#xff09;《P&#xff08;AB&#xff09;0 条件概率的性质 应用 古典概型 应用: 不放回抽样 全概率公式和贝叶斯公式 全概率公式的使用条件:A事件可以被多个事…

20、PPP协议

前面我们分了五节的内容&#xff0c;用了很大的篇幅介绍了局域网的数据链路层的知识&#xff0c;在这其中主要是以太网的相关内容。本节开始我们学习广域网的数据链路层协议&#xff0c;主要学习两种&#xff0c;PPP和HDLC。 PPP协议的特点 PPP全称“Point-to-Point Protocol”…

PPP协议和HDLC协议

广域网 广域网&#xff08;WAN&#xff0c;Wide Area Network&#xff09;&#xff0c;通常跨接很大的物理范围&#xff0c;所覆盖的范围从几十公里到几千公里&#xff0c;它能连接多个城市或国家&#xff0c;或跨越几个洲并能提供远距离通信&#xff0c;形成国际性的远程网络…

PPP协议的简单了解

一.PPP协议的概述&#xff1a;、 PPP协议&#xff0c;即点对点协议&#xff08;应用在直接相连的两个结点的链路上&#xff09;&#xff0c; 全称&#xff1a;Point-to-Point Protocol PPP协议使用的是串行线路通信&#xff0c;面向字节。 设计目的&#xff1a;建立建立点对点…