跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Java RESTful 接口开发实战指南

Java RESTful 接口开发基于 Spring Boot 框架,涵盖项目初始化、控制器设计、服务层架构、数据传输对象 DTO 模式及 MapStruct 映射。内容包含全局异常处理、数据验证、Spring Security 认证授权(JWT)、API 文档集成(OpenAPI/Swagger)及全面测试策略(单元测试与集成测试)。部署部分涉及 Docker 容器化配置与多环境管理,性能优化包括数据库连接池、JPA 调优及多级缓存策略。生产环境实践提供监控告警配置(Prometheus/Grafana)及学习路径规划,帮助开发者构建高可用企业级应用。

栈溢出发布于 2026/3/21更新于 2026/5/2030 浏览
Java RESTful 接口开发实战指南

一、为什么选择 Spring Boot:极速开发的秘密

作为.NET 转 Java 的开发者,您会发现 Spring Boot 与 ASP.NET Core 在理念上惊人的相似,但 Spring Boot 的'约定优于配置'哲学将其发挥到极致。Spring Boot 可以:

  • 在 30 秒内启动一个可运行的 REST API
  • 通过自动配置减少 80% 的配置工作
  • 提供生产就绪的功能(监控、健康检查、指标)
  • 拥有全球最大的 Java 生态支持

在这里插入图片描述

二、极速启动:三步创建第一个 REST 接口

2.1 项目初始化

使用 Spring Initializr(start.spring.io)或 IDE 内置工具创建项目,只需选择:

  • Spring Web:RESTful 支持
  • Spring Data JPA:数据库操作(可选)
  • Validation:数据验证(可选)

2.2 基础代码示例

@RestController
@RequestMapping("/api/v1/users")
public class UserController {
    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.findById(id);
        return ResponseEntity.ok(user);
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public User createUser(@Valid @RequestBody UserDTO userDTO) {
        return userService.create(userDTO);
    }

    @PutMapping("/{id}")
    public User updateUser(@PathVariable Long id, @Valid @RequestBody UserDTO userDTO) {
        return userService.update(id, userDTO);
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteUser(@PathVariable Long id) {
        userService.delete(id);
    }
}

三、Spring Boot RESTful 核心详解

3.1 控制器层最佳实践

3.1.1 RESTful 资源设计原则
// 好的 RESTful 设计示例
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
    // GET /api/v1/orders - 获取所有订单
    @GetMapping
    public List<Order> getAllOrders(@RequestParam(defaultValue = "0") int page,
                                    @RequestParam(defaultValue = "20") int size) {
        return orderService.getOrders(page, size);
    }

    // GET /api/v1/orders/{id} - 获取特定订单
    @GetMapping("/{id}")
    public Order getOrderById(@PathVariable Long id) {
        return orderService.getById(id);
    }

    // GET /api/v1/orders/{id}/items - 获取订单项(子资源)
    @GetMapping("/{id}/items")
    public List<OrderItem> getOrderItems(@PathVariable Long id) {
        return orderService.getOrderItems(id);
    }

    // POST /api/v1/orders - 创建订单
    @PostMapping
    public ResponseEntity<Order> createOrder(@Valid @RequestBody OrderDTO dto) {
        Order created = orderService.create(dto);
        URI location = ServletUriComponentsBuilder.fromCurrentRequest()
                .path("/{id}").buildAndExpand(created.getId()).toUri();
        return ResponseEntity.created(location).body(created);
    }
}
3.1.2 高级请求处理技巧
// 1. 多条件查询参数处理
@GetMapping("/search")
public List<User> searchUsers(
        @RequestParam(required = false) String name,
        @RequestParam(required = false) String email,
        @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
        @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate,
        @RequestParam(defaultValue = "createdAt") String sortBy,
        @RequestParam(defaultValue = "desc") String direction) {
    Specification<User> spec = UserSpecifications.search(name, email, startDate, endDate);
    Sort sort = direction.equalsIgnoreCase("desc") ? Sort.by(sortBy).descending() : Sort.by(sortBy).ascending();
    return userService.search(spec, sort);
}

// 2. 文件上传处理
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file,
                                         @RequestParam("category") String category) {
    if (file.isEmpty()) {
        throw new BadRequestException("文件不能为空");
    }
    String filePath = fileStorageService.store(file, category);
    return ResponseEntity.ok("文件上传成功:" + filePath);
}

// 3. 请求/响应体压缩
@PostMapping("/compress")
public ResponseEntity<byte[]> handleCompressedData(@RequestBody byte[] compressedData) throws IOException {
    byte[] decompressed = CompressionUtils.decompress(compressedData);
    // 处理数据...
    byte[] responseData = "处理成功".getBytes();
    byte[] compressedResponse = CompressionUtils.compress(responseData);
    return ResponseEntity.ok().header("Content-Encoding", "gzip").body(compressedResponse);
}

3.2 服务层设计与实现

3.2.1 服务层架构模式
// 1. 基础服务接口
public interface UserService {
    UserDTO createUser(UserCreateDTO dto);
    UserDTO updateUser(Long id, UserUpdateDTO dto);
    UserDTO getUserById(Long id);
    Page<UserDTO> getUsers(UserQueryDTO query, Pageable pageable);
    void deleteUser(Long id);
}

// 2. 服务实现(使用@Transactional)
@Service
@Transactional
@Slf4j
public class UserServiceImpl implements UserService {
    private final UserRepository userRepository;
    private final UserMapper userMapper;
    private final CacheService cacheService;
    private final EmailService emailService;

    // 构造器注入(推荐方式)
    public UserServiceImpl(UserRepository userRepository, UserMapper userMapper,
                           CacheService cacheService, EmailService emailService) {
        this.userRepository = userRepository;
        this.userMapper = userMapper;
        this.cacheService = cacheService;
        this.emailService = emailService;
    }

    @Override
    public UserDTO createUser(UserCreateDTO dto) {
        log.info("创建用户:{}", dto.getEmail());
        // 验证逻辑
        if (userRepository.existsByEmail(dto.getEmail())) {
            throw new BusinessException("邮箱已存在");
        }
        // DTO 转实体
        User user = userMapper.toEntity(dto);
        user.setStatus(UserStatus.ACTIVE);
        user.setCreatedAt(LocalDateTime.now());
        // 保存到数据库
        User savedUser = userRepository.save(user);
        // 清理缓存
        cacheService.evict("users", savedUser.getId());
        // 发送欢迎邮件(异步)
        emailService.sendWelcomeEmail(savedUser.getEmail());
        // 返回 DTO
        return userMapper.toDTO(savedUser);
    }

    @Override
    @Transactional(readOnly = true)
    public UserDTO getUserById(Long id) {
        // 先尝试从缓存获取
        String cacheKey = "user:" + id;
        UserDTO cached = cacheService.get(cacheKey, UserDTO.class);
        if (cached != null) {
            return cached;
        }
        // 缓存未命中,查询数据库
        User user = userRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("用户不存在"));
        UserDTO dto = userMapper.toDTO(user);
        // 放入缓存
        cacheService.put(cacheKey, dto, Duration.ofMinutes(30));
        return dto;
    }

    @Override
    @Transactional(readOnly = true)
    public Page<UserDTO> getUsers(UserQueryDTO query, Pageable pageable) {
        // 构建查询条件
        Specification<User> spec = buildSpecification(query);
        // 执行分页查询
        Page<User> userPage = userRepository.findAll(spec, pageable);
        // 转换为 DTO
        return userPage.map(userMapper::toDTO);
    }

    private Specification<User> buildSpecification(UserQueryDTO query) {
        return (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (StringUtils.hasText(query.getName())) {
                predicates.add(criteriaBuilder.like(root.get("name"), "%" + query.getName() + "%"));
            }
            if (query.getStatus() != null) {
                predicates.add(criteriaBuilder.equal(root.get("status"), query.getStatus()));
            }
            if (query.getStartDate() != null) {
                predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("createdAt"), query.getStartDate()));
            }
            if (query.getEndDate() != null) {
                predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("createdAt"), query.getEndDate()));
            }
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        };
    }
}
3.2.2 业务逻辑与事务管理
// 复杂业务事务管理示例
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository orderRepository;
    private final InventoryService inventoryService;
    private final PaymentService paymentService;
    private final NotificationService notificationService;

    /**
     * 创建订单的复杂业务流程
     * 使用@Transactional 管理事务边界
     */
    @Transactional(rollbackFor = Exception.class)
    public OrderDTO createOrder(OrderCreateDTO dto) {
        // 1. 验证库存
        inventoryService.checkStock(dto.getItems());
        // 2. 扣减库存(独立事务)
        inventoryService.deductStock(dto.getItems());
        try {
            // 3. 创建订单
            Order order = createOrderEntity(dto);
            order = orderRepository.save(order);
            // 4. 调用支付(外部系统,需要补偿机制)
            paymentService.processPayment(order);
            // 5. 更新订单状态
            order.setStatus(OrderStatus.PAID);
            orderRepository.save(order);
            // 6. 发送通知(异步,不影响主事务)
            notificationService.sendOrderConfirmation(order);
            return convertToDTO(order);
        } catch (PaymentException e) {
            // 支付失败,恢复库存
            inventoryService.restoreStock(dto.getItems());
            throw new BusinessException("支付失败:" + e.getMessage());
        }
    }

    /**
     * 使用编程式事务处理特殊场景
     */
    public void batchUpdateOrderStatus(List<Long> orderIds, OrderStatus status) {
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        transactionTemplate.execute(status -> {
            for (Long orderId : orderIds) {
                Order order = orderRepository.findById(orderId).orElseThrow(() -> new ResourceNotFoundException("订单不存在"));
                // 记录状态变更历史
                order.addStatusHistory(order.getStatus(), status, "批量更新");
                // 更新状态
                order.setStatus(status);
                orderRepository.save(order);
                // 每处理 100 条记录提交一次,防止事务过大
                if (orderIds.indexOf(orderId) % 100 == 0) {
                    entityManager.flush();
                    entityManager.clear();
                }
            }
            return null;
        });
    }
}

3.3 数据传输对象设计

3.3.1 DTO 模式实现
// 1. 请求 DTO(验证注解)
@Data
public class UserCreateDTO {
    @NotBlank(message = "用户名不能为空")
    @Size(min = 2, max = 50, message = "用户名长度必须在 2-50 之间")
    private String username;

    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;

    @NotBlank(message = "密码不能为空")
    @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d@$!%*#?&]{8,}$", message = "密码必须至少 8 位,包含字母和数字")
    private String password;

    @NotNull(message = "角色不能为空")
    private UserRole role;

    @PhoneNumber // 自定义验证注解
    private String phone;
}

// 2. 响应 DTO(嵌套对象)
@Data
@Builder
public class UserDetailDTO {
    private Long id;
    private String username;
    private String email;
    private UserStatus status;
    private LocalDateTime createdAt;
    private List<UserAddressDTO> addresses;
    private UserProfileDTO profile;
}

// 3. 查询参数 DTO
@Data
public class UserQueryDTO {
    private String username;
    private String email;
    private UserStatus status;
    private LocalDateTime startDate;
    private LocalDateTime endDate;
    private String sortField;
    private Sort.Direction sortDirection;

    public Pageable toPageable() {
        if (sortField != null && sortDirection != null) {
            return PageRequest.of(0, 20, Sort.by(sortDirection, sortField));
        }
        return PageRequest.of(0, 20);
    }
}
3.3.2 MapStruct 映射器
// 1. 映射器接口
@Mapper(componentModel = "spring", uses = {AddressMapper.class, ProfileMapper.class},
        unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface UserMapper {
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    // 简单映射 User toEntity(UserCreateDTO dto);
    UserDTO toDTO(User entity);

    // 更新映射(忽略空值)
    @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
    void updateEntity(UserUpdateDTO dto, @MappingTarget User entity);

    // 列表映射
    List<UserDTO> toDTOList(List<User> entities);

    // 分页映射
    default Page<UserDTO> toDTOPage(Page<User> page) {
        return page.map(this::toDTO);
    }

    // 自定义映射方法
    @AfterMapping
    default void afterMapping(UserCreateDTO dto, @MappingTarget User user) {
        if (user.getPassword() != null) {
            user.setPassword(passwordEncoder.encode(user.getPassword()));
        }
        user.setCreatedAt(LocalDateTime.now());
    }
}

// 2. 复杂映射配置
@Mapper(componentModel = "spring")
public interface OrderMapper {
    @Mapping(source = "customer.id", target = "customerId")
    @Mapping(source = "customer.name", target = "customerName")
    @Mapping(source = "items", target = "orderItems")
    @Mapping(target = "totalAmount", expression = "java(calculateTotal(order))")
    OrderDTO toDTO(Order order);

    default BigDecimal calculateTotal(Order order) {
        return order.getItems().stream()
                .map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}

四、全局处理与高级特性

4.1 全局异常处理机制

4.1.1 统一异常处理器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    // 处理验证异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {
        List<String> errors = ex.getBindingResult().getFieldErrors().stream()
                .map(error -> error.getField() + ": " + error.getDefaultMessage())
                .collect(Collectors.toList());
        ErrorResponse response = ErrorResponse.builder()
                .timestamp(LocalDateTime.now())
                .status(HttpStatus.BAD_REQUEST.value())
                .error("验证失败")
                .message("请求参数无效")
                .errors(errors)
                .path(getRequestPath())
                .build();
        return ResponseEntity.badRequest().body(response);
    }

    // 处理业务异常
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex, HttpServletRequest request) {
        ErrorResponse response = ErrorResponse.builder()
                .timestamp(LocalDateTime.now())
                .status(HttpStatus.CONFLICT.value())
                .error("业务错误")
                .message(ex.getMessage())
                .path(request.getRequestURI())
                .build();
        return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
    }

    // 处理资源不存在异常
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFoundException(ResourceNotFoundException ex, HttpServletRequest request) {
        ErrorResponse response = ErrorResponse.builder()
                .timestamp(LocalDateTime.now())
                .status(HttpStatus.NOT_FOUND.value())
                .error("资源未找到")
                .message(ex.getMessage())
                .path(request.getRequestURI())
                .build();
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
    }

    // 处理所有未捕获的异常
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleAllExceptions(Exception ex, HttpServletRequest request) {
        log.error("未处理的异常:", ex);
        ErrorResponse response = ErrorResponse.builder()
                .timestamp(LocalDateTime.now())
                .status(HttpStatus.INTERNAL_SERVER_ERROR.value())
                .error("服务器内部错误")
                .message("系统繁忙,请稍后重试")
                .path(request.getRequestURI())
                .build();
        return ResponseEntity.internalServerError().body(response);
    }

    private String getRequestPath() {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        if (attributes instanceof ServletRequestAttributes) {
            return ((ServletRequestAttributes) attributes).getRequest().getRequestURI();
        }
        return null;
    }
}

// 错误响应 DTO
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ErrorResponse {
    private LocalDateTime timestamp;
    private int status;
    private String error;
    private String message;
    private List<String> errors;
    private String path;
}
4.1.2 自定义异常体系
// 基础业务异常
public abstract class BaseException extends RuntimeException {
    private final String code;
    private final Map<String, Object> data;

    public BaseException(String code, String message) {
        super(message);
        this.code = code;
        this.data = new HashMap<>();
    }

    public BaseException(String code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
        this.data = new HashMap<>();
    }

    public BaseException withData(String key, Object value) {
        this.data.put(key, value);
        return this;
    }

    public abstract HttpStatus getHttpStatus();
}

// 具体业务异常
public class BusinessException extends BaseException {
    public BusinessException(String message) {
        super("BUSINESS_ERROR", message);
    }

    public BusinessException(String code, String message) {
        super(code, message);
    }

    @Override
    public HttpStatus getHttpStatus() {
        return HttpStatus.CONFLICT;
    }
}

public class ValidationException extends BaseException {
    public ValidationException(String message) {
        super("VALIDATION_ERROR", message);
    }

    @Override
    public HttpStatus getHttpStatus() {
        return HttpStatus.BAD_REQUEST;
    }
}

public class AuthenticationException extends BaseException {
    public AuthenticationException(String message) {
        super("AUTH_ERROR", message);
    }

    @Override
    public HttpStatus getHttpStatus() {
        return HttpStatus.UNAUTHORIZED;
    }
}

4.2 数据验证高级技巧

4.2.1 自定义验证注解
// 1. 自定义注解
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
@Documented
public @interface PhoneNumber {
    String message() default "手机号码格式不正确";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    String region() default "CN"; // 支持不同地区
}

// 2. 验证器实现
public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {
    private String region;
    private static final Map<String, Pattern> REGION_PATTERNS = new HashMap<>();

    static {
        // 中国大陆手机号
        REGION_PATTERNS.put("CN", Pattern.compile("^1[3-9]\\d{9}$"));
        // 美国手机号
        REGION_PATTERNS.put("US", Pattern.compile("^\\(?([0-9]{3})\\)?[-.\\s]?([0-9]{3})[-.\\s]?([0-9]{4})$"));
        // 香港手机号
        REGION_PATTERNS.put("HK", Pattern.compile("^[569]\\d{3}\\d{4}$"));
    }

    @Override
    public void initialize(PhoneNumber constraintAnnotation) {
        this.region = constraintAnnotation.region();
    }

    @Override
    public boolean isValid(String phoneNumber, ConstraintValidatorContext context) {
        if (phoneNumber == null) {
            return true; // 使用@NotNull 处理空值
        }
        Pattern pattern = REGION_PATTERNS.get(region);
        if (pattern == null) {
            throw new IllegalArgumentException("不支持的地区:" + region);
        }
        return pattern.matcher(phoneNumber).matches();
    }
}

// 3. 使用自定义注解
@Data
public class ContactDTO {
    @NotBlank(message = "姓名不能为空")
    private String name;

    @PhoneNumber(region = "CN", message = "请输入有效的中国大陆手机号")
    private String phone;

    @Email(message = "邮箱格式不正确")
    private String email;

    // 跨字段验证
    @AssertTrue(message = "至少提供一种联系方式")
    public boolean isContactInfoProvided() {
        return StringUtils.hasText(phone) || StringUtils.hasText(email);
    }
}
4.2.2 分组验证
// 1. 定义验证组
public interface ValidationGroups {
    interface Create {}
    interface Update {}
    interface Patch {}
}

// 2. 在 DTO 中使用分组
@Data
public class UserDTO {
    @Null(groups = Create.class, message = "ID 必须为空")
    @NotNull(groups = {Update.class, Patch.class}, message = "ID 不能为空")
    private Long id;

    @NotBlank(groups = Create.class, message = "用户名不能为空")
    @Size(min = 3, max = 50, groups = {Create.class, Update.class})
    private String username;

    @Email(groups = {Create.class, Update.class})
    private String email;

    @NotBlank(groups = Create.class, message = "密码不能为空")
    @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d).{8,}$", groups = Create.class)
    private String password;

    @NotNull(groups = Create.class)
    private UserRole role;
}

// 3. 在控制器中使用分组验证
@RestController
@RequestMapping("/api/users")
public class UserController {
    @PostMapping
    public ResponseEntity<UserDTO> createUser(@Validated(ValidationGroups.Create.class) @RequestBody UserDTO userDTO) {
        // 创建逻辑
    }

    @PutMapping("/{id}")
    public ResponseEntity<UserDTO> updateUser(@PathVariable Long id, @Validated(ValidationGroups.Update.class) @RequestBody UserDTO userDTO) {
        // 更新逻辑
    }

    @PatchMapping("/{id}")
    public ResponseEntity<UserDTO> patchUser(@PathVariable Long id, @Validated(ValidationGroups.Patch.class) @RequestBody UserDTO userDTO) {
        // 部分更新逻辑
    }
}

五、安全与认证授权

5.1 Spring Security 集成

5.1.1 安全配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {
    private final JwtAuthenticationFilter jwtAuthenticationFilter;
    private final UserDetailsService userDetailsService;
    private final AuthenticationEntryPoint authenticationEntryPoint;
    private final AccessDeniedHandler accessDeniedHandler;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 禁用 CSRF(REST API 通常不需要)
            .csrf().disable()
            // 会话管理(无状态)
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
            // 异常处理
            .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
            .accessDeniedHandler(accessDeniedHandler).and()
            // 授权配置
            .authorizeHttpRequests(auth -> auth
                // 公开接口
                .requestMatchers("/api/auth/**", "/swagger-ui/**", "/v3/api-docs/**", "/actuator/health").permitAll()
                // 需要认证的接口
                .requestMatchers("/api/users/**").hasAnyRole("USER", "ADMIN")
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                // 其他接口需要认证
                .anyRequest().authenticated())
            // 添加 JWT 过滤器
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
            // 记住我功能(可选)
            .rememberMe().tokenRepository(persistentTokenRepository()).tokenValiditySeconds(86400)// 24 小时
            .and()
            // 安全头配置
            .headers(headers -> headers
                .contentSecurityPolicy("default-src 'self'")
                .frameOptions().sameOrigin());
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        return tokenRepository;
    }
}
5.1.2 JWT 认证实现
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JwtTokenProvider tokenProvider;
    private final UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        try {
            // 从请求头获取 Token
            String token = extractToken(request);
            if (token != null && tokenProvider.validateToken(token)) {
                // 从 Token 中获取用户名
                String username = tokenProvider.getUsernameFromToken(token);
                // 加载用户详情
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                // 创建认证对象
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                // 设置详情
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                // 设置安全上下文
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception ex) {
            logger.error("无法设置用户认证", ex);
        }
        filterChain.doFilter(request, response);
    }

    private String extractToken(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

@Component
public class JwtTokenProvider {
    @Value("${app.jwt.secret}")
    private String secret;

    @Value("${app.jwt.expiration}")
    private Long expiration;

    public String generateToken(Authentication authentication) {
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + expiration);
        return Jwts.builder()
                .setSubject(userDetails.getUsername())
                .setIssuedAt(now)
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    public String getUsernameFromToken(String token) {
        Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        return claims.getSubject();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
            return true;
        } catch (SignatureException ex) {
            logger.error("无效的 JWT 签名");
        } catch (MalformedJwtException ex) {
            logger.error("无效的 JWT 令牌");
        } catch (ExpiredJwtException ex) {
            logger.error("JWT 令牌已过期");
        } catch (UnsupportedJwtException ex) {
            logger.error("不支持的 JWT 令牌");
        } catch (IllegalArgumentException ex) {
            logger.error("JWT claims 字符串为空");
        }
        return false;
    }
}

5.2 方法级安全控制

5.2.1 基于注解的权限控制
@RestController
@RequestMapping("/api/products")
public class ProductController {
    // 基于角色的访问控制
    @PreAuthorize("hasRole('ADMIN')")
    @PostMapping
    public ProductDTO createProduct(@Valid @RequestBody ProductCreateDTO dto) {
        return productService.create(dto);
    }

    // 基于权限的访问控制
    @PreAuthorize("hasAuthority('PRODUCT_READ')")
    @GetMapping("/{id}")
    public ProductDTO getProduct(@PathVariable Long id) {
        return productService.getById(id);
    }

    // 基于表达式的复杂权限控制
    @PreAuthorize("hasRole('ADMIN') or @productSecurity.isOwner(#id, authentication)")
    @PutMapping("/{id}")
    public ProductDTO updateProduct(@PathVariable Long id, @Valid @RequestBody ProductUpdateDTO dto) {
        return productService.update(id, dto);
    }

    // 方法调用后权限检查
    @PostAuthorize("returnObject.status != 'DELETED'")
    @GetMapping("/secure/{id}")
    public ProductDTO getSecureProduct(@PathVariable Long id) {
        return productService.getById(id);
    }

    // 基于过滤器的权限控制
    @PreFilter("filterObject.ownerId == authentication.principal.id")
    @PostMapping("/batch")
    public List<ProductDTO> createProducts(@RequestBody List<ProductCreateDTO> products) {
        return productService.createBatch(products);
    }

    // 方法调用后过滤
    @PostFilter("filterObject.price > 100")
    @GetMapping("/expensive")
    public List<ProductDTO> getExpensiveProducts() {
        return productService.getAll();
    }
}

// 自定义安全表达式处理器
@Component("productSecurity")
public class ProductSecurity {
    private final ProductRepository productRepository;

    public boolean isOwner(Long productId, Authentication authentication) {
        if (authentication == null || !authentication.isAuthenticated()) {
            return false;
        }
        String username = authentication.getName();
        Optional<Product> product = productRepository.findById(productId);
        return product.isPresent() && product.get().getCreatedBy().equals(username);
    }

    public boolean canView(Product product, Authentication authentication) {
        // 复杂的业务逻辑判断
        if (product.isPublic()) {
            return true;
        }
        if (authentication == null) {
            return false;
        }
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        return product.getOwners().contains(userDetails.getUsername()) ||
                userDetails.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
    }
}
5.2.2 权限缓存与性能优化
@Configuration
@EnableCaching
public class SecurityCacheConfig {
    @Bean
    public CacheManager cacheManager() {
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
        cacheManager.setCacheNames(Arrays.asList("userDetails", "permissions", "aclCache"));
        return cacheManager;
    }
}

@Service
public class CachingUserDetailsService implements UserDetailsService {
    private final UserDetailsService delegate;
    private final CacheManager cacheManager;

    @Cacheable(value = "userDetails", key = "#username")
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return delegate.loadUserByUsername(username);
    }

    @CacheEvict(value = "userDetails", key = "#username")
    public void evictUserCache(String username) {
        // 缓存清除
    }
}

// 权限缓存服务
@Service
public class PermissionCacheService {
    @Cacheable(value = "permissions", key = "#userId + ':' + #resource")
    public boolean hasPermission(Long userId, String resource, String action) {
        // 从数据库查询权限
        return permissionRepository.existsByUserIdAndResourceAndAction(userId, resource, action);
    }

    @CacheEvict(value = "permissions", allEntries = true)
    public void clearAllPermissionCache() {
        // 清除所有权限缓存
    }
}

六、API 文档与测试

6.1 OpenAPI/Swagger 集成

6.1.1 Springdoc OpenAPI 配置
@Configuration
@OpenAPIDefinition(
        info = @Info(
                title = "订单管理系统 API",
                version = "1.0.0",
                description = "订单管理系统 REST API 文档",
                contact = @Contact(
                        name = "技术支持",
                        email = "[email protected]",
                        url = "https://example.com"
                ),
                license = @License(
                        name = "Apache 2.0",
                        url = "https://www.apache.org/licenses/LICENSE-2.0"
                ),
                termsOfService = "https://example.com/terms"
        ),
        servers = {
                @Server(url = "http://localhost:8080", description = "开发环境"),
                @Server(url = "https://api.example.com", description = "生产环境")
        },
        security = @SecurityRequirement(name = "bearerAuth")
)
@SecurityScheme(
        name = "bearerAuth",
        type = SecuritySchemeType.HTTP,
        bearerFormat = "JWT",
        scheme = "bearer"
)
public class OpenApiConfig {
    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
                .components(new Components()
                        .addSchemas("ErrorResponse", new Schema<ErrorResponse>()
                                .type("object")
                                .addProperty("timestamp", new Schema<String>().type("string").format("date-time"))
                                .addProperty("status", new Schema<Integer>().type("integer"))
                                .addProperty("error", new Schema<String>().type("string"))
                                .addProperty("message", new Schema<String>().type("string"))
                                .addProperty("path", new Schema<String>().type("string")))
                        .addSecuritySchemes("bearerAuth", new SecurityScheme()
                                .type(SecurityScheme.Type.HTTP)
                                .scheme("bearer")
                                .bearerFormat("JWT")))
                .externalDocs(new ExternalDocumentation()
                        .description("更多文档")
                        .url("https://docs.example.com"));
    }

    @Bean
    public GroupedOpenApi publicApi() {
        return GroupedOpenApi.builder()
                .group("public")
                .pathsToMatch("/api/**")
                .build();
    }

    @Bean
    public GroupedOpenApi adminApi() {
        return GroupedOpenApi.builder()
                .group("admin")
                .pathsToMatch("/api/admin/**")
                .build();
    }
}
6.1.2 控制器文档注解
@RestController
@RequestMapping("/api/orders")
@Tag(name = "订单管理", description = "订单相关操作")
public class OrderController {
    @Operation(
            summary = "获取订单列表",
            description = "分页获取订单列表,支持多种查询条件",
            parameters = {
                    @Parameter(name = "page", description = "页码,从 0 开始", example = "0"),
                    @Parameter(name = "size", description = "每页大小", example = "20"),
                    @Parameter(name = "status", description = "订单状态"),
                    @Parameter(name = "startDate", description = "开始日期", example = "2024-01-01"),
                    @Parameter(name = "endDate", description = "结束日期", example = "2024-12-31")
            }
    )
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "成功获取订单列表",
                    content = @Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = OrderDTO.class)))),
            @ApiResponse(responseCode = "401", description = "未授权访问"),
            @ApiResponse(responseCode = "403", description = "权限不足")
    })
    @GetMapping
    @PreAuthorize("hasRole('USER')")
    public Page<OrderDTO> getOrders(@ParameterObject Pageable pageable, @ParameterObject OrderQueryDTO query) {
        return orderService.getOrders(query, pageable);
    }

    @Operation(summary = "创建订单", description = "创建新订单,需要商品信息和收货地址")
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public ResponseEntity<OrderDTO> createOrder(
            @io.swagger.v3.oas.annotations.parameters.RequestBody(
                    description = "订单创建信息", required = true,
                    content = @Content(schema = @Schema(implementation = OrderCreateDTO.class)))
            @Valid @RequestBody OrderCreateDTO dto) {
        OrderDTO created = orderService.createOrder(dto);
        URI location = ServletUriComponentsBuilder.fromCurrentRequest()
                .path("/{id}").buildAndExpand(created.getId()).toUri();
        return ResponseEntity.created(location).body(created);
    }

    @Operation(summary = "获取订单详情", description = "根据 ID 获取订单详细信息")
    @GetMapping("/{id}")
    public OrderDTO getOrder(@Parameter(description = "订单 ID", required = true, example = "123") @PathVariable Long id) {
        return orderService.getOrderById(id);
    }

    @Operation(summary = "更新订单状态", description = "更新订单状态,支持取消、完成等操作")
    @PatchMapping("/{id}/status")
    public OrderDTO updateOrderStatus(@Parameter(description = "订单 ID", required = true) @PathVariable Long id,
                                      @RequestBody OrderStatusUpdateDTO dto) {
        return orderService.updateStatus(id, dto);
    }

    @Operation(summary = "导出订单", description = "导出订单数据为 Excel 文件")
    @GetMapping("/export")
    public void exportOrders(@ParameterObject OrderQueryDTO query, HttpServletResponse response) throws IOException {
        List<OrderDTO> orders = orderService.exportOrders(query);
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Content-Disposition", "attachment; filename=orders.xlsx");
        exportService.exportToExcel(orders, response.getOutputStream());
    }
}

6.2 全面测试策略

6.2.1 单元测试
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @Mock
    private UserRepository userRepository;

    @Mock
    private PasswordEncoder passwordEncoder;

    @Mock
    private EmailService emailService;

    @InjectMocks
    private UserServiceImpl userService;

    private UserMapper userMapper = Mappers.getMapper(UserMapper.class);

    @Test
    void createUser_ShouldReturnUserDTO_WhenValidInput() {
        // 准备测试数据
        UserCreateDTO createDTO = new UserCreateDTO();
        createDTO.setUsername("testuser");
        createDTO.setEmail("[email protected]");
        createDTO.setPassword("password123");
        User user = new User();
        user.setId(1L);
        user.setUsername("testuser");
        user.setEmail("[email protected]");

        // 设置 Mock 行为
        when(userRepository.existsByEmail(anyString())).thenReturn(false);
        when(passwordEncoder.encode(anyString())).thenReturn("encodedPassword");
        when(userRepository.save(any(User.class))).thenReturn(user);

        // 执行测试
        UserDTO result = userService.createUser(createDTO);

        // 验证结果
        assertNotNull(result);
        assertEquals(1L, result.getId());
        assertEquals("testuser", result.getUsername());
        assertEquals("[email protected]", result.getEmail());

        // 验证交互
        verify(userRepository).existsByEmail("[email protected]");
        verify(passwordEncoder).encode("password123");
        verify(userRepository).save(any(User.class));
        verify(emailService).sendWelcomeEmail("[email protected]");
    }

    @Test
    void createUser_ShouldThrowException_WhenEmailExists() {
        // 准备测试数据
        UserCreateDTO createDTO = new UserCreateDTO();
        createDTO.setEmail("[email protected]");

        // 设置 Mock 行为
        when(userRepository.existsByEmail("[email protected]")).thenReturn(true);

        // 执行测试并验证异常
        BusinessException exception = assertThrows(BusinessException.class, () -> userService.createUser(createDTO));
        assertEquals("邮箱已存在", exception.getMessage());
    }

    @Test
    @DisplayName("根据 ID 获取用户 - 用户存在")
    void getUserById_ShouldReturnUser_WhenUserExists() {
        // 准备测试数据
        Long userId = 1L;
        User user = new User();
        user.setId(userId);
        user.setUsername("testuser");

        // 设置 Mock 行为
        when(userRepository.findById(userId)).thenReturn(Optional.of(user));

        // 执行测试
        UserDTO result = userService.getUserById(userId);

        // 验证结果
        assertNotNull(result);
        assertEquals(userId, result.getId());
        assertEquals("testuser", result.getUsername());
    }

    @Test
    @DisplayName("根据 ID 获取用户 - 用户不存在")
    void getUserById_ShouldThrowException_WhenUserNotFound() {
        // 设置 Mock 行为
        when(userRepository.findById(anyLong())).thenReturn(Optional.empty());

        // 执行测试并验证异常
        ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, () -> userService.getUserById(1L));
        assertEquals("用户不存在", exception.getMessage());
    }

    @ParameterizedTest
    @CsvSource({"1, admin, ADMIN", "2, user, USER", "3, manager, MANAGER"})
    void getUserById_WithDifferentUsers_ShouldReturnCorrectUser(Long id, String username, UserRole role) {
        User user = new User();
        user.setId(id);
        user.setUsername(username);
        user.setRole(role);
        when(userRepository.findById(id)).thenReturn(Optional.of(user));

        UserDTO result = userService.getUserById(id);
        assertEquals(id, result.getId());
        assertEquals(username, result.getUsername());
        assertEquals(role, result.getRole());
    }

    @Test
    void updateUser_ShouldUpdateAndReturnUser() {
        // 准备测试数据
        Long userId = 1L;
        UserUpdateDTO updateDTO = new UserUpdateDTO();
        updateDTO.setUsername("updateduser");
        updateDTO.setEmail("[email protected]");
        User existingUser = new User();
        existingUser.setId(userId);
        existingUser.setUsername("olduser");
        existingUser.setEmail("[email protected]");
        User updatedUser = new User();
        updatedUser.setId(userId);
        updatedUser.setUsername("updateduser");
        updatedUser.setEmail("[email protected]");

        // 设置 Mock 行为
        when(userRepository.findById(userId)).thenReturn(Optional.of(existingUser));
        when(userRepository.save(any(User.class))).thenReturn(updatedUser);

        // 执行测试
        UserDTO result = userService.updateUser(userId, updateDTO);

        // 验证结果
        assertEquals("updateduser", result.getUsername());
        assertEquals("[email protected]", result.getEmail());

        // 验证交互
        verify(userRepository).findById(userId);
        verify(userRepository).save(existingUser);
        assertEquals("updateduser", existingUser.getUsername());
        assertEquals("[email protected]", existingUser.getEmail());
    }
}
6.2.2 集成测试
@SpringBootTest
@AutoConfigureMockMvc
@Testcontainers
@Transactional
class UserControllerIntegrationTest {
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private UserRepository userRepository;

    @BeforeEach
    void setUp() {
        userRepository.deleteAll();
    }

    @Test
    void createUser_ShouldReturnCreatedUser() throws Exception {
        // 准备测试数据
        UserCreateDTO createDTO = new UserCreateDTO();
        createDTO.setUsername("integrationtest");
        createDTO.setEmail("[email protected]");
        createDTO.setPassword("Password123!");
        createDTO.setRole(UserRole.USER);

        // 执行请求
        mockMvc.perform(post("/api/users")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(createDTO)))
                // 验证响应
                .andExpect(status().isCreated())
                .andExpect(header().exists("Location"))
                .andExpect(jsonPath("$.id").exists())
                .andExpect(jsonPath("$.username").value("integrationtest"))
                .andExpect(jsonPath("$.email").value("[email protected]"))
                .andExpect(jsonPath("$.role").value("USER"));

        // 验证数据库
        Optional<User> savedUser = userRepository.findByEmail("[email protected]");
        assertTrue(savedUser.isPresent());
        assertEquals("integrationtest", savedUser.get().getUsername());
    }

    @Test
    void createUser_ShouldReturnBadRequest_WhenInvalidInput() throws Exception {
        // 准备无效的测试数据
        UserCreateDTO createDTO = new UserCreateDTO();
        createDTO.setEmail("invalid-email");

        mockMvc.perform(post("/api/users")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(createDTO)))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.errors").exists());
    }

    @Test
    void getUser_ShouldReturnUser_WhenUserExists() throws Exception {
        // 准备测试数据
        User user = new User();
        user.setUsername("testuser");
        user.setEmail("[email protected]");
        user.setPassword("encodedPassword");
        user.setRole(UserRole.USER);
        user = userRepository.save(user);

        // 执行请求
        mockMvc.perform(get("/api/users/{id}", user.getId()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(user.getId()))
                .andExpect(jsonPath("$.username").value("testuser"))
                .andExpect(jsonPath("$.email").value("[email protected]"));
    }

    @Test
    void getUser_ShouldReturnNotFound_WhenUserNotExists() throws Exception {
        mockMvc.perform(get("/api/users/{id}", 999))
                .andExpect(status().isNotFound());
    }

    @Test
    void updateUser_ShouldUpdateUser() throws Exception {
        // 创建测试用户
        User user = new User();
        user.setUsername("olduser");
        user.setEmail("[email protected]");
        user.setPassword("encodedPassword");
        user.setRole(UserRole.USER);
        user = userRepository.save(user);

        // 准备更新数据
        UserUpdateDTO updateDTO = new UserUpdateDTO();
        updateDTO.setUsername("updateduser");
        updateDTO.setEmail("[email protected]");

        // 执行更新请求
        mockMvc.perform(put("/api/users/{id}", user.getId())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(updateDTO)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.username").value("updateduser"))
                .andExpect(jsonPath("$.email").value("[email protected]"));

        // 验证数据库更新
        Optional<User> updatedUser = userRepository.findById(user.getId());
        assertTrue(updatedUser.isPresent());
        assertEquals("updateduser", updatedUser.get().getUsername());
        assertEquals("[email protected]", updatedUser.get().getEmail());
    }

    @Test
    void deleteUser_ShouldDeleteUser() throws Exception {
        // 创建测试用户
        User user = new User();
        user.setUsername("tobedeleted");
        user.setEmail("[email protected]");
        user.setPassword("encodedPassword");
        user = userRepository.save(user);

        // 执行删除请求
        mockMvc.perform(delete("/api/users/{id}", user.getId()))
                .andExpect(status().isNoContent());

        // 验证用户已删除
        assertFalse(userRepository.existsById(user.getId()));
    }

    @Test
    void getUsers_ShouldReturnPaginatedUsers() throws Exception {
        // 创建测试数据
        for (int i = 1; i <= 25; i++) {
            User user = new User();
            user.setUsername("user" + i);
            user.setEmail("user" + i + "@example.com");
            user.setPassword("password" + i);
            userRepository.save(user);
        }

        // 执行分页查询
        mockMvc.perform(get("/api/users")
                        .param("page", "0")
                        .param("size", "10")
                        .param("sort", "username,asc"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.content").isArray())
                .andExpect(jsonPath("$.content.length()").value(10))
                .andExpect(jsonPath("$.totalPages").value(3))
                .andExpect(jsonPath("$.totalElements").value(25))
                .andExpect(jsonPath("$.first").value(true))
                .andExpect(jsonPath("$.last").value(false));
    }

    @Test
    @WithMockUser(username = "admin", roles = {"ADMIN"})
    void adminEndpoint_ShouldBeAccessible_ForAdminUser() throws Exception {
        mockMvc.perform(get("/api/admin/users"))
                .andExpect(status().isOk());
    }

    @Test
    @WithMockUser(username = "user", roles = {"USER"})
    void adminEndpoint_ShouldBeForbidden_ForNonAdminUser() throws Exception {
        mockMvc.perform(get("/api/admin/users"))
                .andExpect(status().isForbidden());
    }
}

七、部署与监控

7.1 Docker 容器化部署

7.1.1 Dockerfile 配置
# 构建阶段
FROM maven:3.8.4-openjdk-17-slim AS build
# 设置工作目录
WORKDIR /app
# 复制项目文件
COPY pom.xml .
COPY src ./src
# 下载依赖并构建(利用 Docker 层缓存)
RUN mvn dependency:go-offline
RUN mvn clean package -DskipTests

# 运行时阶段
FROM openjdk:17-jdk-slim
# 安装必要的工具
RUN apt-get update && apt-get install -y \
    curl \
    tzdata \
    && rm -rf /var/lib/apt/lists/*
# 设置时区
ENV TZ=Asia/Shanghai
# 创建非 root 用户
RUN groupadd -r spring && useradd -r -g spring spring
USER spring:spring
# 设置工作目录
WORKDIR /app
# 从构建阶段复制 JAR 文件
COPY --from=build /app/target/*.jar app.jar
# 暴露端口
EXPOSE 8080
# JVM 参数
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp"
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
    CMD curl -f http://localhost:8080/actuator/health || exit 1
# 启动命令
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar /app/app.jar"]
7.1.2 Docker Compose 配置
version: '3.8'
services:
  # 主应用服务
  app:
    build: .
    container_name: spring-app
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/appdb
      - SPRING_DATASOURCE_USERNAME=appuser
      - SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD}
      - SPRING_REDIS_HOST=redis
      - SPRING_REDIS_PORT=6379
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - backend
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  # PostgreSQL 数据库
  postgres:
    image: postgres:15-alpine
    container_name: app-postgres
    environment:
      - POSTGRES_DB=appdb
      - POSTGRES_USER=appuser
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init-db:/docker-entrypoint-initdb.d
    ports:
      - "5432:5432"
    networks:
      - backend
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Redis 缓存
  redis:
    image: redis:7-alpine
    container_name: app-redis
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    ports:
      - "6379:6379"
    networks:
      - backend
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Nginx 反向代理
  nginx:
    image: nginx:alpine
    container_name: app-nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - app
    networks:
      - backend
    restart: unless-stopped

  # 监控系统 (Prometheus + Grafana)
  prometheus:
    image: prom/prometheus:latest
    container_name: app-prometheus
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    ports:
      - "9090:9090"
    networks:
      - backend
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    container_name: app-grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    ports:
      - "3000:3000"
    networks:
      - backend
    restart: unless-stopped

networks:
  backend:
    driver: bridge

volumes:
  postgres_data:
  redis_data:
  prometheus_data:
  grafana_data:

7.2 性能监控与指标

7.2.1 Spring Boot Actuator 配置
# application-prod.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
      base-path: /actuator
      cors:
        allowed-origins: "*"
        allowed-methods: GET,POST
  endpoint:
    health:
      show-details: when_authorized
      roles: ADMIN
      probes:
        enabled: true
      groups:
        liveness: include: livenessState
        readiness: include: readinessState
  metrics:
    enabled: true
    prometheus:
      enabled: true
    metrics:
      export:
        prometheus:
          enabled: true
          step: 1m
      distribution:
        percentiles-histogram: "[http.server.requests]": true
        sla: "[http.server.requests]": 10ms, 50ms, 100ms, 200ms, 500ms, 1s, 2s
  info:
    env:
      enabled: true
    java:
      enabled: true
    os:
      enabled: true
  tracing:
    sampling:
      probability: 0.1
server:
  port: 8081 # 单独的监控端口
7.2.2 自定义健康检查
@Component
public class DatabaseHealthIndicator implements HealthIndicator {
    private final DataSource dataSource;
    private final JdbcTemplate jdbcTemplate;

    public DatabaseHealthIndicator(DataSource dataSource) {
        this.dataSource = dataSource;
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Override
    public Health health() {
        try {
            // 检查数据库连接
            Integer result = jdbcTemplate.queryForObject("SELECT 1", Integer.class);
            if (result != null && result == 1) {
                // 检查数据库性能
                Map<String, Object> details = new HashMap<>();
                // 获取连接池信息
                if (dataSource instanceof HikariDataSource) {
                    HikariDataSource hikari = (HikariDataSource) dataSource;
                    details.put("activeConnections", hikari.getHikariPoolMXBean().getActiveConnections());
                    details.put("idleConnections", hikari.getHikariPoolMXBean().getIdleConnections());
                    details.put("totalConnections", hikari.getHikariPoolMXBean().getTotalConnections());
                }
                return Health.up().withDetails(details).build();
            }
            return Health.down().withDetail("error", "数据库查询返回异常结果").build();
        } catch (Exception e) {
            return Health.down().withException(e).withDetail("error", "数据库连接失败:" + e.getMessage()).build();
        }
    }
}

@Component
public class CacheHealthIndicator implements HealthIndicator {
    private final CacheManager cacheManager;

    public CacheHealthIndicator(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    @Override
    public Health health() {
        Map<String, Object> details = new HashMap<>();
        for (String cacheName : cacheManager.getCacheNames()) {
            Cache cache = cacheManager.getCache(cacheName);
            if (cache != null && cache.getNativeCache() instanceof com.github.benmanes.caffeine.cache.Cache) {
                @SuppressWarnings("unchecked")
                com.github.benmanes.caffeine.cache.Cache<Object, Object> caffeineCache =
                        (com.github.benmanes.caffeine.cache.Cache<Object, Object>) cache.getNativeCache();
                Map<String, Object> cacheStats = new HashMap<>();
                cacheStats.put("estimatedSize", caffeineCache.estimatedSize());
                cacheStats.put("stats", caffeineCache.stats());
                details.put(cacheName, cacheStats);
            }
        }
        return Health.up().withDetails(details).build();
    }
}

@Component
public class ExternalServiceHealthIndicator extends AbstractHealthIndicator {
    private final RestTemplate restTemplate;
    private final String serviceUrl;

    public ExternalServiceHealthIndicator(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder
                .setConnectTimeout(Duration.ofSeconds(5))
                .setReadTimeout(Duration.ofSeconds(10))
                .build();
        this.serviceUrl = "https://api.external-service.com/health";
    }

    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        try {
            ResponseEntity<String> response = restTemplate.getForEntity(serviceUrl, String.class);
            if (response.getStatusCode().is2xxSuccessful()) {
                builder.up().withDetail("status", response.getStatusCode().value()).withDetail("responseTime", "正常");
            } else {
                builder.down().withDetail("status", response.getStatusCode().value()).withDetail("error", "外部服务返回异常状态码");
            }
        } catch (ResourceAccessException e) {
            builder.down().withException(e).withDetail("error", "连接外部服务超时");
        } catch (Exception e) {
            builder.down().withException(e).withDetail("error", "检查外部服务健康状态时发生异常");
        }
    }
}

八、性能优化与最佳实践

8.1 数据库性能优化

8.1.1 连接池配置
# application-prod.yml
spring:
  datasource:
    hikari:
      # 连接池配置
      maximum-pool-size: 20
      minimum-idle: 10
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
      # 性能优化
      auto-commit: false
      connection-test-query: SELECT 1
      validation-timeout: 5000
      leak-detection-threshold: 60000
      # 连接属性
      data-source-properties:
        prepStmtCacheSize: 250
        prepStmtCacheSqlLimit: 2048
        cachePrepStmts: true
        useServerPrepStmts: true
        useLocalSessionState: true
        rewriteBatchedStatements: true
        cacheResultSetMetadata: true
        cacheServerConfiguration: true
        elideSetAutoCommits: true
        maintainTimeStats: false
8.1.2 JPA 性能优化
@Configuration
@EnableJpaRepositories(basePackages = "com.example.repository", repositoryBaseClass = CustomJpaRepositoryImpl.class)
@EnableTransactionManagement
public class JpaConfig {
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, DataSource dataSource) {
        Map<String, Object> properties = new HashMap<>();
        properties.put("hibernate.jdbc.batch_size", 50);
        properties.put("hibernate.order_inserts", true);
        properties.put("hibernate.order_updates", true);
        properties.put("hibernate.batch_versioned_data", true);
        properties.put("hibernate.query.in_clause_parameter_padding", true);
        properties.put("hibernate.default_batch_fetch_size", 16);
        properties.put("hibernate.max_fetch_depth", 3);
        properties.put("hibernate.jdbc.fetch_size", 100);
        // 生产环境禁用 DDL 自动更新
        properties.put("hibernate.hbm2ddl.auto", "validate");
        return builder
                .dataSource(dataSource)
                .packages("com.example.entity")
                .persistenceUnit("default")
                .properties(properties)
                .build();
    }

    @Bean
    public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }
}

// 自定义 Repository 实现
@NoRepositoryBean
public class CustomJpaRepositoryImpl<T, ID> extends SimpleJpaRepository<T, ID> implements CustomJpaRepository<T, ID> {
    private final EntityManager entityManager;

    public CustomJpaRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityManager = entityManager;
    }

    @Override
    @Transactional(readOnly = true)
    public List<T> findAllWithPagination(int offset, int limit, Sort sort) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<T> query = cb.createQuery(getDomainClass());
        Root<T> root = query.from(getDomainClass());
        query.select(root);
        if (sort != null) {
            List<Order> orders = new ArrayList<>();
            sort.forEach(order -> {
                if (order.isAscending()) {
                    orders.add(cb.asc(root.get(order.getProperty())));
                } else {
                    orders.add(cb.desc(root.get(order.getProperty())));
                }
            });
            query.orderBy(orders);
        }
        return entityManager.createQuery(query).setFirstResult(offset).setMaxResults(limit).getResultList();
    }

    @Override
    @Transactional
    public int batchInsert(List<T> entities) {
        int batchSize = 50;
        int count = 0;
        for (int i = 0; i < entities.size(); i++) {
            entityManager.persist(entities.get(i));
            if (i % batchSize == 0 && i > 0) {
                entityManager.flush();
                entityManager.clear();
                count += batchSize;
            }
        }
        entityManager.flush();
        entityManager.clear();
        return count + (entities.size() % batchSize);
    }
}

8.2 缓存策略优化

8.2.1 多级缓存配置
@Configuration
@EnableCaching
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        // 全局缓存配置
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .expireAfterWrite(Duration.ofMinutes(30))
                .maximumSize(10000)
                .recordStats());

        // 自定义缓存配置
        Map<String, Caffeine<Object, Object>> cacheConfigs = new HashMap<>();
        // 用户缓存 - 较短时间,高频访问
        cacheConfigs.put("users", Caffeine.newBuilder()
                .expireAfterWrite(Duration.ofMinutes(10))
                .maximumSize(1000)
                .recordStats());
        // 商品缓存 - 较长时间,低频更新
        cacheConfigs.put("products", Caffeine.newBuilder()
                .expireAfterWrite(Duration.ofHours(2))
                .maximumSize(5000)
                .recordStats());
        // 配置缓存 - 永不过期,手动刷新
        cacheConfigs.put("config", Caffeine.newBuilder()
                .maximumSize(100)
                .recordStats());
        cacheManager.setCacheSpecification(cacheConfigs);
        return cacheManager;
    }

    @Bean
    public CacheManager redisCacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(1))
                .disableCachingNullValues()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        // 不同缓存不同配置
        Map<String, RedisCacheConfiguration> cacheConfigs = new HashMap<>();
        cacheConfigs.put("users", config.entryTtl(Duration.ofMinutes(30)));
        cacheConfigs.put("products", config.entryTtl(Duration.ofHours(2)));
        return RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .withInitialCacheConfigurations(cacheConfigs)
                .transactionAware()
                .build();
    }

    @Bean
    public CacheManager multiLevelCacheManager(CacheManager localCacheManager, CacheManager redisCacheManager) {
        return new MultiLevelCacheManager(localCacheManager, redisCacheManager);
    }
}

// 多级缓存实现
public class MultiLevelCacheManager implements CacheManager {
    private final CacheManager localCacheManager; // L1: Caffeine
    private final CacheManager redisCacheManager; // L2: Redis

    public MultiLevelCacheManager(CacheManager localCacheManager, CacheManager redisCacheManager) {
        this.localCacheManager = localCacheManager;
        this.redisCacheManager = redisCacheManager;
    }

    @Override
    public Cache getCache(String name) {
        Cache localCache = localCacheManager.getCache(name);
        Cache remoteCache = redisCacheManager.getCache(name);
        return new MultiLevelCache(name, localCache, remoteCache);
    }

    @Override
    public Collection<String> getCacheNames() {
        Set<String> names = new HashSet<>();
        names.addAll(localCacheManager.getCacheNames());
        names.addAll(redisCacheManager.getCacheNames());
        return names;
    }
}

class MultiLevelCache implements Cache {
    private final String name;
    private final Cache localCache;
    private final Cache remoteCache;

    public MultiLevelCache(String name, Cache localCache, Cache remoteCache) {
        this.name = name;
        this.localCache = localCache;
        this.remoteCache = remoteCache;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public Object getNativeCache() {
        return remoteCache.getNativeCache();
    }

    @Override
    public ValueWrapper get(Object key) {
        // 先查本地缓存
        ValueWrapper value = localCache.get(key);
        if (value != null) {
            return value;
        }
        // 本地缓存未命中,查 Redis
        value = remoteCache.get(key);
        if (value != null) {
            // 回写到本地缓存
            localCache.put(key, value.get());
        }
        return value;
    }

    @Override
    public <T> T get(Object key, Class<T> type) {
        // 实现类似 get 方法
        T value = localCache.get(key, type);
        if (value != null) {
            return value;
        }
        value = remoteCache.get(key, type);
        if (value != null) {
            localCache.put(key, value);
        }
        return value;
    }

    @Override
    public void put(Object key, Object value) {
        // 同时写入两级缓存
        localCache.put(key, value);
        remoteCache.put(key, value);
    }

    @Override
    public void evict(Object key) {
        // 同时清除两级缓存
        localCache.evict(key);
        remoteCache.evict(key);
    }

    @Override
    public void clear() {
        localCache.clear();
        remoteCache.clear();
    }
}

九、生产环境最佳实践

9.1 应用配置管理

9.1.1 多环境配置
# application.yml (基础配置)
spring:
  application:
    name: order-service
  profiles:
    active: @spring.profiles.active@
  # 日志配置
logging:
  level:
    com.example: INFO
    org.springframework.web: INFO
    org.hibernate.SQL: WARN
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: logs/application.log
  logback:
    rollingpolicy:
      max-file-size: 10MB
      max-history: 30
# application-dev.yml (开发环境)
server:
  port: 8080
spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password:
jpa:
  hibernate:
    ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        format_sql: true
  h2:
    console:
      enabled: true
      path: /h2-console
logging:
  level:
    com.example: DEBUG
    org.springframework.web: DEBUG
# application-prod.yml (生产环境)
server:
  port: 8080
  compression:
    enabled: true
    mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
    min-response-size: 1024
  tomcat:
    max-connections: 10000
    max-threads: 200
    min-spare-threads: 10
    connection-timeout: 5000
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 10
  jpa:
    hibernate:
      ddl-auto: validate
      show-sql: false
  redis:
    host: ${REDIS_HOST:localhost}
    port: ${REDIS_PORT:6379}
    password: ${REDIS_PASSWORD:}
    timeout: 2000ms
    lettuce:
      pool:
        max-active: 20
        max-idle: 10
        min-idle: 5
# 生产环境日志配置
logging:
  level:
    com.example: INFO
    org.springframework.web: WARN
    org.hibernate: WARN
  logstash:
    enabled: true
    host: ${LOGSTASH_HOST}
    port: ${LOGSTASH_PORT}

9.2 监控告警配置

9.2.1 Prometheus 监控配置
# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s
alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - alertmanager:9093
rule_files:
  - "alert_rules.yml"
scrape_configs:
  - job_name: 'spring-boot-app'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['app:8080']
        labels:
          application: 'order-service'
          environment: 'production'
  - job_name: 'postgres'
    static_configs:
      - targets: ['postgres-exporter:9187']
  - job_name: 'redis'
    static_configs:
      - targets: ['redis-exporter:9121']
9.2.2 告警规则配置
# alert_rules.yml
groups:
  - name: spring_boot_alerts
    rules:
      - alert: HighErrorRate
        expr: rate(http_server_requests_seconds_count{status=~"5..",uri!~".*actuator.*"}[5m]) > 0.05
        for: 2m
        labels:
          severity: critical
          team: backend
        annotations:
          summary: "高错误率报警"
          description: "{{ $labels.instance }}的错误率超过 5% (当前值:{{ $value }})"

      - alert: HighResponseTime
        expr: histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m])) > 1
        for: 5m
        labels:
          severity: warning
          team: backend
        annotations:
          summary: "高响应时间报警"
          description: "{{ $labels.instance }}的 95% 响应时间超过 1 秒 (当前值:{{ $value }}s)"

      - alert: ServiceDown
        expr: up{job="spring-boot-app"} == 0
        for: 1m
        labels:
          severity: critical
          team: backend
        annotations:
          summary: "服务宕机报警"
          description: "{{ $labels.instance }}服务已宕机"

      - alert: HighMemoryUsage
        expr: (sum(jvm_memory_used_bytes{area="heap"}) / sum(jvm_memory_max_bytes{area="heap"})) > 0.8
        for: 5m
        labels:
          severity: warning
          team: backend
        annotations:
          summary: "高内存使用率报警"
          description: "{{ $labels.instance }}内存使用率超过 80% (当前值:{{ $value }})"

      - alert: HighCPULoad
        expr: system_cpu_usage > 0.8
        for: 5m
        labels:
          severity: warning
          team: backend
        annotations:
          summary: "高 CPU 使用率报警"
          description: "{{ $labels.instance }}CPU 使用率超过 80% (当前值:{{ $value }})"

十、学习路径规划

10.1 初学者入门路径(1-2 周)

  1. 掌握 Spring Boot 基础
    • 理解 Spring Boot 自动配置原理
    • 掌握 RESTful API 设计原则
    • 学习 Spring MVC 注解使用
  2. 完成第一个 CRUD 项目
    • 创建用户管理系统
    • 实现增删改查接口
    • 添加数据验证

10.2 进阶提升路径(3-4 周)

  1. 深入 Spring 生态
    • 学习 Spring Security 实现安全控制
    • 掌握 Spring Data JPA 高级特性
    • 了解 Spring Cache 缓存机制
  2. 项目实战
    • 实现电商系统核心模块
    • 集成第三方服务(支付、短信)
    • 添加 API 文档和单元测试

10.3 专家精通路径(2-3 个月)

  1. 性能优化
    • JVM 调优实践
    • 数据库查询优化
    • 缓存策略设计
  2. 微服务架构
    • Spring Cloud 学习
    • 服务注册与发现
    • 分布式事务处理
  3. 生产实践
    • Docker 容器化部署
    • CI/CD 流水线搭建
    • 监控告警系统建设

通过以上系统学习路径,可以从 Spring Boot 新手逐步成长为 RESTful API 开发专家,掌握企业级应用开发的全套技能栈。

在这里插入图片描述

在这里插入图片描述

目录

  1. 一、为什么选择 Spring Boot:极速开发的秘密
  2. 二、极速启动:三步创建第一个 REST 接口
  3. 2.1 项目初始化
  4. 2.2 基础代码示例
  5. 三、Spring Boot RESTful 核心详解
  6. 3.1 控制器层最佳实践
  7. 3.1.1 RESTful 资源设计原则
  8. 3.1.2 高级请求处理技巧
  9. 3.2 服务层设计与实现
  10. 3.2.1 服务层架构模式
  11. 3.2.2 业务逻辑与事务管理
  12. 3.3 数据传输对象设计
  13. 3.3.1 DTO 模式实现
  14. 3.3.2 MapStruct 映射器
  15. 四、全局处理与高级特性
  16. 4.1 全局异常处理机制
  17. 4.1.1 统一异常处理器
  18. 4.1.2 自定义异常体系
  19. 4.2 数据验证高级技巧
  20. 4.2.1 自定义验证注解
  21. 4.2.2 分组验证
  22. 五、安全与认证授权
  23. 5.1 Spring Security 集成
  24. 5.1.1 安全配置
  25. 5.1.2 JWT 认证实现
  26. 5.2 方法级安全控制
  27. 5.2.1 基于注解的权限控制
  28. 5.2.2 权限缓存与性能优化
  29. 六、API 文档与测试
  30. 6.1 OpenAPI/Swagger 集成
  31. 6.1.1 Springdoc OpenAPI 配置
  32. 6.1.2 控制器文档注解
  33. 6.2 全面测试策略
  34. 6.2.1 单元测试
  35. 6.2.2 集成测试
  36. 七、部署与监控
  37. 7.1 Docker 容器化部署
  38. 7.1.1 Dockerfile 配置
  39. 构建阶段
  40. 设置工作目录
  41. 复制项目文件
  42. 下载依赖并构建(利用 Docker 层缓存)
  43. 运行时阶段
  44. 安装必要的工具
  45. 设置时区
  46. 创建非 root 用户
  47. 设置工作目录
  48. 从构建阶段复制 JAR 文件
  49. 暴露端口
  50. JVM 参数
  51. 健康检查
  52. 启动命令
  53. 7.1.2 Docker Compose 配置
  54. 主应用服务
  55. PostgreSQL 数据库
  56. Redis 缓存
  57. Nginx 反向代理
  58. 监控系统 (Prometheus + Grafana)
  59. 7.2 性能监控与指标
  60. 7.2.1 Spring Boot Actuator 配置
  61. application-prod.yml
  62. 7.2.2 自定义健康检查
  63. 八、性能优化与最佳实践
  64. 8.1 数据库性能优化
  65. 8.1.1 连接池配置
  66. application-prod.yml
  67. 8.1.2 JPA 性能优化
  68. 8.2 缓存策略优化
  69. 8.2.1 多级缓存配置
  70. 九、生产环境最佳实践
  71. 9.1 应用配置管理
  72. 9.1.1 多环境配置
  73. application.yml (基础配置)
  74. 日志配置
  75. application-dev.yml (开发环境)
  76. application-prod.yml (生产环境)
  77. 生产环境日志配置
  78. 9.2 监控告警配置
  79. 9.2.1 Prometheus 监控配置
  80. prometheus.yml
  81. 9.2.2 告警规则配置
  82. alert_rules.yml
  83. 十、学习路径规划
  84. 10.1 初学者入门路径(1-2 周)
  85. 10.2 进阶提升路径(3-4 周)
  86. 10.3 专家精通路径(2-3 个月)
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Python 列表基础:创建、操作与切片详解
  • Python 爬虫实战:解析并下载百度文库内容
  • C++ 毕业设计项目效率提升实战:从冗余代码到高性能架构
  • VSCode Copilot 无法登录原因分析及快速恢复指南
  • 快速排序算法原理与实现详解
  • 提示工程(Prompt Engineering):本质、技术与最佳实践
  • 大模型现状及行业落地趋势分析
  • Ubuntu 安装 OpenClaw 并接入飞书机器人
  • 大模型测评:千问 DeepSeek 等工具降英文 AI 率横向对比
  • 大模型 LLM 微调技术论文精选汇总
  • OpenClaw 集成飞书机器人配置指南
  • Python 循环结构详解:for-in 与 while 用法
  • Python Web UI 自动化测试:Jenkins 构建项目与定时执行
  • Java 数据结构:二叉树与哈希表详解
  • OpenClaw 接入飞书机器人与 Kimi2.5 配置指南
  • OpenClaw 配置飞书机器人与 Kimi2.5 接入指南
  • Java 互联网医疗场景核心技术面试解析
  • Java 动态代理原理与实现
  • OpenClaw 接入飞书机器人与 Kimi2.5 配置指南
  • Python 开发实战:集成本地 DocsGPT 构建智能 IDE

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online