3.1 Spring MVC架构

MVC模式概述

MVC(Model-View-Controller)是一种软件架构模式,将应用程序分为三个核心组件:

  • Model(模型):负责数据和业务逻辑
  • View(视图):负责用户界面展示
  • Controller(控制器):负责处理用户输入和协调Model与View

Spring MVC工作流程

1. 用户请求 -> DispatcherServlet(前端控制器)
2. DispatcherServlet -> HandlerMapping(处理器映射器)
3. HandlerMapping -> Handler(处理器/Controller)
4. Handler -> HandlerAdapter(处理器适配器)
5. HandlerAdapter -> ViewResolver(视图解析器)
6. ViewResolver -> View(视图)
7. View -> 响应给用户

核心组件

@RestController
@RequestMapping("/api")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping("/users")
    public List<User> getAllUsers() {
        return userService.findAll();
    }
    
    @PostMapping("/users")
    public User createUser(@RequestBody User user) {
        return userService.save(user);
    }
}

3.2 控制器开发

@Controller vs @RestController

// 传统Controller,返回视图名称
@Controller
public class PageController {
    
    @RequestMapping("/home")
    public String home(Model model) {
        model.addAttribute("message", "Hello World");
        return "home"; // 返回视图名称
    }
}

// RESTful Controller,直接返回数据
@RestController
public class ApiController {
    
    @GetMapping("/api/data")
    public Map<String, Object> getData() {
        Map<String, Object> data = new HashMap<>();
        data.put("message", "Hello World");
        return data; // 直接返回JSON
    }
}

请求映射注解

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    // GET请求
    @GetMapping
    public List<User> getAllUsers() {
        return userService.findAll();
    }
    
    // GET请求,带路径参数
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.findById(id);
    }
    
    // POST请求
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.save(user);
    }
    
    // PUT请求
    @PutMapping("/{id}")
    public User updateUser(@PathVariable Long id, @RequestBody User user) {
        user.setId(id);
        return userService.update(user);
    }
    
    // DELETE请求
    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        userService.deleteById(id);
    }
    
    // PATCH请求
    @PatchMapping("/{id}")
    public User patchUser(@PathVariable Long id, @RequestBody Map<String, Object> updates) {
        return userService.partialUpdate(id, updates);
    }
}

请求参数处理

@RestController
public class ParameterController {
    
    // 路径参数
    @GetMapping("/users/{id}/posts/{postId}")
    public Post getUserPost(@PathVariable Long id, @PathVariable Long postId) {
        return postService.findByUserIdAndPostId(id, postId);
    }
    
    // 查询参数
    @GetMapping("/users")
    public List<User> getUsers(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(required = false) String name) {
        return userService.findUsers(page, size, name);
    }
    
    // 请求头
    @GetMapping("/profile")
    public User getProfile(@RequestHeader("Authorization") String token) {
        return userService.findByToken(token);
    }
    
    // Cookie
    @GetMapping("/preferences")
    public UserPreferences getPreferences(@CookieValue("sessionId") String sessionId) {
        return userService.getPreferences(sessionId);
    }
    
    // 表单数据
    @PostMapping("/login")
    public ResponseEntity<String> login(
            @RequestParam String username,
            @RequestParam String password) {
        boolean success = authService.authenticate(username, password);
        return success ? ResponseEntity.ok("Login successful") 
                      : ResponseEntity.status(401).body("Login failed");
    }
}

请求体处理

@RestController
public class RequestBodyController {
    
    // JSON请求体
    @PostMapping("/users")
    public User createUser(@RequestBody User user) {
        return userService.save(user);
    }
    
    // 验证请求体
    @PostMapping("/users/validated")
    public User createValidatedUser(@Valid @RequestBody User user) {
        return userService.save(user);
    }
    
    // 部分更新
    @PatchMapping("/users/{id}")
    public User patchUser(
            @PathVariable Long id,
            @RequestBody Map<String, Object> updates) {
        return userService.partialUpdate(id, updates);
    }
    
    // 文件上传
    @PostMapping("/upload")
    public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            return ResponseEntity.badRequest().body("File is empty");
        }
        
        try {
            String filename = fileService.store(file);
            return ResponseEntity.ok("File uploaded: " + filename);
        } catch (Exception e) {
            return ResponseEntity.status(500).body("Upload failed: " + e.getMessage());
        }
    }
}

3.3 响应处理

ResponseEntity使用

@RestController
public class ResponseController {
    
    // 基本响应
    @GetMapping("/users/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        if (user != null) {
            return ResponseEntity.ok(user);
        } else {
            return ResponseEntity.notFound().build();
        }
    }
    
    // 自定义状态码和头部
    @PostMapping("/users")
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User savedUser = userService.save(user);
        
        HttpHeaders headers = new HttpHeaders();
        headers.add("Location", "/api/users/" + savedUser.getId());
        
        return ResponseEntity
                .status(HttpStatus.CREATED)
                .headers(headers)
                .body(savedUser);
    }
    
    // 错误响应
    @GetMapping("/users/{id}/sensitive")
    public ResponseEntity<?> getSensitiveData(@PathVariable Long id) {
        if (!authService.hasPermission(id)) {
            return ResponseEntity
                    .status(HttpStatus.FORBIDDEN)
                    .body(Map.of("error", "Access denied"));
        }
        
        return ResponseEntity.ok(userService.getSensitiveData(id));
    }
}

统一响应格式

// 响应包装类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse<T> {
    private int code;
    private String message;
    private T data;
    private long timestamp;
    
    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(200, "Success", data, System.currentTimeMillis());
    }
    
    public static <T> ApiResponse<T> error(int code, String message) {
        return new ApiResponse<>(code, message, null, System.currentTimeMillis());
    }
}

// 使用响应包装
@RestController
public class UnifiedResponseController {
    
    @GetMapping("/users")
    public ApiResponse<List<User>> getUsers() {
        List<User> users = userService.findAll();
        return ApiResponse.success(users);
    }
    
    @PostMapping("/users")
    public ApiResponse<User> createUser(@RequestBody User user) {
        try {
            User savedUser = userService.save(user);
            return ApiResponse.success(savedUser);
        } catch (Exception e) {
            return ApiResponse.error(500, "Failed to create user: " + e.getMessage());
        }
    }
}

3.4 异常处理

全局异常处理

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    // 处理验证异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ApiResponse<Map<String, String>>> handleValidationException(
            MethodArgumentNotValidException ex) {
        
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error -> 
            errors.put(error.getField(), error.getDefaultMessage())
        );
        
        ApiResponse<Map<String, String>> response = 
            ApiResponse.error(400, "Validation failed");
        response.setData(errors);
        
        return ResponseEntity.badRequest().body(response);
    }
    
    // 处理资源不存在异常
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ApiResponse<Void>> handleResourceNotFoundException(
            ResourceNotFoundException ex) {
        
        ApiResponse<Void> response = ApiResponse.error(404, ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
    }
    
    // 处理业务异常
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ApiResponse<Void>> handleBusinessException(
            BusinessException ex) {
        
        ApiResponse<Void> response = ApiResponse.error(ex.getCode(), ex.getMessage());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
    }
    
    // 处理系统异常
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiResponse<Void>> handleGenericException(Exception ex) {
        log.error("Unexpected error occurred", ex);
        
        ApiResponse<Void> response = ApiResponse.error(500, "Internal server error");
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
    }
}

// 自定义异常类
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

public class BusinessException extends RuntimeException {
    private final int code;
    
    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
    }
    
    public int getCode() {
        return code;
    }
}

局部异常处理

@RestController
public class UserController {
    
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }
    
    // 控制器级别异常处理
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
    }
}

3.5 数据验证

Bean Validation

// 实体类验证
@Entity
@Data
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @NotBlank(message = "Username cannot be blank")
    @Size(min = 3, max = 20, message = "Username must be between 3 and 20 characters")
    private String username;
    
    @NotBlank(message = "Email cannot be blank")
    @Email(message = "Email should be valid")
    private String email;
    
    @NotNull(message = "Age cannot be null")
    @Min(value = 18, message = "Age must be at least 18")
    @Max(value = 100, message = "Age must be less than 100")
    private Integer age;
    
    @Pattern(regexp = "^\\+?[1-9]\\d{1,14}$", message = "Phone number is invalid")
    private String phone;
    
    @Past(message = "Birth date must be in the past")
    private LocalDate birthDate;
}

// 控制器中使用验证
@RestController
public class UserController {
    
    @PostMapping("/users")
    public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
        User savedUser = userService.save(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
    }
}

自定义验证器

// 自定义验证注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueUsernameValidator.class)
public @interface UniqueUsername {
    String message() default "Username already exists";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

// 验证器实现
@Component
public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, String> {
    
    @Autowired
    private UserRepository userRepository;
    
    @Override
    public boolean isValid(String username, ConstraintValidatorContext context) {
        if (username == null) {
            return true; // 让@NotNull处理null值
        }
        return !userRepository.existsByUsername(username);
    }
}

// 使用自定义验证
@Entity
public class User {
    
    @NotBlank
    @UniqueUsername
    private String username;
    
    // 其他字段...
}

分组验证

// 验证分组接口
public interface CreateGroup {}
public interface UpdateGroup {}

// 实体类分组验证
@Entity
@Data
public class User {
    
    @Null(groups = CreateGroup.class, message = "ID must be null when creating")
    @NotNull(groups = UpdateGroup.class, message = "ID cannot be null when updating")
    private Long id;
    
    @NotBlank(groups = {CreateGroup.class, UpdateGroup.class})
    private String username;
    
    @NotBlank(groups = CreateGroup.class)
    private String password;
}

// 控制器中使用分组验证
@RestController
public class UserController {
    
    @PostMapping("/users")
    public User createUser(@Validated(CreateGroup.class) @RequestBody User user) {
        return userService.save(user);
    }
    
    @PutMapping("/users/{id}")
    public User updateUser(
            @PathVariable Long id,
            @Validated(UpdateGroup.class) @RequestBody User user) {
        user.setId(id);
        return userService.update(user);
    }
}

3.6 内容协商

多格式响应

@RestController
public class ContentNegotiationController {
    
    @GetMapping(value = "/users/{id}", 
                produces = {MediaType.APPLICATION_JSON_VALUE, 
                           MediaType.APPLICATION_XML_VALUE})
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }
    
    @PostMapping(value = "/users",
                 consumes = {MediaType.APPLICATION_JSON_VALUE,
                            MediaType.APPLICATION_XML_VALUE},
                 produces = {MediaType.APPLICATION_JSON_VALUE,
                            MediaType.APPLICATION_XML_VALUE})
    public User createUser(@RequestBody User user) {
        return userService.save(user);
    }
}

配置内容协商

@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer
            .favorParameter(true)
            .parameterName("format")
            .ignoreAcceptHeader(false)
            .useRegisteredExtensionsOnly(false)
            .defaultContentType(MediaType.APPLICATION_JSON)
            .mediaType("json", MediaType.APPLICATION_JSON)
            .mediaType("xml", MediaType.APPLICATION_XML);
    }
}

3.7 跨域处理

@CrossOrigin注解

@RestController
@CrossOrigin(origins = "http://localhost:3000")
public class ApiController {
    
    @GetMapping("/data")
    public Map<String, Object> getData() {
        return Map.of("message", "Hello from API");
    }
    
    @CrossOrigin(origins = "*", methods = {RequestMethod.GET, RequestMethod.POST})
    @PostMapping("/submit")
    public ResponseEntity<String> submitData(@RequestBody Map<String, Object> data) {
        return ResponseEntity.ok("Data submitted successfully");
    }
}

全局CORS配置

@Configuration
public class CorsConfig {
    
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOriginPatterns(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

// 或者通过WebMvcConfigurer配置
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://localhost:3000", "https://example.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

3.8 拦截器

自定义拦截器

@Component
public class LoggingInterceptor implements HandlerInterceptor {
    
    private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        String requestURI = request.getRequestURI();
        String method = request.getMethod();
        
        logger.info("Request started: {} {}", method, requestURI);
        
        // 记录请求开始时间
        request.setAttribute("startTime", System.currentTimeMillis());
        
        return true; // 继续处理请求
    }
    
    @Override
    public void postHandle(HttpServletRequest request, 
                          HttpServletResponse response, 
                          Object handler, 
                          ModelAndView modelAndView) throws Exception {
        
        logger.info("Request processed successfully");
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, 
                               HttpServletResponse response, 
                               Object handler, 
                               Exception ex) throws Exception {
        
        long startTime = (Long) request.getAttribute("startTime");
        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;
        
        String requestURI = request.getRequestURI();
        int status = response.getStatus();
        
        if (ex != null) {
            logger.error("Request failed: {} - Status: {} - Duration: {}ms - Error: {}", 
                        requestURI, status, duration, ex.getMessage());
        } else {
            logger.info("Request completed: {} - Status: {} - Duration: {}ms", 
                       requestURI, status, duration);
        }
    }
}

// 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Autowired
    private LoggingInterceptor loggingInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loggingInterceptor)
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/health", "/api/metrics");
    }
}

认证拦截器

@Component
public class AuthInterceptor implements HandlerInterceptor {
    
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        // 检查是否需要认证
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            
            // 检查方法或类上是否有@NoAuth注解
            if (handlerMethod.hasMethodAnnotation(NoAuth.class) ||
                handlerMethod.getBeanType().isAnnotationPresent(NoAuth.class)) {
                return true;
            }
        }
        
        // 获取token
        String token = request.getHeader("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            response.getWriter().write("Missing or invalid token");
            return false;
        }
        
        token = token.substring(7); // 移除"Bearer "前缀
        
        // 验证token
        try {
            if (jwtTokenUtil.isTokenValid(token)) {
                String username = jwtTokenUtil.getUsernameFromToken(token);
                request.setAttribute("currentUser", username);
                return true;
            } else {
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                response.getWriter().write("Invalid token");
                return false;
            }
        } catch (Exception e) {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            response.getWriter().write("Token validation failed");
            return false;
        }
    }
}

// 免认证注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NoAuth {
}

3.9 过滤器

自定义过滤器

@Component
@Order(1)
public class RequestResponseLoggingFilter implements Filter {
    
    private static final Logger logger = LoggerFactory.getLogger(RequestResponseLoggingFilter.class);
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        // 包装请求和响应以便读取内容
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(httpRequest);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(httpResponse);
        
        long startTime = System.currentTimeMillis();
        
        try {
            // 执行请求
            chain.doFilter(requestWrapper, responseWrapper);
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            
            // 记录请求信息
            logRequest(requestWrapper, duration);
            
            // 记录响应信息
            logResponse(responseWrapper, duration);
            
            // 复制响应内容到原始响应
            responseWrapper.copyBodyToResponse();
        }
    }
    
    private void logRequest(ContentCachingRequestWrapper request, long duration) {
        String uri = request.getRequestURI();
        String method = request.getMethod();
        String queryString = request.getQueryString();
        
        logger.info("Request: {} {} {} - Duration: {}ms", 
                   method, uri, queryString != null ? "?" + queryString : "", duration);
        
        // 记录请求体(仅对POST/PUT请求)
        if ("POST".equals(method) || "PUT".equals(method)) {
            byte[] content = request.getContentAsByteArray();
            if (content.length > 0) {
                String requestBody = new String(content, StandardCharsets.UTF_8);
                logger.debug("Request Body: {}", requestBody);
            }
        }
    }
    
    private void logResponse(ContentCachingResponseWrapper response, long duration) {
        int status = response.getStatus();
        
        logger.info("Response: Status {} - Duration: {}ms", status, duration);
        
        // 记录响应体
        byte[] content = response.getContentAsByteArray();
        if (content.length > 0) {
            String responseBody = new String(content, StandardCharsets.UTF_8);
            logger.debug("Response Body: {}", responseBody);
        }
    }
}

字符编码过滤器

@Configuration
public class FilterConfig {
    
    @Bean
    public FilterRegistrationBean<CharacterEncodingFilter> characterEncodingFilter() {
        FilterRegistrationBean<CharacterEncodingFilter> registrationBean = new FilterRegistrationBean<>();
        
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceEncoding(true);
        
        registrationBean.setFilter(characterEncodingFilter);
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        
        return registrationBean;
    }
}

3.10 文件上传下载

文件上传

@RestController
@RequestMapping("/api/files")
public class FileController {
    
    @Value("${file.upload.path:/tmp/uploads}")
    private String uploadPath;
    
    // 单文件上传
    @PostMapping("/upload")
    public ResponseEntity<Map<String, String>> uploadFile(
            @RequestParam("file") MultipartFile file) {
        
        if (file.isEmpty()) {
            return ResponseEntity.badRequest()
                    .body(Map.of("error", "File is empty"));
        }
        
        try {
            // 检查文件类型
            String contentType = file.getContentType();
            if (!isValidFileType(contentType)) {
                return ResponseEntity.badRequest()
                        .body(Map.of("error", "Invalid file type"));
            }
            
            // 生成唯一文件名
            String originalFilename = file.getOriginalFilename();
            String extension = getFileExtension(originalFilename);
            String filename = UUID.randomUUID().toString() + extension;
            
            // 保存文件
            Path uploadDir = Paths.get(uploadPath);
            if (!Files.exists(uploadDir)) {
                Files.createDirectories(uploadDir);
            }
            
            Path filePath = uploadDir.resolve(filename);
            Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
            
            Map<String, String> response = new HashMap<>();
            response.put("filename", filename);
            response.put("originalName", originalFilename);
            response.put("size", String.valueOf(file.getSize()));
            response.put("url", "/api/files/download/" + filename);
            
            return ResponseEntity.ok(response);
            
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(Map.of("error", "Upload failed: " + e.getMessage()));
        }
    }
    
    // 多文件上传
    @PostMapping("/upload/multiple")
    public ResponseEntity<List<Map<String, String>>> uploadMultipleFiles(
            @RequestParam("files") MultipartFile[] files) {
        
        List<Map<String, String>> results = new ArrayList<>();
        
        for (MultipartFile file : files) {
            if (!file.isEmpty()) {
                // 调用单文件上传逻辑
                ResponseEntity<Map<String, String>> result = uploadFile(file);
                results.add(result.getBody());
            }
        }
        
        return ResponseEntity.ok(results);
    }
    
    private boolean isValidFileType(String contentType) {
        return contentType != null && (
                contentType.startsWith("image/") ||
                contentType.equals("application/pdf") ||
                contentType.equals("text/plain")
        );
    }
    
    private String getFileExtension(String filename) {
        if (filename == null || filename.lastIndexOf('.') == -1) {
            return "";
        }
        return filename.substring(filename.lastIndexOf('.'));
    }
}

文件下载

@RestController
@RequestMapping("/api/files")
public class FileController {
    
    // 文件下载
    @GetMapping("/download/{filename}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
        try {
            Path filePath = Paths.get(uploadPath).resolve(filename);
            Resource resource = new UrlResource(filePath.toUri());
            
            if (!resource.exists() || !resource.isReadable()) {
                return ResponseEntity.notFound().build();
            }
            
            // 获取文件的MIME类型
            String contentType = Files.probeContentType(filePath);
            if (contentType == null) {
                contentType = "application/octet-stream";
            }
            
            return ResponseEntity.ok()
                    .contentType(MediaType.parseMediaType(contentType))
                    .header(HttpHeaders.CONTENT_DISPOSITION, 
                           "attachment; filename=\"" + resource.getFilename() + "\"")
                    .body(resource);
                    
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
    
    // 在线预览
    @GetMapping("/preview/{filename}")
    public ResponseEntity<Resource> previewFile(@PathVariable String filename) {
        try {
            Path filePath = Paths.get(uploadPath).resolve(filename);
            Resource resource = new UrlResource(filePath.toUri());
            
            if (!resource.exists() || !resource.isReadable()) {
                return ResponseEntity.notFound().build();
            }
            
            String contentType = Files.probeContentType(filePath);
            if (contentType == null) {
                contentType = "application/octet-stream";
            }
            
            return ResponseEntity.ok()
                    .contentType(MediaType.parseMediaType(contentType))
                    .header(HttpHeaders.CONTENT_DISPOSITION, "inline")
                    .body(resource);
                    
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
}

文件上传配置

# application.properties

# 文件上传配置
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=50MB
spring.servlet.multipart.file-size-threshold=2KB

# 自定义上传路径
file.upload.path=/app/uploads

总结

本章详细介绍了Spring Boot Web开发的核心内容:

  1. Spring MVC架构:理解MVC模式和工作流程
  2. 控制器开发:@Controller、@RestController、请求映射
  3. 参数处理:路径参数、查询参数、请求体处理
  4. 响应处理:ResponseEntity、统一响应格式
  5. 异常处理:全局异常处理、自定义异常
  6. 数据验证:Bean Validation、自定义验证器
  7. 内容协商:多格式响应支持
  8. 跨域处理:CORS配置
  9. 拦截器和过滤器:请求预处理和后处理
  10. 文件操作:文件上传下载功能

这些知识点构成了Spring Boot Web开发的基础,掌握这些内容可以开发出功能完整的Web应用程序。