背景

本文是《Java 后端从小白到大神》修仙系列之框架学习,Java框架之 SpringBoot 框架第四篇SpringBoot框架 可以说是微服务的基石,很多复杂的系统几乎都是通过微服务构造而来,若想详细学习请点击首篇博文开始,现在开始学习。

文章概览

  1. Spring Boot 集成 Spring Security 实现登录认证
  2. Spring Boot 项目持续集成(Jenkins + Git)
  3. 链路追踪(Sleuth + Zipkin)实现服务调用监控
  4. 开发一个自定义 Spring Boot Starter
  5. Spring Boot 总结与进阶指引

1. Spring Boot 集成 Spring Security 实现登录认证

一、引入依赖

1
2
3
4
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

二、定义配置类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
          .authorizeRequests()
            .antMatchers("/public/**").permitAll()
            .anyRequest().authenticated()
          .and()
            .formLogin();
    }
}

三、自定义用户信息

1
2
3
4
5
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
        .withUser("admin").password("{noop}123456").roles("ADMIN");
}

2. Spring Boot 项目持续集成(Jenkins + Git)

一、Jenkins Job 基本配置:

  • 源码地址配置 GitHub 仓库
  • 构建触发器设置为 Git push 或定时拉取

二、构建步骤:

1
2
3
4
#!/bin/bash
mvn clean package -DskipTests
docker build -t myapp:latest .
docker-compose down && docker-compose up -d

3. 链路追踪(Sleuth + Zipkin)实现服务调用监控

一、引入依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
老版本:spring boot 2.x
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

新版本:spring boot 3.x
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
</dependency>

二、配置 Zipkin 地址

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
老版本:spring boot 2.x
spring:
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      probability: 1.0

新版本:spring boot 3.x
spring:
  zipkin:
    base-url: http://zipkin:9411
    enabled: true

management:
  tracing:
    sampling:
      probability: 1.0   # 全采样,开发阶段建议设为 1.0
    propagation:
      type: b3           # 或者使用 w3c,默认也是 b3,保持兼容

4. 开发一个自定义 Spring Boot Starter

一、创建模块并定义 AutoConfiguration 类

1
2
3
4
5
6
7
8
@Configuration
@ConditionalOnClass(MyService.class)
public class MyServiceAutoConfiguration {
  @Bean
  public MyService myService() {
    return new MyService();
  }
}

二、添加 spring.factories

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
  com.example.MyServiceAutoConfiguration

5. Spring Boot 总结与进阶指引

一、核心知识体系回顾

  • 自动配置机制(@EnableAutoConfiguration
  • 配置文件加载机制(application.yml/properties
  • 多环境支持(@Profile
  • 配置元数据提示(spring-configuration-metadata.json

二、开发经验要点

  • 日志与配置分类管理
  • 合理使用自动配置和手动配置的边界
  • 项目结构模块化拆分建议

三、为学习 Spring Cloud 做准备

  • 服务注册与发现(Eureka / Nacos)
  • 配置中心 / 网关 / 熔断降级 / 服务间通信(Feign)

6. 综合上述内容练习

项目名称:CloudTask 平台,这是个小项目,纯纯练手的,但是五脏俱全,一个基于 Spring Cloud 构建的定时任务调度与执行平台,支持用户登录、任务注册、定时触发、执行状态跟踪、调用链监控、统一配置管理、消息通知等功能。所有服务都部署在 docker 上面,通过 jenkins 自动构建和部署。

项目架构图(微服务拆分)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
                                 +-------------+
                                 |   Zipkin    |
                                 +------+------+
                                        |
                            +-----------▼-----------+
                            |    Gateway 网关服务    |
                            +-----------+-----------+
                                        |
              +-------------------------+-------------------------+
              |                         |                         |
     +--------▼--------+      +--------▼--------+      +--------▼--------+
     | auth-service    |      | task-service     |      | user-service    |
     | 登录认证服务     |      | 任务调度执行服务 |      | 用户信息管理服务 |
     +--------+--------+      +--------+--------+      +--------+--------+
              |                         |                         |
      +-------▼--------+      +--------▼--------+      +--------▼--------+
      | Spring Security |      |  Redis缓存任务  |      | MySQL + JPA ORM |
      +-----------------+      +-----------------+      +-----------------+

项目总目录结构:cloud-task

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
cloud-task/
├── docker/                   # 所有 Docker 容器配置(包括 redis、rabbitmq、nacos)
│── └── docker-compose.yml    # Docker 容器启动配置
│── └── jdbc.properties       # Ncaos 数据库连接信息                  
├── gateway-service/          # 网关服务(Spring Cloud Gateway)
│── └── Dockerfile            # Dockerfile 文件
├── auth-service/             # 登录服务(JWT 登录 + Redis 缓存)
│── └── Dockerfile            # Dockerfile 文件
├── user-service/             # 用户服务(注册、查询等)
│── └── Dockerfile            # Dockerfile 文件
├── task-service/             # 定时任务调度与消费服务(含 @Scheduled 与 MQ 消费)
│── └── Dockerfile            # Dockerfile 文件
├── starter-task-common/      # 自定义 Starter,用于任务日志 AOP 记录
├── common/                   # 公共模块(DTO、返回结果、全局异常、工具类等)
├── config/                   # Nacos 配置文件(集中管理服务配置)
│── └── nacos-config.yaml     # Nacos 配置文件
├── └── auth-service-dev.yaml     # 认证服务开发环境配置
├── └── auth-service-prod.yaml    # 认证服务生产环境配置
├── └── gateway-service-dev.yaml  # 网关服务开发环境配置
├── └── gateway-service-prod.yaml # 网关服务生产环境配置
├── └── task-service-dev.yaml     # 任务服务开发环境配置
├── └── task-service-prod.yaml    # 任务服务生产环境配置
├── └── user-service-dev.yaml     # 用户服务开发环境配置
├── └── user-service-prod.yaml    # 用户服务生产环境配置
├── └── shared-common.yaml        # 服务通用配置
├── Jenkinsfile               # 自动化构建脚本
└── README.md

项目运行环境:nacos、redis、mysql、rabbitmq、zipkin、jenkins、auth-service、user-service、task-service、gateway-service 这些服务都部署在docker容器中。代码提交 https://gitee.com/null_493_9051/cloudtask.git 后,通过 jenkin 自动部署服务。

项目代码:

auth service 模块代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.yutao.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

// 表示这是一个配置类,会被 Spring 容器识别和加载
@Configuration

// 启用 Spring Security 的 Web 安全支持
@EnableWebSecurity
public class SecurityConfig {

    // 定义一个 SecurityFilterChain Bean,用来配置 Spring Security 的过滤器链
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // 关闭 CSRF(跨站请求伪造)保护,通常对前后端分离的 REST 接口不需要开启
        http.csrf(csrf -> csrf.disable())

            // 设置所有请求不进行认证,全部放行
            .authorizeRequests(requests -> requests.anyRequest().permitAll());

        // 返回构建好的安全过滤器链
        return http.build();
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.yutao.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.yutao.dto.ApiResponse;
import com.yutao.dto.LoginDTO;
import com.yutao.dto.RegisterDTO;
import com.yutao.service.AuthService;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequestMapping("")
public class AuthController {
    @Autowired private AuthService authService;

    @PostMapping("/login")
    public ApiResponse<?> login(@RequestBody LoginDTO dto) {
        log.info("login接口请求参数: {}", dto.getUsername());

        String token = authService.login(dto);
        return ApiResponse.success(token);
    }

    @PostMapping("/register")
    public ApiResponse<?> register(@RequestBody RegisterDTO dto) {
        log.info("register接口请求参数: {}", dto.getUsername());
        String token = authService.register(dto);
        return ApiResponse.success(token); // 返回 token
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.yutao.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.yutao.dto.ApiResponse;
import com.yutao.dto.LoginDTO;
import com.yutao.dto.RegisterDTO;
import com.yutao.service.AuthService;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequestMapping("")
public class AuthController {
    @Autowired private AuthService authService;

    @PostMapping("/login")
    public ApiResponse<?> login(@RequestBody LoginDTO dto) {
        log.info("login接口请求参数: {}", dto.getUsername());

        String token = authService.login(dto);
        return ApiResponse.success(token);
    }

    @PostMapping("/register")
    public ApiResponse<?> register(@RequestBody RegisterDTO dto) {
        log.info("register接口请求参数: {}", dto.getUsername());
        String token = authService.register(dto);
        return ApiResponse.success(token); // 返回 token
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package com.yutao;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.yutao.feign") // 指定Feign接口所在包
public class AuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuthApplication.class, args);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
application.yml

server:
  port: 8001

spring:
  application:
    name: auth-service
  cloud:
    nacos:
      discovery:
        server-addr: nacos:8848
  zipkin:
    base-url: http://zipkin:9411
    enabled: true

management:
  tracing:
    sampling:
      probability: 1.0   # 全采样,开发阶段建议设为 1.0
    propagation:
      type: b3           # 或者使用 w3c,默认也是 b3,保持兼容

feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000
  loadbalancer:
    enabled: true

logging:
  level:
    '[com.yutao.feign]': DEBUG
    feign: DEBUG

jwt:
  secret: my_super_secret_key_that_is_at_least_32_chars_long!
  expiration: 3600000
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
bootstrap.yml 文件

spring:
  application:
    name: auth-service

  cloud:
    nacos:
      config:
        server-addr: nacos:8848
        namespace: public
        group: DEFAULT_GROUP
        file-extension: yaml
      discovery:
        server-addr: nacos:8848
        namespace: public

  profiles:
    active: dev
1
2
3
4
5
FROM eclipse-temurin:21-jdk-alpine
WORKDIR /app
COPY target/auth-service-*.jar app.jar
EXPOSE 9000
ENTRYPOINT ["java", "-jar", "app.jar"]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
pom 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
          <groupId>com.yutao</groupId>
          <artifactId>cloudtask</artifactId>
          <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.yutao</groupId>
    <artifactId>auth-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

          <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
          </dependency>
 
      <dependency> 
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
      </dependency>
      <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      </dependency>
      <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
            <optional>true</optional>
      </dependency>
      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
      </dependency>
      <dependency>
            <groupId>com.yutao</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
      </dependency>
      <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-tracing-bridge-brave</artifactId>
      </dependency>
      <dependency>
            <groupId>io.zipkin.reporter2</groupId>
            <artifactId>zipkin-reporter-brave</artifactId>
      </dependency>
        <dependency>
            <groupId>com.yutao</groupId>
            <artifactId>user-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
          </dependency>
    </dependencies>
    <build>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
      </plugins>
    </build>
    
</project>
common 模块代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.yutao.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse<T> {
    private int code;
    private String message;
    private T data;

    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(200, "OK", data);
    }

    public static ApiResponse<?> error(String msg) {
        return new ApiResponse<>(500, msg, null);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package com.yutao.exception;

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import com.yutao.dto.ApiResponse;

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ServiceException.class)
    public ApiResponse<?> handle(ServiceException ex) {
        return ApiResponse.error(ex.getMessage());
    }
}
1
2
3
4
5
6
7
package com.yutao.exception;

public class ServiceException extends RuntimeException {
    public ServiceException(String message) {
        super(message);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.yutao.utils;

import java.security.Key;
import java.util.Date;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;

@RefreshScope
@Component
public class JwtUtil {

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration:3600000}")
    private long expiration;

    // 不再使用 @PostConstruct 初始化 Key,改为每次使用动态生成
    private Key getSigningKey() {
        return Keys.hmacShaKeyFor(secret.getBytes());
    }

    public String generateToken(String username) {
        Date now = new Date();
        Date expireAt = new Date(now.getTime() + expiration);

        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(now)
                .setExpiration(expireAt)
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }

    public String getUsernameFromToken(String token) throws JwtException {
        return Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }

    public boolean isTokenExpired(String token) {
        try {
            Date expiration = Jwts.parserBuilder()
                    .setSigningKey(getSigningKey())
                    .build()
                    .parseClaimsJws(token)
                    .getBody()
                    .getExpiration();
            return expiration.before(new Date());
        } catch (JwtException e) {
            return true;
        }
    }
}
config 配置文件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
auth-servcice-dev.yml # 配置文件

server:
  port: 9000

spring:
  datasource:
    url: jdbc:mysql://mysql:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  application:
    name: auth-service
  profiles:
    active: dev
  cloud:
    nacos:
      config:
        server-addr: nacos:8848
        namespace: public
        group: DEFAULT_GROUP
        refresh-enabled: true
        file-extension: yaml
        shared-configs:
          - data-id: shared-common.yaml
            group: DEFAULT_GROUP
            refresh: true
      discovery:
        server-addr: nacos:8848
        namespace: public

  redis:
    host: redis
    port: 6379

  rabbitmq:
    host: rabbitmq
    port: 5672
    username: guest
    password: guest

  zipkin:
    base-url: http://zipkin:9411
    enabled: true

custom:
  jwt:
    secret: my_super_secret_key_that_is_at_least_32_chars_long!
    expiration: 3600000
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
auth-servcice-pro.yml # 配置文件

server:
  port: 9001

spring:
  datasource:
    url: jdbc:mysql://mysql:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  application:
    name: auth-service
  profiles:
    active: pro
  cloud:
    nacos:
      config:
        server-addr: nacos:8848
        namespace: public
        group: DEFAULT_GROUP
        refresh-enabled: true
        file-extension: yaml
        shared-configs:
          - data-id: shared-common.yaml
            group: DEFAULT_GROUP
            refresh: true
      discovery:
        server-addr: nacos:8848
        namespace: public

  redis:
    host: redis
    port: 6379

  rabbitmq:
    host: rabbitmq
    port: 5672
    username: guest
    password: guest

  zipkin:
    base-url: http://zipkin:9411
    enabled: true

custom:
  jwt:
    secret: my_super_secret_key_that_is_at_least_32_chars_long!
    expiration: 3600000
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
gateway-service-dev.yaml # 配置文件

server:
  port: 8888

spring:
  application:
    name: gateway-service
  profiles:
    active: dev
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: auth
          uri: lb://auth-service
          predicates:
            - Path=/auth/**
          filters:
            - StripPrefix=1
        - id: user
          uri: lb://user-service
          predicates:
            - Path=/users/**
          filters:
            - StripPrefix=1
        - id: task
          uri: lb://task-service
          predicates:
            - Path=/tasks/**
          filters:
            - StripPrefix=1
    nacos:
      config:
        server-addr: nacos:8848
        namespace: public
        group: DEFAULT_GROUP
        file-extension: yaml
        refresh-enabled: true
        shared-configs:
          - data-id: shared-common.yaml
            group: DEFAULT_GROUP
            refresh: true
    discovery:
      server-addr: nacos:8848
      namespace: public

  zipkin:
    base-url: http://zipkin:9411
    enabled: true
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
gateway-service-pro.yaml # 配置文件

server:
  port: 8080

spring:
  application:
    name: gateway-service
  profiles:
    active: pro
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: auth
          uri: lb://auth-service
          predicates:
            - Path=/auth/**
          filters:
            - StripPrefix=1
        - id: user
          uri: lb://user-service
          predicates:
            - Path=/users/**
          filters:
            - StripPrefix=1
        - id: task
          uri: lb://task-service
          predicates:
            - Path=/tasks/**
          filters:
            - StripPrefix=1
    nacos:
      config:
        server-addr: nacos:8848
        namespace: public
        group: DEFAULT_GROUP
        refresh-enabled: true
        file-extension: yaml
        shared-configs:
          - data-id: shared-common.yaml
            group: DEFAULT_GROUP
            refresh: true
    discovery:
      server-addr: nacos:8848
      namespace: public

  zipkin:
    base-url: http://zipkin:9411
    enabled: true
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
nacos-config.yaml # 配置文件

spring:
  application:
    name: ${spring.application.name}

  profiles:
    active: dev

  datasource:
    url: jdbc:mysql://mysql:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

  redis:
    host: redis
    port: 6379
    password: 
    database: 0

  rabbitmq:
    host: rabbitmq
    port: 5672
    username: guest
    password: guest

  zipkin:
    base-url: http://zipkin:9411
    enabled: true

  cloud:
    nacos:
      discovery:
        server-addr: nacos:8848
        namespace: public
      config:
        server-addr: nacos:8848
        file-extension: yaml
        group: DEFAULT_GROUP
        refresh-enabled: true
        namespace: public
        shared-configs:
          - data-id: shared-common.yaml
            group: DEFAULT_GROUP
            refresh: true

management:
  endpoints:
    web:
      exposure:
        include: '*'

# 自定义配置项,可在服务中通过 @Value 或 @ConfigurationProperties 注入使用
custom:
  jwt:
    secret: my_super_secret_key_that_is_at_least_32_chars_long!
    expiration: 3600000
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
shared-common.yaml # 配置文件

spring:
  application:
    name: ${spring.application.name}

  profiles:
    active: dev

  datasource:
    url: jdbc:mysql://mysql:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

  redis:
    host: redis
    port: 6379
    password: 
    database: 0

  rabbitmq:
    host: rabbitmq
    port: 5672
    username: guest
    password: guest

  zipkin:
    base-url: http://zipkin:9411
    enabled: true

  cloud:
    nacos:
      discovery:
        server-addr: nacos:8848
        namespace: public
      config:
        server-addr: nacos:8848
        file-extension: yaml
        group: DEFAULT_GROUP
        namespace: public
        shared-configs:
          - data-id: shared-common.yaml
            group: DEFAULT_GROUP
            refresh: true

management:
  endpoints:
    web:
      exposure:
        include: '*'

# 自定义配置项,可在服务中通过 @Value 或 @ConfigurationProperties 注入使用
custom:
  jwt:
    secret: my_super_secret_key_that_is_at_least_32_chars_long!
    expiration: 3600000
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
task-service-dev.yaml # 配置文件

server:
  port: 9004

spring:
  application:
    name: task-service
  profiles:
    active: dev
  zipkin:
    base-url: http://zipkin:9411
    enabled: true
  cloud:
    nacos:
      config:
        server-addr: nacos:8848
        namespace: public
        group: DEFAULT_GROUP
        refresh-enabled: true
        file-extension: yaml
        shared-configs:
          - data-id: shared-common.yaml
            group: DEFAULT_GROUP
            refresh: true
    discovery:  
      server-addr: nacos:8848
      namespace: public      

  rabbitmq:
    host: rabbitmq
    port: 5672
    username: guest
    password: guest
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
task-service-pro.yaml #配置文件

server:
  port: 9005

spring:
  application:
    name: task-service
  profiles:
    active: pro
  zipkin:
    base-url: http://zipkin:9411
    enabled: true
  cloud:
    nacos:
      config:
        server-addr: nacos:8848
        namespace: public
        group: DEFAULT_GROUP
        refresh-enabled: true
        file-extension: yaml
        shared-configs:
          - data-id: shared-common.yaml
            group: DEFAULT_GROUP
            refresh: true
    discovery:
      server-addr: nacos:8848
      namespace: public   

  rabbitmq:
    host: rabbitmq
    port: 5672
    username: guest
    password: guest
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
user-service-dev.yaml # 配置文件

server:
  port: 9002

spring:
  application:
    name: user-service
  profiles:
    active: dev
  datasource:
    url: jdbc:mysql://mysql:3306/user_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  cloud:
    nacos:
      config:
        server-addr: nacos:8848
        namespace: public
        group: DEFAULT_GROUP
        refresh-enabled: true
        file-extension: yaml
        shared-configs:
          - data-id: shared-common.yaml
            group: DEFAULT_GROUP
            refresh: true
    discovery:
      server-addr: nacos:8848
      namespace: public
  zipkin:
    base-url: http://zipkin:9411
    enabled: true
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
user-service-pro.yaml # 配置文件

server:
  port: 9003

spring:
  application:
    name: user-service
  profiles:
    active: pro
  datasource:
    url: jdbc:mysql://mysql:3306/user_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  cloud:
    nacos:
      config:
        server-addr: nacos:8848
        namespace: public
        group: DEFAULT_GROUP
        refresh-enabled: true
        file-extension: yaml
        shared-configs:
          - data-id: shared-common.yaml
            group: DEFAULT_GROUP
            refresh: true
    discovery: 
      server-addr: nacos:8848
      namespace: public       

  zipkin:
    base-url: http://zipkin:9411
    enabled: true
docker 配置文件
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
docker-compose.yml # 配置文件

version: '3'

services:
  nacos:
    image: nacos/nacos-server:v2.3.0
    platform: linux/amd64
    ports:
      - "8848:8848"
      - "9848:9848"
    environment:
      MODE: standalone
      SPRING_DATASOURCE_PLATFORM: mysql
      MYSQL_SERVICE_HOST: mysql
      MYSQL_SERVICE_PORT: 3306
      MYSQL_SERVICE_USER: root
      MYSQL_SERVICE_PASSWORD: root
      MYSQL_SERVICE_DB_PARAM: characterEncoding=utf8&connectTimeout=2000&allowPublicKeyRetrieval=true&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
      MYSQL_SERVICE_DB_NAME: nacos_config
    networks:
      - cloudtask-net
    volumes:
      - /Users/yutao/Downloads/sourcecode/cloudtask/docker/jdbc.properties:/home/nacos/init.d/jdbc.properties
    restart: always

  redis:
    image: redis:7.2
    networks:
      - cloudtask-net
    ports:
      - "6379:6379"

  rabbitmq:
    image: rabbitmq:3-management
    networks:
      - cloudtask-net
    ports:
      - "15672:15672"
      - "5672:5672"

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
    ports:
      - "3306:3306"
    networks:
      - cloudtask-net
    volumes:
      - /Users/yutao/Downloads/devtool/docker/mysqldata:/var/lib/mysql

  zipkin:
    image: openzipkin/zipkin
    container_name: zipkin
    networks:
      - cloudtask-net
    ports:
      - "9411:9411"

  jenkins:
    image: jenkins/jenkins:lts-jdk17
    user: root
    container_name: jenkins
    ports:
      - "8080:8080"
      - "50000:50000"
    volumes:
      - /Users/yutao/Downloads/devtool/jenkins_home:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock
    restart: always

  auth-service:
      image: auth-service
      container_name: auth-service
      ports:
        - "9000:9000"
      depends_on:
        - nacos
        - mysql
      networks:
        - cloudtask-net
      restart: always
      environment:
        SPRING_PROFILES_ACTIVE: dev
        MANAGEMENT_ZIPKIN_TRACING_ENDPOINT: http://zipkin:9411/api/v2/spans
        MANAGEMENT_TRACING_SAMPLING_PROBABILITY: "1.0"
        MANAGEMENT_TRACING_ENABLED: "true"

  user-service:
      image: user-service
      container_name: user-service
      ports:
        - "9001:9001"
      depends_on:
        - nacos
        - mysql
      networks:
        - cloudtask-net
      restart: always
      environment:
        SPRING_PROFILES_ACTIVE: dev
        MANAGEMENT_ZIPKIN_TRACING_ENDPOINT: http://zipkin:9411/api/v2/spans
        MANAGEMENT_TRACING_SAMPLING_PROBABILITY: "1.0"
        MANAGEMENT_TRACING_ENABLED: "true"

  task-service:
      image: task-service
      container_name: task-service
      ports:
        - "9002:9002"
      depends_on:
        - nacos
        - mysql
        - rabbitmq
        - zipkin
      networks:
        - cloudtask-net
      restart: always
      environment:
        SPRING_PROFILES_ACTIVE: dev
        MANAGEMENT_ZIPKIN_TRACING_ENDPOINT: http://zipkin:9411/api/v2/spans
        MANAGEMENT_TRACING_SAMPLING_PROBABILITY: "1.0"
        MANAGEMENT_TRACING_ENABLED: "true"

  gateway-service:
      image: gateway-service
      container_name: gateway-service
      ports:
        - "8888:8888"
      depends_on:
        - auth-service
        - user-service
        - task-service
      networks:
        - cloudtask-net
      restart: always
      environment:
        SPRING_PROFILES_ACTIVE: dev
        MANAGEMENT_ZIPKIN_TRACING_ENDPOINT: http://zipkin:9411/api/v2/spans
        MANAGEMENT_TRACING_SAMPLING_PROBABILITY: "1.0"
        MANAGEMENT_TRACING_ENABLED: "true"

networks:
  cloudtask-net:
    driver: bridge
1
2
3
4
5
6
7
jdbc.properties #配置文件

spring.sql.init.platform=mysql
db.num=1
db.url.0=jdbc:mysql://mysql:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
db.user.0=root
db.password.0=root
gateway service 模块代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.yutao;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
application.yml #配置文件

server:
  port: 8888
spring:
  application:
    name: gateway-service
  cloud:
    nacos:
      discovery:
        server-addr: nacos:8848
    gateway:
      # discovery:
      #   locator:
      #     enabled: true
      routes:
        - id: auth
          uri: lb://auth-service
          predicates:
            - Path=/auth/**
          filters:
            - StripPrefix=1
        - id: user
          uri: lb://user-service
          predicates:
            - Path=/users/**
          filters:
            - StripPrefix=1
        - id: task
          uri: lb://task-service
          predicates:
            - Path=/tasks/**
          filters:
            - StripPrefix=1
  zipkin:
    base-url: http://zipkin:9411
    enabled: true

management:
  tracing:
    sampling:
      probability: 1.0   # 全采样,开发阶段建议设为 1.0
    propagation:
      type: b3           # 或者使用 w3c,默认也是 b3,保持兼容

logging:
  level:
    org.springframework.cloud.gateway: DEBUG
    reactor.netty: DEBUG
    org.springframework.web: DEBUG
    org.springframework.http.server.reactive: DEBUG
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
bootstrap.yml # 配置文件

spring:
  application:
    name: gateway-service

  cloud:
    nacos:
      config:
        server-addr: nacos:8848
        namespace: public
        group: DEFAULT_GROUP
        file-extension: yaml
      discovery:
        server-addr: nacos:8848
        namespace: public

  profiles:
    active: dev
1
2
3
4
5
FROM eclipse-temurin:21-jdk-alpine
WORKDIR /app
COPY target/gateway-service-*.jar app.jar
EXPOSE 8888
ENTRYPOINT ["java", "-jar", "app.jar"]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
pom 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
          <groupId>com.yutao</groupId>
          <artifactId>cloudtask</artifactId>
          <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.yutao</groupId>
    <artifactId>gateway-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      </dependency>
      <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-tracing-bridge-brave</artifactId>
    </dependency>
    <dependency>
        <groupId>io.zipkin.reporter2</groupId>
        <artifactId>zipkin-reporter-brave</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-resolver-dns-native-macos</artifactId>
        <version>4.1.96.Final</version> <!-- 或使用与Spring Boot兼容的版本 -->
        <classifier>osx-aarch_64</classifier> <!-- Apple Silicon (M1/M2) -->
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    </dependencies>
    <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
        </plugins>
      </build>
      
</project>
starter task common 模块代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.yutao.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableTaskLog {
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.yutao.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Aspect
public class TaskLogAspect {
    @Around("@annotation(com.yutao.annotation.EnableTaskLog)")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        log.info("开始执行任务: " + pjp.getSignature());
        Object result = pjp.proceed();
        log.info("任务结束: " + pjp.getSignature());
        return result;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package com.yutao.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import com.yutao.aspect.TaskLogAspect;

@Configuration
@ComponentScan("com.yutao.annotation") // 可选,取决于你的切面是否需要扫描
public class TaskLogAutoConfig {
    @Bean
    public TaskLogAspect taskLogAspect() {
        return new TaskLogAspect();
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
pom 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
          <groupId>com.yutao</groupId>
          <artifactId>cloudtask</artifactId>
          <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.yutao</groupId>
    <artifactId>starter-task-common</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies> 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
          </dependency>
    </dependencies>

</project>
task service 模块代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package com.yutao.config;

import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

    public static final String TASK_QUEUE = "task-queue";

    @Bean
    public Queue taskQueue() {
        return new Queue(TASK_QUEUE, true); // durable=true
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.yutao.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.yutao.annotation.EnableTaskLog;
import com.yutao.dto.ApiResponse;
import com.yutao.feign.UserClient;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequestMapping("")
public class TaskController {

    @Autowired
    private UserClient userClient;

    @GetMapping("/ping")
    @EnableTaskLog
    public String ping() {
        log.info("Ping 接口请求.");
        return "Task service is running.";
    }

    @GetMapping("/user-info/{id}")
    public ApiResponse<?> getUserInfo(@PathVariable Long id) {
        log.info("user-info接口请求: {}", id);
        return userClient.getUserById(id);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package com.yutao.listener;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class TaskConsumer {
    @RabbitListener(queues = "task-queue")
    public void consume(String messageString) {
        log.info("收到原始JSON字符串: {}", messageString);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.yutao;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients(basePackages = "com.yutao.feign") // 指向你的 feign 接口包
public class TaskApplication {
    public static void main(String[] args) {
        SpringApplication.run(TaskApplication.class, args);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
application.yml #配置文件

server:
  port: 8003

spring:
  application:
    name: task-service
  cloud:
    nacos:
      discovery:
        server-addr: nacos:8848

  zipkin:
    base-url: http://zipkin:9411
    enabled: true

  rabbitmq:
    host: rabbitmq
    port: 5672
    username: guest
    password: guest

  data:
    redis:
      host: redis
      port: 6379

jwt:
  secret: my_super_secret_key_that_is_at_least_32_chars_long!
  expiration: 3600000

management:
  tracing:
    sampling:
      probability: 1.0   # 全采样,开发阶段建议设为 1.0
    propagation:
      type: b3           # 或者使用 w3c,默认也是 b3,保持兼容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
bootstrap.yml # 配置文件

spring:
  application:
    name: task-service

  cloud:
    nacos:
      config:
        server-addr: nacos:8848
        namespace: public
        group: DEFAULT_GROUP
        file-extension: yaml
      discovery:
        server-addr: nacos:8848
        namespace: public

  profiles:
    active: dev
1
2
3
4
5
FROM eclipse-temurin:21-jdk-alpine
WORKDIR /app
COPY target/task-service-*.jar app.jar
EXPOSE 9002
ENTRYPOINT ["java", "-jar", "app.jar"]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
pom 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
          <groupId>com.yutao</groupId>
          <artifactId>cloudtask</artifactId>
          <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.yutao</groupId>
    <artifactId>task-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies> 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.yutao</groupId>
            <artifactId>starter-task-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-tracing-bridge-brave</artifactId>
        </dependency>
        <dependency>
            <groupId>io.zipkin.reporter2</groupId>
            <artifactId>zipkin-reporter-brave</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.yutao</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
      </dependency>
      <dependency>
        <groupId>com.yutao</groupId>
        <artifactId>starter-task-common</artifactId>
        <version>1.0-SNAPSHOT</version>
      </dependency>
      <dependency>
        <groupId>com.yutao</groupId>
        <artifactId>user-api</artifactId>
        <version>1.0-SNAPSHOT</version>
      </dependency>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
      </dependency>
      <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-tracing-bridge-brave</artifactId>
      </dependency>
      <dependency>
        <groupId>io.zipkin.reporter2</groupId>
        <artifactId>zipkin-reporter-brave</artifactId>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
    </dependencies>
    <build>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
      </plugins>
    </build>
    
</project>
user api 模块代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package com.yutao.dto;

import jakarta.validation.constraints.NotBlank;
import lombok.Data;

@Data
public class LoginDTO {

    @NotBlank(message = "用户名不能为空")
    private String username;

    @NotBlank(message = "密码不能为空")
    private String password;
}
1
2
3
4
5
6
7
8
9
package com.yutao.dto;

import lombok.Data;

@Data
public class RegisterDTO {
    private String username;
    private String password;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package com.yutao.dto;

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO implements Serializable {

    private Long id;
    private String username;
    private String password;
    private String email;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package com.yutao.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import com.yutao.dto.ApiResponse;
import com.yutao.dto.RegisterDTO;
import com.yutao.dto.UserDTO;

@FeignClient(name = "user-service")
public interface UserClient {

    @GetMapping("/{id}")
    ApiResponse<UserDTO> getUserById(@PathVariable("id") Long id);

    @PostMapping("/create")
    ApiResponse<UserDTO> createUser(@RequestBody RegisterDTO userDTO);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
pom 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
          <groupId>com.yutao</groupId>
          <artifactId>cloudtask</artifactId>
          <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.yutao</groupId>
    <artifactId>user-api</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies> 
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.yutao</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
      </dependency>
        <dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
        </dependency>
    </dependencies>

</project>
user service 模块代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.yutao.config;

import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {
    
    @Bean
    public MessageConverter jsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }
    
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(jsonMessageConverter());
        return template;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.yutao.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.yutao.dto.ApiResponse;
import com.yutao.dto.RegisterDTO;
import com.yutao.dto.UserDTO;
import com.yutao.service.UserService;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequestMapping("")
public class UserController {

    @Autowired 
    private UserService userService;

    @GetMapping
    public ApiResponse<?> findAll() {
        log.info("查询所有用户接口");
        List<UserDTO> uList = userService.findAll();
        return ApiResponse.success(uList);
    }

    @PostMapping("/create")
    public ApiResponse<UserDTO> createUser(@RequestBody RegisterDTO dto) {
        log.info("创建用户接口请求参数: {}", dto.getUsername());
        UserDTO savedUser = userService.createUser(dto);
        return ApiResponse.success(savedUser);
    }

    @GetMapping("/{id}")
    public ApiResponse<UserDTO> getUserById(@PathVariable Long id) {
        log.info("查询用户接口请求参数: {}", id);
        UserDTO user = userService.getUserById(id);
        return ApiResponse.success(user);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.yutao.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    @Id @GeneratedValue
    private Long id;
    private String username;
    private String password;
    private String email;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.yutao.messaging;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.yutao.entity.User;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class UserProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendUserRegisteredMessage(User user) {
        try {
            // 发送User对象,RabbitTemplate默认使用Jackson2JsonMessageConverter进行序列化
            rabbitTemplate.convertAndSend("task-queue", user);
            log.info("发送用户注册消息,用户ID: {}", user.getId());
        } catch (Exception e) {
            log.error("发送用户注册消息失败", e);
        }
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package com.yutao.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.yutao.entity.User;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    boolean existsByUsername(String username);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.yutao.service;

import java.util.List;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import com.yutao.dto.RegisterDTO;
import com.yutao.dto.UserDTO;
import com.yutao.entity.User;
import com.yutao.exception.ServiceException;
import com.yutao.messaging.UserProducer;
import com.yutao.repository.UserRepository;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private UserProducer userProducer;

    public UserDTO createUser(RegisterDTO dto) {
        log.info("接收到注册请求: {}", dto.getUsername());

        if (userRepository.existsByUsername(dto.getUsername())) {
            throw new ServiceException("用户名已存在");
        }

        User user = new User();
        user.setUsername(dto.getUsername());
        user.setPassword(dto.getPassword());
        user.setEmail("默认@gmail.com"); // 或默认值

        user = userRepository.save(user); // 假设你用 JPA

        // 然后再发送注册成功的消息
        userProducer.sendUserRegisteredMessage(user);

        UserDTO userDTO = new UserDTO();
        BeanUtils.copyProperties(user, userDTO);
        return userDTO;
    }

    @Cacheable(value = "user", key = "#id")
    public UserDTO getUserById(Long id) {
        log.info("UserById接口请求参数: {}", id);
        User user = userRepository.findById(id)
                .orElseThrow(() -> new ServiceException("用户不存在"));
        UserDTO userDTO = new UserDTO();
        BeanUtils.copyProperties(user, userDTO);
        return userDTO;
    }

    @Cacheable(value = "userList", key = "'all'")
    public List<UserDTO> findAll() {
        log.info("查询所有用户接口请求参数: {}");
        List<User> users = userRepository.findAll();
        return users.stream()
                .map(user -> {
                    UserDTO userDTO = new UserDTO();
                    BeanUtils.copyProperties(user, userDTO);
                    return userDTO;
                })
                .toList();
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package com.yutao;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
@EnableCaching  // 加到启动类上
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
application.yml # 配置文件

server:
  port: 8002

spring:
  application:
    name: user-service
  cloud:
    nacos:
      config:
        server-addr: nacos:8848
        namespace: public
        group: DEFAULT_GROUP
        file-extension: yaml
      discovery:
        server-addr: nacos:8848
  datasource:
    url: jdbc:mysql://mysql:3306/user_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    database-platform: org.hibernate.dialect.MySQL8Dialect
  cache:
    type: redis
  data:
    redis:
      host: redis
      port: 6379

  zipkin:
    base-url: http://zipkin:9411
    enabled: true

  rabbitmq:
    host: rabbitmq
    port: 5672
    username: guest
    password: guest

jwt:
  secret: my_super_secret_key_that_is_at_least_32_chars_long!
  expiration: 3600000

management:
  tracing:
    sampling:
      probability: 1.0   # 全采样,开发阶段建议设为 1.0
    propagation:
      type: b3           # 或者使用 w3c,默认也是 b3,保持兼容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
bootstrap.yml # 配置文件

spring:
  application:
    name: user-service

  cloud:
    nacos:
      config:
        server-addr: nacos:8848
        namespace: public
        group: DEFAULT_GROUP
        file-extension: yaml
      discovery:
        server-addr: nacos:8848
        namespace: public
  
  rabbitmq:
    host: rabbitmq
    port: 5672
    username: guest
    password: guest
  
  profiles:
    active: dev
1
2
3
4
5
FROM eclipse-temurin:21-jdk-alpine
WORKDIR /app
COPY target/user-service-*.jar app.jar
EXPOSE 9001
ENTRYPOINT ["java", "-jar", "app.jar"]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
pom 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
          <groupId>com.yutao</groupId>
          <artifactId>cloudtask</artifactId>
          <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.yutao</groupId>
    <artifactId>user-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies> 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.yutao</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-tracing-bridge-brave</artifactId>
        </dependency>
        <dependency>
            <groupId>io.zipkin.reporter2</groupId>
            <artifactId>zipkin-reporter-brave</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
        <dependency>
            <groupId>com.yutao</groupId>
            <artifactId>user-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <!-- Redis 支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- Spring 缓存注解支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
          </dependency>
    </dependencies>
    <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
        </plugins>
      </build>
      
</project>
Jenkinsfile 配置文件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
pipeline {
  agent any

  tools {
    jdk 'jdk21'         // 你在 Jenkins -> Global Tool Configuration 中配置的名字
    maven 'maven-3.9.9'     // 同上
  }

  environment {
    IMAGE_TAG = "latest"
  }

  stages {
    stage('Build') {
      steps {
        // 给 mvnw 添加执行权限,防止执行失败(如果用了 mvnw)
        sh 'chmod +x mvnw || true'

        // 构建所有模块
        sh 'mvn clean package -DskipTests'
      }
    }

    stage('Docker Build') {
      steps {
        script {
          def modules = ['auth-service', 'gateway-service', 'task-service', 'user-service']
          for (module in modules) {
            echo "▶ Building Docker image for ${module}"
            sh "docker build -t ${module}:${IMAGE_TAG} ./${module}"
          }
        }
      }
    }

    stage('Deploy') {
      steps {
        dir('docker') {
          sh 'docker-compose up -d'
        }
      }
    }
  }
}
父 pom 文件
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  
  <!-- Maven 的模型版本 -->
  <modelVersion>4.0.0</modelVersion>

  <!-- 项目坐标 -->
  <groupId>com.yutao</groupId>              <!-- 项目所属组织 -->
  <artifactId>cloudtask</artifactId>        <!-- 项目构件 ID -->
  <version>1.0-SNAPSHOT</version>           <!-- 项目版本 -->
  <packaging>pom</packaging>                <!-- 打包方式:父项目通常为 pom -->
  <name>cloudtask</name>                    <!-- 项目名称 -->

  <!-- 模块列表:定义了子模块(子项目) -->
  <modules>
    <module>auth-service</module>           <!-- 鉴权微服务 -->
    <module>common</module>                 <!-- 公共模块(工具类、通用配置等) -->
    <module>user-service</module>           <!-- 用户微服务 -->
    <module>task-service</module>           <!-- 任务微服务 -->
    <module>starter-task-common</module>    <!-- 自定义 Spring Boot Starter 模块 -->
    <module>gateway-service</module>
    <module>user-api</module>        <!-- 网关服务模块(Spring Cloud Gateway) -->
  </modules>

  <!-- 项目属性 -->
  <properties>
    <java.version>21</java.version>               <!-- Java 编译版本 -->
    <spring-boot.version>3.1.7</spring-boot.version> <!-- Spring Boot 版本 -->
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- 编码 -->
    <spring-cloud.version>2022.0.4</spring-cloud.version> <!-- spring cloud 模块版本 -->
    <spring.cloud.alibaba.version>2023.0.3.3</spring.cloud.alibaba.version> <!-- spring cloud alibaba 模块版本 -->
  </properties>

  <!-- 依赖管理:统一管理各模块使用的依赖版本 -->
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
        <version>${spring-boot.version}</version>
      </dependency>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
      <!-- 导入 Spring Boot 官方提供的 BOM(依赖版本管理) -->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <!-- Spring Boot Web -->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>${spring-boot.version}</version>
      </dependency>

      <!-- Spring Security -->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
        <version>${spring-boot.version}</version>
      </dependency>

      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version> ${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

      <!-- 配置处理器 -->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <version>${spring-boot.version}</version>
        <optional>true</optional>
      </dependency>

      <!-- Alibaba Nacos -->
      <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        <version>${spring.cloud.alibaba.version}</version>
      </dependency>
      <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        <version>${spring.cloud.alibaba.version}</version>
      </dependency>
      
      <!-- Spring Cloud Alibaba BOM (需额外仓库)-->
      <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>${spring.cloud.alibaba.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

      <!-- Lombok -->
      <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.30</version>
        <scope>provided</scope>
      </dependency>

      <!-- 测试 -->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <version>${spring-boot.version}</version>
        <scope>test</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <!-- 公共依赖:对子模块生效 -->
  <dependencies>
    <!-- JUnit5 测试核心 API -->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <scope>test</scope>
    </dependency>
    <!-- JUnit5 参数化测试支持 -->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-params</artifactId>
      <scope>test</scope>
    </dependency>
    <!-- JUnit5 测试引擎 -->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.30</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <!-- 构建相关插件配置 -->
  <build>
    <pluginManagement>
      <plugins>
        <!-- Java 编译插件 -->
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.11.0</version>
          <configuration>
            <release>${java.version}</release> <!-- 使用 Java 21 特性编译 -->
          </configuration>
        </plugin>

        <!-- 单元测试执行插件 -->
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>3.2.5</version>
        </plugin>

          <!-- Spring Boot 打包插件 -->
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
          <version>${spring-boot.version}</version>
          <executions>
            <execution>
              <goals>
                <goal>repackage</goal>
              </goals>
            </execution>
          </executions>
        </plugin>

        <!-- 清理插件(可选) -->
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.3.1</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
  
</project>

总结

Spring Boot 博客系列共 6 篇,涵盖从项目构建、Web开发、数据访问、缓存消息、部署监控、安全认证到微服务准备等内容。后续将开启 Spring Cloud 博客系列,系统讲解微服务架构实现。