JC-Club 项目开发笔记 Day02

日期: 2026-02-15
学习主题: DDD 架构深入、代码修改、问题排查


1. 今日学习内容概览

序号 内容 类型
1 DDD 分层架构详解 理论
2 DTO、BO、Entity 区别与转换 理论+实践
3 @Mapper (MapStruct) 注解详解 理论
4 Result 统一返回封装 实践
5 日志框架配置 (Log4j2) 实践
6 多个 Bug 修复 实践

2. DDD 分层架构详解

2.1 为什么 Controller 要引入 Domain 层?

问题: 在 application-controller 中为什么要引入 domain 层?

答案: 这是 DDD 架构的核心设计原则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────────────────────────────────────────────────────┐
│ application-controller (应用层) │
│ 职责:接收请求、参数校验、调用Domain、返回响应 │
└─────────────────────┬───────────────────────────────────────┘
│ 引入 Domain

┌─────────────────────────────────────────────────────────────┐
│ domain (领域层) │
│ 职责:业务逻辑、规则校验、对象转换 │
└─────────────────────┬───────────────────────────────────────┘
│ 调用

┌─────────────────────────────────────────────────────────────┐
│ infra (基础设施层) │
│ 职责:数据库操作、第三方服务调用 │
└─────────────────────────────────────────────────────────────┘

为什么需要引入 Domain 层?

原因 说明
业务逻辑内聚 所有业务规则集中在 Domain 层,而不是散落在 Controller
可复用 Domain 服务可以被多个 Controller 调用(如:HTTP API、MQ 消息、定时任务)
可测试 单元测试可以直接测试 Domain 逻辑,不需要启动 Web 容器
防腐 Controller 不直接感知数据库结构变化

3. DTO、BO、Entity 的区别

3.1 数据流转图

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
┌─────────────────────────────────────────────────────────────┐
│ 前端 (JSON) │
└──────────────────────────────┬──────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ DTO (Data Transfer Object) - 数据传输对象 │
│ 位置:application/dto/ │
│ 职责:前端 ↔ Controller 之间传输数据 │
└──────────────────────────────┬──────────────────────────────┘
│ 转换器1: DTO → BO

┌─────────────────────────────────────────────────────────────┐
│ BO (Business Object) - 业务对象 │
│ 位置:domain/entity/ │
│ 职责:领域层业务逻辑使用 │
└──────────────────────────────┬──────────────────────────────┘
│ 转换器2: BO → Entity

┌─────────────────────────────────────────────────────────────┐
│ Entity - 数据库实体 │
│ 位置:infra/basic/entity/ │
│ 职责:对应数据库表 │
│ 包含:业务字段 + 审计字段 (createdBy, createdTime 等) │
└─────────────────────────────────────────────────────────────┘

3.2 对比表格

特性 DTO BO Entity
位置 application/dto domain/entity infra/basic/entity
职责 前端数据传输 业务逻辑处理 数据库映射
包含字段 前端需要的字段 业务需要的字段 数据库所有字段
审计字段 ❌ 无 ❌ 无 ✅ 有

4. @Mapper (MapStruct) 注解详解

4.1 作用

@MapperMapStruct 框架 的注解,用于自动生成对象转换代码

4.2 工作原理

阶段 说明
编写阶段 你只定义接口方法,没有实现
编译阶段 MapStruct 扫描到 @Mapper,自动生成实现类
运行阶段 通过 INSTANCE 调用自动生成的实现

4.3 示例代码

1
2
3
4
5
6
7
@Mapper
public interface SubjecttCategoryDTOCoverter {
SubjecttCategoryDTOCoverter INSTANCE = Mappers.getMapper(SubjecttCategoryDTOCoverter.class);

// 声明方法,编译时自动生成实现
SubjectCategoryBO coverDtoToBo(SubjectCategoryDto dto);
}

5. 代码修改记录

5.1 SubjectCategoryController.java

修改内容:

修改项 修改前 修改后
HTTP注解 @GetMapping @PostMapping
变量类型 SubjectCategory (Entity) SubjectCategoryBO (BO)
转换方法名 coverBoToCategory coverDtoToBo
return语句 缺失 添加 return "success"
注释 添加类注释和方法注释

5.2 SubjecttCategoryDTOCoverter.java

修改内容:

修改项 修改前 修改后
方法名 coverBoToCategory coverDtoToBo
返回类型 SubjectCategory SubjectCategoryBO
导入 包含多余的 Entity 删除

6. Bug 修复记录

6.1 Bug 1: MySQL 连接失败

错误信息:

1
java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed

原因: MySQL 8.0+ 的身份验证插件问题,参数名拼写错误。

解决: 修改 application.yml

1
2
3
4
5
# 错误
url: jdbc:mysql://127.0.0.1:3306/jc-club?...&allowPublicKey=true

# 正确
url: jdbc:mysql://127.0.0.1:3306/jc-club?...&allowPublicKeyRetrieval=true

6.2 Bug 2: 缺少 @Service 注解

错误信息:

1
A component required a bean of type 'SubjectCategoryDomainService' that could not be found.

原因: SubjectCategoryDomainSerivceImpl 类缺少 @Service 注解。

6.3 Bug 3: Result 类泛型错误

问题: private T data; 但类没有声明泛型参数。

解决: public class Resultpublic class Result<T>

6.4 Bug 5: 日志框架冲突

错误信息:

1
2
3
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [logback-classic]
SLF4J: Found binding in [log4j-slf4j-impl]

原因: 同时引入了 Logback 和 Log4j2。

解决: 使用 Log4j2,配置 log4j2-spring.xml


7. Log4j2 配置详解

7.1 配置文件结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<configuration>
<!-- 属性定义 -->
<property name="APP_NAME" value="jc-club"/>
<property name="LOG_PATH" value="logs/${APP_NAME}"/>
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>

<!-- Appender (输出目标) -->
<appender name="CONSOLE">...</appender> <!-- 控制台 -->
<appender name="FILE_INFO">...</appender> <!-- Info 文件 -->
<appender name="FILE_ERROR">...</appender> <!-- Error 文件 -->

<!-- 环境配置 -->
<springProfile name="dev">...</springProfile>
<springProfile name="prod">...</springProfile>
</configuration>

7.2 功能说明

配置项 说明
CONSOLE 控制台输出,开发时看
FILE_INFO info.log 文件,只记录 INFO 级别
FILE_ERROR error.log 文件,只记录 ERROR 级别
滚动策略 每天一个文件,保留30天,单文件最大100MB

8. 日志输出优化

8.1 为什么需要 if 判断?

1
2
3
4
5
6
7
// ❌ 性能差:每次都转换
log.info("dto: {}", JSON.toJSONString(dto));

// ✅ 性能好:按需转换
if (log.isInfoEnabled()) {
log.info("dto: {}", JSON.toJSONString(dto));
}

8.2 为什么要转 JSON?

  • 日志方法只接受字符串,不接受对象
  • 默认 toString() 只显示类名地址,不显示内容
  • JSON 格式清晰、结构化、方便排查问题

9. 完整调用链路

1
2
3
4
5
6
7
8
9
10
11
12
13
前端 POST /subject/category/add

SubjectCategoryController.add()
↓ (DTO → BO)
SubjecttCategoryDTOCoverter.coverDtoToBo()
↓ (调用)
SubjectCategoryDomainServiceImpl.add()
↓ (BO → Entity)
SubjecttCategoryCoverter.coverBoToCategory()
↓ (调用)
SubjectCategoryService.insert()
↓ (调用)
SubjectCategoryDao.insert() → MySQL

10. Git 提交记录

10.1 提交信息

1
2
3
4
5
6
7
8
9
10
feat: 完成 DDD 架构主体搭建及核心功能实现

- DDD 分层架构:domain、infra、application、common 模块
- 实体类:SubjectCategoryBO、SubjectCategory、DTO 转换器
- Domain Service 层实现:SubjectCategoryDomainService
- Infra 层:MyBatis Mapper、Service、DAO 完整实现
- Controller 层:SubjectCategoryController、POST 接口
- 统一返回封装:Result、ResultCodeEnum
- 日志配置:log4j2-spring.xml 完整配置
- 修复问题:MySQL 连接参数、泛型声明、HTTP 方法注解

11. 今日总结

技能点 掌握程度
DDD 分层架构 ✅ 理解
DTO/BO/Entity 转换 ✅ 掌握
MapStruct 注解 ✅ 理解
Log4j2 配置 ✅ 掌握
Bug 排查能力 ✅ 提升
Git 版本控制 ✅ 熟悉

12. 明日计划

  • 完成 Category 的 CRUD 接口
  • 引入 MyBatis Plus 简化开发
  • 添加全局异常处理
  • 完善日志记录

持续更新中…