diff --git a/ARCHITECTURE_STANDARDS.md b/ARCHITECTURE_STANDARDS.md new file mode 100644 index 0000000..cd72699 --- /dev/null +++ b/ARCHITECTURE_STANDARDS.md @@ -0,0 +1,1966 @@ +# RuoYi-Cloud 微服务架构开发规范 + +> 本文档定义了 RuoYi-Cloud 微服务项目的标准架构、编码规范和最佳实践。 +> +> **版本**: v1.0 +> **更新日期**: 2026-01-17 + +--- + +## 目录 + +- [一、项目结构规范](#一项目结构规范) +- [二、Maven配置规范](#二maven配置规范) +- [三、配置文件规范](#三配置文件规范) +- [四、数据库层规范](#四数据库层规范) +- [五、领域层规范](#五领域层domain规范) +- [六、服务层规范](#六服务层service规范) +- [七、控制器层规范](#七控制器层controller规范) +- [八、API层规范](#八api层规范) +- [九、依赖注入规范](#九依赖注入规范) +- [十、命名规范](#十命名规范) + +--- + +## 一、项目结构规范 + +### 1.1 模块目录结构 + +``` +ruoyi-modules/{module-name}/ +├── src/main/java/com/ruoyi/{module}/ +│ ├── controller/ # 控制器层 +│ │ ├── {Entity}Controller.java +│ │ └── convert/ # Controller层对象转换 +│ ├── service/ # 服务层 +│ │ ├── api/ # 服务接口 +│ │ │ └── I{Entity}Service.java +│ │ ├── impl/ # 服务实现 +│ │ │ └── {Entity}ServiceImpl.java +│ │ ├── dto/ # 数据传输对象 +│ │ │ └── {Entity}DTO.java +│ │ └── convert/ # Service层对象转换 +│ │ └── {Entity}ServiceConvert.java +│ ├── domain/ # 领域层 +│ │ ├── api/ # 领域接口 +│ │ │ └── I{Entity}Domain.java +│ │ ├── impl/ # 领域实现 +│ │ │ └── {Entity}DomainImpl.java +│ │ ├── model/ # 领域模型 +│ │ │ └── {Entity}.java +│ │ └── convert/ # Domain层对象转换 +│ │ └── {Entity}DomainConvert.java +│ └── mapper/ # 数据访问层 +│ ├── entity/ # 数据库实体 +│ │ └── {Entity}Entity.java +│ └── {Entity}Mapper.java # MyBatis Mapper接口 +├── src/main/resources/ +│ ├── bootstrap.yml # 启动配置 +│ └── mapper/{module}/ # MyBatis XML映射文件 +│ └── {Entity}Mapper.xml +└── pom.xml # Maven配置 + +ruoyi-api/{module-name}-api-{submodule}/ +├── src/main/java/com/ruoyi/{module}/api/ +│ ├── domain/ # VO对象 +│ │ └── {Entity}VO.java +│ ├── factory/ # Feign降级工厂 +│ │ └── Remote{Module}FallbackFactory.java +│ └── Remote{Module}Service.java # Feign客户端接口 +└── pom.xml +``` + +### 1.2 分层架构说明 + +``` +Controller Layer (控制器层) + ↓ VO → DTO +Service Layer (服务层) + ↓ DTO → Model +Domain Layer (领域层) + ↓ Model → Entity +Mapper Layer (数据访问层) + ↓ +Database (数据库) +``` + +**各层职责:** + +- **Controller层**: 处理HTTP请求,参数验证,调用Service层,返回响应 +- **Service层**: 业务逻辑编排,事务管理,调用Domain层 +- **Domain层**: 领域业务逻辑,封装Mapper操作,对象转换 +- **Mapper层**: 数据库访问,SQL执行 + +### 1.3 端口分配规范 + +| 端口范围 | 用途 | 示例 | +|---------|------|------| +| 9200-9209 | 基础服务 | ruoyi-auth(9200), ruoyi-modules-system(9201), ruoyi-modules-gen(9202), ruoyi-modules-job(9203) | +| 9210-9299 | 业务服务 | tuoheng-device(9210), tuoheng-airline(9211), tuoheng-approval(9212), tuoheng-fms(9213), tuoheng-media(9214), tuoheng-task(9215) | +| 9100 | 监控服务 | ruoyi-visual-monitor(9100) | +| 9300+ | 特殊服务 | ruoyi-modules-file(9300) | + +**端口分配原则:** +- 新增业务服务从 9216 开始递增 +- 避免端口冲突 +- Docker 端口映射必须与 bootstrap.yml 配置一致 + +--- + +## 二、Maven配置规范 + +### 2.1 根 pom.xml 配置 + +#### 2.1.1 添加服务名称常量 + +在 `ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/ServiceNameConstants.java` 中添加: + +```java +/** + * {模块中文名}服务的serviceid + */ +public static final String {MODULE}_SERVICE = "{module-name}"; +``` + +**示例:** +```java +/** + * 设备服务的serviceid + */ +public static final String DEVICE_SERVICE = "tuoheng-device"; + +/** + * 航线服务的serviceid + */ +public static final String AIRLINE_SERVICE = "tuoheng-airline"; +``` + +#### 2.1.2 添加依赖管理 + +在根 `pom.xml` 的 `` 中添加: + +```xml + + + com.ruoyi + {module-name}-api-{submodule} + ${ruoyi.version} + +``` + +**示例:** +```xml + + + com.ruoyi + tuoheng-api-device + ${ruoyi.version} + +``` + +### 2.2 ruoyi-api/pom.xml 配置 + +在 `ruoyi-api/pom.xml` 的 `` 中添加: + +```xml +{module-name}-api-{submodule} +``` + +**示例:** +```xml + + ruoyi-api-system + tuoheng-api-device + tuoheng-api-airline + +``` + +### 2.3 业务模块 pom.xml 标准配置 + +```xml + + + + com.ruoyi + ruoyi-modules + 3.6.7 + + 4.0.0 + + {module-name} + + + {module-name}系统模块 + + + + + + com.ruoyi + {module-name}-api-{submodule} + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + com.mysql + mysql-connector-j + + + + + com.ruoyi + ruoyi-common-datasource + + + + + com.ruoyi + ruoyi-common-datascope + + + + + com.ruoyi + ruoyi-common-log + + + + + com.ruoyi + ruoyi-common-swagger + + + + + org.flywaydb + flyway-core + + + + + org.flywaydb + flyway-mysql + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + +``` + +### 2.4 API 模块 pom.xml 标准配置 + +```xml + + + + com.ruoyi + ruoyi-api + 3.6.7 + + 4.0.0 + + {module-name}-api-{submodule} + + + {module-name} API模块 + + + + + + com.ruoyi + ruoyi-common-core + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + +``` + +--- + +## 三、配置文件规范 + +### 3.1 bootstrap.yml 标准配置 + +```yaml +# Tomcat +server: + port: {port} # 按照端口分配规范设置,例如: 9210 + +# Spring +spring: + application: + # 应用名称 + name: {module-name} + profiles: + # 环境配置 + active: prod + flyway: + table: flyway_{module}_schema_history # 自定义历史表名 + baseline-on-migrate: true # 在nacos中也有配置 + baseline-version: 0 # 在nacos中也有配置 + cloud: + nacos: + discovery: + # 服务注册地址 + server-addr: ruoyi-nacos:8848 + config: + # 配置中心地址 + server-addr: ruoyi-nacos:8848 + # 配置文件格式 + file-extension: yml + # 共享配置 + shared-configs: + - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} +``` + +**配置说明:** +- `server.port`: 必须按照端口分配规范设置 +- `spring.application.name`: 服务名称,用于服务注册和发现 +- `flyway.table`: Flyway 历史表名,格式为 `flyway_{module}_schema_history` + +### 3.2 Docker Compose 配置 + +在 `docker/docker-compose.yml` 中添加服务配置: + +```yaml + {module-name}-modules-{submodule}: + container_name: {module-name}-modules-{submodule} + image: {module-name}-modules-{submodule}-runtime + build: + context: ./ruoyi/modules/{submodule} + dockerfile: dockerfile + environment: + - TZ=Asia/Shanghai + ports: + - "{port}:{port}" # 宿主机端口:容器端口,必须与bootstrap.yml一致 + depends_on: + - ruoyi-redis + - ruoyi-mysql + links: + - ruoyi-redis + - ruoyi-mysql +``` + +**重要提示:** +- Docker 端口映射格式:`宿主机端口:容器端口` +- 容器端口必须与 `bootstrap.yml` 中的 `server.port` 一致 +- 宿主机端口不能重复,但容器端口可以相同(因为在不同容器中) + +--- + +## 四、数据库层规范 + +### 4.1 Flyway 迁移脚本命名规范 + +**目录结构:** +``` +src/main/resources/db/migration/ +├── V1__Create_{module}_initial_tables.sql +├── V2__Add_{feature}_tables.sql +├── V3__Modify_{table}_add_{field}.sql +├── V4__Add_{table}_indexes.sql +└── V5__Update_{table}_data.sql +``` + +**命名规则:** +- 格式:`V{版本号}__{描述}.sql` +- 版本号:从1开始递增,不能跳号 +- 描述:使用下划线分隔的英文描述,首字母大写 +- 操作类型前缀: + - `Create`: 创建表或数据库对象 + - `Add`: 添加字段、索引或数据 + - `Modify`: 修改表结构或字段 + - `Drop`: 删除表或字段 + - `Update`: 更新数据 + +**示例:** +``` +V1__Create_device_tables.sql +V2__Add_device_gateway_field.sql +V3__Add_foreign_key_indexes.sql +V4__Modify_device_add_status_field.sql +``` + +### 4.2 数据库表设计规范 + +```sql +CREATE TABLE IF NOT EXISTS {module}_{table_name} ( + {table}_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + {field_name} VARCHAR(100) COMMENT '字段说明', + -- 继承自BaseEntity的字段 + create_by VARCHAR(64) DEFAULT '' COMMENT '创建者', + create_time DATETIME COMMENT '创建时间', + update_by VARCHAR(64) DEFAULT '' COMMENT '更新者', + update_time DATETIME COMMENT '更新时间', + remark VARCHAR(500) DEFAULT NULL COMMENT '备注', + PRIMARY KEY ({table}_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{表中文说明}'; +``` + +**设计原则:** + +1. **表名格式**: `{module}_{table_name}`,全小写,下划线分隔 + - 示例:`device_device`, `device_dock`, `airline_route` + +2. **主键命名**: `{table}_id`,类型为 `BIGINT`,`AUTO_INCREMENT` + - 示例:`device_id`, `dock_id`, `route_id` + +3. **BaseEntity 字段**: 所有表必须包含以下5个字段 + ```sql + create_by VARCHAR(64) DEFAULT '' COMMENT '创建者', + create_time DATETIME COMMENT '创建时间', + update_by VARCHAR(64) DEFAULT '' COMMENT '更新者', + update_time DATETIME COMMENT '更新时间', + remark VARCHAR(500) DEFAULT NULL COMMENT '备注' + ``` + +4. **字符集**: 统一使用 `utf8mb4` + +5. **存储引擎**: 统一使用 `InnoDB` + +6. **注释**: 必须添加表注释和字段注释 + +7. **外键**: 不使用数据库外键约束,通过应用层维护关联关系 + +### 4.3 索引设计规范 + +```sql +-- 外键索引 +CREATE INDEX idx_{table}_{foreign_key} ON {module}_{table}({foreign_key}); + +-- 复合索引 +CREATE INDEX idx_{table}_{field1}_{field2} ON {module}_{table}({field1}, {field2}); + +-- 唯一索引 +CREATE UNIQUE INDEX uk_{table}_{field} ON {module}_{table}({field}); +``` + +**索引命名规则:** +- 普通索引:`idx_{table}_{field}` +- 唯一索引:`uk_{table}_{field}` +- 复合索引:`idx_{table}_{field1}_{field2}` + +**索引设计原则:** +- 所有外键字段必须添加索引 +- 频繁查询的字段添加索引 +- 复合索引遵循最左前缀原则 +- 避免过多索引影响写入性能 + +**示例:** +```sql +-- 外键索引 +CREATE INDEX idx_dock_device_id ON device_dock(device_id); +CREATE INDEX idx_aircraft_device_id ON device_aircraft(device_id); + +-- 复合索引 +CREATE INDEX idx_dock_device_status ON device_dock(device_id, status); + +-- 唯一索引 +CREATE UNIQUE INDEX uk_device_sn ON device_device(device_sn); +``` + +### 4.4 Entity 实体类规范 + +**位置**: `src/main/java/com/ruoyi/{module}/mapper/entity/{Entity}Entity.java` + +```java +package com.ruoyi.{module}.mapper.entity; + +import com.ruoyi.common.core.web.domain.BaseEntity; + +/** + * {表中文说明}实体对象 {module}_{table} + * Mapper 层实体,对应数据库表 + * + * @author ruoyi + * @date {date} + */ +public class {Entity}Entity extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键ID */ + private Long {entity}Id; + + /** 字段说明 */ + private String fieldName; + + public Long get{Entity}Id() + { + return {entity}Id; + } + + public void set{Entity}Id(Long {entity}Id) + { + this.{entity}Id = {entity}Id; + } + + public String getFieldName() + { + return fieldName; + } + + public void setFieldName(String fieldName) + { + this.fieldName = fieldName; + } + + @Override + public String toString() + { + return "{Entity}Entity{" + + "{entity}Id=" + {entity}Id + + ", fieldName='" + fieldName + '\'' + + '}'; + } +} +``` + +**规范要点:** +- 必须继承 `BaseEntity` +- 类名格式:`{Entity}Entity` +- 必须实现 `serialVersionUID` +- 使用标准的 Getter/Setter 方法 +- 重写 `toString()` 方法 + +### 4.5 Mapper 接口规范 + +**位置**: `src/main/java/com/ruoyi/{module}/mapper/{Entity}Mapper.java` + +```java +package com.ruoyi.{module}.mapper; + +import com.ruoyi.{module}.mapper.entity.{Entity}Entity; +import java.util.List; + +/** + * {表中文说明}Mapper接口 + * + * @author ruoyi + * @date {date} + */ +public interface {Entity}Mapper +{ + /** + * 查询{表中文说明}列表 + * + * @param {entity}Entity {表中文说明} + * @return {表中文说明}集合 + */ + List<{Entity}Entity> select{Entity}List({Entity}Entity {entity}Entity); + + /** + * 根据ID查询{表中文说明} + * + * @param {entity}Id 主键ID + * @return {表中文说明} + */ + {Entity}Entity select{Entity}ById(Long {entity}Id); + + /** + * 新增{表中文说明} + * + * @param {entity}Entity {表中文说明} + * @return 结果 + */ + int insert{Entity}({Entity}Entity {entity}Entity); + + /** + * 修改{表中文说明} + * + * @param {entity}Entity {表中文说明} + * @return 结果 + */ + int update{Entity}({Entity}Entity {entity}Entity); + + /** + * 删除{表中文说明} + * + * @param {entity}Id 主键ID + * @return 结果 + */ + int delete{Entity}ById(Long {entity}Id); + + /** + * 批量删除{表中文说明} + * + * @param {entity}Ids 主键ID数组 + * @return 结果 + */ + int delete{Entity}ByIds(Long[] {entity}Ids); +} +``` + +**方法命名规范:** +- 查询列表:`select{Entity}List` +- 查询单个:`select{Entity}ById` +- 新增:`insert{Entity}` +- 修改:`update{Entity}` +- 删除:`delete{Entity}ById` +- 批量删除:`delete{Entity}ByIds` + + +### 4.6 Mapper XML 规范 + +**位置**: `src/main/resources/mapper/{module}/{Entity}Mapper.xml` + +```xml + + + + + + + + + + + + + + + + select {entity}_id, field_name, create_by, create_time, update_by, update_time, remark + from {module}_{table} + + + + + + + + insert into {module}_{table} + + field_name, + create_by, + remark, + create_time + + + #{fieldName}, + #{createBy}, + #{remark}, + sysdate() + + + + + update {module}_{table} + + field_name = #{fieldName}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where {entity}_id = #{{{entity}Id}} + + + + delete from {module}_{table} where {entity}_id = #{{{entity}Id}} + + + + delete from {module}_{table} where {entity}_id in + + #{{{entity}Id}} + + + + +``` + +**规范要点:** +- 使用 `` 定义结果映射 +- 使用 `` 定义可复用的 SQL 片段 +- 使用动态 SQL 标签:``, ``, `` +- 插入操作使用 `useGeneratedKeys="true"` 返回主键 +- 时间字段使用 `sysdate()` 函数 + +--- + +## 五、领域层(Domain)规范 + +### 5.1 Model 领域模型规范 + +**位置**: `src/main/java/com/ruoyi/{module}/domain/model/{Entity}.java` + +```java +package com.ruoyi.{module}.domain.model; + +import com.ruoyi.common.core.web.domain.BaseEntity; + +/** + * {表中文说明}领域模型 + * Domain 层模型,用于业务逻辑处理 + * + * @author ruoyi + * @date {date} + */ +public class {Entity} extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键ID */ + private Long {entity}Id; + + /** 字段说明 */ + private String fieldName; + + public Long get{Entity}Id() + { + return {entity}Id; + } + + public void set{Entity}Id(Long {entity}Id) + { + this.{entity}Id = {entity}Id; + } + + public String getFieldName() + { + return fieldName; + } + + public void setFieldName(String fieldName) + { + this.fieldName = fieldName; + } + + @Override + public String toString() + { + return "{Entity}{" + + "{entity}Id=" + {entity}Id + + ", fieldName='" + fieldName + '\'' + + '}'; + } +} +``` + +**规范要点:** +- 类名不带后缀,直接使用实体名 +- 必须继承 `BaseEntity` +- 字段和方法与 Entity 保持一致 + + +### 5.2 Domain API 接口规范 + +**位置**: `src/main/java/com/ruoyi/{module}/domain/api/I{Entity}Domain.java` + +```java +package com.ruoyi.{module}.domain.api; + +import com.ruoyi.{module}.domain.model.{Entity}; +import java.util.List; + +/** + * {表中文说明}领域接口 + * + * @author ruoyi + * @date {date} + */ +public interface I{Entity}Domain +{ + /** + * 查询{表中文说明}列表 + */ + List<{Entity}> select{Entity}List({Entity} {entity}); + + /** + * 根据ID查询{表中文说明} + */ + {Entity} select{Entity}ById(Long {entity}Id); + + /** + * 新增{表中文说明} + */ + int insert{Entity}({Entity} {entity}); + + /** + * 修改{表中文说明} + */ + int update{Entity}({Entity} {entity}); + + /** + * 删除{表中文说明} + */ + int delete{Entity}ById(Long {entity}Id); + + /** + * 批量删除{表中文说明} + */ + int delete{Entity}ByIds(Long[] {entity}Ids); +} +``` + + +### 5.3 Domain 实现类规范 + +**位置**: `src/main/java/com/ruoyi/{module}/domain/impl/{Entity}DomainImpl.java` + +```java +package com.ruoyi.{module}.domain.impl; + +import com.ruoyi.{module}.domain.api.I{Entity}Domain; +import com.ruoyi.{module}.domain.convert.{Entity}DomainConvert; +import com.ruoyi.{module}.domain.model.{Entity}; +import com.ruoyi.{module}.mapper.{Entity}Mapper; +import com.ruoyi.{module}.mapper.entity.{Entity}Entity; +import org.springframework.stereotype.Component; +import java.util.List; + +/** + * {表中文说明}领域实现 + * + * @author ruoyi + * @date {date} + */ +@Component +public class {Entity}DomainImpl implements I{Entity}Domain +{ + private final {Entity}Mapper {entity}Mapper; + + public {Entity}DomainImpl({Entity}Mapper {entity}Mapper) + { + this.{entity}Mapper = {entity}Mapper; + } + + @Override + public List<{Entity}> select{Entity}List({Entity} {entity}) + { + {Entity}Entity entity = {Entity}DomainConvert.toEntity({entity}); + List<{Entity}Entity> entityList = {entity}Mapper.select{Entity}List(entity); + return {Entity}DomainConvert.toModelList(entityList); + } + + @Override + public {Entity} select{Entity}ById(Long {entity}Id) + { + {Entity}Entity entity = {entity}Mapper.select{Entity}ById({entity}Id); + return {Entity}DomainConvert.toModel(entity); + } + + @Override + public int insert{Entity}({Entity} {entity}) + { + {Entity}Entity entity = {Entity}DomainConvert.toEntity({entity}); + return {entity}Mapper.insert{Entity}(entity); + } + + @Override + public int update{Entity}({Entity} {entity}) + { + {Entity}Entity entity = {Entity}DomainConvert.toEntity({entity}); + return {entity}Mapper.update{Entity}(entity); + } + + @Override + public int delete{Entity}ById(Long {entity}Id) + { + return {entity}Mapper.delete{Entity}ById({entity}Id); + } + + @Override + public int delete{Entity}ByIds(Long[] {entity}Ids) + { + return {entity}Mapper.delete{Entity}ByIds({entity}Ids); + } +} +``` + +**重要规范:** +- 使用 `@Component` 注解 +- 使用**构造器注入**(Constructor Injection) +- 字段声明为 `private final` +- 通过 Convert 类进行对象转换 + + +### 5.4 Domain Convert 转换类规范 + +**位置**: `src/main/java/com/ruoyi/{module}/domain/convert/{Entity}DomainConvert.java` + +```java +package com.ruoyi.{module}.domain.convert; + +import com.ruoyi.{module}.domain.model.{Entity}; +import com.ruoyi.{module}.mapper.entity.{Entity}Entity; +import org.springframework.beans.BeanUtils; +import java.util.ArrayList; +import java.util.List; + +/** + * {表中文说明}领域转换类 + * + * @author ruoyi + * @date {date} + */ +public class {Entity}DomainConvert +{ + /** + * Model 转 Entity + */ + public static {Entity}Entity toEntity({Entity} model) + { + if (model == null) + { + return null; + } + {Entity}Entity entity = new {Entity}Entity(); + BeanUtils.copyProperties(model, entity); + return entity; + } + + /** + * Entity 转 Model + */ + public static {Entity} toModel({Entity}Entity entity) + { + if (entity == null) + { + return null; + } + {Entity} model = new {Entity}(); + BeanUtils.copyProperties(entity, model); + return model; + } + + /** + * Entity List 转 Model List + */ + public static List<{Entity}> toModelList(List<{Entity}Entity> entityList) + { + if (entityList == null || entityList.isEmpty()) + { + return new ArrayList<>(); + } + List<{Entity}> modelList = new ArrayList<>(); + for ({Entity}Entity entity : entityList) + { + modelList.add(toModel(entity)); + } + return modelList; + } + + /** + * Model List 转 Entity List + */ + public static List<{Entity}Entity> toEntityList(List<{Entity}> modelList) + { + if (modelList == null || modelList.isEmpty()) + { + return new ArrayList<>(); + } + List<{Entity}Entity> entityList = new ArrayList<>(); + for ({Entity} model : modelList) + { + entityList.add(toEntity(model)); + } + return entityList; + } +} +``` + +**规范要点:** +- 工具类,所有方法为 `public static` +- 使用 `BeanUtils.copyProperties` 进行属性拷贝 +- 提供空值检查 +- 提供单个对象和列表转换方法 + +--- + +## 六、服务层(Service)规范 + +### 6.1 DTO 数据传输对象规范 + +**位置**: `src/main/java/com/ruoyi/{module}/service/dto/{Entity}DTO.java` + +```java +package com.ruoyi.{module}.service.dto; + +import com.ruoyi.common.core.web.domain.BaseEntity; + +/** + * {表中文说明}数据传输对象 + * Service 层 DTO,用于业务逻辑编排 + * + * @author ruoyi + * @date {date} + */ +public class {Entity}DTO extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键ID */ + private Long {entity}Id; + + /** 字段说明 */ + private String fieldName; + + public Long get{Entity}Id() + { + return {entity}Id; + } + + public void set{Entity}Id(Long {entity}Id) + { + this.{entity}Id = {entity}Id; + } + + public String getFieldName() + { + return fieldName; + } + + public void setFieldName(String fieldName) + { + this.fieldName = fieldName; + } + + @Override + public String toString() + { + return "{Entity}DTO{" + + "{entity}Id=" + {entity}Id + + ", fieldName='" + fieldName + '\'' + + '}'; + } +} +``` + +**规范要点:** +- 类名格式:`{Entity}DTO` +- 必须继承 `BaseEntity` +- 字段和方法与 Model 保持一致 + +### 6.2 Service API 接口规范 + +**位置**: `src/main/java/com/ruoyi/{module}/service/api/I{Entity}Service.java` + +```java +package com.ruoyi.{module}.service.api; + +import com.ruoyi.{module}.service.dto.{Entity}DTO; +import java.util.List; + +/** + * {表中文说明}服务接口 + * + * @author ruoyi + * @date {date} + */ +public interface I{Entity}Service +{ + /** + * 查询{表中文说明}列表 + */ + List<{Entity}DTO> select{Entity}List({Entity}DTO {entity}DTO); + + /** + * 根据ID查询{表中文说明} + */ + {Entity}DTO select{Entity}ById(Long {entity}Id); + + /** + * 新增{表中文说明} + */ + int insert{Entity}({Entity}DTO {entity}DTO); + + /** + * 修改{表中文说明} + */ + int update{Entity}({Entity}DTO {entity}DTO); + + /** + * 删除{表中文说明} + */ + int delete{Entity}ById(Long {entity}Id); + + /** + * 批量删除{表中文说明} + */ + int delete{Entity}ByIds(Long[] {entity}Ids); +} +``` + + +### 6.3 Service 实现类规范 + +**位置**: `src/main/java/com/ruoyi/{module}/service/impl/{Entity}ServiceImpl.java` + +```java +package com.ruoyi.{module}.service.impl; + +import com.ruoyi.{module}.domain.api.I{Entity}Domain; +import com.ruoyi.{module}.domain.model.{Entity}; +import com.ruoyi.{module}.service.api.I{Entity}Service; +import com.ruoyi.{module}.service.convert.{Entity}ServiceConvert; +import com.ruoyi.{module}.service.dto.{Entity}DTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.List; + +/** + * {表中文说明}服务实现 + * + * @author ruoyi + * @date {date} + */ +@Service +public class {Entity}ServiceImpl implements I{Entity}Service +{ + @Autowired + private I{Entity}Domain {entity}Domain; + + @Override + public List<{Entity}DTO> select{Entity}List({Entity}DTO {entity}DTO) + { + {Entity} model = {Entity}ServiceConvert.toModel({entity}DTO); + List<{Entity}> modelList = {entity}Domain.select{Entity}List(model); + return {Entity}ServiceConvert.toDTOList(modelList); + } + + @Override + public {Entity}DTO select{Entity}ById(Long {entity}Id) + { + {Entity} model = {entity}Domain.select{Entity}ById({entity}Id); + return {Entity}ServiceConvert.toDTO(model); + } + + @Override + public int insert{Entity}({Entity}DTO {entity}DTO) + { + {Entity} model = {Entity}ServiceConvert.toModel({entity}DTO); + return {entity}Domain.insert{Entity}(model); + } + + @Override + public int update{Entity}({Entity}DTO {entity}DTO) + { + {Entity} model = {Entity}ServiceConvert.toModel({entity}DTO); + return {entity}Domain.update{Entity}(model); + } + + @Override + public int delete{Entity}ById(Long {entity}Id) + { + return {entity}Domain.delete{Entity}ById({entity}Id); + } + + @Override + public int delete{Entity}ByIds(Long[] {entity}Ids) + { + return {entity}Domain.delete{Entity}ByIds({entity}Ids); + } +} +``` + +**重要规范:** +- 使用 `@Service` 注解 +- 使用 `@Autowired` 字段注入(Service层使用字段注入) +- 通过 Convert 类进行 DTO ↔ Model 转换 + +### 6.4 Service Convert 转换类规范 + +**位置**: `src/main/java/com/ruoyi/{module}/service/convert/{Entity}ServiceConvert.java` + +```java +package com.ruoyi.{module}.service.convert; + +import com.ruoyi.{module}.domain.model.{Entity}; +import com.ruoyi.{module}.service.dto.{Entity}DTO; +import org.springframework.beans.BeanUtils; +import java.util.ArrayList; +import java.util.List; + +/** + * {表中文说明}服务转换类 + * + * @author ruoyi + * @date {date} + */ +public class {Entity}ServiceConvert +{ + /** + * DTO 转 Model + */ + public static {Entity} toModel({Entity}DTO dto) + { + if (dto == null) + { + return null; + } + {Entity} model = new {Entity}(); + BeanUtils.copyProperties(dto, model); + return model; + } + + /** + * Model 转 DTO + */ + public static {Entity}DTO toDTO({Entity} model) + { + if (model == null) + { + return null; + } + {Entity}DTO dto = new {Entity}DTO(); + BeanUtils.copyProperties(model, dto); + return dto; + } + + /** + * Model List 转 DTO List + */ + public static List<{Entity}DTO> toDTOList(List<{Entity}> modelList) + { + if (modelList == null || modelList.isEmpty()) + { + return new ArrayList<>(); + } + List<{Entity}DTO> dtoList = new ArrayList<>(); + for ({Entity} model : modelList) + { + dtoList.add(toDTO(model)); + } + return dtoList; + } + + /** + * DTO List 转 Model List + */ + public static List<{Entity}> toModelList(List<{Entity}DTO> dtoList) + { + if (dtoList == null || dtoList.isEmpty()) + { + return new ArrayList<>(); + } + List<{Entity}> modelList = new ArrayList<>(); + for ({Entity}DTO dto : dtoList) + { + modelList.add(toModel(dto)); + } + return modelList; + } +} +``` + +--- + +## 七、控制器层(Controller)规范 + +### 7.1 Controller 类规范 + +**位置**: `src/main/java/com/ruoyi/{module}/controller/{Entity}Controller.java` + +```java +package com.ruoyi.{module}.controller; + +import com.ruoyi.common.core.web.controller.BaseController; +import com.ruoyi.common.core.web.domain.AjaxResult; +import com.ruoyi.common.core.web.page.TableDataInfo; +import com.ruoyi.common.log.annotation.Log; +import com.ruoyi.common.log.enums.BusinessType; +import com.ruoyi.common.security.annotation.RequiresPermissions; +import com.ruoyi.{module}.controller.convert.{Entity}ControllerConvert; +import com.ruoyi.{module}.service.api.I{Entity}Service; +import com.ruoyi.{module}.service.dto.{Entity}DTO; +import com.ruoyi.{module}.api.domain.{Entity}VO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import java.util.List; + +/** + * {表中文说明}控制器 + * + * @author ruoyi + * @date {date} + */ +@RestController +@RequestMapping("/{entity}") +public class {Entity}Controller extends BaseController +{ + @Autowired + private I{Entity}Service {entity}Service; + + /** + * 查询{表中文说明}列表 + */ + @RequiresPermissions("{module}:{entity}:list") + @GetMapping("/list") + public TableDataInfo list({Entity}VO {entity}VO) + { + startPage(); + {Entity}DTO dto = {Entity}ControllerConvert.toDTO({entity}VO); + List<{Entity}DTO> list = {entity}Service.select{Entity}List(dto); + List<{Entity}VO> voList = {Entity}ControllerConvert.toVOList(list); + return getDataTable(voList); + } + + /** + * 获取{表中文说明}详细信息 + */ + @RequiresPermissions("{module}:{entity}:query") + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable("id") Long {entity}Id) + { + {Entity}DTO dto = {entity}Service.select{Entity}ById({entity}Id); + {Entity}VO vo = {Entity}ControllerConvert.toVO(dto); + return success(vo); + } + + /** + * 新增{表中文说明} + */ + @RequiresPermissions("{module}:{entity}:add") + @Log(title = "{表中文说明}", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody {Entity}VO {entity}VO) + { + {Entity}DTO dto = {Entity}ControllerConvert.toDTO({entity}VO); + return toAjax({entity}Service.insert{Entity}(dto)); + } + + /** + * 修改{表中文说明} + */ + @RequiresPermissions("{module}:{entity}:edit") + @Log(title = "{表中文说明}", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody {Entity}VO {entity}VO) + { + {Entity}DTO dto = {Entity}ControllerConvert.toDTO({entity}VO); + return toAjax({entity}Service.update{Entity}(dto)); + } + + /** + * 删除{表中文说明} + */ + @RequiresPermissions("{module}:{entity}:remove") + @Log(title = "{表中文说明}", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) + { + return toAjax({entity}Service.delete{Entity}ByIds(ids)); + } +} +``` + +**规范要点:** +- 继承 `BaseController` +- 使用 `@RestController` 和 `@RequestMapping` +- 使用 `@Autowired` 字段注入 +- 使用 `@RequiresPermissions` 进行权限控制 +- 使用 `@Log` 记录操作日志 +- 通过 Convert 类进行 VO ↔ DTO 转换 + + +### 7.2 Controller Convert 转换类规范 + +**位置**: `src/main/java/com/ruoyi/{module}/controller/convert/{Entity}ControllerConvert.java` + +```java +package com.ruoyi.{module}.controller.convert; + +import com.ruoyi.{module}.api.domain.{Entity}VO; +import com.ruoyi.{module}.service.dto.{Entity}DTO; +import org.springframework.beans.BeanUtils; +import java.util.ArrayList; +import java.util.List; + +/** + * {表中文说明}控制器转换类 + * + * @author ruoyi + * @date {date} + */ +public class {Entity}ControllerConvert +{ + /** + * VO 转 DTO + */ + public static {Entity}DTO toDTO({Entity}VO vo) + { + if (vo == null) + { + return null; + } + {Entity}DTO dto = new {Entity}DTO(); + BeanUtils.copyProperties(vo, dto); + return dto; + } + + /** + * DTO 转 VO + */ + public static {Entity}VO toVO({Entity}DTO dto) + { + if (dto == null) + { + return null; + } + {Entity}VO vo = new {Entity}VO(); + BeanUtils.copyProperties(dto, vo); + return vo; + } + + /** + * DTO List 转 VO List + */ + public static List<{Entity}VO> toVOList(List<{Entity}DTO> dtoList) + { + if (dtoList == null || dtoList.isEmpty()) + { + return new ArrayList<>(); + } + List<{Entity}VO> voList = new ArrayList<>(); + for ({Entity}DTO dto : dtoList) + { + voList.add(toVO(dto)); + } + return voList; + } + + /** + * VO List 转 DTO List + */ + public static List<{Entity}DTO> toDTOList(List<{Entity}VO> voList) + { + if (voList == null || voList.isEmpty()) + { + return new ArrayList<>(); + } + List<{Entity}DTO> dtoList = new ArrayList<>(); + for ({Entity}VO vo : voList) + { + dtoList.add(toDTO(vo)); + } + return dtoList; + } +} +``` + +--- + +## 八、API层规范 + +### 8.1 VO 视图对象规范 + +**位置**: `ruoyi-api/{module-name}-api-{submodule}/src/main/java/com/ruoyi/{module}/api/domain/{Entity}VO.java` + +```java +package com.ruoyi.{module}.api.domain; + +import com.ruoyi.common.core.web.domain.BaseEntity; + +/** + * {表中文说明}视图对象 + * API 层 VO,用于前后端数据交互 + * + * @author ruoyi + * @date {date} + */ +public class {Entity}VO extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键ID */ + private Long {entity}Id; + + /** 字段说明 */ + private String fieldName; + + public Long get{Entity}Id() + { + return {entity}Id; + } + + public void set{Entity}Id(Long {entity}Id) + { + this.{entity}Id = {entity}Id; + } + + public String getFieldName() + { + return fieldName; + } + + public void setFieldName(String fieldName) + { + this.fieldName = fieldName; + } + + @Override + public String toString() + { + return "{Entity}VO{" + + "{entity}Id=" + {entity}Id + + ", fieldName='" + fieldName + '\'' + + '}'; + } +} +``` + +### 8.2 Remote Service 接口规范 + +**位置**: `ruoyi-api/{module-name}-api-{submodule}/src/main/java/com/ruoyi/{module}/api/Remote{Module}Service.java` + +```java +package com.ruoyi.{module}.api; + +import com.ruoyi.common.core.constant.SecurityConstants; +import com.ruoyi.common.core.constant.ServiceNameConstants; +import com.ruoyi.common.core.domain.R; +import com.ruoyi.{module}.api.domain.{Entity}VO; +import com.ruoyi.{module}.api.factory.Remote{Module}FallbackFactory; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; + +/** + * {模块中文名}远程服务 + * + * @author ruoyi + * @date {date} + */ +@FeignClient(contextId = "remote{Module}Service", value = ServiceNameConstants.{MODULE}_SERVICE, fallbackFactory = Remote{Module}FallbackFactory.class) +public interface Remote{Module}Service +{ + /** + * 根据ID获取{表中文说明} + */ + @GetMapping("/{entity}/{id}") + R<{Entity}VO> get{Entity}ById(@PathVariable("id") Long id, @RequestHeader(SecurityConstants.FROM_SOURCE) String source); +} +``` + +**规范要点:** +- 使用 `@FeignClient` 注解 +- `contextId` 格式:`remote{Module}Service` +- `value` 使用 `ServiceNameConstants` 中定义的常量 +- 配置 `fallbackFactory` 降级工厂 +- 方法参数必须包含 `@RequestHeader(SecurityConstants.FROM_SOURCE) String source` + + +### 8.3 Fallback Factory 降级工厂规范 + +**位置**: `ruoyi-api/{module-name}-api-{submodule}/src/main/java/com/ruoyi/{module}/api/factory/Remote{Module}FallbackFactory.java` + +```java +package com.ruoyi.{module}.api.factory; + +import com.ruoyi.common.core.domain.R; +import com.ruoyi.{module}.api.Remote{Module}Service; +import com.ruoyi.{module}.api.domain.{Entity}VO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.stereotype.Component; + +/** + * {模块中文名}服务降级处理 + * + * @author ruoyi + * @date {date} + */ +@Component +public class Remote{Module}FallbackFactory implements FallbackFactory +{ + private static final Logger log = LoggerFactory.getLogger(Remote{Module}FallbackFactory.class); + + @Override + public Remote{Module}Service create(Throwable throwable) + { + log.error("{模块中文名}服务调用失败:{}", throwable.getMessage()); + return new Remote{Module}Service() + { + @Override + public R<{Entity}VO> get{Entity}ById(Long id, String source) + { + return R.fail("获取{表中文说明}失败:" + throwable.getMessage()); + } + }; + } +} +``` + +**规范要点:** +- 使用 `@Component` 注解 +- 实现 `FallbackFactory` +- 记录错误日志 +- 返回友好的错误信息 + +--- + +## 九、依赖注入规范 + +### 9.1 依赖注入方式选择 + +**Domain 层:使用构造器注入(Constructor Injection)** + +```java +@Component +public class DeviceDomainImpl implements IDeviceDomain +{ + private final DeviceMapper deviceMapper; + + public DeviceDomainImpl(DeviceMapper deviceMapper) + { + this.deviceMapper = deviceMapper; + } +} +``` + +**优点:** +- 依赖不可变(final 字段) +- 更好的可测试性 +- 避免循环依赖 +- 符合 Spring 官方推荐 + +**Service 层和 Controller 层:使用字段注入(Field Injection)** + +```java +@Service +public class DeviceServiceImpl implements IDeviceService +{ + @Autowired + private IDeviceDomain deviceDomain; +} + +@RestController +public class DeviceController extends BaseController +{ + @Autowired + private IDeviceService deviceService; +} +``` + +**原因:** +- 代码简洁 +- 符合项目现有风格 +- 减少样板代码 + +### 9.2 依赖注入最佳实践 + +1. **Domain 层必须使用构造器注入** +2. **Service 层和 Controller 层使用字段注入** +3. **避免循环依赖** +4. **依赖接口而非实现** +5. **使用 `private` 修饰注入字段** + +--- + +## 十、命名规范 + +### 10.1 包命名规范 + +| 层级 | 包名 | 说明 | +|------|------|------| +| 控制器层 | `controller` | HTTP 请求处理 | +| 控制器转换 | `controller.convert` | VO ↔ DTO 转换 | +| 服务接口 | `service.api` | 服务接口定义 | +| 服务实现 | `service.impl` | 服务接口实现 | +| 服务DTO | `service.dto` | 数据传输对象 | +| 服务转换 | `service.convert` | DTO ↔ Model 转换 | +| 领域接口 | `domain.api` | 领域接口定义 | +| 领域实现 | `domain.impl` | 领域接口实现 | +| 领域模型 | `domain.model` | 领域模型对象 | +| 领域转换 | `domain.convert` | Model ↔ Entity 转换 | +| 数据访问 | `mapper` | MyBatis Mapper 接口 | +| 数据实体 | `mapper.entity` | 数据库实体对象 | +| API视图 | `api.domain` | VO 视图对象 | +| API服务 | `api` | Feign 远程服务 | +| API工厂 | `api.factory` | Feign 降级工厂 | + +### 10.2 类命名规范 + +| 类型 | 命名格式 | 示例 | +|------|---------|------| +| Controller | `{Entity}Controller` | `DeviceController` | +| Service 接口 | `I{Entity}Service` | `IDeviceService` | +| Service 实现 | `{Entity}ServiceImpl` | `DeviceServiceImpl` | +| Domain 接口 | `I{Entity}Domain` | `IDeviceDomain` | +| Domain 实现 | `{Entity}DomainImpl` | `DeviceDomainImpl` | +| Mapper 接口 | `{Entity}Mapper` | `DeviceMapper` | +| Entity 实体 | `{Entity}Entity` | `DeviceEntity` | +| Domain Model | `{Entity}` | `Device` | +| Service DTO | `{Entity}DTO` | `DeviceDTO` | +| API VO | `{Entity}VO` | `DeviceVO` | +| Domain Convert | `{Entity}DomainConvert` | `DeviceDomainConvert` | +| Service Convert | `{Entity}ServiceConvert` | `DeviceServiceConvert` | +| Controller Convert | `{Entity}ControllerConvert` | `DeviceControllerConvert` | +| Remote Service | `Remote{Module}Service` | `RemoteDeviceService` | +| Fallback Factory | `Remote{Module}FallbackFactory` | `RemoteDeviceFallbackFactory` | + + +### 10.3 方法命名规范 + +| 操作类型 | 命名格式 | 示例 | +|---------|---------|------| +| 查询列表 | `select{Entity}List` | `selectDeviceList` | +| 查询单个 | `select{Entity}ById` | `selectDeviceById` | +| 新增 | `insert{Entity}` | `insertDevice` | +| 修改 | `update{Entity}` | `updateDevice` | +| 删除 | `delete{Entity}ById` | `deleteDeviceById` | +| 批量删除 | `delete{Entity}ByIds` | `deleteDeviceByIds` | +| 转换为Entity | `toEntity` | `toEntity` | +| 转换为Model | `toModel` | `toModel` | +| 转换为DTO | `toDTO` | `toDTO` | +| 转换为VO | `toVO` | `toVO` | +| 转换为List | `toModelList`, `toDTOList`, `toVOList` | `toModelList` | + +### 10.4 字段命名规范 + +| 字段类型 | 命名格式 | 示例 | +|---------|---------|------| +| 主键ID | `{entity}_id` (数据库)
`{entity}Id` (Java) | `device_id`
`deviceId` | +| 普通字段 | `field_name` (数据库)
`fieldName` (Java) | `device_name`
`deviceName` | +| 外键字段 | `{ref_entity}_id` (数据库)
`{refEntity}Id` (Java) | `dock_id`
`dockId` | +| 布尔字段 | `is_{field}` (数据库)
`is{Field}` (Java) | `is_active`
`isActive` | +| 时间字段 | `{action}_time` (数据库)
`{action}Time` (Java) | `create_time`
`createTime` | + +### 10.5 数据库命名规范 + +| 对象类型 | 命名格式 | 示例 | +|---------|---------|------| +| 表名 | `{module}_{table}` | `device_device`, `device_dock` | +| 主键 | `{table}_id` | `device_id`, `dock_id` | +| 普通索引 | `idx_{table}_{field}` | `idx_dock_device_id` | +| 唯一索引 | `uk_{table}_{field}` | `uk_device_sn` | +| 复合索引 | `idx_{table}_{field1}_{field2}` | `idx_dock_device_status` | +| Flyway历史表 | `flyway_{module}_schema_history` | `flyway_device_schema_history` | + +--- + +## 十一、对象转换流程 + +### 11.1 数据流转图 + +``` +前端请求 + ↓ +Controller 接收 VO + ↓ ControllerConvert.toDTO(vo) +Service 处理 DTO + ↓ ServiceConvert.toModel(dto) +Domain 处理 Model + ↓ DomainConvert.toEntity(model) +Mapper 操作 Entity + ↓ 数据库 +Database + ↑ +Mapper 返回 Entity + ↑ DomainConvert.toModel(entity) +Domain 返回 Model + ↑ ServiceConvert.toDTO(model) +Service 返回 DTO + ↑ ControllerConvert.toVO(dto) +Controller 返回 VO + ↑ +前端响应 +``` + +### 11.2 对象职责说明 + +| 对象类型 | 所属层 | 职责 | 特点 | +|---------|-------|------|------| +| **VO** (View Object) | API层 | 前后端数据交互 | 可包含展示逻辑字段 | +| **DTO** (Data Transfer Object) | Service层 | 业务逻辑编排 | 可包含业务组合字段 | +| **Model** | Domain层 | 领域业务逻辑 | 纯业务模型 | +| **Entity** | Mapper层 | 数据库映射 | 与数据库表一一对应 | + +### 11.3 转换类职责 + +| 转换类 | 转换方向 | 位置 | +|-------|---------|------| +| **ControllerConvert** | VO ↔ DTO | `controller.convert` | +| **ServiceConvert** | DTO ↔ Model | `service.convert` | +| **DomainConvert** | Model ↔ Entity | `domain.convert` | + +--- + +## 十二、最佳实践 + +### 12.1 代码规范 + +1. **分层清晰**:严格遵守 Controller → Service → Domain → Mapper 分层 +2. **单一职责**:每个类只负责一个职责 +3. **依赖倒置**:依赖接口而非实现 +4. **对象转换**:使用专门的 Convert 类进行对象转换 +5. **异常处理**:使用统一的异常处理机制 +6. **日志记录**:关键操作必须记录日志 +7. **权限控制**:Controller 方法必须添加权限注解 + +### 12.2 数据库规范 + +1. **表名规范**:`{module}_{table}` 格式 +2. **主键规范**:`{table}_id`,BIGINT,AUTO_INCREMENT +3. **BaseEntity字段**:所有表必须包含 create_by, create_time, update_by, update_time, remark +4. **索引规范**:外键字段必须添加索引 +5. **字符集**:统一使用 utf8mb4 +6. **存储引擎**:统一使用 InnoDB +7. **Flyway迁移**:所有数据库变更必须通过 Flyway 脚本 + +### 12.3 配置规范 + +1. **端口分配**: + - 基础服务:9200-9209 + - 业务服务:9210-9299 + - 监控服务:9100 + - 特殊服务:9300+ + +2. **配置同步**: + - bootstrap.yml 的 server.port 必须与 docker-compose.yml 的容器端口一致 + - Flyway 历史表名格式:`flyway_{module}_schema_history` + +3. **服务命名**: + - 服务名称格式:`{module-name}` + - 在 ServiceNameConstants 中定义常量 + +### 12.4 API 规范 + +1. **Feign 客户端**: + - 接口命名:`Remote{Module}Service` + - contextId:`remote{Module}Service` + - 必须配置 fallbackFactory + +2. **降级处理**: + - 工厂命名:`Remote{Module}FallbackFactory` + - 必须记录错误日志 + - 返回友好的错误信息 + +3. **安全头**: + - 所有 Feign 方法必须包含 `@RequestHeader(SecurityConstants.FROM_SOURCE) String source` + +### 12.5 Maven 规范 + +1. **新增服务步骤**: + - 在 ServiceNameConstants 中添加服务名常量 + - 在根 pom.xml 的 dependencyManagement 中添加 API 依赖 + - 在 ruoyi-api/pom.xml 中添加模块声明 + - 创建业务模块和 API 模块 + +2. **依赖管理**: + - 版本号统一在根 pom.xml 管理 + - 使用 `${ruoyi.version}` 引用版本 + +### 12.6 开发流程 + +1. **数据库设计** → 编写 Flyway 迁移脚本 +2. **Mapper 层** → Entity + Mapper 接口 + Mapper XML +3. **Domain 层** → Model + Domain 接口 + Domain 实现 + Domain Convert +4. **Service 层** → DTO + Service 接口 + Service 实现 + Service Convert +5. **Controller 层** → Controller + Controller Convert +6. **API 层** → VO + Remote Service + Fallback Factory +7. **配置** → bootstrap.yml + docker-compose.yml +8. **测试** → 单元测试 + 集成测试 + +--- + +## 附录:快速参考 + +### A. 创建新模块检查清单 + +- [ ] 数据库表设计(遵循命名规范) +- [ ] Flyway 迁移脚本(V{版本号}__{描述}.sql) +- [ ] Entity 实体类(继承 BaseEntity) +- [ ] Mapper 接口和 XML +- [ ] Domain Model(领域模型) +- [ ] Domain 接口和实现(构造器注入) +- [ ] Domain Convert(对象转换) +- [ ] Service DTO(数据传输对象) +- [ ] Service 接口和实现(字段注入) +- [ ] Service Convert(对象转换) +- [ ] Controller(权限控制、日志记录) +- [ ] Controller Convert(对象转换) +- [ ] API VO(视图对象) +- [ ] Remote Service(Feign 客户端) +- [ ] Fallback Factory(降级处理) +- [ ] ServiceNameConstants(服务名常量) +- [ ] 根 pom.xml(依赖管理) +- [ ] ruoyi-api/pom.xml(模块声明) +- [ ] bootstrap.yml(端口配置) +- [ ] docker-compose.yml(容器配置) + +### B. 常见问题 + +**Q: 为什么 Domain 层使用构造器注入,Service 层使用字段注入?** +A: Domain 层使用构造器注入是为了保证依赖不可变和更好的可测试性;Service 层使用字段注入是为了代码简洁和符合项目现有风格。 + +**Q: 为什么需要这么多对象(VO、DTO、Model、Entity)?** +A: 不同层次的对象有不同的职责,这样可以实现关注点分离,提高代码的可维护性和可扩展性。 + +**Q: Convert 类为什么使用静态方法?** +A: Convert 类是无状态的工具类,使用静态方法可以避免不必要的对象创建,提高性能。 + +**Q: 如何处理端口冲突?** +A: 按照端口分配规范,基础服务使用 9200-9209,业务服务使用 9210-9299,确保 bootstrap.yml 和 docker-compose.yml 中的端口配置一致。 + +**Q: Flyway 迁移脚本命名有什么要求?** +A: 格式为 `V{版本号}__{描述}.sql`,版本号从1开始递增不能跳号,描述使用下划线分隔的英文,首字母大写。 + +--- + +**文档结束** + +> 本文档将持续更新,如有疑问或建议,请联系架构团队。