Java学习第24天 - Spring Cloud Gateway与容器化部署

学习目标

掌握Spring Cloud Gateway现代API网关技术,学习分布式链路追踪,了解微服务安全认证,掌握Docker容器化部署和Kubernetes基础编排管理。


1. Spring Cloud Gateway现代API网关

1.1 Gateway核心概念与架构

核心概念:

  • 路由(Route):网关的基本构建块
  • 断言(Predicate):匹配条件
  • 过滤器(Filter):请求和响应的处理逻辑
  • 网关过滤器链:请求处理的完整流程

代码示例:

java 复制代码
// 基础Gateway配置
@Configuration
@EnableWebFlux
public class GatewayConfig {
    
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            // 用户服务路由
            .route("user-service", r -> r.path("/api/users/**")
                .filters(f -> f
                    .stripPrefix(2)
                    .addRequestHeader("X-Gateway", "true")
                    .addRequestHeader("X-Request-ID", generateRequestId())
                    .circuitBreaker(config -> config
                        .setName("user-service")
                        .setFallbackUri("forward:/fallback/user-service"))
                    .retry(config -> config
                        .setRetries(3)
                        .setMethods(HttpMethod.GET, HttpMethod.POST)
                        .setStatuses(HttpStatus.INTERNAL_SERVER_ERROR)
                        .setBackoff(Duration.ofMillis(100), Duration.ofMillis(1000), 2, true))
                    .requestRateLimiter(config -> config
                        .setRateLimiter(redisRateLimiter())
                        .setKeyResolver(ipKeyResolver()))
                    .modifyResponseBody(String.class, String.class, 
                        (exchange, response) -> {
                            ServerHttpResponse httpResponse = exchange.getResponse();
                            httpResponse.getHeaders().add("X-Response-Time", 
                                String.valueOf(System.currentTimeMillis()));
                            return Mono.just(response);
                        }))
                .uri("lb://user-service"))
            
            // 订单服务路由
            .route("order-service", r -> r.path("/api/orders/**")
                .filters(f -> f
                    .stripPrefix(2)
                    .addRequestHeader("X-Gateway", "true")
                    .addRequestHeader("X-Request-ID", generateRequestId())
                    .circuitBreaker(config -> config
                        .setName("order-service")
                        .setFallbackUri("forward:/fallback/order-service"))
                    .retry(config -> config
                        .setRetries(2)
                        .setMethods(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT)
                        .setStatuses(HttpStatus.INTERNAL_SERVER_ERROR)
                        .setBackoff(Duration.ofMillis(200), Duration.ofMillis(2000), 2, true)))
                .uri("lb://order-service"))
            
            // 通知服务路由
            .route("notification-service", r -> r.path("/api/notifications/**")
                .filters(f -> f
                    .stripPrefix(2)
                    .addRequestHeader("X-Gateway", "true")
                    .addRequestHeader("X-Request-ID", generateRequestId())
                    .circuitBreaker(config -> config
                        .setName("notification-service")
                        .setFallbackUri("forward:/fallback/notification-service")))
                .uri("lb://notification-service"))
            
            // 静态资源路由
            .route("static-resources", r -> r.path("/static/**")
                .filters(f -> f.stripPrefix(1))
                .uri("file:./static/"))
            
            .build();
    }
    
    @Bean
    public RedisRateLimiter redisRateLimiter() {
        return new RedisRateLimiter(10, 20, 1);
    }
    
    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
    
    private String generateRequestId() {
        return UUID.randomUUID().toString();
    }
}

1.2 自定义过滤器开发

全局过滤器:

java 复制代码
// 认证过滤器
@Component
@Order(-100)
@Slf4j
public class AuthenticationFilter implements GlobalFilter {
    
    @Autowired
    private JwtUtil jwtUtil;
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        
        // 跳过认证的路径
        if (isSkipAuthPath(path)) {
            return chain.filter(exchange);
        }
        
        String token = extractToken(request);
        if (token == null) {
            return unauthorizedResponse(exchange, "缺少认证令牌");
        }
        
        try {
            Claims claims = jwtUtil.parseToken(token);
            String userId = claims.getSubject();
            String username = claims.get("username", String.class);
            
            // 将用户信息添加到请求头
            ServerHttpRequest mutatedRequest = request.mutate()
                .header("X-User-ID", userId)
                .header("X-Username", username)
                .build();
            
            return chain.filter(exchange.mutate().request(mutatedRequest).build());
        } catch (Exception e) {
            log.error("Token验证失败", e);
            return unauthorizedResponse(exchange, "认证令牌无效");
        }
    }
    
    private boolean isSkipAuthPath(String path) {
        return path.startsWith("/api/auth/") || 
               path.startsWith("/api/public/") ||
               path.startsWith("/actuator/") ||
               path.startsWith("/static/");
    }
    
    private String extractToken(ServerHttpRequest request) {
        String authHeader = request.getHeaders().getFirst("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            return authHeader.substring(7);
        }
        return null;
    }
    
    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json");
        
        String body = String.format("{\"error\":\"%s\",\"timestamp\":\"%s\"}", 
            message, LocalDateTime.now());
        
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes());
        return response.writeWith(Mono.just(buffer));
    }
}

// 日志过滤器
@Component
@Order(-50)
@Slf4j
public class LoggingFilter implements GlobalFilter {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String requestId = request.getHeaders().getFirst("X-Request-ID");
        
        long startTime = System.currentTimeMillis();
        
        return chain.filter(exchange)
            .doOnSuccess(response -> {
                long duration = System.currentTimeMillis() - startTime;
                log.info("Gateway Request: {} {} - {} {}ms", 
                    request.getMethod(), request.getURI().getPath(), 
                    exchange.getResponse().getStatusCode(), duration);
            })
            .doOnError(error -> {
                long duration = System.currentTimeMillis() - startTime;
                log.error("Gateway Error: {} {} - {} {}ms", 
                    request.getMethod(), request.getURI().getPath(), 
                    error.getMessage(), duration);
            });
    }
}

// 限流过滤器
@Component
@Order(-10)
@Slf4j
public class RateLimitFilter implements GlobalFilter {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String clientIp = getClientIp(exchange.getRequest());
        String key = "rate_limit:" + clientIp;
        
        return checkRateLimit(key)
            .flatMap(allowed -> {
                if (allowed) {
                    return chain.filter(exchange);
                } else {
                    return rateLimitExceededResponse(exchange);
                }
            });
    }
    
    private Mono<Boolean> checkRateLimit(String key) {
        return Mono.fromCallable(() -> {
            String count = redisTemplate.opsForValue().get(key);
            if (count == null) {
                redisTemplate.opsForValue().set(key, "1", Duration.ofMinutes(1));
                return true;
            }
            
            int currentCount = Integer.parseInt(count);
            if (currentCount >= 100) { // 每分钟100次请求
                return false;
            }
            
            redisTemplate.opsForValue().increment(key);
            return true;
        });
    }
    
    private String getClientIp(ServerHttpRequest request) {
        String xForwardedFor = request.getHeaders().getFirst("X-Forwarded-For");
        if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
            return xForwardedFor.split(",")[0].trim();
        }
        return request.getRemoteAddress() != null ? 
            request.getRemoteAddress().getAddress().getHostAddress() : "unknown";
    }
    
    private Mono<Void> rateLimitExceededResponse(ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
        response.getHeaders().add("Content-Type", "application/json");
        
        String body = "{\"error\":\"Rate limit exceeded\",\"timestamp\":\"" + 
            LocalDateTime.now() + "\"}";
        
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes());
        return response.writeWith(Mono.just(buffer));
    }
}

1.3 动态路由配置

动态路由管理:

java 复制代码
// 动态路由服务
@Service
@Slf4j
public class DynamicRouteService {
    
    @Autowired
    private RouteDefinitionLocator routeDefinitionLocator;
    
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
    
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;
    
    public void addRoute(RouteDefinition routeDefinition) {
        try {
            routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
            applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
            log.info("动态路由添加成功: {}", routeDefinition.getId());
        } catch (Exception e) {
            log.error("动态路由添加失败", e);
        }
    }
    
    public void updateRoute(RouteDefinition routeDefinition) {
        try {
            routeDefinitionWriter.delete(Mono.just(routeDefinition.getId())).subscribe();
            routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
            applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
            log.info("动态路由更新成功: {}", routeDefinition.getId());
        } catch (Exception e) {
            log.error("动态路由更新失败", e);
        }
    }
    
    public void deleteRoute(String routeId) {
        try {
            routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
            applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
            log.info("动态路由删除成功: {}", routeId);
        } catch (Exception e) {
            log.error("动态路由删除失败", e);
        }
    }
    
    public Flux<RouteDefinition> getRoutes() {
        return routeDefinitionLocator.getRouteDefinitions();
    }
}

// 路由管理控制器
@RestController
@RequestMapping("/api/gateway/routes")
@Slf4j
public class RouteController {
    
    @Autowired
    private DynamicRouteService dynamicRouteService;
    
    @PostMapping
    public ResponseEntity<String> addRoute(@RequestBody RouteDefinition routeDefinition) {
        dynamicRouteService.addRoute(routeDefinition);
        return ResponseEntity.ok("路由添加成功");
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<String> updateRoute(@PathVariable String id, 
                                           @RequestBody RouteDefinition routeDefinition) {
        routeDefinition.setId(id);
        dynamicRouteService.updateRoute(routeDefinition);
        return ResponseEntity.ok("路由更新成功");
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<String> deleteRoute(@PathVariable String id) {
        dynamicRouteService.deleteRoute(id);
        return ResponseEntity.ok("路由删除成功");
    }
    
    @GetMapping
    public ResponseEntity<Flux<RouteDefinition>> getRoutes() {
        return ResponseEntity.ok(dynamicRouteService.getRoutes());
    }
}

2. Spring Cloud Sleuth分布式链路追踪

2.1 Sleuth基础配置

核心概念:

  • Trace:一次完整的请求链路
  • Span:链路中的每个节点
  • TraceId:整个链路的唯一标识
  • SpanId:每个节点的唯一标识

代码示例:

java 复制代码
// Sleuth配置
@Configuration
@EnableSleuth
public class SleuthConfig {
    
    @Bean
    public Sampler alwaysSampler() {
        return Sampler.ALWAYS_SAMPLE;
    }
    
    @Bean
    public Sampler probabilityBasedSampler() {
        return ProbabilityBasedSampler.create(0.5f); // 50%采样率
    }
}

// 自定义Span处理器
@Component
@Slf4j
public class CustomSpanProcessor implements SpanHandler {
    
    @Override
    public boolean end(TraceContext traceContext, MutableSpan span, Cause cause) {
        // 记录Span信息
        log.info("Span结束: traceId={}, spanId={}, name={}, duration={}ms", 
            traceContext.traceId(), 
            traceContext.spanId(), 
            span.name(), 
            span.finishTimestamp() - span.startTimestamp());
        
        // 添加自定义标签
        span.tag("custom.tag", "custom-value");
        span.tag("service.version", "1.0.0");
        
        return true; // 返回true表示处理完成
    }
}

// 链路追踪工具类
@Component
@Slf4j
public class TracingUtil {
    
    @Autowired
    private Tracer tracer;
    
    public void addCustomTag(String key, String value) {
        Span currentSpan = tracer.currentSpan();
        if (currentSpan != null) {
            currentSpan.tag(key, value);
        }
    }
    
    public void addCustomEvent(String eventName) {
        Span currentSpan = tracer.currentSpan();
        if (currentSpan != null) {
            currentSpan.event(eventName);
        }
    }
    
    public void addCustomAnnotation(String annotation) {
        Span currentSpan = tracer.currentSpan();
        if (currentSpan != null) {
            currentSpan.annotate(annotation);
        }
    }
    
    public Span createNewSpan(String spanName) {
        return tracer.nextSpan().name(spanName).start();
    }
}

2.2 自定义链路追踪

业务链路追踪:

java 复制代码
// 订单服务链路追踪
@Service
@Slf4j
public class OrderService {
    
    @Autowired
    private Tracer tracer;
    
    @Autowired
    private TracingUtil tracingUtil;
    
    @Autowired
    private UserServiceClient userServiceClient;
    
    @Autowired
    private PaymentServiceClient paymentServiceClient;
    
    public Order createOrder(OrderRequest request) {
        Span span = tracer.nextSpan()
            .name("create-order")
            .tag("order.amount", request.getAmount().toString())
            .tag("order.userId", request.getUserId().toString())
            .start();
        
        try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
            tracingUtil.addCustomEvent("订单创建开始");
            
            // 验证用户
            User user = validateUser(request.getUserId());
            tracingUtil.addCustomTag("user.status", user.getStatus().name());
            
            // 处理支付
            PaymentResult payment = processPayment(request);
            tracingUtil.addCustomTag("payment.transactionId", payment.getTransactionId());
            
            // 创建订单
            Order order = buildOrder(request, user, payment);
            tracingUtil.addCustomTag("order.id", order.getId().toString());
            
            tracingUtil.addCustomEvent("订单创建完成");
            return order;
        } finally {
            span.end();
        }
    }
    
    private User validateUser(Long userId) {
        Span span = tracer.nextSpan()
            .name("validate-user")
            .tag("user.id", userId.toString())
            .start();
        
        try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
            tracingUtil.addCustomEvent("开始验证用户");
            
            User user = userServiceClient.getUserById(userId);
            if (user == null) {
                throw new UserNotFoundException("用户不存在");
            }
            
            tracingUtil.addCustomTag("user.found", "true");
            tracingUtil.addCustomEvent("用户验证完成");
            
            return user;
        } finally {
            span.end();
        }
    }
    
    private PaymentResult processPayment(OrderRequest request) {
        Span span = tracer.nextSpan()
            .name("process-payment")
            .tag("payment.amount", request.getAmount().toString())
            .start();
        
        try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
            tracingUtil.addCustomEvent("开始处理支付");
            
            PaymentRequest paymentRequest = new PaymentRequest();
            paymentRequest.setOrderId(request.getOrderId());
            paymentRequest.setAmount(request.getAmount());
            paymentRequest.setPaymentMethod(request.getPaymentMethod());
            
            PaymentResult payment = paymentServiceClient.charge(paymentRequest);
            
            tracingUtil.addCustomTag("payment.success", String.valueOf(payment.isSuccess()));
            tracingUtil.addCustomEvent("支付处理完成");
            
            return payment;
        } finally {
            span.end();
        }
    }
    
    private Order buildOrder(OrderRequest request, User user, PaymentResult payment) {
        Span span = tracer.nextSpan()
            .name("build-order")
            .start();
        
        try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
            tracingUtil.addCustomEvent("开始构建订单");
            
            Order order = new Order();
            order.setId(request.getOrderId());
            order.setUserId(request.getUserId());
            order.setAmount(request.getAmount());
            order.setStatus(OrderStatus.PENDING);
            order.setCreatedAt(LocalDateTime.now());
            order.setOrderNumber(generateOrderNumber());
            order.setTransactionId(payment.getTransactionId());
            
            Order savedOrder = orderRepository.save(order);
            
            tracingUtil.addCustomTag("order.saved", "true");
            tracingUtil.addCustomEvent("订单构建完成");
            
            return savedOrder;
        } finally {
            span.end();
        }
    }
    
    private String generateOrderNumber() {
        return "ORD" + System.currentTimeMillis() + RandomStringUtils.randomNumeric(4);
    }
}

2.3 Zipkin集成配置

Zipkin服务端配置:

java 复制代码
// Zipkin配置
@Configuration
@EnableZipkinServer
public class ZipkinConfig {
    
    @Bean
    public Sampler alwaysSampler() {
        return Sampler.ALWAYS_SAMPLE;
    }
}

// application.yml配置
spring:
  application:
    name: zipkin-server
  sleuth:
    zipkin:
      base-url: http://localhost:9411
    sampler:
      probability: 1.0
  zipkin:
    base-url: http://localhost:9411

server:
  port: 9411

management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: always

客户端配置:

yaml 复制代码
# 微服务应用配置
spring:
  application:
    name: order-service
  sleuth:
    zipkin:
      base-url: http://localhost:9411
    sampler:
      probability: 1.0
  zipkin:
    base-url: http://localhost:9411

logging:
  level:
    org.springframework.cloud.sleuth: DEBUG
    brave: DEBUG

3. Spring Cloud Security微服务安全认证

3.1 JWT认证配置

JWT工具类:

java 复制代码
// JWT工具类
@Component
@Slf4j
public class JwtUtil {
    
    @Value("${jwt.secret}")
    private String secret;
    
    @Value("${jwt.expiration}")
    private Long expiration;
    
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("username", userDetails.getUsername());
        claims.put("authorities", userDetails.getAuthorities());
        return createToken(claims, userDetails.getUsername());
    }
    
    public String generateToken(String username, Collection<? extends GrantedAuthority> authorities) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("username", username);
        claims.put("authorities", authorities);
        return createToken(claims, username);
    }
    
    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
    
    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = getUsernameFromToken(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }
    
    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }
    
    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }
    
    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }
    
    private Claims getAllClaimsFromToken(String token) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    }
    
    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }
    
    public String refreshToken(String token) {
        final Claims claims = getAllClaimsFromToken(token);
        claims.setIssuedAt(new Date());
        return createToken(claims, claims.getSubject());
    }
}

3.2 安全配置

微服务安全配置:

java 复制代码
// 安全配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
    
    @Autowired
    private JwtUtil jwtUtil;
    
    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    
    @Autowired
    private JwtRequestFilter jwtRequestFilter;
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/actuator/**").permitAll()
                .requestMatchers("/api/users/**").hasRole("USER")
                .requestMatchers("/api/orders/**").hasRole("USER")
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
            .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        
        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
        
        return http.build();
    }
}

// JWT认证过滤器
@Component
@Slf4j
public class JwtRequestFilter extends OncePerRequestFilter {
    
    @Autowired
    private JwtUtil jwtUtil;
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, 
                                  FilterChain chain) throws ServletException, IOException {
        
        final String requestTokenHeader = request.getHeader("Authorization");
        
        String username = null;
        String jwtToken = null;
        
        if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
            jwtToken = requestTokenHeader.substring(7);
            try {
                username = jwtUtil.getUsernameFromToken(jwtToken);
            } catch (IllegalArgumentException e) {
                log.error("Unable to get JWT Token");
            } catch (ExpiredJwtException e) {
                log.error("JWT Token has expired");
            } catch (MalformedJwtException e) {
                log.error("JWT Token is malformed");
            } catch (SignatureException e) {
                log.error("JWT Token signature is invalid");
            }
        } else {
            log.warn("JWT Token does not begin with Bearer String");
        }
        
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            
            if (jwtUtil.validateToken(jwtToken, userDetails)) {
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = 
                    new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken
                    .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
        chain.doFilter(request, response);
    }
}

// 认证控制器
@RestController
@RequestMapping("/api/auth")
@Slf4j
public class AuthController {
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private JwtUtil jwtUtil;
    
    @Autowired
    private UserService userService;
    
    @PostMapping("/login")
    public ResponseEntity<AuthResponse> login(@RequestBody LoginRequest loginRequest) {
        try {
            Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                    loginRequest.getUsername(), 
                    loginRequest.getPassword()
                )
            );
            
            UserDetails userDetails = (UserDetails) authentication.getPrincipal();
            String token = jwtUtil.generateToken(userDetails);
            
            AuthResponse response = new AuthResponse();
            response.setToken(token);
            response.setType("Bearer");
            response.setUsername(userDetails.getUsername());
            response.setAuthorities(userDetails.getAuthorities());
            
            return ResponseEntity.ok(response);
        } catch (BadCredentialsException e) {
            log.error("Invalid credentials for user: {}", loginRequest.getUsername());
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body(new AuthResponse("Invalid credentials"));
        }
    }
    
    @PostMapping("/register")
    public ResponseEntity<AuthResponse> register(@RequestBody RegisterRequest registerRequest) {
        try {
            User user = userService.createUser(registerRequest);
            
            String token = jwtUtil.generateToken(user.getUsername(), user.getAuthorities());
            
            AuthResponse response = new AuthResponse();
            response.setToken(token);
            response.setType("Bearer");
            response.setUsername(user.getUsername());
            response.setAuthorities(user.getAuthorities());
            
            return ResponseEntity.ok(response);
        } catch (Exception e) {
            log.error("User registration failed", e);
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(new AuthResponse("Registration failed: " + e.getMessage()));
        }
    }
    
    @PostMapping("/refresh")
    public ResponseEntity<AuthResponse> refresh(@RequestBody RefreshTokenRequest request) {
        try {
            String newToken = jwtUtil.refreshToken(request.getToken());
            
            AuthResponse response = new AuthResponse();
            response.setToken(newToken);
            response.setType("Bearer");
            
            return ResponseEntity.ok(response);
        } catch (Exception e) {
            log.error("Token refresh failed", e);
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body(new AuthResponse("Token refresh failed"));
        }
    }
}

4. Docker容器化部署

4.1 Dockerfile编写

多阶段构建Dockerfile:

dockerfile 复制代码
# 多阶段构建 - 构建阶段
FROM maven:3.8.4-openjdk-11-slim AS builder

WORKDIR /app

# 复制pom.xml并下载依赖
COPY pom.xml .
RUN mvn dependency:go-offline -B

# 复制源代码并构建
COPY src ./src
RUN mvn clean package -DskipTests

# 运行阶段
FROM openjdk:11-jre-slim

# 安装必要的工具
RUN apt-get update && \
    apt-get install -y curl && \
    rm -rf /var/lib/apt/lists/*

# 创建应用用户
RUN groupadd -r appuser && useradd -r -g appuser appuser

# 设置工作目录
WORKDIR /app

# 复制jar文件
COPY --from=builder /app/target/*.jar app.jar

# 创建日志目录
RUN mkdir -p /app/logs && chown -R appuser:appuser /app

# 切换到应用用户
USER appuser

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
    CMD curl -f http://localhost:8080/actuator/health || exit 1

# 暴露端口
EXPOSE 8080

# 设置JVM参数
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:+UseContainerSupport"

# 启动应用
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

4.2 Docker Compose配置

微服务Docker Compose:

yaml 复制代码
# docker-compose.yml
version: '3.8'

services:
  # Eureka注册中心
  eureka-server:
    build: ./eureka-server
    container_name: eureka-server
    ports:
      - "8761:8761"
    environment:
      - SPRING_PROFILES_ACTIVE=docker
    networks:
      - microservices-network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8761/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  # 配置中心
  config-server:
    build: ./config-server
    container_name: config-server
    ports:
      - "8888:8888"
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://eureka-server:8761/eureka/
    depends_on:
      - eureka-server
    networks:
      - microservices-network

  # API网关
  api-gateway:
    build: ./api-gateway
    container_name: api-gateway
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://eureka-server:8761/eureka/
      - SPRING_CLOUD_CONFIG_URI=http://config-server:8888
    depends_on:
      - eureka-server
      - config-server
    networks:
      - microservices-network

  # 用户服务
  user-service:
    build: ./user-service
    container_name: user-service
    ports:
      - "8081:8081"
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://eureka-server:8761/eureka/
      - SPRING_CLOUD_CONFIG_URI=http://config-server:8888
      - DATABASE_URL=jdbc:mysql://mysql:3306/userdb
      - DATABASE_USERNAME=user
      - DATABASE_PASSWORD=password
    depends_on:
      - eureka-server
      - config-server
      - mysql
    networks:
      - microservices-network

  # 订单服务
  order-service:
    build: ./order-service
    container_name: order-service
    ports:
      - "8082:8082"
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://eureka-server:8761/eureka/
      - SPRING_CLOUD_CONFIG_URI=http://config-server:8888
      - DATABASE_URL=jdbc:mysql://mysql:3306/orderdb
      - DATABASE_USERNAME=user
      - DATABASE_PASSWORD=password
    depends_on:
      - eureka-server
      - config-server
      - mysql
    networks:
      - microservices-network

  # MySQL数据库
  mysql:
    image: mysql:8.0
    container_name: mysql
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=rootpassword
      - MYSQL_DATABASE=userdb
      - MYSQL_USER=user
      - MYSQL_PASSWORD=password
    volumes:
      - mysql_data:/var/lib/mysql
      - ./mysql/init:/docker-entrypoint-initdb.d
    networks:
      - microservices-network

  # Redis缓存
  redis:
    image: redis:6.2-alpine
    container_name: redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    networks:
      - microservices-network

  # Zipkin链路追踪
  zipkin:
    image: openzipkin/zipkin:latest
    container_name: zipkin
    ports:
      - "9411:9411"
    environment:
      - STORAGE_TYPE=mysql
      - MYSQL_HOST=mysql
      - MYSQL_USER=user
      - MYSQL_PASS=password
      - MYSQL_DB=zipkin
    depends_on:
      - mysql
    networks:
      - microservices-network

volumes:
  mysql_data:
  redis_data:

networks:
  microservices-network:
    driver: bridge

4.3 容器优化配置

应用配置优化:

yaml 复制代码
# application-docker.yml
spring:
  application:
    name: user-service
  profiles:
    active: docker
  datasource:
    url: ${DATABASE_URL}
    username: ${DATABASE_USERNAME}
    password: ${DATABASE_PASSWORD}
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 10
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: false
    properties:
      hibernate:
        format_sql: true
        use_sql_comments: true
  redis:
    host: ${REDIS_HOST:redis}
    port: ${REDIS_PORT:6379}
    timeout: 2000ms
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0

server:
  port: 8081
  servlet:
    context-path: /
  tomcat:
    threads:
      max: 200
      min-spare: 10
    max-connections: 8192
    accept-count: 100

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: when-authorized
  metrics:
    export:
      prometheus:
        enabled: true

logging:
  level:
    root: INFO
    com.example: DEBUG
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%X{traceId:-},%X{spanId:-}] %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%X{traceId:-},%X{spanId:-}] %logger{36} - %msg%n"
  file:
    name: /app/logs/application.log
    max-size: 100MB
    max-history: 30

5. Kubernetes基础编排管理

5.1 Kubernetes基础概念

核心概念:

  • Pod:Kubernetes的最小部署单元
  • Service:服务发现和负载均衡
  • Deployment:声明式更新管理
  • ConfigMap:配置管理
  • Secret:敏感信息管理

5.2 Kubernetes部署配置

用户服务Deployment:

yaml 复制代码
# user-service-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
  labels:
    app: user-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
    spec:
      containers:
      - name: user-service
        image: user-service:latest
        ports:
        - containerPort: 8081
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "k8s"
        - name: EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE
          value: "http://eureka-service:8761/eureka/"
        - name: DATABASE_URL
          value: "jdbc:mysql://mysql-service:3306/userdb"
        - name: DATABASE_USERNAME
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: username
        - name: DATABASE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /actuator/health
            port: 8081
          initialDelaySeconds: 60
          periodSeconds: 30
        readinessProbe:
          httpGet:
            path: /actuator/health
            port: 8081
          initialDelaySeconds: 30
          periodSeconds: 10
        volumeMounts:
        - name: config-volume
          mountPath: /app/config
      volumes:
      - name: config-volume
        configMap:
          name: user-service-config

用户服务Service:

yaml 复制代码
# user-service-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: user-service
  labels:
    app: user-service
spec:
  selector:
    app: user-service
  ports:
  - protocol: TCP
    port: 8081
    targetPort: 8081
  type: ClusterIP

ConfigMap配置:

yaml 复制代码
# user-service-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: user-service-config
data:
  application.yml: |
    spring:
      application:
        name: user-service
      datasource:
        url: jdbc:mysql://mysql-service:3306/userdb
        driver-class-name: com.mysql.cj.jdbc.Driver
        hikari:
          maximum-pool-size: 10
          minimum-idle: 5
      jpa:
        hibernate:
          ddl-auto: update
        show-sql: false
      redis:
        host: redis-service
        port: 6379
    server:
      port: 8081
    management:
      endpoints:
        web:
          exposure:
            include: health,info,metrics

Secret配置:

yaml 复制代码
# mysql-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
type: Opaque
data:
  username: dXNlcg==  # base64 encoded 'user'
  password: cGFzc3dvcmQ=  # base64 encoded 'password'

5.3 Ingress配置

API网关Ingress:

yaml 复制代码
# api-gateway-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-gateway-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    nginx.ingress.kubernetes.io/rate-limit: "100"
    nginx.ingress.kubernetes.io/rate-limit-window: "1m"
spec:
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /api/users
        pathType: Prefix
        backend:
          service:
            name: user-service
            port:
              number: 8081
      - path: /api/orders
        pathType: Prefix
        backend:
          service:
            name: order-service
            port:
              number: 8082
      - path: /api/notifications
        pathType: Prefix
        backend:
          service:
            name: notification-service
            port:
              number: 8083

6. 学习总结与练习

6.1 核心知识点回顾

  1. Spring Cloud Gateway

    • 现代API网关架构设计
    • 路由、断言、过滤器机制
    • 自定义过滤器和动态路由
    • 限流、熔断、重试策略
  2. 分布式链路追踪

    • Sleuth链路追踪原理
    • Zipkin集成和配置
    • 自定义Span和标签
    • 业务链路监控
  3. 微服务安全认证

    • JWT令牌认证机制
    • Spring Security配置
    • 微服务间安全通信
    • 权限控制和授权
  4. Docker容器化

    • 多阶段构建优化
    • Docker Compose编排
    • 容器健康检查
    • 镜像优化策略
  5. Kubernetes编排

    • Pod、Service、Deployment概念
    • ConfigMap和Secret管理
    • Ingress网络配置
    • 资源限制和健康检查

6.2 实践练习

练习1:API网关开发

  • 实现自定义认证过滤器
  • 配置动态路由管理
  • 实现限流和熔断功能
  • 添加请求日志和监控

练习2:链路追踪集成

  • 配置Sleuth和Zipkin
  • 实现业务链路追踪
  • 添加自定义Span和标签
  • 分析链路性能瓶颈

练习3:微服务安全

  • 实现JWT认证机制
  • 配置微服务间安全通信
  • 实现基于角色的权限控制
  • 添加安全审计日志

练习4:容器化部署

  • 编写优化的Dockerfile
  • 配置Docker Compose编排
  • 实现容器健康检查
  • 优化镜像大小和启动时间

练习5:Kubernetes部署

  • 编写Kubernetes部署文件
  • 配置Service和Ingress
  • 实现配置和密钥管理
  • 设置资源限制和监控

6.3 学习建议

  1. 理论学习:深入理解云原生架构和容器编排原理
  2. 实践操作:通过实际项目练习容器化和K8s部署
  3. 监控运维:掌握分布式系统的监控和运维技能
  4. 安全防护:了解微服务安全最佳实践
  5. 性能优化:学习容器和K8s性能调优方法

7. 明日预告

第25天将学习:

  • Spring Cloud Alibaba:阿里巴巴微服务解决方案
  • Nacos:服务注册发现和配置管理
  • Sentinel:流量控制和熔断降级
  • Seata:分布式事务解决方案
  • RocketMQ:消息队列中间件
相关推荐
天天摸鱼的java工程师2 小时前
SpringBoot + RabbitMQ + Redis + MySQL:社交平台私信发送、已读状态同步与历史消息缓存
java·后端
JC032 小时前
JAVA解题——求阶乘和(附源代码)
java·开发语言·算法
psgogogo20252 小时前
Apache POI:Java操作Office文档的利器
java·开发语言·其他·apache
麦兜*2 小时前
Redis数据迁移实战:从自建到云托管(阿里云/腾讯云)的平滑过渡
java·spring boot·redis·spring·spring cloud·阿里云·腾讯云
间彧3 小时前
ThreadPoolTaskExecutor和ThreadPoolExecutor有何区别
java
渣哥3 小时前
多线程乱成一锅粥?教你把线程按顺序乖乖排队!
java
向前跑丶加油3 小时前
IDEA lombok注解无效的问题,运行时提示java: 找不到符号或者方法
java·开发语言·intellij-idea
企鹅虎3 小时前
ElasticStack高级搜索教程【Java培训】
java
柯南二号3 小时前
【安装配置】【搭建本地Maven私服】
java·maven