小明:最近我在做一个校园宿舍管理系统,感觉挺复杂的。特别是下载功能,我有点搞不清楚怎么实现。
小李:哦,你说的是文件下载功能吧?比如学生可以下载自己的宿舍分配表或者通知文档之类的。
小明:对,就是这个意思。我用的是Spring Boot框架,但是不知道该怎么写下载接口。
小李:那我们可以一步步来。首先,你需要一个控制器(Controller),用来处理下载请求。
小明:好的,那控制器怎么写呢?
小李:你可以使用Spring MVC中的ResponseEntity来返回文件内容。例如,你可以从服务器上读取文件,然后设置响应头,告诉浏览器这是一个文件下载。
小明:听起来有点抽象,能给我一个具体的例子吗?
小李:当然可以。下面是一个简单的下载控制器代码:
@RestController
public class DownloadController {
@GetMapping("/download")
public ResponseEntity downloadFile() throws IOException {
// 假设你要下载的文件路径是“/upload/example.txt”
Path filePath = Paths.get("upload/example.txt");
byte[] fileData = Files.readAllBytes(filePath);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", "example.txt");
return new ResponseEntity<>(fileData, headers, HttpStatus.OK);
}
}
小明:这个代码看起来不错,但我要是从数据库里获取文件内容怎么办?比如文件可能存储在数据库中,而不是本地路径。
小李:那你就需要从数据库中查询出文件内容,然后以字节数组的形式返回。假设你有一个FileService类,它可以从数据库中获取文件数据。
小明:明白了,那如果文件比较大,比如几十MB甚至更大,会不会有性能问题?
小李:确实,直接读取整个文件到内存可能会导致内存溢出。这时候可以考虑分块读取,或者使用流式传输。
小明:那怎么实现流式传输呢?
小李:你可以使用InputStream,然后通过ResponseEntity的body参数传入,这样就不会一次性加载整个文件到内存中。
小明:那我可以改写一下刚才的代码,改成使用流的方式吗?
小李:当然可以,下面是改进后的版本:
@RestController
public class DownloadController {
@GetMapping("/download")
public ResponseEntity downloadFile() throws IOException {
Resource resource = new InputStreamResource(new FileInputStream("upload/example.txt"));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", "example.txt");
return new ResponseEntity<>(resource, headers, HttpStatus.OK);
}
}
小明:这个更高效了,因为它是通过流来传输的,不会占用太多内存。
小李:没错,这在处理大文件时非常有用。另外,你还可以根据不同的用户角色来限制下载权限。
小明:对啊,比如只有管理员才能下载某些敏感文件,普通学生只能下载自己的信息。
小李:没错,这部分可以通过Spring Security来实现。你可以在控制器方法上添加@PreAuthorize注解,指定用户权限。
小明:那如何配置Spring Security呢?
小李:你可以创建一个SecurityConfig类,继承WebSecurityConfigurerAdapter,并配置访问规则。例如:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/download").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin();
}
}
小明:这样就能控制谁可以下载文件了。不过,我还想知道,如果文件是动态生成的,比如根据用户的ID生成,应该怎么处理?
小李:这种情况下,你可以将用户ID作为参数传递给下载接口,然后根据ID从数据库中查找对应的文件。
小明:那接口应该怎么设计呢?
小李:可以使用@GetMapping,参数用@PathVariable,例如:
@GetMapping("/download/{userId}")
public ResponseEntity downloadUserFile(@PathVariable String userId) throws IOException {
// 根据userId查询文件路径或内容
String filePath = "upload/user_" + userId + ".txt";
Resource resource = new InputStreamResource(new FileInputStream(filePath));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", "user_" + userId + ".txt");
return new ResponseEntity<>(resource, headers, HttpStatus.OK);
}
小明:这样就可以动态生成下载链接了。不过,如果用户没有上传文件,会报错吗?
小李:是的,这时候你需要做异常处理。可以使用try-catch块来捕获IOException,或者在方法中抛出异常。
小明:有没有更优雅的处理方式?比如统一返回错误信息?
小李:当然可以,你可以使用@RestControllerAdvice来全局处理异常。
小明:那我可以写一个全局异常处理器吗?
小李:是的,下面是一个简单的例子:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IOException.class)
public ResponseEntity handleIOException(IOException ex) {
return new ResponseEntity<>("文件下载失败:" + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
小明:这样就解决了异常处理的问题,用户体验更好了。
小李:没错,现在你的下载功能已经比较完善了。接下来你可以考虑增加日志记录、文件版本管理、多格式支持等功能。
小明:嗯,这些都是未来可以扩展的方向。感谢你的帮助,我现在对下载功能有了更深的理解。
小李:不客气,如果你还有其他问题,随时来找我讨论。
