# 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开始递增不能跳号,描述使用下划线分隔的英文,首字母大写。 --- **文档结束** > 本文档将持续更新,如有疑问或建议,请联系架构团队。