08-Spring Boot微服务最佳实践
·
Spring Boot微服务最佳实践
概述Spring Boot作为Java生态系统中最受欢迎的微服务框架之一,提供了构建生产级微服务应用的完整解决方案。本文将深入探讨Spring Boot微服务开发的最佳实践,涵盖架构设计、服务治理、数据管理、安全性等关键方面。## 微服务架构基础### 服务拆分原则java// 按业务领域拆分服务@RestController@RequestMapping("/api/users")public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService = userService; } @GetMapping("/{id}") public ResponseEntity<UserDto> getUser(@PathVariable Long id) { UserDto user = userService.findById(id); return ResponseEntity.ok(user); } @PostMapping public ResponseEntity<UserDto> createUser(@Valid @RequestBody CreateUserRequest request) { UserDto user = userService.createUser(request); return ResponseEntity.status(HttpStatus.CREATED).body(user); }}// 用户服务实体@Entity@Table(name = "users")public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true) private String username; @Column(nullable = false) private String email; @Column(nullable = false) private String password; @CreationTimestamp private LocalDateTime createdAt; @UpdateTimestamp private LocalDateTime updatedAt; // 构造函数、getter、setter}// 用户服务业务逻辑@Service@Transactionalpublic class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; private final UserMapper userMapper; public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, UserMapper userMapper) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; this.userMapper = userMapper; } @Transactional(readOnly = true) public UserDto findById(Long id) { User user = userRepository.findById(id) .orElseThrow(() -> new UserNotFoundException("用户不存在: " + id)); return userMapper.toDto(user); } public UserDto createUser(CreateUserRequest request) { // 检查用户名是否已存在 if (userRepository.existsByUsername(request.getUsername())) { throw new UserAlreadyExistsException("用户名已存在: " + request.getUsername()); } User user = new User(); user.setUsername(request.getUsername()); user.setEmail(request.getEmail()); user.setPassword(passwordEncoder.encode(request.getPassword())); User savedUser = userRepository.save(user); return userMapper.toDto(savedUser); }}### 配置管理yaml# application.yml - 基础配置spring: application: name: user-service profiles: active: ${SPRING_PROFILES_ACTIVE:dev} # 数据库配置 datasource: url: ${DATABASE_URL:jdbc:postgresql://localhost:5432/userdb} username: ${DATABASE_USERNAME:user} password: ${DATABASE_PASSWORD:password} driver-class-name: org.postgresql.Driver # JPA配置 jpa: hibernate: ddl-auto: validate show-sql: false properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true # Redis配置 redis: host: ${REDIS_HOST:localhost} port: ${REDIS_PORT:6379} password: ${REDIS_PASSWORD:} timeout: 2000ms lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0# 服务器配置server: port: ${SERVER_PORT:8080} servlet: context-path: /user-service# 管理端点配置management: endpoints: web: exposure: include: health,info,metrics,prometheus endpoint: health: show-details: always metrics: export: prometheus: enabled: true# 日志配置logging: level: com.example.userservice: DEBUG org.springframework.security: DEBUG pattern: console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"``````java// 配置类@Configuration@EnableConfigurationProperties({AppProperties.class})public class ApplicationConfig { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public RestTemplate restTemplate() { return new RestTemplate(); } @Bean @ConditionalOnProperty(name = "app.cache.enabled", havingValue = "true") public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(10)) .serializeKeysWith(RedisSerializationContext.SerializationPair .fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair .fromSerializer(new GenericJackson2JsonRedisSerializer())); return RedisCacheManager.builder(connectionFactory) .cacheDefaults(config) .build(); }}// 自定义配置属性@ConfigurationProperties(prefix = "app")@Datapublic class AppProperties { private Security security = new Security(); private Cache cache = new Cache(); private Integration integration = new Integration(); @Data public static class Security { private String jwtSecret = "mySecret"; private long jwtExpirationMs = 86400000; // 24小时 } @Data public static class Cache { private boolean enabled = true; private int ttl = 600; // 10分钟 } @Data public static class Integration { private String orderServiceUrl = "http://order-service"; private String paymentServiceUrl = "http://payment-service"; private int timeout = 5000; }}## 服务间通信### HTTP客户端java// Feign客户端配置@FeignClient( name = "order-service", url = "${app.integration.order-service-url}", configuration = FeignConfig.class)public interface OrderServiceClient { @GetMapping("/api/orders/user/{userId}") List<OrderDto> getOrdersByUserId(@PathVariable("userId") Long userId); @PostMapping("/api/orders") OrderDto createOrder(@RequestBody CreateOrderRequest request); @GetMapping("/api/orders/{orderId}") OrderDto getOrder(@PathVariable("orderId") Long orderId);}// Feign配置@Configurationpublic class FeignConfig { @Bean public Logger.Level feignLoggerLevel() { return Logger.Level.BASIC; } @Bean public RequestInterceptor requestInterceptor() { return requestTemplate -> { // 添加认证头 String token = SecurityContextHolder.getContext() .getAuthentication() .getCredentials() .toString(); requestTemplate.header("Authorization", "Bearer " + token); // 添加请求ID String requestId = MDC.get("requestId"); if (requestId != null) { requestTemplate.header("X-Request-ID", requestId); } }; } @Bean public ErrorDecoder errorDecoder() { return new CustomErrorDecoder(); }}// 自定义错误解码器public class CustomErrorDecoder implements ErrorDecoder { private final ErrorDecoder defaultErrorDecoder = new Default(); @Override public Exception decode(String methodKey, Response response) { switch (response.status()) { case 400: return new BadRequestException("请求参数错误"); case 404: return new ResourceNotFoundException("资源不存在"); case 500: return new InternalServerException("服务内部错误"); default: return defaultErrorDecoder.decode(methodKey, response); } }}// 服务调用示例@Servicepublic class UserOrderService { private final OrderServiceClient orderServiceClient; private final CircuitBreaker circuitBreaker; public UserOrderService(OrderServiceClient orderServiceClient, CircuitBreakerFactory circuitBreakerFactory) { this.orderServiceClient = orderServiceClient; this.circuitBreaker = circuitBreakerFactory.create("order-service"); } public List<OrderDto> getUserOrders(Long userId) { return circuitBreaker.executeSupplier(() -> { try { return orderServiceClient.getOrdersByUserId(userId); } catch (Exception e) { log.error("调用订单服务失败", e); throw new ServiceUnavailableException("订单服务暂时不可用"); } }); }}### 消息队列java// RabbitMQ配置@Configuration@EnableRabbitpublic class RabbitConfig { public static final String USER_EXCHANGE = "user.exchange"; public static final String USER_CREATED_QUEUE = "user.created.queue"; public static final String USER_UPDATED_QUEUE = "user.updated.queue"; @Bean public TopicExchange userExchange() { return new TopicExchange(USER_EXCHANGE); } @Bean public Queue userCreatedQueue() { return QueueBuilder.durable(USER_CREATED_QUEUE) .withArgument("x-dead-letter-exchange", "dlx.exchange") .withArgument("x-dead-letter-routing-key", "user.created.dlq") .build(); } @Bean public Queue userUpdatedQueue() { return QueueBuilder.durable(USER_UPDATED_QUEUE) .withArgument("x-dead-letter-exchange", "dlx.exchange") .withArgument("x-dead-letter-routing-key", "user.updated.dlq") .build(); } @Bean public Binding userCreatedBinding() { return BindingBuilder .bind(userCreatedQueue()) .to(userExchange()) .with("user.created"); } @Bean public Binding userUpdatedBinding() { return BindingBuilder .bind(userUpdatedQueue()) .to(userExchange()) .with("user.updated"); } @Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate template = new RabbitTemplate(connectionFactory); template.setMessageConverter(new Jackson2JsonMessageConverter()); template.setRetryTemplate(retryTemplate()); return template; } @Bean public RetryTemplate retryTemplate() { return RetryTemplate.builder() .maxAttempts(3) .exponentialBackoff(1000, 2, 10000) .build(); }}// 事件发布@Componentpublic class UserEventPublisher { private final RabbitTemplate rabbitTemplate; public UserEventPublisher(RabbitTemplate rabbitTemplate) { this.rabbitTemplate = rabbitTemplate; } public void publishUserCreated(UserCreatedEvent event) { try { rabbitTemplate.convertAndSend( RabbitConfig.USER_EXCHANGE, "user.created", event ); log.info("用户创建事件已发布: {}", event.getUserId()); } catch (Exception e) { log.error("发布用户创建事件失败", e); throw new EventPublishException("事件发布失败", e); } } public void publishUserUpdated(UserUpdatedEvent event) { try { rabbitTemplate.convertAndSend( RabbitConfig.USER_EXCHANGE, "user.updated", event ); log.info("用户更新事件已发布: {}", event.getUserId()); } catch (Exception e) { log.error("发布用户更新事件失败", e); throw new EventPublishException("事件发布失败", e); } }}// 事件监听@Component@RabbitListener(queues = RabbitConfig.USER_CREATED_QUEUE)public class UserEventListener { private final NotificationService notificationService; public UserEventListener(NotificationService notificationService) { this.notificationService = notificationService; } @RabbitHandler public void handleUserCreated(UserCreatedEvent event) { try { log.info("处理用户创建事件: {}", event.getUserId()); // 发送欢迎邮件 notificationService.sendWelcomeEmail(event.getEmail()); // 创建用户配置文件 notificationService.createUserProfile(event.getUserId()); } catch (Exception e) { log.error("处理用户创建事件失败", e); throw new AmqpRejectAndDontRequeueException("事件处理失败", e); } }}// 事件模型@Data@AllArgsConstructor@NoArgsConstructorpublic class UserCreatedEvent { private Long userId; private String username; private String email; private LocalDateTime createdAt;}@Data@AllArgsConstructor@NoArgsConstructorpublic class UserUpdatedEvent { private Long userId; private String username; private String email; private LocalDateTime updatedAt;}## 数据管理### 数据库设计java// 基础实体类@MappedSuperclass@EntityListeners(AuditingEntityListener.class)public abstract class BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @CreatedDate @Column(nullable = false, updatable = false) private LocalDateTime createdAt; @LastModifiedDate @Column(nullable = false) private LocalDateTime updatedAt; @CreatedBy @Column(updatable = false) private String createdBy; @LastModifiedBy private String lastModifiedBy; @Version private Long version; // getter和setter}// 用户实体扩展@Entity@Table(name = "users", indexes = { @Index(name = "idx_username", columnList = "username"), @Index(name = "idx_email", columnList = "email"), @Index(name = "idx_created_at", columnList = "createdAt")})public class User extends BaseEntity { @Column(nullable = false, unique = true, length = 50) private String username; @Column(nullable = false, unique = true, length = 100) private String email; @Column(nullable = false) private String password; @Enumerated(EnumType.STRING) @Column(nullable = false) private UserStatus status = UserStatus.ACTIVE; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private Set<UserRole> roles = new HashSet<>(); @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private UserProfile profile; // 构造函数、getter、setter}// 用户状态枚举public enum UserStatus { ACTIVE, INACTIVE, SUSPENDED, DELETED}// Repository接口@Repositorypublic interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> { Optional<User> findByUsername(String username); Optional<User> findByEmail(String email); boolean existsByUsername(String username); boolean existsByEmail(String email); @Query("SELECT u FROM User u WHERE u.status = :status AND u.createdAt >= :since") List<User> findActiveUsersSince(@Param("status") UserStatus status, @Param("since") LocalDateTime since); @Modifying @Query("UPDATE User u SET u.status = :status WHERE u.id = :id") int updateUserStatus(@Param("id") Long id, @Param("status") UserStatus status);}### 缓存策略java// 缓存配置@Configuration@EnableCachingpublic class CacheConfig { @Bean public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(10)) .disableCachingNullValues() .serializeKeysWith(RedisSerializationContext.SerializationPair .fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair .fromSerializer(new GenericJackson2JsonRedisSerializer())); Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>(); // 用户缓存 - 30分钟 cacheConfigurations.put("users", config.entryTtl(Duration.ofMinutes(30))); // 用户配置缓存 - 1小时 cacheConfigurations.put("userProfiles", config.entryTtl(Duration.ofHours(1))); // 短期缓存 - 5分钟 cacheConfigurations.put("shortTerm", config.entryTtl(Duration.ofMinutes(5))); return RedisCacheManager.builder(connectionFactory) .cacheDefaults(config) .withInitialCacheConfigurations(cacheConfigurations) .build(); }}// 缓存服务@Servicepublic class CachedUserService { private final UserRepository userRepository; private final RedisTemplate<String, Object> redisTemplate; public CachedUserService(UserRepository userRepository, RedisTemplate<String, Object> redisTemplate) { this.userRepository = userRepository; this.redisTemplate = redisTemplate; } @Cacheable(value = "users", key = "#id") public UserDto findById(Long id) { User user = userRepository.findById(id) .orElseThrow(() -> new UserNotFoundException("用户不存在: " + id)); return UserMapper.INSTANCE.toDto(user); } @Cacheable(value = "users", key = "#username") public UserDto findByUsername(String username) { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UserNotFoundException("用户不存在: " + username)); return UserMapper.INSTANCE.toDto(user); } @CachePut(value = "users", key = "#result.id") public UserDto updateUser(Long id, UpdateUserRequest request) { User user = userRepository.findById(id) .orElseThrow(() -> new UserNotFoundException("用户不存在: " + id)); user.setEmail(request.getEmail()); // 更新其他字段... User savedUser = userRepository.save(user); // 清除相关缓存 evictUserCaches(savedUser.getUsername()); return UserMapper.INSTANCE.toDto(savedUser); } @CacheEvict(value = "users", key = "#id") public void deleteUser(Long id) { User user = userRepository.findById(id) .orElseThrow(() -> new UserNotFoundException("用户不存在: " + id)); userRepository.delete(user); // 清除相关缓存 evictUserCaches(user.getUsername()); } @CacheEvict(value = {"users", "userProfiles"}, allEntries = true) public void clearAllCaches() { log.info("清除所有用户相关缓存"); } private void evictUserCaches(String username) { redisTemplate.delete("users::" + username); } // 分布式锁示例 public UserDto getOrCreateUser(String username) { String lockKey = "user:create:" + username; String lockValue = UUID.randomUUID().toString(); try { // 尝试获取分布式锁 Boolean acquired = redisTemplate.opsForValue() .setIfAbsent(lockKey, lockValue, Duration.ofSeconds(10)); if (Boolean.TRUE.equals(acquired)) { try { // 双重检查 Optional<User> existingUser = userRepository.findByUsername(username); if (existingUser.isPresent()) { return UserMapper.INSTANCE.toDto(existingUser.get()); } // 创建新用户 User newUser = new User(); newUser.setUsername(username); // 设置其他默认值... User savedUser = userRepository.save(newUser); return UserMapper.INSTANCE.toDto(savedUser); } finally { // 释放锁 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " + "return redis.call('del', KEYS[1]) else return 0 end"; redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(lockKey), lockValue); } } else { throw new ConcurrentOperationException("用户创建操作正在进行中"); } } catch (Exception e) { log.error("获取或创建用户失败", e); throw new ServiceException("操作失败", e); } }}## 安全性### JWT认证java// JWT工具类@Componentpublic class JwtTokenProvider { private final String jwtSecret; private final long jwtExpirationMs; public JwtTokenProvider(AppProperties appProperties) { this.jwtSecret = appProperties.getSecurity().getJwtSecret(); this.jwtExpirationMs = appProperties.getSecurity().getJwtExpirationMs(); } public String generateToken(UserPrincipal userPrincipal) { Date expiryDate = new Date(System.currentTimeMillis() + jwtExpirationMs); return Jwts.builder() .setSubject(Long.toString(userPrincipal.getId())) .setIssuedAt(new Date()) .setExpiration(expiryDate) .claim("username", userPrincipal.getUsername()) .claim("authorities", userPrincipal.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList())) .signWith(SignatureAlgorithm.HS512, jwtSecret) .compact(); } public Long getUserIdFromToken(String token) { Claims claims = Jwts.parser() .setSigningKey(jwtSecret) .parseClaimsJws(token) .getBody(); return Long.parseLong(claims.getSubject()); } public boolean validateToken(String authToken) { try { Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken); return true; } catch (SignatureException ex) { log.error("Invalid JWT signature"); } catch (MalformedJwtException ex) { log.error("Invalid JWT token"); } catch (ExpiredJwtException ex) { log.error("Expired JWT token"); } catch (UnsupportedJwtException ex) { log.error("Unsupported JWT token"); } catch (IllegalArgumentException ex) { log.error("JWT claims string is empty"); } return false; }}// JWT认证过滤器public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider tokenProvider; private final CustomUserDetailsService userDetailsService; public JwtAuthenticationFilter(JwtTokenProvider tokenProvider, CustomUserDetailsService userDetailsService) { this.tokenProvider = tokenProvider; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String jwt = getJwtFromRequest(request); if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) { Long userId = tokenProvider.getUserIdFromToken(jwt); UserDetails userDetails = userDetailsService.loadUserById(userId); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception ex) { log.error("Could not set user authentication in security context", ex); } filterChain.doFilter(request, response); } private String getJwtFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; }}// 安全配置@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class SecurityConfig { private final CustomUserDetailsService userDetailsService; private final JwtAuthenticationEntryPoint unauthorizedHandler; private final JwtTokenProvider tokenProvider; public SecurityConfig(CustomUserDetailsService userDetailsService, JwtAuthenticationEntryPoint unauthorizedHandler, JwtTokenProvider tokenProvider) { this.userDetailsService = userDetailsService; this.unauthorizedHandler = unauthorizedHandler; this.tokenProvider = tokenProvider; } @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(tokenProvider, userDetailsService); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager( AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.cors().and().csrf().disable() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeHttpRequests(authz -> authz .requestMatchers("/api/auth/**").permitAll() .requestMatchers("/actuator/health").permitAll() .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/users/*/public").permitAll() .requestMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ); http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); }}### 权限控制java// 权限注解@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@PreAuthorize("hasPermission(#targetId, #targetType, #permission)")public @interface RequirePermission { String permission(); String targetType() default "USER"; String targetId() default "#id";}// 权限评估器@Componentpublic class CustomPermissionEvaluator implements PermissionEvaluator { private final UserPermissionService permissionService; public CustomPermissionEvaluator(UserPermissionService permissionService) { this.permissionService = permissionService; } @Override public boolean hasPermission(Authentication authentication, Object targetId, Object permission) { if (authentication == null || !authentication.isAuthenticated()) { return false; } UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); return permissionService.hasPermission( userPrincipal.getId(), targetId.toString(), permission.toString() ); } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { return hasPermission(authentication, targetId, permission); }}// 权限服务@Servicepublic class UserPermissionService { private final UserRepository userRepository; private final RedisTemplate<String, Object> redisTemplate; public UserPermissionService(UserRepository userRepository, RedisTemplate<String, Object> redisTemplate) { this.userRepository = userRepository; this.redisTemplate = redisTemplate; } @Cacheable(value = "permissions", key = "#userId + ':' + #targetId + ':' + #permission") public boolean hasPermission(Long userId, String targetId, String permission) { // 检查用户是否存在 User user = userRepository.findById(userId).orElse(null); if (user == null) { return false; } // 超级管理员拥有所有权限 if (hasRole(user, "SUPER_ADMIN")) { return true; } // 检查具体权限 switch (permission) { case "READ": return canRead(user, targetId); case "WRITE": return canWrite(user, targetId); case "DELETE": return canDelete(user, targetId); default: return false; } } private boolean hasRole(User user, String roleName) { return user.getRoles().stream() .anyMatch(userRole -> userRole.getRole().getName().equals(roleName)); } private boolean canRead(User user, String targetId) { // 用户可以读取自己的信息 if (targetId.equals(user.getId().toString())) { return true; } // 检查是否有读取权限 return hasRole(user, "USER_READER") || hasRole(user, "ADMIN"); } private boolean canWrite(User user, String targetId) { // 用户可以修改自己的信息 if (targetId.equals(user.getId().toString())) { return true; } // 检查是否有写入权限 return hasRole(user, "USER_WRITER") || hasRole(user, "ADMIN"); } private boolean canDelete(User user, String targetId) { // 用户不能删除自己 if (targetId.equals(user.getId().toString())) { return false; } // 只有管理员可以删除用户 return hasRole(user, "ADMIN"); }}// 使用权限控制的控制器@RestController@RequestMapping("/api/users")public class SecureUserController { private final UserService userService; public SecureUserController(UserService userService) { this.userService = userService; } @GetMapping("/{id}") @RequirePermission(permission = "READ", targetId = "#id") public ResponseEntity<UserDto> getUser(@PathVariable Long id) { UserDto user = userService.findById(id); return ResponseEntity.ok(user); } @PutMapping("/{id}") @RequirePermission(permission = "WRITE", targetId = "#id") public ResponseEntity<UserDto> updateUser(@PathVariable Long id, @Valid @RequestBody UpdateUserRequest request) { UserDto user = userService.updateUser(id, request); return ResponseEntity.ok(user); } @DeleteMapping("/{id}") @RequirePermission(permission = "DELETE", targetId = "#id") public ResponseEntity<Void> deleteUser(@PathVariable Long id) { userService.deleteUser(id); return ResponseEntity.noContent().build(); } @GetMapping @PreAuthorize("hasRole('ADMIN')") public ResponseEntity<Page<UserDto>> getUsers( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size, @RequestParam(defaultValue = "id") String sort) { Pageable pageable = PageRequest.of(page, size, Sort.by(sort)); Page<UserDto> users = userService.findAll(pageable); return ResponseEntity.ok(users); }}## 监控和日志### 应用监控java// 自定义健康检查@Componentpublic class DatabaseHealthIndicator implements HealthIndicator { private final DataSource dataSource; public DatabaseHealthIndicator(DataSource dataSource) { this.dataSource = dataSource; } @Override public Health health() { try (Connection connection = dataSource.getConnection()) { if (connection.isValid(1)) { return Health.up() .withDetail("database", "Available") .withDetail("validationQuery", "SELECT 1") .build(); } else { return Health.down() .withDetail("database", "Connection validation failed") .build(); } } catch (Exception e) { return Health.down() .withDetail("database", "Unavailable") .withException(e) .build(); } }}// 自定义指标@Componentpublic class UserMetrics { private final Counter userCreatedCounter; private final Counter userLoginCounter; private final Timer userOperationTimer; private final Gauge activeUsersGauge; private final UserRepository userRepository; public UserMetrics(MeterRegistry meterRegistry, UserRepository userRepository) { this.userRepository = userRepository; this.userCreatedCounter = Counter.builder("users.created") .description("Number of users created") .register(meterRegistry); this.userLoginCounter = Counter.builder("users.login") .description("Number of user logins") .tag("status", "success") .register(meterRegistry); this.userOperationTimer = Timer.builder("users.operation.duration") .description("User operation duration") .register(meterRegistry); this.activeUsersGauge = Gauge.builder("users.active") .description("Number of active users") .register(meterRegistry, this, UserMetrics::getActiveUserCount); } public void incrementUserCreated() { userCreatedCounter.increment(); } public void incrementUserLogin() { userLoginCounter.increment(); } public Timer.Sample startTimer() { return Timer.start(); } public void recordOperationTime(Timer.Sample sample) { sample.stop(userOperationTimer); } private double getActiveUserCount() { return userRepository.countByStatus(UserStatus.ACTIVE); }}// 使用指标的服务@Servicepublic class MonitoredUserService { private final UserService userService; private final UserMetrics userMetrics; public MonitoredUserService(UserService userService, UserMetrics userMetrics) { this.userService = userService; this.userMetrics = userMetrics; } public UserDto createUser(CreateUserRequest request) { Timer.Sample sample = userMetrics.startTimer(); try { UserDto user = userService.createUser(request); userMetrics.incrementUserCreated(); return user; } finally { userMetrics.recordOperationTime(sample); } } public void recordLogin(String username) { userMetrics.incrementUserLogin(); log.info("用户登录: {}", username); }}### 结构化日志java// 日志配置@Configurationpublic class LoggingConfig { @Bean public Logger structuredLogger() { LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); // JSON编码器 JsonEncoder jsonEncoder = new JsonEncoder(); jsonEncoder.setContext(context); jsonEncoder.start(); // 控制台输出 ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>(); consoleAppender.setContext(context); consoleAppender.setEncoder(jsonEncoder); consoleAppender.start(); // 根日志器 ch.qos.logback.classic.Logger rootLogger = context.getLogger(Logger.ROOT_LOGGER_NAME); rootLogger.addAppender(consoleAppender); rootLogger.setLevel(Level.INFO); return rootLogger; }}// 请求日志过滤器@Componentpublic class RequestLoggingFilter implements Filter { private static final Logger log = LoggerFactory.getLogger(RequestLoggingFilter.class); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; String requestId = UUID.randomUUID().toString(); MDC.put("requestId", requestId); MDC.put("method", httpRequest.getMethod()); MDC.put("uri", httpRequest.getRequestURI()); long startTime = System.currentTimeMillis(); try { chain.doFilter(request, response); } finally { long duration = System.currentTimeMillis() - startTime; MDC.put("status", String.valueOf(httpResponse.getStatus())); MDC.put("duration", String.valueOf(duration)); log.info("HTTP请求完成"); MDC.clear(); } }}// 业务日志记录@Aspect@Componentpublic class BusinessLogAspect { private static final Logger log = LoggerFactory.getLogger(BusinessLogAspect.class); @Around("@annotation(BusinessLog)") public Object logBusinessOperation(ProceedingJoinPoint joinPoint) throws Throwable { BusinessLog businessLog = getBusinessLogAnnotation(joinPoint); String operation = businessLog.operation(); String module = businessLog.module(); MDC.put("operation", operation); MDC.put("module", module); long startTime = System.currentTimeMillis(); try { Object result = joinPoint.proceed(); long duration = System.currentTimeMillis() - startTime; log.info("业务操作成功: {} - {}ms", operation, duration); return result; } catch (Exception e) { long duration = System.currentTimeMillis() - startTime; log.error("业务操作失败: {} - {}ms", operation, duration, e); throw e; } finally { MDC.remove("operation"); MDC.remove("module"); } } private BusinessLog getBusinessLogAnnotation(ProceedingJoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); return signature.getMethod().getAnnotation(BusinessLog.class); }}// 业务日志注解@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface BusinessLog { String operation(); String module() default "USER";}// 使用业务日志@Servicepublic class LoggedUserService { private final UserRepository userRepository; public LoggedUserService(UserRepository userRepository) { this.userRepository = userRepository; } @BusinessLog(operation = "CREATE_USER", module = "USER_MANAGEMENT") public UserDto createUser(CreateUserRequest request) { log.info("开始创建用户: {}", request.getUsername()); try { // 创建用户逻辑 User user = new User(); user.setUsername(request.getUsername()); user.setEmail(request.getEmail()); User savedUser = userRepository.save(user); log.info("用户创建成功: id={}, username={}", savedUser.getId(), savedUser.getUsername()); return UserMapper.INSTANCE.toDto(savedUser); } catch (Exception e) { log.error("用户创建失败: username={}", request.getUsername(), e); throw e; } }}## 测试策略### 单元测试java// 服务层单元测试@ExtendWith(MockitoExtension.class)class UserServiceTest { @Mock private UserRepository userRepository; @Mock private PasswordEncoder passwordEncoder; @Mock private UserEventPublisher eventPublisher; @InjectMocks private UserService userService; @Test @DisplayName("根据ID查找用户 - 成功") void findById_Success() { // Given Long userId = 1L; User user = createTestUser(userId, "testuser", "test@example.com"); when(userRepository.findById(userId)).thenReturn(Optional.of(user)); // When UserDto result = userService.findById(userId); // Then assertThat(result).isNotNull(); assertThat(result.getId()).isEqualTo(userId); assertThat(result.getUsername()).isEqualTo("testuser"); assertThat(result.getEmail()).isEqualTo("test@example.com"); verify(userRepository).findById(userId); } @Test @DisplayName("根据ID查找用户 - 用户不存在") void findById_UserNotFound() { // Given Long userId = 999L; when(userRepository.findById(userId)).thenReturn(Optional.empty()); // When & Then assertThatThrownBy(() -> userService.findById(userId)) .isInstanceOf(UserNotFoundException.class) .hasMessage("用户不存在: " + userId); verify(userRepository).findById(userId); } @Test @DisplayName("创建用户 - 成功") void createUser_Success() { // Given CreateUserRequest request = new CreateUserRequest(); request.setUsername("newuser"); request.setEmail("newuser@example.com"); request.setPassword("password123"); User savedUser = createTestUser(1L, "newuser", "newuser@example.com"); when(userRepository.existsByUsername("newuser")).thenReturn(false); when(userRepository.existsByEmail("newuser@example.com")).thenReturn(false); when(passwordEncoder.encode("password123")).thenReturn("encodedPassword"); when(userRepository.save(any(User.class))).thenReturn(savedUser); // When UserDto result = userService.createUser(request); // Then assertThat(result).isNotNull(); assertThat(result.getUsername()).isEqualTo("newuser"); assertThat(result.getEmail()).isEqualTo("newuser@example.com"); verify(userRepository).existsByUsername("newuser"); verify(userRepository).existsByEmail("newuser@example.com"); verify(passwordEncoder).encode("password123"); verify(userRepository).save(any(User.class)); verify(eventPublisher).publishUserCreated(any(UserCreatedEvent.class)); } @Test @DisplayName("创建用户 - 用户名已存在") void createUser_UsernameExists() { // Given CreateUserRequest request = new CreateUserRequest(); request.setUsername("existinguser"); request.setEmail("new@example.com"); request.setPassword("password123"); when(userRepository.existsByUsername("existinguser")).thenReturn(true); // When & Then assertThatThrownBy(() -> userService.createUser(request)) .isInstanceOf(UserAlreadyExistsException.class) .hasMessage("用户名已存在: existinguser"); verify(userRepository).existsByUsername("existinguser"); verify(userRepository, never()).save(any(User.class)); verify(eventPublisher, never()).publishUserCreated(any(UserCreatedEvent.class)); } private User createTestUser(Long id, String username, String email) { User user = new User(); user.setId(id); user.setUsername(username); user.setEmail(email); user.setPassword("encodedPassword"); user.setStatus(UserStatus.ACTIVE); user.setCreatedAt(LocalDateTime.now()); user.setUpdatedAt(LocalDateTime.now()); return user; }}### 集成测试java// 控制器集成测试@SpringBootTest@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)@TestPropertySource(properties = { "spring.datasource.url=jdbc:h2:mem:testdb", "spring.jpa.hibernate.ddl-auto=create-drop"})@Transactionalclass UserControllerIntegrationTest { @Autowired private TestRestTemplate restTemplate; @Autowired private UserRepository userRepository; @Autowired private PasswordEncoder passwordEncoder; @LocalServerPort private int port; private String baseUrl; @BeforeEach void setUp() { baseUrl = "http://localhost:" + port + "/api/users"; } @Test @DisplayName("创建用户 - 集成测试") void createUser_IntegrationTest() { // Given CreateUserRequest request = new CreateUserRequest(); request.setUsername("integrationuser"); request.setEmail("integration@example.com"); request.setPassword("password123"); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity<CreateUserRequest> entity = new HttpEntity<>(request, headers); // When ResponseEntity<UserDto> response = restTemplate.postForEntity( baseUrl, entity, UserDto.class); // Then assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); assertThat(response.getBody()).isNotNull(); assertThat(response.getBody().getUsername()).isEqualTo("integrationuser"); assertThat(response.getBody().getEmail()).isEqualTo("integration@example.com"); // 验证数据库中的数据 Optional<User> savedUser = userRepository.findByUsername("integrationuser"); assertThat(savedUser).isPresent(); assertThat(savedUser.get().getEmail()).isEqualTo("integration@example.com"); } @Test @DisplayName("获取用户 - 需要认证") void getUser_RequiresAuthentication() { // Given User user = createAndSaveTestUser(); // When ResponseEntity<String> response = restTemplate.getForEntity( baseUrl + "/" + user.getId(), String.class); // Then assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); } @Test @DisplayName("获取用户 - 带认证") void getUser_WithAuthentication() { // Given User user = createAndSaveTestUser(); String token = generateJwtToken(user); HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(token); HttpEntity<String> entity = new HttpEntity<>(headers); // When ResponseEntity<UserDto> response = restTemplate.exchange( baseUrl + "/" + user.getId(), HttpMethod.GET, entity, UserDto.class); // Then assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).isNotNull(); assertThat(response.getBody().getId()).isEqualTo(user.getId()); } private User createAndSaveTestUser() { User user = new User(); user.setUsername("testuser"); user.setEmail("test@example.com"); user.setPassword(passwordEncoder.encode("password")); user.setStatus(UserStatus.ACTIVE); return userRepository.save(user); } private String generateJwtToken(User user) { // 生成JWT token的逻辑 return "mock-jwt-token"; }}### 性能测试java// 性能测试@SpringBootTest@TestPropertySource(properties = { "spring.datasource.url=jdbc:h2:mem:perfdb", "spring.jpa.hibernate.ddl-auto=create-drop"})class UserServicePerformanceTest { @Autowired private UserService userService; @Autowired private UserRepository userRepository; @Test @DisplayName("批量创建用户性能测试") void batchCreateUsers_PerformanceTest() { // Given int userCount = 1000; List<CreateUserRequest> requests = new ArrayList<>(); for (int i = 0; i < userCount; i++) { CreateUserRequest request = new CreateUserRequest(); request.setUsername("user" + i); request.setEmail("user" + i + "@example.com"); request.setPassword("password123"); requests.add(request); } // When long startTime = System.currentTimeMillis(); for (CreateUserRequest request : requests) { userService.createUser(request); } long endTime = System.currentTimeMillis(); long duration = endTime - startTime; // Then assertThat(duration).isLessThan(10000); // 应该在10秒内完成 assertThat(userRepository.count()).isEqualTo(userCount); System.out.println("创建 " + userCount + " 个用户耗时: " + duration + "ms"); System.out.println("平均每个用户: " + (duration / userCount) + "ms"); } @Test @DisplayName("并发查询用户性能测试") void concurrentUserQuery_PerformanceTest() throws InterruptedException { // Given User testUser = createTestUser(); int threadCount = 10; int queriesPerThread = 100; ExecutorService executor = Executors.newFixedThreadPool(threadCount); CountDownLatch latch = new CountDownLatch(threadCount); AtomicLong totalTime = new AtomicLong(0); // When long startTime = System.currentTimeMillis(); for (int i = 0; i < threadCount; i++) { executor.submit(() -> { try { long threadStartTime = System.currentTimeMillis(); for (int j = 0; j < queriesPerThread; j++) { userService.findById(testUser.getId()); } long threadEndTime = System.currentTimeMillis(); totalTime.addAndGet(threadEndTime - threadStartTime); } finally { latch.countDown(); } }); } latch.await(); executor.shutdown(); long endTime = System.currentTimeMillis(); long totalDuration = endTime - startTime; // Then int totalQueries = threadCount * queriesPerThread; assertThat(totalDuration).isLessThan(5000); // 应该在5秒内完成 System.out.println("总查询数: " + totalQueries); System.out.println("总耗时: " + totalDuration + "ms"); System.out.println("QPS: " + (totalQueries * 1000 / totalDuration)); System.out.println("平均响应时间: " + (totalTime.get() / totalQueries) + "ms"); } private User createTestUser() { CreateUserRequest request = new CreateUserRequest(); request.setUsername("perfuser"); request.setEmail("perf@example.com"); request.setPassword("password123"); UserDto userDto = userService.createUser(request); return userRepository.findById(userDto.getId()).orElseThrow(); }}## 部署和运维### Docker化dockerfile# DockerfileFROM openjdk:17-jre-slim# 设置工作目录WORKDIR /app# 复制jar文件COPY target/user-service-*.jar app.jar# 创建非root用户RUN addgroup --system spring && adduser --system spring --ingroup springUSER spring:spring# 暴露端口EXPOSE 8080# 健康检查HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1# 启动应用ENTRYPOINT ["java", "-jar", "/app/app.jar"]``````yaml# docker-compose.ymlversion: '3.8'services: user-service: build: . ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=docker - DATABASE_URL=jdbc:postgresql://postgres:5432/userdb - DATABASE_USERNAME=user - DATABASE_PASSWORD=password - REDIS_HOST=redis - REDIS_PORT=6379 depends_on: - postgres - redis networks: - microservices restart: unless-stopped postgres: image: postgres:15 environment: - POSTGRES_DB=userdb - POSTGRES_USER=user - POSTGRES_PASSWORD=password volumes: - postgres_data:/var/lib/postgresql/data networks: - microservices restart: unless-stopped redis: image: redis:7-alpine networks: - microservices restart: unless-stoppedvolumes: postgres_data:networks: microservices: driver: bridge### Kubernetes部署yaml# k8s-deployment.ymlapiVersion: apps/v1kind: Deploymentmetadata: name: user-service labels: app: user-servicespec: replicas: 3 selector: matchLabels: app: user-service template: metadata: labels: app: user-service spec: containers: - name: user-service image: user-service:latest ports: - containerPort: 8080 env: - name: SPRING_PROFILES_ACTIVE value: "k8s" - name: DATABASE_URL valueFrom: secretKeyRef: name: user-service-secrets key: database-url - name: DATABASE_USERNAME valueFrom: secretKeyRef: name: user-service-secrets key: database-username - name: DATABASE_PASSWORD valueFrom: secretKeyRef: name: user-service-secrets key: database-password resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "1Gi" cpu: "500m" livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 5 periodSeconds: 5---apiVersion: v1kind: Servicemetadata: name: user-servicespec: selector: app: user-service ports: - protocol: TCP port: 80 targetPort: 8080 type: ClusterIP---apiVersion: v1kind: Secretmetadata: name: user-service-secretstype: Opaquedata: database-url: <base64-encoded-url> database-username: <base64-encoded-username> database-password: <base64-encoded-password>## 总结Spring Boot微服务开发涉及多个方面的最佳实践:### 关键要点1. 架构设计:合理的服务拆分和边界定义2. 配置管理:外部化配置和环境隔离3. 服务通信:HTTP客户端和消息队列的合理使用4. 数据管理:数据库设计和缓存策略5. 安全性:认证授权和权限控制6. 监控日志:全面的监控和结构化日志7. 测试策略:单元测试、集成测试和性能测试8. 部署运维:容器化和云原生部署### 最佳实践总结- 遵循单一职责原则进行服务拆分- 使用外部化配置管理不同环境- 实现优雅的服务间通信和错误处理- 采用多层缓存策略提升性能- 建立完善的安全防护体系- 实施全面的监控和日志记录- 编写充分的自动化测试- 采用容器化和云原生部署方式通过遵循这些最佳实践,可以构建出高质量、可维护、可扩展的Spring Boot微服务应用。### 进阶建议1. 持续学习:关注Spring Boot和Spring Cloud的最新发展2. 社区参与:积极参与开源社区,贡献代码和经验3. 实践总结:在实际项目中不断实践和总结经验4. 技术选型:根据业务需求选择合适的技术栈5. 团队协作:建立良好的开发规范和协作流程Spring Boot微服务开发是一个持续演进的过程,需要在实践中不断优化和改进。希望本文的最佳实践能够为您的微服务开发之路提供有价值的参考。大数据
更多推荐



所有评论(0)