Skip to content

Web 开发

LCGYL Framework 提供完整的 Web 开发支持,包括 RESTful API、请求处理、响应渲染等功能。

快速开始

创建控制器

java
@Controller
@RequestMapping("/api/users")
public class UserController {
    
    @Inject
    private UserService userService;
    
    @GetMapping
    public List<User> list() {
        return userService.findAll();
    }
    
    @GetMapping("/{id}")
    public User get(@PathVariable Long id) {
        return userService.findById(id);
    }
    
    @PostMapping
    public User create(@RequestBody User user) {
        return userService.create(user);
    }
    
    @PutMapping("/{id}")
    public User update(@PathVariable Long id, @RequestBody User user) {
        return userService.update(id, user);
    }
    
    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) {
        userService.delete(id);
    }
}

请求映射

HTTP 方法

java
@Controller
@RequestMapping("/api/orders")
public class OrderController {
    
    @GetMapping           // GET 请求
    public List<Order> list() { }
    
    @PostMapping          // POST 请求
    public Order create() { }
    
    @PutMapping("/{id}")  // PUT 请求
    public Order update() { }
    
    @PatchMapping("/{id}") // PATCH 请求
    public Order patch() { }
    
    @DeleteMapping("/{id}") // DELETE 请求
    public void delete() { }
}

路径变量

java
@GetMapping("/users/{userId}/orders/{orderId}")
public Order getOrder(
        @PathVariable Long userId,
        @PathVariable Long orderId) {
    return orderService.findByUserIdAndOrderId(userId, orderId);
}

// 正则表达式约束
@GetMapping("/files/{filename:.+}")
public Resource getFile(@PathVariable String filename) {
    return fileService.getFile(filename);
}

请求参数

java
@GetMapping("/search")
public List<User> search(
        @RequestParam String keyword,
        @RequestParam(required = false) String status,
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "10") int size) {
    return userService.search(keyword, status, page, size);
}

请求头

java
@GetMapping("/info")
public UserInfo getInfo(
        @RequestHeader("Authorization") String token,
        @RequestHeader(value = "X-Request-Id", required = false) String requestId) {
    return userService.getInfo(token);
}

请求体

JSON 请求体

java
@PostMapping("/users")
public User createUser(@RequestBody CreateUserRequest request) {
    return userService.create(request);
}

public record CreateUserRequest(
    String name,
    String email,
    String password
) {}

表单数据

java
@PostMapping("/login")
public AuthResult login(
        @RequestParam String username,
        @RequestParam String password) {
    return authService.login(username, password);
}

文件上传

java
@PostMapping("/upload")
public FileInfo upload(@RequestParam("file") MultipartFile file) {
    String filename = file.getOriginalFilename();
    byte[] content = file.getBytes();
    return fileService.save(filename, content);
}

@PostMapping("/upload/multiple")
public List<FileInfo> uploadMultiple(@RequestParam("files") List<MultipartFile> files) {
    return files.stream()
        .map(file -> fileService.save(file.getOriginalFilename(), file.getBytes()))
        .collect(Collectors.toList());
}

响应处理

返回 JSON

java
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
    return userService.findById(id);  // 自动转换为 JSON
}

自定义响应

java
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    User user = userService.findById(id);
    if (user == null) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(user);
}

@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
    User created = userService.create(user);
    URI location = URI.create("/api/users/" + created.getId());
    return ResponseEntity.created(location).body(created);
}

统一响应格式

java
public record ApiResponse<T>(
    int code,
    String message,
    T data
) {
    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(200, "success", data);
    }
    
    public static <T> ApiResponse<T> error(int code, String message) {
        return new ApiResponse<>(code, message, null);
    }
}

@GetMapping("/users/{id}")
public ApiResponse<User> getUser(@PathVariable Long id) {
    User user = userService.findById(id);
    return ApiResponse.success(user);
}

文件下载

java
@GetMapping("/download/{filename}")
public ResponseEntity<Resource> download(@PathVariable String filename) {
    Resource resource = fileService.getFile(filename);
    
    return ResponseEntity.ok()
        .header(HttpHeaders.CONTENT_DISPOSITION, 
            "attachment; filename=\"" + filename + "\"")
        .contentType(MediaType.APPLICATION_OCTET_STREAM)
        .body(resource);
}

参数验证

使用验证注解

java
public record CreateUserRequest(
    @NotBlank(message = "用户名不能为空")
    @Size(min = 2, max = 20, message = "用户名长度必须在2-20之间")
    String name,
    
    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    String email,
    
    @NotBlank(message = "密码不能为空")
    @Size(min = 6, max = 20, message = "密码长度必须在6-20之间")
    String password,
    
    @Min(value = 0, message = "年龄不能为负数")
    @Max(value = 150, message = "年龄不能超过150")
    Integer age
) {}

@PostMapping("/users")
public User createUser(@Valid @RequestBody CreateUserRequest request) {
    return userService.create(request);
}

自定义验证器

java
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
    String message() default "手机号格式不正确";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class PhoneValidator implements ConstraintValidator<Phone, String> {
    
    private static final Pattern PHONE_PATTERN = 
        Pattern.compile("^1[3-9]\\d{9}$");
    
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }
        return PHONE_PATTERN.matcher(value).matches();
    }
}

// 使用
public record CreateUserRequest(
    @Phone
    String phone
) {}

异常处理

全局异常处理

java
@ControllerAdvice
public class GlobalExceptionHandler {
    
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
    @ExceptionHandler(NotFoundException.class)
    public ResponseEntity<ApiResponse<Void>> handleNotFound(NotFoundException e) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(ApiResponse.error(404, e.getMessage()));
    }
    
    @ExceptionHandler(ValidationException.class)
    public ResponseEntity<ApiResponse<Void>> handleValidation(ValidationException e) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
            .body(ApiResponse.error(400, e.getMessage()));
    }
    
    @ExceptionHandler(UnauthorizedException.class)
    public ResponseEntity<ApiResponse<Void>> handleUnauthorized(UnauthorizedException e) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
            .body(ApiResponse.error(401, e.getMessage()));
    }
    
    @ExceptionHandler(ForbiddenException.class)
    public ResponseEntity<ApiResponse<Void>> handleForbidden(ForbiddenException e) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN)
            .body(ApiResponse.error(403, e.getMessage()));
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiResponse<Void>> handleException(Exception e) {
        logger.error("未处理的异常", e);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(ApiResponse.error(500, "服务器内部错误"));
    }
}

验证异常处理

java
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Map<String, String>>> handleValidationErrors(
        MethodArgumentNotValidException e) {
    
    Map<String, String> errors = new HashMap<>();
    e.getBindingResult().getFieldErrors().forEach(error -> 
        errors.put(error.getField(), error.getDefaultMessage())
    );
    
    return ResponseEntity.status(HttpStatus.BAD_REQUEST)
        .body(new ApiResponse<>(400, "参数验证失败", errors));
}

过滤器和拦截器

过滤器

java
@Component
@Order(1)
public class LoggingFilter implements Filter {
    
    private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class);
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        
        long startTime = System.currentTimeMillis();
        logger.info("请求开始: {} {}", httpRequest.getMethod(), httpRequest.getRequestURI());
        
        try {
            chain.doFilter(request, response);
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            logger.info("请求结束: {} {} - {}ms", 
                httpRequest.getMethod(), 
                httpRequest.getRequestURI(), 
                duration);
        }
    }
}

拦截器

java
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
    
    @Inject
    private TokenService tokenService;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
            Object handler) throws Exception {
        
        // 跳过不需要认证的路径
        if (isPublicPath(request.getRequestURI())) {
            return true;
        }
        
        String token = extractToken(request);
        if (token == null || !tokenService.validateToken(token)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("{\"code\":401,\"message\":\"未授权\"}");
            return false;
        }
        
        User user = tokenService.getUserFromToken(token);
        SecurityContext.setCurrentUser(user);
        
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
            Object handler, Exception ex) throws Exception {
        SecurityContext.clear();
    }
}

注册拦截器

java
@Component
public class WebConfig implements WebMvcConfigurer {
    
    @Inject
    private AuthenticationInterceptor authInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor)
            .addPathPatterns("/api/**")
            .excludePathPatterns("/api/auth/**", "/api/public/**");
    }
}

CORS 配置

全局 CORS

java
@Component
public class CorsConfig implements WebMvcConfigurer {
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("http://localhost:3000", "https://example.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
            .allowedHeaders("*")
            .allowCredentials(true)
            .maxAge(3600);
    }
}

控制器级别 CORS

java
@Controller
@RequestMapping("/api/users")
@CrossOrigin(origins = "http://localhost:3000")
public class UserController {
    // ...
}

分页

分页请求

java
@GetMapping("/users")
public Page<User> listUsers(
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "10") int size,
        @RequestParam(defaultValue = "id") String sortBy,
        @RequestParam(defaultValue = "asc") String sortDir) {
    
    PageRequest pageRequest = new PageRequest(page, size, sortBy, sortDir);
    return userService.findAll(pageRequest);
}

分页响应

java
public record Page<T>(
    List<T> content,
    int page,
    int size,
    long totalElements,
    int totalPages,
    boolean first,
    boolean last
) {}

API 版本控制

URL 版本

java
@Controller
@RequestMapping("/api/v1/users")
public class UserControllerV1 {
    // V1 版本
}

@Controller
@RequestMapping("/api/v2/users")
public class UserControllerV2 {
    // V2 版本
}

Header 版本

java
@Controller
@RequestMapping("/api/users")
public class UserController {
    
    @GetMapping(headers = "X-API-Version=1")
    public List<UserV1> listV1() {
        // V1 版本
    }
    
    @GetMapping(headers = "X-API-Version=2")
    public List<UserV2> listV2() {
        // V2 版本
    }
}

WebSocket

配置 WebSocket

java
@Component
public class WebSocketConfig implements WebSocketConfigurer {
    
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(chatHandler(), "/ws/chat")
            .setAllowedOrigins("*");
    }
    
    @Bean
    public WebSocketHandler chatHandler() {
        return new ChatWebSocketHandler();
    }
}

WebSocket 处理器

java
@Component
public class ChatWebSocketHandler extends TextWebSocketHandler {
    
    private final Set<WebSocketSession> sessions = ConcurrentHashMap.newKeySet();
    
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        sessions.add(session);
    }
    
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) {
        // 广播消息
        sessions.forEach(s -> {
            try {
                s.sendMessage(message);
            } catch (IOException e) {
                // 处理异常
            }
        });
    }
    
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        sessions.remove(session);
    }
}

最佳实践

1. RESTful 设计

java
// ✅ 推荐:使用名词
GET    /api/users          // 获取用户列表
GET    /api/users/{id}     // 获取单个用户
POST   /api/users          // 创建用户
PUT    /api/users/{id}     // 更新用户
DELETE /api/users/{id}     // 删除用户

// ❌ 不推荐:使用动词
GET    /api/getUsers
POST   /api/createUser
POST   /api/deleteUser

2. 统一响应格式

java
// ✅ 推荐:统一格式
{
    "code": 200,
    "message": "success",
    "data": { ... }
}

3. 合理使用 HTTP 状态码

java
// ✅ 推荐
200 OK           // 成功
201 Created      // 创建成功
204 No Content   // 删除成功
400 Bad Request  // 参数错误
401 Unauthorized // 未认证
403 Forbidden    // 无权限
404 Not Found    // 资源不存在
500 Internal Server Error // 服务器错误

4. 参数验证

java
// ✅ 推荐:使用验证注解
@PostMapping("/users")
public User create(@Valid @RequestBody CreateUserRequest request) { }

常见问题

Q: 如何处理跨域问题?

A: 配置 CORS 或使用代理。

Q: 如何处理大文件上传?

A:

  1. 配置文件大小限制
  2. 使用分片上传
  3. 异步处理

Q: 如何实现接口限流?

A: 使用限流注解或过滤器。

java
@RateLimit(limit = 100, period = 60)
@GetMapping("/api/data")
public Data getData() { }

下一步

Released under the Apache License 2.0