1392 lines
38 KiB
Markdown
1392 lines
38 KiB
Markdown
# AgileBoot DDD/CQRS 开发指南 - 新增表完整实现
|
||
|
||
## 📖 文档概述
|
||
|
||
本文档详细说明了在 AgileBoot 框架中新增一张数据表后,如何按照 DDD/CQRS 架构规范写出完整的 `command`、`db`、`dto`、`model`、`query`、`ApplicationService` 等全套代码。
|
||
|
||
基于对 `agileboot-domain` 模块的深度分析,结合 `system/user`、`shop`、`cabinet` 等真实业务模块的代码实践,本指南提供了可直接使用的代码模板和最佳实践。
|
||
|
||
---
|
||
|
||
## 🏗️ 目录结构概览
|
||
|
||
新增一张表后,在 `agileboot-domain/src/main/java/com/agileboot/domain/{module}/` 目录下需要创建以下结构:
|
||
|
||
```
|
||
{module}/
|
||
├── command/ # 命令对象(写操作)
|
||
│ ├── Add{Module}Command.java
|
||
│ ├── Update{Module}Command.java
|
||
│ ├── Delete{Module}Command.java
|
||
│ └── ...
|
||
├── db/ # 数据层
|
||
│ ├── {Module}Entity.java # 实体类
|
||
│ ├── {Module}Service.java # 服务接口
|
||
│ ├── {Module}ServiceImpl.java # 服务实现
|
||
│ ├── {Module}Mapper.java # 数据访问
|
||
│ └── Search{Module}DO.java # 数据对象(复杂查询)
|
||
├── dto/ # 数据传输对象
|
||
│ ├── {Module}DTO.java
|
||
│ ├── {Module}DetailDTO.java
|
||
│ └── {Module}ProfileDTO.java
|
||
├── model/ # 领域模型
|
||
│ ├── {Module}Model.java
|
||
│ └── {Module}ModelFactory.java
|
||
├── query/ # 查询对象
|
||
│ └── Search{Module}Query.java
|
||
└── {Module}ApplicationService.java # 应用服务层
|
||
```
|
||
|
||
---
|
||
|
||
## 🏛️ 架构原则与文件组织规范
|
||
|
||
### 目录层级规范
|
||
|
||
**严格遵守单层子文件夹原则**:
|
||
|
||
1. **每个模块下最多只能有一层子文件夹**,不允许出现多层嵌套的包结构
|
||
2. **例外情况**:
|
||
- command 包:可以直接包含多个 Command 类文件,无需再分层
|
||
- query 包:可以直接包含多个 Query 类文件,无需再分层
|
||
- dto 包:可以直接包含多个 DTO 类文件,无需再分层
|
||
- model 包:可以直接包含 Model 和 ModelFactory 类文件,无需再分层
|
||
|
||
3. **正确的组织方式**:
|
||
```
|
||
✓ db/{Module}Entity.java
|
||
✓ db/{Module}Service.java
|
||
✓ db/{Module}ServiceImpl.java
|
||
✓ db/{Module}Mapper.java
|
||
✓ db/Search{Module}DO.java
|
||
|
||
✗ 错误:db/entity/{Module}Entity.java
|
||
✗ 错误:db/service/{Module}Service.java
|
||
✗ 错误:db/service/impl/{Module}ServiceImpl.java
|
||
✗ 错误:db/mapper/{Module}Mapper.java
|
||
✗ 错误:db/do/Search{Module}DO.java
|
||
```
|
||
|
||
### 命名规范
|
||
|
||
- **包名**:全部使用小写字母,单词间用下划线分隔(如 `command`、`dto`、`model`)
|
||
- **文件命名**:采用 PascalCase 命名法,体现类的职责
|
||
- **目录名**:采用小写字母,与包名保持一致
|
||
|
||
### 目录职责划分
|
||
|
||
| 目录 | 职责 | 文件类型 |
|
||
|------|------|----------|
|
||
| `command/` | 封装写操作请求参数 | Add/Update/Delete 命令对象 |
|
||
| `query/` | 封装读操作查询条件 | Search 查询对象 |
|
||
| `dto/` | 向前端传输数据 | DTO、详情DTO、配置文件DTO |
|
||
| `model/` | 领域模型和业务逻辑 | Model、ModelFactory |
|
||
| `db/` | 数据访问层 | Entity、Service、Mapper、DO |
|
||
| ApplicationService | 应用服务层 | 事务脚本、流程编排 |
|
||
|
||
---
|
||
|
||
## 📝 核心组件详解
|
||
|
||
### 1. Entity(实体类)
|
||
|
||
**位置**: `db/{Module}Entity.java`
|
||
|
||
**职责**: 数据库表的 Java 映射,继承 `BaseEntity` 获得通用字段
|
||
|
||
**关键特性**:
|
||
- 继承 `BaseEntity<SELF>`
|
||
- 实现 `pkVal()` 返回主键
|
||
- 使用 MyBatis-Plus 注解:`@TableName`、`@TableId`、`@TableField`
|
||
- 所有字段使用包装类型(Long, Integer),避免空指针
|
||
|
||
#### 代码示例
|
||
|
||
```java
|
||
package com.agileboot.domain.example.user.db;
|
||
|
||
import com.agileboot.common.core.base.BaseEntity;
|
||
import com.baomidou.mybatisplus.annotation.IdType;
|
||
import com.baomidou.mybatisplus.annotation.TableField;
|
||
import com.baomidou.mybatisplus.annotation.TableId;
|
||
import com.baomidou.mybatisplus.annotation.TableName;
|
||
import io.swagger.annotations.ApiModel;
|
||
import io.swagger.annotations.ApiModelProperty;
|
||
import java.io.Serializable;
|
||
import java.math.BigDecimal;
|
||
import java.util.Date;
|
||
import lombok.Data;
|
||
import lombok.EqualsAndHashCode;
|
||
|
||
/**
|
||
* 用户信息表
|
||
*
|
||
* @author your-name
|
||
* @since 2025-01-01
|
||
*/
|
||
@Data
|
||
@EqualsAndHashCode(callSuper = true)
|
||
@TableName("example_user")
|
||
@ApiModel(value = "ExampleUserEntity对象", description = "用户信息表")
|
||
public class ExampleUserEntity extends BaseEntity<ExampleUserEntity> {
|
||
|
||
private static final long serialVersionUID = 1L;
|
||
|
||
@ApiModelProperty("用户ID")
|
||
@TableId(value = "user_id", type = IdType.AUTO)
|
||
private Long userId;
|
||
|
||
@ApiModelProperty("用户姓名")
|
||
@TableField("user_name")
|
||
private String userName;
|
||
|
||
@ApiModelProperty("年龄")
|
||
@TableField("age")
|
||
private Integer age;
|
||
|
||
@ApiModelProperty("邮箱")
|
||
@TableField("email")
|
||
private String email;
|
||
|
||
@ApiModelProperty("余额")
|
||
@TableField("balance")
|
||
private BigDecimal balance;
|
||
|
||
@ApiModelProperty("状态(1正常 2停用)")
|
||
@TableField("status")
|
||
private Integer status;
|
||
|
||
@ApiModelProperty("部门ID")
|
||
@TableField("dept_id")
|
||
private Long deptId;
|
||
|
||
@ApiModelProperty("备注")
|
||
@TableField("remark")
|
||
private String remark;
|
||
|
||
@Override
|
||
public Serializable pkVal() {
|
||
return this.userId;
|
||
}
|
||
}
|
||
```
|
||
|
||
**关键点**:
|
||
- 类名: `{Module}Entity`,表名使用下划线命名 `{module}_{name}`
|
||
- 主键字段: `{module}_id`,类型为 `Long`,注解 `@TableId(type = IdType.AUTO)`
|
||
- 继承 `BaseEntity` 获得: `creatorId`, `createTime`, `updaterId`, `updateTime`, `deleted`
|
||
|
||
---
|
||
|
||
### 2. Service(服务层)
|
||
|
||
**位置**: `db/{Module}Service.java` 和 `db/{Module}ServiceImpl.java`
|
||
|
||
**职责**: 封装数据库操作和简单业务逻辑,继承 MyBatis-Plus 的 `IService`
|
||
|
||
#### 接口定义
|
||
|
||
```java
|
||
package com.agileboot.domain.example.user.db;
|
||
|
||
import com.agileboot.common.core.page.AbstractPageQuery;
|
||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||
import com.baomidou.mybatisplus.extension.service.IService;
|
||
|
||
/**
|
||
* 用户信息表 服务类
|
||
*
|
||
* @author your-name
|
||
* @since 2025-01-01
|
||
*/
|
||
public interface ExampleUserService extends IService<ExampleUserEntity> {
|
||
|
||
/**
|
||
* 检测邮箱是否唯一
|
||
*
|
||
* @param email 邮箱
|
||
* @param userId 用户ID(更新时传入,排除自身)
|
||
* @return 是否唯一
|
||
*/
|
||
boolean isEmailUnique(String email, Long userId);
|
||
|
||
/**
|
||
* 检测用户名是否唯一
|
||
*
|
||
* @param userName 用户名
|
||
* @return 是否唯一
|
||
*/
|
||
boolean isUserNameUnique(String userName);
|
||
|
||
/**
|
||
* 获取用户的部门信息
|
||
*
|
||
* @param userId 用户ID
|
||
* @return 部门信息
|
||
*/
|
||
Long getDeptIdOfUser(Long userId);
|
||
|
||
/**
|
||
* 根据条件分页查询用户列表
|
||
*
|
||
* @param query 查询参数
|
||
* @return 用户信息集合
|
||
*/
|
||
Page<ExampleUserEntity> getUserList(AbstractPageQuery<ExampleUserEntity> query);
|
||
|
||
/**
|
||
* 根据条件分页查询用户列表(带额外字段)
|
||
*
|
||
* @param query 查询参数
|
||
* @return 用户信息集合(含关联字段)
|
||
*/
|
||
Page<SearchUserDO> getUserListWithJoin(AbstractPageQuery<SearchUserDO> query);
|
||
}
|
||
```
|
||
|
||
#### 实现类
|
||
|
||
```java
|
||
package com.agileboot.domain.example.user.db.impl;
|
||
|
||
import cn.hutool.core.util.StrUtil;
|
||
import com.agileboot.common.core.page.AbstractPageQuery;
|
||
import com.agileboot.domain.example.user.db.ExampleUserEntity;
|
||
import com.agileboot.domain.example.user.db.SearchUserDO;
|
||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||
import java.util.Objects;
|
||
import lombok.RequiredArgsConstructor;
|
||
import org.springframework.stereotype.Service;
|
||
|
||
/**
|
||
* 用户信息表 服务实现类
|
||
*
|
||
* @author your-name
|
||
* @since 2025-01-01
|
||
*/
|
||
@Service
|
||
@RequiredArgsConstructor
|
||
public class ExampleUserServiceImpl extends ServiceImpl<ExampleUserMapper, ExampleUserEntity> implements ExampleUserService {
|
||
|
||
@Override
|
||
public boolean isEmailUnique(String email, Long userId) {
|
||
if (StrUtil.isEmpty(email)) {
|
||
return true;
|
||
}
|
||
|
||
LambdaQueryWrapper<ExampleUserEntity> wrapper = new LambdaQueryWrapper<>();
|
||
|
||
wrapper.eq(ExampleUserEntity::getEmail, email)
|
||
.eq(ExampleUserEntity::getDeleted, 0);
|
||
|
||
// 如果是更新操作,排除自身
|
||
if (userId != null) {
|
||
wrapper.ne(ExampleUserEntity::getUserId, userId);
|
||
}
|
||
|
||
return !(this.count(wrapper) > 0);
|
||
}
|
||
|
||
@Override
|
||
public boolean isUserNameUnique(String userName) {
|
||
if (StrUtil.isEmpty(userName)) {
|
||
return true;
|
||
}
|
||
|
||
LambdaQueryWrapper<ExampleUserEntity> wrapper = new LambdaQueryWrapper<>();
|
||
|
||
wrapper.eq(ExampleUserEntity::getUserName, userName)
|
||
.eq(ExampleUserEntity::getDeleted, 0);
|
||
|
||
return !(this.count(wrapper) > 0);
|
||
}
|
||
|
||
@Override
|
||
public Long getDeptIdOfUser(Long userId) {
|
||
if (userId == null) {
|
||
return null;
|
||
}
|
||
|
||
ExampleUserEntity entity = this.getById(userId);
|
||
return entity != null ? entity.getDeptId() : null;
|
||
}
|
||
|
||
@Override
|
||
public Page<ExampleUserEntity> getUserList(AbstractPageQuery<ExampleUserEntity> query) {
|
||
return this.page(query.toPage(), query.addQueryCondition());
|
||
}
|
||
|
||
@Override
|
||
public Page<SearchUserDO> getUserListWithJoin(AbstractPageQuery<SearchUserDO> query) {
|
||
// 自定义 SQL 查询,返回关联数据
|
||
return this.getBaseMapper().selectUserListWithJoin(query.toPage(), query);
|
||
}
|
||
}
|
||
```
|
||
|
||
**关键点**:
|
||
- 接口继承 `IService<Entity>`
|
||
- 实现类继承 `ServiceImpl<Mapper, Entity>`
|
||
- 简单业务逻辑在 Service 中处理
|
||
- 复杂业务逻辑交给 Model 处理
|
||
|
||
---
|
||
|
||
### 3. Mapper(数据访问层)
|
||
|
||
**位置**: `db/{Module}Mapper.java`
|
||
|
||
**职责**: MyBatis 数据访问接口,通常使用 MyBatis-Plus 的 `BaseMapper`
|
||
|
||
#### 代码示例
|
||
|
||
```java
|
||
package com.agileboot.domain.example.user.db;
|
||
|
||
import com.agileboot.common.core.page.AbstractPageQuery;
|
||
import com.agileboot.domain.example.user.db.SearchUserDO;
|
||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||
import org.apache.ibatis.annotations.Mapper;
|
||
import org.apache.ibatis.annotations.Param;
|
||
import org.apache.ibatis.annotations.Select;
|
||
|
||
/**
|
||
* 用户信息表 Mapper 接口
|
||
*
|
||
* @author your-name
|
||
* @since 2025-01-01
|
||
*/
|
||
@Mapper
|
||
public interface ExampleUserMapper extends BaseMapper<ExampleUserEntity> {
|
||
|
||
/**
|
||
* 分页查询用户列表(带关联数据)
|
||
*
|
||
* @param page 分页参数
|
||
* @param query 查询条件
|
||
* @return 用户列表
|
||
*/
|
||
@Select({
|
||
"<script>",
|
||
"SELECT u.*, d.dept_name, r.role_name",
|
||
"FROM example_user u",
|
||
"LEFT JOIN sys_dept d ON u.dept_id = d.dept_id",
|
||
"LEFT JOIN sys_role r ON u.role_id = r.role_id",
|
||
"WHERE u.deleted = 0",
|
||
"<if test='query.userName != null and query.userName != \"\"'>",
|
||
" AND u.user_name LIKE CONCAT('%', #{query.userName}, '%')",
|
||
"</if>",
|
||
"<if test='query.email != null and query.email != \"\"'>",
|
||
" AND u.email LIKE CONCAT('%', #{query.email}, '%')",
|
||
"</if>",
|
||
"ORDER BY u.create_time DESC",
|
||
"</script>"
|
||
})
|
||
Page<SearchUserDO> selectUserListWithJoin(
|
||
Page<SearchUserDO> page,
|
||
@Param("query") AbstractPageQuery<SearchUserDO> query
|
||
);
|
||
}
|
||
```
|
||
|
||
**关键点**:
|
||
- 继承 `BaseMapper<Entity>`
|
||
- 复杂查询使用 `@Select` 注解或 XML 文件
|
||
- 使用 `#{}` 进行参数绑定,避免 SQL 注入
|
||
|
||
---
|
||
|
||
### 4. DO(数据对象)
|
||
|
||
**位置**: `db/Search{Module}DO.java`
|
||
|
||
**职责**: 复杂查询返回的数据对象,包含关联表字段
|
||
|
||
#### 代码示例
|
||
|
||
```java
|
||
package com.agileboot.domain.example.user.db;
|
||
|
||
import com.baomidou.mybatisplus.annotation.TableField;
|
||
import java.math.BigDecimal;
|
||
import java.util.Date;
|
||
import lombok.Data;
|
||
|
||
/**
|
||
* 用户查询数据对象(包含关联字段)
|
||
*
|
||
* @author your-name
|
||
* @since 2025-01-01
|
||
*/
|
||
@Data
|
||
public class SearchUserDO {
|
||
|
||
private Long userId;
|
||
|
||
private String userName;
|
||
|
||
private Integer age;
|
||
|
||
private String email;
|
||
|
||
private BigDecimal balance;
|
||
|
||
private Integer status;
|
||
|
||
private Long deptId;
|
||
|
||
@TableField("dept_name")
|
||
private String deptName;
|
||
|
||
@TableField("role_name")
|
||
private String roleName;
|
||
|
||
private Date createTime;
|
||
|
||
private Date updateTime;
|
||
|
||
private String remark;
|
||
}
|
||
```
|
||
|
||
**关键点**:
|
||
- 用于复杂查询,包含关联表字段
|
||
- 字段名使用数据库列名或 `as` 别名
|
||
- 不需要继承任何类
|
||
|
||
---
|
||
|
||
### 5. Command(命令对象)
|
||
|
||
**位置**: `command/Add{Module}Command.java`、`Update{Module}Command.java`
|
||
|
||
**职责**: 封装写操作的输入参数,用于接收前端传入的数据
|
||
|
||
#### 添加命令
|
||
|
||
```java
|
||
package com.agileboot.domain.example.user.command;
|
||
|
||
import com.agileboot.common.annotation.ExcelColumn;
|
||
import lombok.Data;
|
||
|
||
/**
|
||
* 添加用户命令
|
||
*
|
||
* @author your-name
|
||
* @since 2025-01-01
|
||
*/
|
||
@Data
|
||
public class AddUserCommand {
|
||
|
||
@ExcelColumn(name = "用户姓名")
|
||
private String userName;
|
||
|
||
@ExcelColumn(name = "年龄")
|
||
private Integer age;
|
||
|
||
@ExcelColumn(name = "邮箱")
|
||
private String email;
|
||
|
||
@ExcelColumn(name = "余额")
|
||
private String balance; // 使用 String 接收,前端传入
|
||
|
||
@ExcelColumn(name = "状态")
|
||
private Integer status;
|
||
|
||
@ExcelColumn(name = "部门ID")
|
||
private Long deptId;
|
||
|
||
@ExcelColumn(name = "备注")
|
||
private String remark;
|
||
}
|
||
```
|
||
|
||
#### 更新命令
|
||
|
||
```java
|
||
package com.agileboot.domain.example.user.command;
|
||
|
||
import com.agileboot.common.annotation.ExcelColumn;
|
||
import lombok.Data;
|
||
|
||
/**
|
||
* 更新用户命令
|
||
*
|
||
* @author your-name
|
||
* @since 2025-01-01
|
||
*/
|
||
@Data
|
||
public class UpdateUserCommand extends AddUserCommand {
|
||
|
||
@ExcelColumn(name = "用户ID")
|
||
private Long userId;
|
||
}
|
||
```
|
||
|
||
**关键点**:
|
||
- 纯 POJO 类,仅用于数据传输
|
||
- 使用 `@ExcelColumn` 注解支持 Excel 导入导出
|
||
- 命令对象通常继承或包含基础字段
|
||
- 字段名与前端保持一致
|
||
|
||
---
|
||
|
||
### 6. Query(查询对象)
|
||
|
||
**位置**: `query/Search{Module}Query.java`
|
||
|
||
**职责**: 封装查询条件和分页参数,构建 MyBatis 查询条件
|
||
|
||
#### 代码示例
|
||
|
||
```java
|
||
package com.agileboot.domain.example.user.query;
|
||
|
||
import cn.hutool.core.util.StrUtil;
|
||
import com.agileboot.common.core.page.AbstractPageQuery;
|
||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||
import lombok.Data;
|
||
import lombok.EqualsAndHashCode;
|
||
|
||
/**
|
||
* 用户查询条件
|
||
*
|
||
* @author your-name
|
||
* @since 2025-01-01
|
||
*/
|
||
@EqualsAndHashCode(callSuper = true)
|
||
@Data
|
||
public class SearchUserQuery<T> extends AbstractPageQuery<T> {
|
||
|
||
private Long userId;
|
||
|
||
private String userName;
|
||
|
||
private String email;
|
||
|
||
private Integer status;
|
||
|
||
private Long deptId;
|
||
|
||
private Integer minAge;
|
||
|
||
private Integer maxAge;
|
||
|
||
@Override
|
||
public QueryWrapper<T> addQueryCondition() {
|
||
QueryWrapper<T> queryWrapper = new QueryWrapper<>();
|
||
|
||
queryWrapper.like(StrUtil.isNotEmpty(userName), "user_name", userName)
|
||
.like(StrUtil.isNotEmpty(email), "email", email)
|
||
.eq(userId != null, "user_id", userId)
|
||
.eq(status != null, "status", status)
|
||
.eq(deptId != null, "dept_id", deptId)
|
||
.eq("deleted", 0)
|
||
.between(minAge != null && maxAge != null, "age", minAge, maxAge);
|
||
|
||
// 设置时间范围排序字段
|
||
this.timeRangeColumn = "create_time";
|
||
|
||
return queryWrapper;
|
||
}
|
||
}
|
||
```
|
||
|
||
**关键点**:
|
||
- 继承 `AbstractPageQuery<T>`
|
||
- 实现 `addQueryCondition()` 方法构建查询条件
|
||
- 使用链式调用构建查询条件
|
||
- 非空判断:`StrUtil.isNotEmpty()` 和 `!= null`
|
||
- 设置默认排序字段
|
||
|
||
---
|
||
|
||
### 7. DTO(数据传输对象)
|
||
|
||
**位置**: `dto/{Module}DTO.java`、`{Module}DetailDTO.java`、`{Module}ProfileDTO.java`
|
||
|
||
**职责**: 向前端传输数据,支持缓存集成和字段转换
|
||
|
||
#### 基础 DTO
|
||
|
||
```java
|
||
package com.agileboot.domain.example.user.dto;
|
||
|
||
import cn.hutool.core.bean.BeanUtil;
|
||
import com.agileboot.common.annotation.ExcelColumn;
|
||
import com.agileboot.common.annotation.ExcelSheet;
|
||
import com.agileboot.domain.common.cache.CacheCenter;
|
||
import com.agileboot.domain.example.user.db.ExampleUserEntity;
|
||
import com.agileboot.domain.example.user.db.SearchUserDO;
|
||
import java.math.BigDecimal;
|
||
import java.util.Date;
|
||
import lombok.Data;
|
||
|
||
/**
|
||
* 用户DTO
|
||
*
|
||
* @author your-name
|
||
* @since 2025-01-01
|
||
*/
|
||
@ExcelSheet(name = "用户列表")
|
||
@Data
|
||
public class UserDTO {
|
||
|
||
public UserDTO(ExampleUserEntity entity) {
|
||
if (entity != null) {
|
||
BeanUtil.copyProperties(entity, this);
|
||
|
||
// 从缓存中获取关联数据
|
||
if (entity.getDeptId() != null) {
|
||
// 示例:获取部门名称(如果缓存中有)
|
||
// this.deptName = CacheCenter.deptCache.get(entity.getDeptId() + "")...;
|
||
}
|
||
}
|
||
}
|
||
|
||
public UserDTO(SearchUserDO entity) {
|
||
if (entity != null) {
|
||
BeanUtil.copyProperties(entity, this);
|
||
}
|
||
}
|
||
|
||
@ExcelColumn(name = "用户ID")
|
||
private Long userId;
|
||
|
||
@ExcelColumn(name = "用户姓名")
|
||
private String userName;
|
||
|
||
@ExcelColumn(name = "年龄")
|
||
private Integer age;
|
||
|
||
@ExcelColumn(name = "邮箱")
|
||
private String email;
|
||
|
||
@ExcelColumn(name = "余额")
|
||
private BigDecimal balance;
|
||
|
||
@ExcelColumn(name = "状态")
|
||
private String status; // 可以转换为文字描述
|
||
|
||
@ExcelColumn(name = "部门ID")
|
||
private Long deptId;
|
||
|
||
@ExcelColumn(name = "部门名称")
|
||
private String deptName;
|
||
|
||
@ExcelColumn(name = "创建时间")
|
||
private Date createTime;
|
||
|
||
@ExcelColumn(name = "备注")
|
||
private String remark;
|
||
}
|
||
```
|
||
|
||
#### 详情 DTO(包含更多选项数据)
|
||
|
||
```java
|
||
package com.agileboot.domain.example.user.dto;
|
||
|
||
import com.agileboot.domain.example.user.db.ExampleUserEntity;
|
||
import com.agileboot.domain.system.dept.dto.DeptDTO;
|
||
import com.agileboot.domain.system.role.dto.RoleDTO;
|
||
import java.util.List;
|
||
import lombok.Data;
|
||
|
||
/**
|
||
* 用户详情DTO(包含选项数据)
|
||
*
|
||
* @author your-name
|
||
* @since 2025-01-01
|
||
*/
|
||
@Data
|
||
public class UserDetailDTO {
|
||
|
||
private UserDTO user;
|
||
|
||
private List<DeptDTO> deptOptions;
|
||
|
||
private List<RoleDTO> roleOptions;
|
||
}
|
||
```
|
||
|
||
**关键点**:
|
||
- 支持从 Entity 或 DO 转换
|
||
- 使用 `BeanUtil.copyProperties()` 进行属性复制
|
||
- 可以集成多级缓存获取关联数据
|
||
- 支持 Excel 导入导出(`@ExcelColumn`)
|
||
- 可包含选项数据(如部门列表、角色列表)
|
||
|
||
---
|
||
|
||
### 8. Model(领域模型)
|
||
|
||
**位置**: `model/{Module}Model.java`
|
||
|
||
**职责**: 领域模型,封装核心业务逻辑,包括校验、状态转换等
|
||
|
||
#### 代码示例
|
||
|
||
```java
|
||
package com.agileboot.domain.example.user.model;
|
||
|
||
import cn.hutool.core.bean.BeanUtil;
|
||
import cn.hutool.core.util.StrUtil;
|
||
import com.agileboot.common.exception.ApiException;
|
||
import com.agileboot.common.exception.error.ErrorCode;
|
||
import com.agileboot.domain.example.user.command.AddUserCommand;
|
||
import com.agileboot.domain.example.user.command.UpdateUserCommand;
|
||
import com.agileboot.domain.example.user.db.ExampleUserEntity;
|
||
import com.agileboot.domain.example.user.db.ExampleUserService;
|
||
import java.math.BigDecimal;
|
||
import lombok.Data;
|
||
import lombok.EqualsAndHashCode;
|
||
import lombok.NoArgsConstructor;
|
||
|
||
/**
|
||
* 用户领域模型
|
||
*
|
||
* @author your-name
|
||
* @since 2025-01-01
|
||
*/
|
||
@EqualsAndHashCode(callSuper = true)
|
||
@Data
|
||
@NoArgsConstructor
|
||
public class UserModel extends ExampleUserEntity {
|
||
|
||
private ExampleUserService userService;
|
||
|
||
public UserModel(ExampleUserEntity entity, ExampleUserService userService) {
|
||
if (entity != null) {
|
||
BeanUtil.copyProperties(entity, this);
|
||
}
|
||
this.userService = userService;
|
||
}
|
||
|
||
public UserModel(ExampleUserService userService) {
|
||
this.userService = userService;
|
||
}
|
||
|
||
/**
|
||
* 加载添加用户命令
|
||
*/
|
||
public void loadAddUserCommand(AddUserCommand command) {
|
||
if (command != null) {
|
||
BeanUtil.copyProperties(command, this, "userId");
|
||
|
||
// 转换余额字段
|
||
if (StrUtil.isNotEmpty(command.getBalance())) {
|
||
try {
|
||
this.setBalance(new BigDecimal(command.getBalance()));
|
||
} catch (NumberFormatException e) {
|
||
throw new ApiException(ErrorCode.Business.COMMON_BAD_REQUEST, "余额格式不正确");
|
||
}
|
||
}
|
||
|
||
// 设置默认值
|
||
if (this.getStatus() == null) {
|
||
this.setStatus(1); // 默认正常状态
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载更新用户命令
|
||
*/
|
||
public void loadUpdateUserCommand(UpdateUserCommand command) {
|
||
if (command != null) {
|
||
loadAddUserCommand(command);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 校验用户名唯一性
|
||
*/
|
||
public void checkUserNameIsUnique() {
|
||
if (!userService.isUserNameUnique(getUserName())) {
|
||
throw new ApiException(ErrorCode.Business.USER_NAME_IS_NOT_UNIQUE);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 校验邮箱唯一性
|
||
*/
|
||
public void checkEmailIsUnique() {
|
||
if (!userService.isEmailUnique(getEmail(), getUserId())) {
|
||
throw new ApiException(ErrorCode.Business.USER_EMAIL_IS_NOT_UNIQUE);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 校验关联数据是否存在
|
||
*/
|
||
public void checkFieldRelatedEntityExist() {
|
||
// 示例:校验部门是否存在
|
||
if (getDeptId() != null) {
|
||
// 可以通过其他 Service 校验部门是否存在
|
||
// Example: deptService.getById(getDeptId());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 校验是否可以删除
|
||
*/
|
||
public void checkCanBeDelete() {
|
||
// 业务逻辑:例如检查用户是否有未完成的订单
|
||
// if (hasUnfinishedOrders()) {
|
||
// throw new ApiException(ErrorCode.Business.COMMON_BAD_REQUEST, "用户存在未完成的订单,无法删除");
|
||
// }
|
||
|
||
// 示例:检查是否为系统管理员
|
||
if (this.getIsAdmin() != null && this.getIsAdmin()) {
|
||
throw new ApiException(ErrorCode.Business.COMMON_BAD_REQUEST, "系统管理员不能删除");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置密码(加密)
|
||
*/
|
||
public void setEncryptedPassword(String rawPassword) {
|
||
// 示例:加密密码
|
||
// this.setPassword(PasswordUtils.encrypt(rawPassword));
|
||
}
|
||
|
||
/**
|
||
* 校验年龄是否合法
|
||
*/
|
||
public void checkAgeIsValid() {
|
||
if (getAge() != null && (getAge() < 0 || getAge() > 150)) {
|
||
throw new ApiException(ErrorCode.Business.COMMON_BAD_REQUEST, "年龄必须在0-150之间");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 预保存校验
|
||
*/
|
||
public void validateBeforeSave() {
|
||
if (StrUtil.isEmpty(getUserName())) {
|
||
throw new ApiException(ErrorCode.Business.COMMON_BAD_REQUEST, "用户名不能为空");
|
||
}
|
||
|
||
checkUserNameIsUnique();
|
||
checkEmailIsUnique();
|
||
checkFieldRelatedEntityExist();
|
||
checkAgeIsValid();
|
||
}
|
||
}
|
||
```
|
||
|
||
**关键点**:
|
||
- 继承实体类,获得所有字段
|
||
- 注入 Service 用于数据操作
|
||
- 包含 `load*Command()` 方法加载命令数据
|
||
- 包含 `check*()` 方法进行业务校验
|
||
- 包含业务逻辑处理方法
|
||
- 在 Model 中封装所有业务规则
|
||
|
||
---
|
||
|
||
### 9. ModelFactory(模型工厂)
|
||
|
||
**位置**: `model/{Module}ModelFactory.java`
|
||
|
||
**职责**: 统一创建 Model 实例,管理依赖注入和错误处理
|
||
|
||
#### 代码示例
|
||
|
||
```java
|
||
package com.agileboot.domain.example.user.model;
|
||
|
||
import com.agileboot.common.exception.ApiException;
|
||
import com.agileboot.common.exception.error.ErrorCode;
|
||
import com.agileboot.domain.example.user.db.ExampleUserEntity;
|
||
import com.agileboot.domain.example.user.db.ExampleUserService;
|
||
import lombok.RequiredArgsConstructor;
|
||
import org.springframework.stereotype.Component;
|
||
|
||
/**
|
||
* 用户模型工厂
|
||
*
|
||
* @author your-name
|
||
* @since 2025-01-01
|
||
*/
|
||
@Component
|
||
@RequiredArgsConstructor
|
||
public class UserModelFactory {
|
||
|
||
private final ExampleUserService userService;
|
||
|
||
/**
|
||
* 根据ID加载用户模型
|
||
*
|
||
* @param userId 用户ID
|
||
* @return 用户模型
|
||
*/
|
||
public UserModel loadById(Long userId) {
|
||
ExampleUserEntity entity = userService.getById(userId);
|
||
if (entity == null) {
|
||
throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, userId, "用户");
|
||
}
|
||
return new UserModel(entity, userService);
|
||
}
|
||
|
||
/**
|
||
* 创建新用户模型
|
||
*
|
||
* @return 用户模型
|
||
*/
|
||
public UserModel create() {
|
||
return new UserModel(userService);
|
||
}
|
||
}
|
||
```
|
||
|
||
**关键点**:
|
||
- 使用 `@Component` 注解注入 Spring 容器
|
||
- 使用 `@RequiredArgsConstructor` 自动注入依赖
|
||
- `loadById()` 检查数据是否存在,不存在则抛异常
|
||
- `create()` 创建空模型,用于新增操作
|
||
|
||
---
|
||
|
||
### 10. ApplicationService(应用服务)
|
||
|
||
**位置**: `{Module}ApplicationService.java`
|
||
|
||
**职责**: 事务脚本层,协调 Model 和 Service,处理业务流程编排
|
||
|
||
#### 代码示例
|
||
|
||
```java
|
||
package com.agileboot.domain.example.user;
|
||
|
||
import com.agileboot.common.core.page.PageDTO;
|
||
import com.agileboot.domain.common.command.BulkOperationCommand;
|
||
import com.agileboot.domain.example.user.command.AddUserCommand;
|
||
import com.agileboot.domain.example.user.command.UpdateUserCommand;
|
||
import com.agileboot.domain.example.user.db.SearchUserDO;
|
||
import com.agileboot.domain.example.user.dto.UserDTO;
|
||
import com.agileboot.domain.example.user.dto.UserDetailDTO;
|
||
import com.agileboot.domain.example.user.model.UserModel;
|
||
import com.agileboot.domain.example.user.model.UserModelFactory;
|
||
import com.agileboot.domain.example.user.query.SearchUserQuery;
|
||
import com.agileboot.domain.system.dept.db.SysDeptService;
|
||
import com.agileboot.domain.system.dept.dto.DeptDTO;
|
||
import com.agileboot.domain.example.user.db.ExampleUserEntity;
|
||
import com.agileboot.domain.example.user.db.ExampleUserService;
|
||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||
import java.util.List;
|
||
import java.util.stream.Collectors;
|
||
import lombok.RequiredArgsConstructor;
|
||
import org.springframework.stereotype.Service;
|
||
import org.springframework.transaction.annotation.Transactional;
|
||
|
||
/**
|
||
* 用户应用服务
|
||
*
|
||
* @author your-name
|
||
* @since 2025-01-01
|
||
*/
|
||
@Service
|
||
@RequiredArgsConstructor
|
||
public class UserApplicationService {
|
||
|
||
private final ExampleUserService userService;
|
||
|
||
private final SysDeptService deptService;
|
||
|
||
private final UserModelFactory userModelFactory;
|
||
|
||
/**
|
||
* 获取用户列表(分页)
|
||
*/
|
||
public PageDTO<UserDTO> getUserList(SearchUserQuery<SearchUserDO> query) {
|
||
Page<SearchUserDO> userPage = userService.getUserListWithJoin(query);
|
||
List<UserDTO> userDTOList = userPage.getRecords()
|
||
.stream()
|
||
.map(UserDTO::new)
|
||
.collect(Collectors.toList());
|
||
return new PageDTO<>(userDTOList, userPage.getTotal());
|
||
}
|
||
|
||
/**
|
||
* 获取用户详情
|
||
*/
|
||
public UserDetailDTO getUserDetailInfo(Long userId) {
|
||
ExampleUserEntity userEntity = userService.getById(userId);
|
||
|
||
UserDetailDTO detailDTO = new UserDetailDTO();
|
||
detailDTO.setUser(new UserDTO(userEntity));
|
||
|
||
// 获取部门选项
|
||
List<DeptDTO> deptOptions = deptService.list()
|
||
.stream()
|
||
.map(DeptDTO::new)
|
||
.collect(Collectors.toList());
|
||
detailDTO.setDeptOptions(deptOptions);
|
||
|
||
return detailDTO;
|
||
}
|
||
|
||
/**
|
||
* 添加用户
|
||
*/
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public void addUser(AddUserCommand command) {
|
||
UserModel model = userModelFactory.create();
|
||
model.loadAddUserCommand(command);
|
||
|
||
// 业务校验
|
||
model.validateBeforeSave();
|
||
|
||
// 保存
|
||
model.insert();
|
||
}
|
||
|
||
/**
|
||
* 更新用户
|
||
*/
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public void updateUser(UpdateUserCommand command) {
|
||
UserModel model = userModelFactory.loadById(command.getUserId());
|
||
model.loadUpdateUserCommand(command);
|
||
|
||
// 业务校验
|
||
model.checkEmailIsUnique();
|
||
model.checkFieldRelatedEntityExist();
|
||
model.checkAgeIsValid();
|
||
|
||
// 更新
|
||
model.updateById();
|
||
}
|
||
|
||
/**
|
||
* 删除用户(单个)
|
||
*/
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public void deleteUser(Long userId) {
|
||
UserModel model = userModelFactory.loadById(userId);
|
||
|
||
// 业务校验
|
||
model.checkCanBeDelete();
|
||
|
||
// 软删除
|
||
model.deleteById();
|
||
}
|
||
|
||
/**
|
||
* 批量删除用户
|
||
*/
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public void deleteUsers(BulkOperationCommand<Long> command) {
|
||
for (Long userId : command.getIds()) {
|
||
UserModel model = userModelFactory.loadById(userId);
|
||
model.checkCanBeDelete();
|
||
model.deleteById();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更改用户状态
|
||
*/
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public void changeUserStatus(Long userId, Integer status) {
|
||
UserModel model = userModelFactory.loadById(userId);
|
||
|
||
model.setStatus(status);
|
||
model.updateById();
|
||
}
|
||
|
||
/**
|
||
* 重置用户密码
|
||
*/
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public void resetPassword(Long userId, String newPassword) {
|
||
UserModel model = userModelFactory.loadById(userId);
|
||
|
||
model.setEncryptedPassword(newPassword);
|
||
model.updateById();
|
||
}
|
||
}
|
||
```
|
||
|
||
**关键点**:
|
||
- 使用 `@Service` 注解
|
||
- 使用 `@RequiredArgsConstructor` 注入依赖
|
||
- 写操作使用 `@Transactional` 注解保证事务
|
||
- 协调 Model 和 Service
|
||
- 返回 DTO 给 Controller
|
||
- 包含完整的 CRUD 操作
|
||
|
||
---
|
||
|
||
## 🔄 CQRS 流程
|
||
|
||
### Command(写操作)流程
|
||
|
||
```
|
||
Controller → Command → ApplicationService → Model → 业务校验 → 保存
|
||
```
|
||
|
||
**示例**:
|
||
```java
|
||
// 1. Controller 接收参数,封装为 Command
|
||
AddUserCommand command = new AddUserCommand();
|
||
// ... 设置参数
|
||
|
||
// 2. ApplicationService 处理
|
||
userApplicationService.addUser(command);
|
||
|
||
// 3. ApplicationService 创建 Model 并加载 Command
|
||
UserModel model = userModelFactory.create();
|
||
model.loadAddUserCommand(command);
|
||
|
||
// 4. Model 进行业务校验
|
||
model.validateBeforeSave();
|
||
|
||
// 5. Model 执行保存操作
|
||
model.insert();
|
||
```
|
||
|
||
### Query(读操作)流程
|
||
|
||
```
|
||
Controller → Query → Service → Mapper → DTO
|
||
```
|
||
|
||
**示例**:
|
||
```java
|
||
// 1. Controller 接收查询参数,封装为 Query
|
||
SearchUserQuery<SearchUserDO> query = new SearchUserQuery<>();
|
||
// ... 设置查询条件
|
||
|
||
// 2. ApplicationService 处理
|
||
PageDTO<UserDTO> result = userApplicationService.getUserList(query);
|
||
|
||
// 3. Service 执行查询
|
||
Page<SearchUserDO> userPage = userService.getUserListWithJoin(query);
|
||
|
||
// 4. 转换为 DTO
|
||
List<UserDTO> userDTOList = userPage.getRecords()
|
||
.stream()
|
||
.map(UserDTO::new)
|
||
.collect(Collectors.toList());
|
||
|
||
return new PageDTO<>(userDTOList, userPage.getTotal());
|
||
```
|
||
|
||
---
|
||
|
||
## 📌 最佳实践
|
||
|
||
### 1. 命名规范
|
||
|
||
| 类型 | 命名规则 | 示例 |
|
||
|------|----------|------|
|
||
| Entity | `{Module}Entity` | `SysUserEntity` |
|
||
| Service 接口 | `{Module}Service` | `SysUserService` |
|
||
| Service 实现 | `{Module}ServiceImpl` | `SysUserServiceImpl` |
|
||
| Mapper | `{Module}Mapper` | `SysUserMapper` |
|
||
| Model | `{Module}Model` | `UserModel` |
|
||
| ModelFactory | `{Module}ModelFactory` | `UserModelFactory` |
|
||
| Command | `{Operation}{Module}Command` | `AddUserCommand` |
|
||
| Query | `Search{Module}Query` | `SearchUserQuery` |
|
||
| DTO | `{Module}DTO` | `UserDTO` |
|
||
| ApplicationService | `{Module}ApplicationService` | `UserApplicationService` |
|
||
|
||
### 2. 字段命名规范
|
||
|
||
- **Entity**: 使用下划线命名,与数据库保持一致
|
||
- `user_id`, `user_name`, `create_time`
|
||
- **Command/Query/DTO**: 使用驼峰命名,与前端保持一致
|
||
- `userId`, `userName`, `createTime`
|
||
|
||
### 3. 类型选择
|
||
|
||
- **主键**: 使用 `Long`(包装类型)
|
||
- **状态/数量**: 使用 `Integer`(包装类型)
|
||
- **金额**: 使用 `BigDecimal`(避免精度问题)
|
||
- **字符串**: 避免基本类型,使用 `String`
|
||
- **时间**: 使用 `Date` 或 `LocalDateTime`
|
||
|
||
### 4. 空值处理
|
||
|
||
- 始终使用包装类型,避免 NPE
|
||
- 使用 `StrUtil.isNotEmpty()` 判断字符串
|
||
- 使用 `!= null` 判断对象和数字
|
||
- 使用 MyBatis-Plus 的链式调用:`eq(condition, "column", value)`
|
||
|
||
### 5. 业务校验位置
|
||
|
||
- **唯一性校验**: Model 层
|
||
- **关联数据存在性校验**: Model 层
|
||
- **字段格式校验**: Model 层
|
||
- **业务规则校验**: Model 层
|
||
- **数据库约束**: Entity 层
|
||
|
||
### 6. 缓存集成
|
||
|
||
DTO 中可以从缓存获取关联数据:
|
||
```java
|
||
public UserDTO(ExampleUserEntity entity) {
|
||
if (entity != null) {
|
||
BeanUtil.copyProperties(entity, this);
|
||
|
||
// 从缓存获取部门名称
|
||
if (entity.getDeptId() != null) {
|
||
SysDeptEntity dept = CacheCenter.deptCache.get(entity.getDeptId() + "");
|
||
this.deptName = dept != null ? dept.getDeptName() : "";
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 7. 事务管理
|
||
|
||
- ApplicationService 负责事务管理
|
||
- 使用 `@Transactional(rollbackFor = Exception.class)`
|
||
- 写操作必须开启事务
|
||
- 读操作可以不加事务(但建议加 `@Transactional(readOnly = true)`)
|
||
|
||
### 8. 异常处理
|
||
|
||
- 业务异常使用 `ApiException`
|
||
- 错误码使用 `ErrorCode` 枚举
|
||
- Model 层抛出异常,ApplicationService 不捕获
|
||
- Controller 层统一处理异常
|
||
|
||
### 9. 代码复用
|
||
|
||
- 公共方法抽取到工具类
|
||
- 通用 DTO/Entity 放在 `common` 模块
|
||
- 公共 Service 方法抽取到基类
|
||
- 使用组合而非继承
|
||
|
||
---
|
||
|
||
## 🛠️ 开发步骤清单
|
||
|
||
新增一张表后的完整开发流程:
|
||
|
||
### 1. 数据库层面
|
||
- [ ] 创建数据库表(`sql/YYYYMMDD_feature_name.sql`)
|
||
- [ ] 添加表注释和字段注释
|
||
- [ ] 创建必要的索引
|
||
|
||
### 2. Entity 层
|
||
- [ ] 在 `db/` 目录下创建 `{Module}Entity.java`,继承 `BaseEntity`
|
||
- [ ] 配置 `@TableName`、`@TableId`、`@TableField` 注解
|
||
- [ ] 实现 `pkVal()` 方法
|
||
|
||
### 3. Mapper 层
|
||
- [ ] 在 `db/` 目录下创建 `{Module}Mapper.java`,继承 `BaseMapper<Entity>`
|
||
- [ ] 添加自定义查询方法(如需要)
|
||
|
||
### 4. Service 层
|
||
- [ ] 在 `db/` 目录下创建 `{Module}Service.java` 接口
|
||
- [ ] 在 `db/` 目录下创建 `{Module}ServiceImpl.java` 实现类
|
||
- [ ] 继承 `ServiceImpl<Mapper, Entity>`
|
||
- [ ] 实现核心业务方法
|
||
|
||
### 5. Command 层
|
||
- [ ] 在 `command/` 目录下创建 `Add{Module}Command.java`
|
||
- [ ] 在 `command/` 目录下创建 `Update{Module}Command.java`(可选:继承基础 Command)
|
||
- [ ] 在 `command/` 目录下创建其他 Command(如 `Delete{Module}Command.java`)
|
||
|
||
### 6. Query 层
|
||
- [ ] 在 `query/` 目录下创建 `Search{Module}Query.java`
|
||
- [ ] 继承 `AbstractPageQuery<T>`
|
||
- [ ] 实现 `addQueryCondition()` 方法
|
||
|
||
### 7. DTO 层
|
||
- [ ] 在 `dto/` 目录下创建 `{Module}DTO.java`
|
||
- [ ] 添加构造函数(从 Entity/DO 转换)
|
||
- [ ] 添加 `@ExcelColumn` 注解(如果需要导出)
|
||
- [ ] 集成缓存获取关联数据(如果需要)
|
||
|
||
### 8. Model 层
|
||
- [ ] 在 `model/` 目录下创建 `{Module}Model.java`,继承 Entity
|
||
- [ ] 添加 `load*Command()` 方法
|
||
- [ ] 添加 `check*()` 业务校验方法
|
||
- [ ] 添加业务逻辑处理方法
|
||
|
||
### 9. ModelFactory 层
|
||
- [ ] 在 `model/` 目录下创建 `{Module}ModelFactory.java`
|
||
- [ ] 使用 `@Component` 注解
|
||
- [ ] 添加 `loadById()` 方法
|
||
- [ ] 添加 `create()` 方法
|
||
|
||
### 10. ApplicationService 层
|
||
- [ ] 创建 `{Module}ApplicationService.java`(位于模块根目录)
|
||
- [ ] 使用 `@Service` 注解
|
||
- [ ] 注入 Service 和 ModelFactory
|
||
- [ ] 实现 CRUD 方法
|
||
- [ ] 添加 `@Transactional` 注解
|
||
|
||
### 11. Controller 层(在 agileboot-admin 中)
|
||
- [ ] 创建 Controller
|
||
- [ ] 注入 ApplicationService
|
||
- [ ] 定义 RESTful API
|
||
- [ ] 添加 `@Operation` 注解
|
||
|
||
### 12. 测试
|
||
- [ ] 编写单元测试(Service、Model)
|
||
- [ ] 编写集成测试
|
||
- [ ] 测试 CRUD 操作
|
||
- [ ] 测试业务校验
|
||
- [ ] 测试缓存(如果使用)
|
||
|
||
### 13. 文档
|
||
- [ ] 更新 API 文档
|
||
- [ ] 补充业务说明
|
||
- [ ] 更新数据库文档
|
||
|
||
---
|
||
|
||
## 📚 参考资料
|
||
|
||
### 代码结构参考
|
||
- **System 用户管理**: `agileboot-domain/src/main/java/com/agileboot/domain/system/user/`
|
||
- **Shop 电商模块**: `agileboot-domain/src/main/java/com/agileboot/domain/shop/`
|
||
- **Cabinet 智能柜**: `agileboot-domain/src/main/java/com/agileboot/domain/cabinet/`
|
||
|
||
### 核心类参考
|
||
- **BaseEntity**: `com.agileboot.common.core.base.BaseEntity`
|
||
- **AbstractPageQuery**: `com.agileboot.common.core.page.AbstractPageQuery`
|
||
- **PageDTO**: `com.agileboot.common.core.page.PageDTO`
|
||
- **ApiException**: `com.agileboot.common.exception.ApiException`
|
||
- **ErrorCode**: `com.agileboot.common.exception.error.ErrorCode`
|
||
- **CacheCenter**: `com.agileboot.domain.common.cache.CacheCenter`
|
||
|
||
---
|
||
|
||
## 🎯 总结
|
||
|
||
AgileBoot 的 DDD/CQRS 架构通过清晰的分层和职责分离,为企业级应用开发提供了强有力的支撑:
|
||
|
||
1. **Entity**: 数据库映射,继承 BaseEntity
|
||
2. **Service**: 数据库操作和简单业务逻辑
|
||
3. **Command**: 写操作参数封装
|
||
4. **Query**: 读操作查询条件
|
||
5. **DTO**: 前端数据传输,支持缓存
|
||
6. **Model**: 领域模型,封装核心业务逻辑
|
||
7. **ModelFactory**: 统一创建 Model
|
||
8. **ApplicationService**: 事务脚本层,协调各组件
|
||
|
||
### 关键架构原则
|
||
|
||
- **单层子文件夹原则**:严格遵守每个模块下最多只能有一层子文件夹的规范,避免深层嵌套
|
||
- **职责分离**:每个目录和文件都有明确的职责边界
|
||
- **依赖倒置**:Domain 层不依赖 Infrastructure 层,通过 ApplicationService 协调
|
||
|
||
遵循本指南的规范,可以快速构建符合 DDD/CQRS 架构的高质量代码。
|
||
|
||
---
|
||
|
||
**文档版本**: v1.0
|
||
**最后更新**: 2025-01-01
|
||
**作者**: AgileBoot Team
|