**摘要:**本文围绕微服务项目拆分构建展开:基于 Spring Cloud Alibaba 搭建项目结构,按职责拆分多子模块,保障微服务正常运行。
创建项目结构
首先,我们需要创建微服务项目的整体结构。在原有项目的根目录下,我们创建一个新的微服务父项目 ai-code-mother-microservice。

然后依次创建各个子模块:
ai-code-mother-microservice/
├── pom.xml # 父项目 POM
├── ai-code-common/ # 公共模块
├── ai-code-model/ # 实体模型模块
├── ai-code-client/ # 服务接口模块
├── ai-code-user/ # 用户服务
├── ai-code-app/ # 应用服务
├── ai-code-ai/ # AI 代码生成
└── ai-code-screenshot/ # 网页截图服务
明确依赖版本
微服务开发中,由于涉及的依赖较多,一定要注意各个依赖版本的兼容性!
根据 Spring Cloud Alibaba 的版本说明,选择以下版本进行开发:
| 技术组件 | 推荐版本 |
|---|---|
| Java | 21 |
| Spring Boot | 3.5.3 |
| Spring Cloud | 2023.0.1 |
| Spring Cloud Alibaba | 20<23.0.1.0> |
| Nacos Server | 2.3.2 |
| Dubbo | 3.3.0 |
| Higress | 2.1.6 |
父项目 POM 配置
父项目的 POM 文件是整个微服务项目的核心配置文件,它定义了全局依赖版本号、引入全局依赖、配置打包插件等:
XML
<?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>
<groupId>com.yupi</groupId>
<artifactId>yu-ai-code-mother-microservice</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>yu-ai-code-common</module>
<module>yu-ai-code-model</module>
<module>yu-ai-code-client</module>
<module>yu-ai-code-user</module>
<module>yu-ai-code-app</module>
<module>yu-ai-code-ai</module>
<module>yu-ai-code-screenshot</module>
</modules>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>3.5.3</spring-boot.version>
<spring-cloud.version>2023.0.1</spring-cloud.version>
<spring-cloud-alibaba.version>2023.0.1.0</spring-cloud-alibaba.version>
</properties>
<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>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.38</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</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>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
<configuration>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
需要注意一个重要的配置:必须配置 Maven 编译插件,并且设置编译时保留参数名信息(-parameters 标志),否则 Spring 无法通过反射获取请求参数名,会导致有些 GET 请求参数失效。
比如这个接口:
java
@GetMapping("/get")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<User> getUserById(long id) {
ThrowUtils.throwIf(id <= 0, ErrorCode.PARAMS_ERROR);
User user = userService.getById(id);
ThrowUtils.throwIf(user == null, ErrorCode.NOT_FOUND_ERROR);
return ResultUtils.success(user);
}
如果没有正确配置编译插件,接口文档就无法识别参数名称,影响开发调试。
通用模块构建
ai-code-common 公共模块
公共模块包含所有模块公用的代码,一般不包含业务逻辑、不使用复杂的依赖。
这个模块的依赖配置如下:
java
<dependencies>
<!-- 腾讯云 COS 对象存储 -->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.227</version>
</dependency>
<!-- MyBatis Flex 代码生成 -->
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-codegen</artifactId>
<version>1.11.0</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
</dependencies>
接下来进行代码迁移,也就是把代码从原单体项目复制到新微服务项目的对应位置。这里要确保迁移的包路径和之前完全一致,减少代码修改量。
迁移内容:
-
common/ 公共请求响应类(BaseResponse、ResultUtils等)
-
constant/ 常量
-
exception/ 异常处理
-
generator/ 代码生成器
-
utils/ 工具类(除 WebScreenshotUtils 外)
-
config/ 配置类(JsonConfig、CorsConfig、CosClientConfig)
-
manager/ 通用能力(CosManager)
-
annotation/ 注解(AuthCheck)
注意事项:
-
ratelimit/ 限流模块涉及到引用 UserService,有一定的业务特性,因此不放到 common 中(可以单独定义成模块)
-
constant/ 常量也可以选择拆分到对应的业务模块中,比如 UserConstant 放到用户模块、AppConstant 放到应用模块
-
引入 CosManager 后,必须要填写 COS 配置才能启动项目,但有些模块是不需要 CosManager 的。因此添加条件注解,没配置就不加载:
java
@Configuration
@ConfigurationProperties(prefix = "cos.client")
@ConditionalOnProperty(
prefix = "cos.client",
name = {"host", "secretId", "secretKey", "region", "bucket"}
)
@Data
public class CosClientConfig {
}
@Component
@ConditionalOnBean(COSClient.class)
@Slf4j
public class CosManager {
}
这样确保了只有在配置了 COS 相关参数时才会加载相关的 Bean,避免了启动时的配置错误。
ai-code-model 实体模型模块
实体模型模块的依赖配置很简单,只需要依赖公共模块:
XML
<dependencies>
<dependency>
<groupId>com.yupi</groupId>
<artifactId>yu-ai-code-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
然后迁移整个 model 包:
-
model/entity/ 实体类(User、App、ChatHistory)
-
model/dto/ 数据传输对象
-
model/vo/ 视图对象
-
model/enums/ 枚举类
由于这个模块依赖其他模块,需要通过父项目进行统一构建,这样可以确保依赖关系正确、版本一致、构建顺序合理。
ai-code-client 服务接口模块
这个模块定义了需要被其他服务内部调用的接口。
这里沿用了基于 Feign 实现的命名,client 的含义是可以和实际服务交互的客户端,也可以重命名为 api 模块。
依赖配置如下:
XML
<dependencies>
<dependency>
<groupId>com.yupi</groupId>
<artifactId>yu-ai-code-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.yupi</groupId>
<artifactId>yu-ai-code-model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
为了和对外提供的接口区分,这个模块内的接口统一取名为 innerService,放到 innerservice 包下。
InnerUserService 定义了用户相关的内部接口:
java
User getLoginUser(HttpServletRequest request);
List<User> listByIds(Collection<? extends Serializable> ids);
User getById(Serializable id);
UserVO getUserVO(User user);
InnerScreenshotService 定义了截图相关的内部接口:
java
String generateAndUploadScreenshot(String webUrl);
其中,用户服务的 getLoginUser 方法比较特殊,由于 HttpServletRequest 对象不好在网络中传递,因此采用静态方法,避免跨服务调用:
java
public interface InnerUserService {
List<User> listByIds(Collection<? extends Serializable> ids);
User getById(Serializable id);
UserVO getUserVO(User user);
// 静态方法,避免跨服务调用
static User getLoginUser(HttpServletRequest request) {
Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
User currentUser = (User) userObj;
if (currentUser == null || currentUser.getId() == null) {
throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
}
return currentUser;
}
}
注意,不能使用默认方法,否则 Dubbo 会尝试序列化,导致报错。
业务服务构建
ai-code-user 用户服务
修改用户服务的依赖配置,包括 Session 管理、数据访问等功能:
XML
<dependencies>
<!-- Spring Session + Redis -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- 必须引入,才能共享 Session -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<artifactId>lettuce-core</artifactId>
<groupId>io.lettuce</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!-- 数据访问 -->
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot3-starter</artifactId>
<version>1.11.0</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.yupi</groupId>
<artifactId>yu-ai-code-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.yupi</groupId>
<artifactId>yu-ai-code-model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
💡 注意,必须引入 spring-boot-starter-data-redis 依赖,才能在微服务环境中共享 Session,并且这里采用了 jedis 连接池替代默认的 lettuce 连接池,让连接池管理更稳定。
编写配置文件 application.yml,包含应用基本信息、Session 配置、数据库配置、Redis 配置等:
bash
spring:
application:
name: yu-ai-code-user
# session
session:
store-type: redis
# 30 days expire
timeout: 2592000
# mysql
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/yu_ai_code_mother
username: root
password: 123456
# redis
data:
redis:
host: localhost
port: 6379
database: 0
password:
profiles:
active: local
server:
port: 8124
servlet:
context-path: /api
# 30 days expire
session:
cookie:
max-age: 2592000
# springdoc-openapi
springdoc:
group-configs:
- group: 'default'
packages-to-scan: com.yupi.yuaicodemother.controller
# knife4j
knife4j:
enable: true
setting:
language: zh_cn
接下来迁移代码,包括:
-
aop/AuthInterceptor.java 权限拦截器
-
controller/UserController.java 用户控制器
-
service/UserService.java 和 service/impl/UserServiceImpl.java 用户服务及实现类
-
mapper/UserMapper.java 和对应的 XML 文件(注意 XML 文件中引入的 Mapper 路径)
创建主启动类,代码如下:
java
@SpringBootApplication
@MapperScan("com.yupi.yuaicodeuser.mapper")
@ComponentScan("com.yupi")
@EnableDubbo
public class YuAiCodeUserApplication {
public static void main(String[] args) {
SpringApplication.run(YuAiCodeUserApplication.class, args);
}
}
ai-code-AI 代码生成服务
方案设计阶段提到过,综合考虑开发复杂度、以及 Dubbo 的序列化机制不支持 Reactor 的 Flux 类型,AI 模块不能单独作为服务调用,而是以依赖的方式引入。
编写依赖配置,包括 LangChain4j 相关的依赖:
XML
<dependencies>
<!-- LangChain4j -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>1.1.0-beta7</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
<version>1.1.0-beta7</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-redis-spring-boot-starter</artifactId>
<version>1.1.0-beta7</version>
</dependency>
<dependency>
<groupId>com.yupi</groupId>
<artifactId>yu-ai-code-model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.yupi</groupId>
<artifactId>yu-ai-code-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
然后迁移代码,内容包括:
-
dev.langchain4j/ LangChain4j 源码修改
-
ai/guardrail/ AI 护轨相关代码
-
ai/model/ AI 模型相关代码
-
ai/tools/ AI 工具相关代码
-
ai/ 所有 AI Service(不迁移工厂类)
-
config/ 和 AI 模型相关的配置
-
resources/prompt/ 提示词文件
考虑到可观测性是独立学习的章节,不迁移可观测性的 monitor 包,因此要修改 StreamingChatModelConfig 和 ReasoningStreamingChatModelConfig 类,移除监听器。
ai-code-app 应用服务
应用服务是最复杂的服务,它的依赖配置包括分布式限流、缓存、Session、数据访问等:
XML
<dependencies>
<!-- Redisson for distributed rate limiting -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.50.0</version>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<!-- Spring Session + Redis -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- 必须引入,才能共享 Session -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<artifactId>lettuce-core</artifactId>
<groupId>io.lettuce</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!-- 数据访问 -->
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot3-starter</artifactId>
<version>1.11.0</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.yupi</groupId>
<artifactId>yu-ai-code-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.yupi</groupId>
<artifactId>yu-ai-code-model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.yupi</groupId>
<artifactId>yu-ai-code-client</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.yupi</groupId>
<artifactId>yu-ai-code-ai</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
修改配置文件,包含应用信息、Session 配置、数据库配置、Redis 配置、AI 模型配置等:
bash
spring:
application:
name: yu-ai-code-app
# session
session:
store-type: redis
# 30 days expire
timeout: 2592000
# mysql
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/yu_ai_code_mother
username: root
password: 123456
# redis
data:
redis:
host: localhost
port: 6379
database: 0
password:
ttl: 3600
server:
port: 8125
servlet:
context-path: /api
# 30 days expire
session:
cookie:
max-age: 2592000
# AI
langchain4j:
open-ai:
chat-model:
base-url: https://api.deepseek.com
api-key: <Your API Key>
model-name: deepseek-chat
max-tokens: 8192
log-requests: true
log-responses: true
streaming-chat-model:
base-url: https://api.deepseek.com
api-key: <Your API Key>
model-name: deepseek-chat
max-tokens: 8192
log-requests: true
log-responses: true
# 推理 AI 模型配置(用于复杂的推理任务)
reasoning-streaming-chat-model:
base-url: https://api.deepseek.com
api-key: <Your API Key>
model-name: deepseek-reasoner
max-tokens: 32768
temperature: 0.1
log-requests: true
log-responses: true
# 智能路由 AI 模型配置
routing-chat-model:
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
api-key: <Your API Key>
model-name: qwen-turbo
max-tokens: 100
log-requests: true
log-responses: true
# springdoc-openapi
springdoc:
group-configs:
- group: 'default'
packages-to-scan: com.yupi.yuaicodemother.controller
# knife4j
knife4j:
enable: true
setting:
language: zh_cn
接下来迁移代码:
-
ai/ AI 服务工厂类(AiCodeGeneratorServiceFactory.java、AiCodeGenTypeRoutingServiceFactory.java)
-
config/ 缓存配置(RedisCacheManagerConfig.java)
-
controller/ 控制器层(AppController.java、ChatHistoryController.java、StaticResourceController.java)
-
core/ 代码生成核心代码
-
mapper/ 数据访问层(AppMapper.java、ChatHistoryMapper.java)
-
ratelimit/ 限流模块
-
service/ 业务服务层(AppService.java、ChatHistoryService.java、ProjectDownloadService.java 及其实现类)
-
resources/mapper/ MyBatis 映射文件
新建主启动类:
java
@SpringBootApplication(exclude = {RedisEmbeddingStoreAutoConfiguration.class})
@MapperScan("com.yupi.yuaicodemother.mapper")
@EnableCaching
public class YuAiCodeAppApplication {
public static void main(String[] args) {
SpringApplication.run(YuAiCodeAppApplication.class, args);
}
}
修改 AppServiceImpl 的代码,移除可观测性配置:
java
// 5. 通过校验后,添加用户消息到对话历史
chatHistoryService.addChatMessage(appId, message, ChatHistoryMessageTypeEnum.USER.getValue(), loginUser.getId());
// 6. 调用 AI 生成代码(流式)
Flux<String> codeStream = aiCodeGeneratorFacade.generateAndSaveCodeStream(message, codeGenTypeEnum, appId);
// 7. 收集 AI 响应内容并在完成后记录到对话历史
return streamHandlerExecutor.doExecute(codeStream, chatHistoryService, appId, loginUser, codeGenTypeEnum);
重点来了,在代码迁移过程中,需要修改调用了其他服务的代码(比如截图服务)。可以先使用 @Lazy 注解代替实际引入,后续会通过 Dubbo 进行服务调用:
java
@Resource
@Lazy
private InnerScreenshotService screenshotService;
类似的,修改所有调用了 UserService 的代码,替换为调用 InnerUserService 的实例方法或静态方法:
java
@Resource
@Lazy
private InnerUserService userService;
// 调用实例方法
User user = userService.getById(userId);
UserVO userVO = userService.getUserVO(user);
// 调用静态方法
User loginUser = InnerUserService.getLoginUser(request);
总结
- 模块边界要清晰,按功能职责拆分
- 严格区分通用层(common/model/client)和业务层(user/app/ai/screenshot):通用层只放全模块共享的工具、实体、接口,不包含业务逻辑;业务层按 "用户管理、应用功能、AI 能力、截图服务" 等单一职责拆分,避免模块功能混杂。
- 依赖管理要统一且兼容
-
父项目通过
dependencyManagement统一管理 Spring Boot、Spring Cloud Alibaba 等核心依赖的版本,避免子模块版本冲突; -
子模块只引入自身需要的依赖(比如 AI 模块只加 LangChain4j,截图模块只加 Selenium),轻量依赖减少冗余。
- 跨服务调用的提前规划
-
用
client模块定义内部调用接口(InnerService),统一跨服务交互契约; -
避免跨服务传递复杂对象(比如
HttpServletRequest),通过静态方法 + 本地处理(如getLoginUser)或简化参数的方式规避序列化问题。
- 配置与启动的兼容性
-
通用模块中涉及第三方服务(如 COS)的配置,通过
@ConditionalOnProperty/@ConditionalOnBean做条件加载,避免无配置时模块启动失败; -
共享资源(如 Session)通过 Redis 统一存储,确保微服务间状态一致。
- 代码迁移的成本控制
-
迁移时保持包路径与原单体项目一致,减少代码修改量;
-
拆分时剥离非核心逻辑(如可观测性),优先保证基础功能正常运行,后续再扩展。
- 服务调用的过渡处理
- 跨服务依赖先通过
@Lazy注解占位,避免模块启动时的循环依赖;后续再通过 Dubbo 实现实际的远程调用,分步完成拆分落地。