From 46b760cef63e796854e7e04f4ce6b4b2daaadf99 Mon Sep 17 00:00:00 2001 From: dzq Date: Wed, 5 Nov 2025 10:50:14 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=96=B0=E5=A2=9EDDD/CQRS=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E6=8C=87=E5=8D=97=E5=92=8C=E9=A1=B9=E7=9B=AE=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加CLAUDE.md项目概述文档和DDD-CQRS开发指南文档 --- .claude/settings.local.json | 41 + CLAUDE.md | 300 ++++++ doc/DDD-CQRS开发指南-新增表完整实现.md | 1342 ++++++++++++++++++++++++ 3 files changed, 1683 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 CLAUDE.md create mode 100644 doc/DDD-CQRS开发指南-新增表完整实现.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..568d453 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,41 @@ +{ + "permissions": { + "allow": [ + "Bash(./mvnw --version)", + "Bash(./mvnw help:active-profiles)", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/command/AddGoodsCommand.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/command/UpdateGoodsCommand.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsEntity.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsMapper.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsService.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsServiceImpl.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/dto/ShopGoodsDTO.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/model/GoodsModel.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/model/GoodsModelFactory.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/query/SearchShopGoodsQuery.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/GoodsApplicationService.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/command/AddCabinetCellCommand.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellEntity.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/model/CabinetCellModel.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user/command/AddAb98UserCommand.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user/db/Ab98UserEntity.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user/model/Ab98UserModel.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/AddUserCommand.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/system/user/db/SysUserEntity.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/system/user/model/UserModel.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/common/core/base/BaseEntity.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/common/command/BulkOperationCommand.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/common/core/page/AbstractPageQuery.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseEntity.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-common/src/main/java/com/agileboot/common/core/page/AbstractPageQuery.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserApplicationService.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/system/user/db/SysUserServiceImpl.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/system/user/model/UserModelFactory.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/system/user/dto/UserDTO.java\")", + "Bash(cat \"/e/code/智柜宝/shop-back-end/agileboot-domain/src/main/java/com/agileboot/domain/system/user/query/SearchUserQuery.java\")" + ], + "deny": [], + "ask": [] + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..68b8285 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,300 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is **AgileBoot** - a Spring Boot 2.7.10 + Vue3 full-stack development framework with **DDD/CQRS architecture**. The project specializes in e-commerce business scenarios and integrates Enterprise WeChat, WeChat Pay, and Smart Cabinet systems. + +**Key Features:** +- DDD (Domain-Driven Design) with CQRS pattern +- Multi-level caching (Caffeine + Guava + Redis) +- Enterprise WeChat integration (supports corpid) +- Smart cabinet device management +- WeChat Pay integration (JSAPI) +- JWT-based authentication with Spring Security + +## Technology Stack + +- **Backend**: Spring Boot 2.7.10, Java 8 +- **Database**: MySQL 8.0, MyBatis Plus 3.5.2 +- **Cache**: Caffeine (local), Redis (distributed), Guava (config) +- **Security**: Spring Security + JWT +- **Documentation**: SpringDoc OpenAPI 3.0 +- **Build Tool**: Maven 3.9+ (use `./mvnw` wrapper) +- **Testing**: JUnit, Mockito + +## Project Structure + +``` +agileboot (multi-module Maven project) +├── agileboot-admin # Management backend interface module +├── agileboot-api # Open API module (for clients) +├── agileboot-common # Common utilities module +├── agileboot-domain # Business domain module (DDD) +├── agileboot-infrastructure # Infrastructure module (config, integration) +└── agileboot-orm # ORM configuration (legacy) + +agileboot-domain (DDD structure - CQRS pattern) +├── {business-module}/ +│ ├── command/ # Command objects (data update) +│ ├── dto/ # Data Transfer Objects +│ ├── query/ # Query objects (data retrieval) +│ ├── model/ # Domain models +│ ├── db/ # Database layer +│ │ ├── entity/ # Entity classes +│ │ ├── service/ # Database service +│ │ └── mapper/ # MyBatis mapper +│ └── {Module}ApplicationService.java # Application service layer +``` + +## Common Commands + +### Build and Run + +```bash +# Build entire project +./mvnw clean install + +# Build specific module +./mvnw clean package -pl agileboot-admin -am + +# Build with tests (tests are skipped by default) +./mvnw clean package -pl agileboot-admin -am -DskipTests=false + +# Run tests +./mvnw test +./mvnw test -pl agileboot-domain # Test specific module +./mvnw test -Dtest=UserModelTest # Run single test class + +# Use build script +build.bat + +# Run admin module +cd agileboot-admin +./mvnw spring-boot:run + +# Or run with embedded database/Redis +java -jar agileboot-admin/target/agileboot-admin.jar +``` + +### Application Profiles + +**Configuration file**: `agileboot-admin/src/main/resources/application.yml` + +- **dev**: Development profile (MySQL + Redis required) +- **test**: Test profile (embedded H2 + embedded Redis) +- **prod**: Production profile + +```yaml +spring: + profiles: + active: basic,dev # Change to basic,test for embedded services + +agileboot: + embedded: + mysql: true # Set to true to use H2 (no external DB needed) + redis: true # Set to true to use embedded Redis +``` + +**Startup classes:** +- Admin module: `com.agileboot.admin.AgileBootAdminApplication` +- API module: `com.agileboot.api.AgileBooApiApplication` +- Integration tests: `com.agileboot.integrationTest.IntegrationTestApplication` + +### Running the Application + +```bash +# With external MySQL and Redis (default dev profile) +1. Import database: sql/agileboot_*.sql (latest version) +2. Configure: agileboot-admin/src/main/resources/application-dev.yml +3. Run: ./mvnw spring-boot:run -pl agileboot-admin + +# Without external dependencies (test profile) +1. Change spring.profiles.active to: basic,test +2. Set agileboot.embedded.mysql: true +3. Set agileboot.embedded.redis: true +4. Run: ./mvnw spring-boot:run -pl agileboot-admin +``` + +**Access points:** +- API Documentation: http://localhost:8080/v3/api-docs +- Druid Monitor: http://localhost:8080/druid/ (admin/123456) +- Login credentials: admin/admin123 + +## Architecture Patterns + +### Request Flow (CQRS) + +**Queries**: Controller → `{Module}Query` → `{Module}ApplicationService` → `{Module}Service` (Db) → `{Module}Mapper` + +**Commands**: Controller → `{Module}Command` → `{Module}ApplicationService` → `{Module}Model` → save/update + +### Module Organization + +Each business domain follows DDD structure. Example: `agileboot-domain/src/main/java/com/agileboot/domain/{module}/` + +- **command/**: Command objects for data updates (Create/Update/Delete) +- **query/**: Query objects for data retrieval +- **dto/**: Data transfer objects for API responses +- **model/**: Domain models with business logic +- **db/**: Data access layer (entity, service, mapper) +- **ApplicationService**: Transaction script layer, orchestrates domain models + +## Business Modules + +Core modules in `agileboot-domain`: +- **system**: User, role, menu, dept, config, log, notice, post +- **cabinet**: Smart cabinet device management +- **shop**: E-commerce (products, orders) +- **wx**: WeChat user integration +- **qywx**: Enterprise WeChat integration (supports corpid) +- **ab98**: User management with tags and balance +- **mqtt**: MQTT server integration +- **asset**: Asset management + +## Caching System + +**Multi-level cache architecture:** +1. **Caffeine** (local): User, role, post, login user cache +2. **Guava** (config): System config, dict data, dept data +3. **Redis** (distributed, optional): Session, captcha, distributed locks + +**Key classes:** +- `agileboot-infrastructure/src/main/java/.../cache/` +- `CacheCenter`: Cache management +- `AbstractCaffeineCacheTemplate`: Caffeine cache template + +## Key Configuration Files + +- `pom.xml`: Maven parent POM with all dependencies +- `GoogleStyle.xml`: Code formatting template (required for IntelliJ) +- `application.yml`: Main config (profiles, embedded services) +- `application-dev.yml`: Dev database/Redis config +- `application-test.yml`: Test with H2/embedded Redis +- `sql/`: Database migration scripts (latest first) + +## Testing + +**Test structure**: Each module has `src/test/java/` +- Unit tests in `*Test.java` +- Integration tests in `*IntegrationTest.java` + +**Running tests:** +```bash +# All tests +./mvnw test + +# Specific module +./mvnw test -pl agileboot-domain + +# Single test class +./mvnw test -Dtest=UserModelTest + +# With coverage +./mvnw jacoco:report # If JaCoCo configured +``` + +**Note**: Tests are skipped by default in build (`true` in parent POM). Set `-DskipTests=false` to run them. + +## Code Standards + +**Required:** +- Import `GoogleStyle.xml` into IntelliJ: Settings → Editor → Code Style → Java → Import Schema +- Properties files encoding: Settings → Editor → File Encodings → Properties Files → Set to UTF-8 +- Use enums instead of dictionary type data +- Centralized error handling with error codes +- Write unit tests for business logic + +**IDE Setup:** +- **IntelliJ IDEA** (recommended) +- Lombok plugin installed +- Google code style applied + +## Important Business Features + +### Enterprise WeChat Integration +- Supports `corpid` field for multi-tenant enterprise WeChat +- Module: `agileboot-domain/qywx/` +- Documentation: `doc/智能柜系统指南.md` + +### Smart Cabinet System +- Device management with cabinet/cell hierarchy +- Real-time cell status monitoring +- Operation logging (open/close/lock) +- Module: `agileboot-domain/cabinet/` and `agileboot-admin/controller/cabinet/` + +### WeChat Pay Integration +- JSAPI payment support +- Refund functionality +- Module: `agileboot-domain/shop/` +- Documentation: `doc/微信支付集成指南.md` + +## Database Schema + +**Core tables** (~10 core tables in AgileBoot): +- `sys_user`, `sys_role`, `sys_menu`, `sys_dept` +- `sys_config`, `sys_dict_type`, `sys_notice` +- Plus business tables (cabinet, shop, ab98_user, etc.) + +**Migration files** in `sql/` directory (latest first): +- `agileboot-*.sql`: Base schema +- `YYYYMMDD_*.sql`: Feature migrations (e.g., 20251029_wx_user.sql) + +## Development Workflow + +1. **New Feature**: + - Create domain module in `agileboot-domain/{feature}/` + - Implement DDD structure (command, query, model, db, service) + - Add controllers in `agileboot-admin/controller/{feature}/` + - Write unit tests + - Update database with migration script + +2. **Database Changes**: + - Create new migration: `sql/YYYYMMDD_feature_name.sql` + - Update CodeGenerator if needed + - Test with test profile (H2 + embedded Redis) + +3. **API Development**: + - Document with `@Operation` annotation + - Use proper DTOs + - Add to `application.yml` springdoc group-configs if needed + +## Troubleshooting + +**Port 8080 busy**: Change `server.port` in `application.yml` + +**Redis port conflict**: Modify `spring.redis.port` in `application-dev.yml` + +**MacOS embedded Redis**: High versions may not support embedded Redis - use external Redis + +**Build fails**: Check Java version (requires Java 8+), Maven 3.9+, ensure `./mvnw clean install` succeeds + +## Documentation + +- **README.md**: Project overview and setup guide +- **doc/项目概述.md**: Detailed architecture overview +- **doc/智能柜系统指南.md**: Smart cabinet system guide +- **doc/缓存系统指南.md**: Caching system guide +- **doc/微信支付集成指南.md**: WeChat Pay integration guide + +## Recent Commits (for context) + +``` +eb41f35 feat(wx): 添加微信小程序登录功能支持 +9562d1c 登录接口更新用户登录信息修复 +7cd08c1 refactor(docs): 重构文档结构并迁移docker安装指南 +e53ff77 feat(智能柜): 添加corpid字段支持企业微信集成 +ddc3c91 feat: 添加企业微信用户ID缓存功能 +``` + +## Tips + +- Use test profile for quick development without external dependencies +- Check `application-dev.yml` for database/Redis configuration +- Druid monitor at `/druid/` shows SQL performance +- API docs available at `/v3/api-docs` and `/swagger-ui.html` +- Code style: Strictly follow GoogleStyle.xml formatting +- All business logic should have unit tests +- Domain models contain business logic, ApplicationService is transaction script layer +- Use multi-level cache for performance-critical data diff --git a/doc/DDD-CQRS开发指南-新增表完整实现.md b/doc/DDD-CQRS开发指南-新增表完整实现.md new file mode 100644 index 0000000..01ed311 --- /dev/null +++ b/doc/DDD-CQRS开发指南-新增表完整实现.md @@ -0,0 +1,1342 @@ +# 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/ # 数据层 +│ ├── entity/ # 实体类 +│ │ └── {Module}Entity.java +│ ├── service/ # 服务层 +│ │ ├── {Module}Service.java +│ │ └── impl/ +│ │ └── {Module}ServiceImpl.java +│ ├── mapper/ # 数据访问 +│ │ └── {Module}Mapper.java +│ └── do/ # 数据对象(复杂查询) +│ └── 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. Entity(实体类) + +**位置**: `db/entity/{Module}Entity.java` + +**职责**: 数据库表的 Java 映射,继承 `BaseEntity` 获得通用字段 + +**关键特性**: +- 继承 `BaseEntity` +- 实现 `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 { + + 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/service/{Module}Service.java` 和 `impl/{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 { + + /** + * 检测邮箱是否唯一 + * + * @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 getUserList(AbstractPageQuery query); + + /** + * 根据条件分页查询用户列表(带额外字段) + * + * @param query 查询参数 + * @return 用户信息集合(含关联字段) + */ + Page getUserListWithJoin(AbstractPageQuery 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 implements ExampleUserService { + + @Override + public boolean isEmailUnique(String email, Long userId) { + if (StrUtil.isEmpty(email)) { + return true; + } + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>() + .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 wrapper = new LambdaQueryWrapper<>() + .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 getUserList(AbstractPageQuery query) { + return this.page(query.buildPage(), query.addQueryCondition()); + } + + @Override + public Page getUserListWithJoin(AbstractPageQuery query) { + // 自定义 SQL 查询,返回关联数据 + return this.getBaseMapper().selectUserListWithJoin(query.buildPage(), query); + } +} +``` + +**关键点**: +- 接口继承 `IService` +- 实现类继承 `ServiceImpl` +- 简单业务逻辑在 Service 中处理 +- 复杂业务逻辑交给 Model 处理 + +--- + +### 3. Mapper(数据访问层) + +**位置**: `db/mapper/{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 { + + /** + * 分页查询用户列表(带关联数据) + * + * @param page 分页参数 + * @param query 查询条件 + * @return 用户列表 + */ + @Select({ + "" + }) + Page selectUserListWithJoin( + Page page, + @Param("query") AbstractPageQuery query + ); +} +``` + +**关键点**: +- 继承 `BaseMapper` +- 复杂查询使用 `@Select` 注解或 XML 文件 +- 使用 `#{}` 进行参数绑定,避免 SQL 注入 + +--- + +### 4. DO(数据对象) + +**位置**: `db/do/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 extends AbstractPageQuery { + + private Long userId; + + private String userName; + + private String email; + + private Integer status; + + private Long deptId; + + private Integer minAge; + + private Integer maxAge; + + @Override + public QueryWrapper addQueryCondition() { + QueryWrapper 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` +- 实现 `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 deptOptions; + + private List 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 cn.hutool.core.convert.Convert; +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 getUserList(SearchUserQuery query) { + Page userPage = userService.getUserListWithJoin(query); + List 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 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 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 query = new SearchUserQuery<>(); +// ... 设置查询条件 + +// 2. ApplicationService 处理 +PageDTO result = userApplicationService.getUserList(query); + +// 3. Service 执行查询 +Page userPage = userService.getUserListWithJoin(query); + +// 4. 转换为 DTO +List 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 层 +- [ ] 创建 `{Module}Entity.java`,继承 `BaseEntity` +- [ ] 配置 `@TableName`、`@TableId`、`@TableField` 注解 +- [ ] 实现 `pkVal()` 方法 + +### 3. Mapper 层 +- [ ] 创建 `{Module}Mapper.java`,继承 `BaseMapper` +- [ ] 添加自定义查询方法(如需要) + +### 4. Service 层 +- [ ] 创建 `{Module}Service.java` 接口 +- [ ] 创建 `{Module}ServiceImpl.java` 实现类 +- [ ] 继承 `ServiceImpl` +- [ ] 实现核心业务方法 + +### 5. Command 层 +- [ ] 创建 `Add{Module}Command.java` +- [ ] 创建 `Update{Module}Command.java`(可选:继承基础 Command) +- [ ] 创建其他 Command(如 `Delete{Module}Command.java`) + +### 6. Query 层 +- [ ] 创建 `Search{Module}Query.java` +- [ ] 继承 `AbstractPageQuery` +- [ ] 实现 `addQueryCondition()` 方法 + +### 7. DTO 层 +- [ ] 创建 `{Module}DTO.java` +- [ ] 添加构造函数(从 Entity/DO 转换) +- [ ] 添加 `@ExcelColumn` 注解(如果需要导出) +- [ ] 集成缓存获取关联数据(如果需要) + +### 8. Model 层 +- [ ] 创建 `{Module}Model.java`,继承 Entity +- [ ] 添加 `load*Command()` 方法 +- [ ] 添加 `check*()` 业务校验方法 +- [ ] 添加业务逻辑处理方法 + +### 9. ModelFactory 层 +- [ ] 创建 `{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**: 事务脚本层,协调各组件 + +遵循本指南的规范,可以快速构建符合 DDD/CQRS 架构的高质量代码。 + +--- + +**文档版本**: v1.0 +**最后更新**: 2025-01-01 +**作者**: AgileBoot Team