统一消息平台
小李:嘿,老王,最近在做高校统一信息门户的项目,遇到了一个关于“下载”功能的问题,你能帮我看看吗?
老王:当然可以,你说说具体情况是什么?
小李:我们这个系统是给高校师生提供一站式服务的,比如课程资料、通知公告、成绩查询等等。现在有一个需求,就是用户需要从系统里下载一些文件,比如课件、PDF文档之类的。但之前一直用的是简单的链接跳转,现在想换成更安全、可控的方式。
老王:明白了,这其实是一个典型的文件下载功能实现问题。你们现在的做法是不是直接用标签来生成下载链接?这样确实不太安全,容易被别人恶意利用。
小李:对,就是这样。而且有时候用户会直接复制链接去分享,造成权限泄露。
老王:那你们有没有考虑过使用服务器端控制下载权限?比如用户登录后,通过后端API获取文件流,然后返回给前端进行下载。
小李:听起来不错,但具体怎么实现呢?能给我举个例子吗?
老王:当然可以。我们可以用Spring Boot作为后端框架,前端用Vue.js或者React。下面我给你写一段代码示例。
小李:太好了,我正需要这样的例子。
老王:首先,后端部分。我们需要创建一个REST API,接收用户的请求,并验证用户是否登录,然后再读取对应的文件,返回给前端。
小李:那这部分代码应该怎么做?
老王:这是一个简单的Spring Boot控制器示例:

@RestController
public class FileDownloadController {
@GetMapping("/download/{fileName}")
public ResponseEntity downloadFile(@PathVariable String fileName, HttpServletRequest request) {
// 验证用户权限
if (!isUserLoggedIn(request)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
// 获取文件路径
String filePath = "/data/files/" + fileName;
try {
byte[] fileBytes = Files.readAllBytes(Paths.get(filePath));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", fileName);
return new ResponseEntity<>(fileBytes, headers, HttpStatus.OK);
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
private boolean isUserLoggedIn(HttpServletRequest request) {
// 这里可以检查session或token
return request.getSession().getAttribute("user") != null;
}
}
小李:这段代码看起来挺完整的。那前端应该怎么调用呢?
老王:前端可以用axios或者fetch发起GET请求,然后处理返回的文件流,触发浏览器的下载行为。
小李:那前端代码怎么写呢?
老王:这里是一个Vue.js的例子,使用axios来请求下载接口:
小李:明白了,这样就能避免直接暴露文件路径了,还能控制权限。
老王:没错,这种方式更安全,也更灵活。你还可以根据不同的用户角色,限制他们能下载的文件范围。
小李:那如果文件很大怎么办?会不会导致内存溢出?
老王:这个问题很有意思。对于大文件,直接读取整个文件到内存是不现实的。这时候可以使用流式传输,也就是分块读取文件,逐步发送给客户端。
小李:那后端该怎么修改呢?
老王:我们可以使用Java的FileInputStream配合Servlet的OutputStream来实现流式传输,而不是一次性读取全部内容。
小李:能给我看一段代码吗?
老王:当然可以,下面是优化后的后端代码:
@GetMapping("/download/{fileName}")
public void downloadFile(@PathVariable String fileName, HttpServletRequest request, HttpServletResponse response) throws IOException {
if (!isUserLoggedIn(request)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
String filePath = "/data/files/" + fileName;
File file = new File(filePath);
if (!file.exists()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
try (FileInputStream fis = new FileInputStream(file);
OutputStream os = response.getOutputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
}
}
小李:哦,原来如此,这样就避免了内存占用过高的问题。
老王:对,这种方式适合处理大文件。不过要注意,如果网络不稳定,可能会导致下载中断,所以还需要考虑断点续传等功能。
小李:那断点续传怎么实现呢?
老王:断点续传可以通过HTTP的Range头来实现,让客户端告诉服务器从哪个位置开始下载。后端需要支持这个功能,读取文件的指定部分并返回。
小李:听起来有点复杂,不过对用户体验来说很重要。
老王:没错。尤其是在高校环境中,学生和老师经常需要下载大体积的课程资料,断点续传可以大大提升用户体验。
小李:好的,我现在对下载功能的理解更深入了。谢谢你的帮助!
老王:不客气,有问题随时问我。祝你项目顺利!