a-cloud-all/ARCHITECTURE_STANDARDS.md

2029 lines
56 KiB
Markdown
Raw Permalink Normal View History

2026-01-17 13:05:43 +08:00
# 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``<dependencyManagement>` 中添加:
```xml
<!-- {模块中文名}接口 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>{module-name}-api-{submodule}</artifactId>
<version>${ruoyi.version}</version>
</dependency>
```
**示例:**
```xml
<!-- 设备接口 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>tuoheng-api-device</artifactId>
<version>${ruoyi.version}</version>
</dependency>
```
### 2.2 ruoyi-api/pom.xml 配置
`ruoyi-api/pom.xml``<modules>` 中添加:
```xml
<module>{module-name}-api-{submodule}</module>
```
**示例:**
```xml
<modules>
<module>ruoyi-api-system</module>
<module>tuoheng-api-device</module>
<module>tuoheng-api-airline</module>
</modules>
```
### 2.3 业务模块 pom.xml 标准配置
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-modules</artifactId>
<version>3.6.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>{module-name}</artifactId>
<description>
{module-name}系统模块
</description>
<dependencies>
<!-- {Module} API -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>{module-name}-api-{submodule}</artifactId>
</dependency>
<!-- SpringCloud Alibaba Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud Alibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- SpringCloud Alibaba Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- SpringBoot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Mysql Connector -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- RuoYi Common DataSource -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-datasource</artifactId>
</dependency>
<!-- RuoYi Common DataScope -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-datascope</artifactId>
</dependency>
<!-- RuoYi Common Log -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-log</artifactId>
</dependency>
<!-- RuoYi Common Swagger -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-swagger</artifactId>
</dependency>
<!-- Flyway Database Migration -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<!-- Flyway MySQL Support -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
```
### 2.4 API 模块 pom.xml 标准配置
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-api</artifactId>
<version>3.6.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>{module-name}-api-{submodule}</artifactId>
<description>
{module-name} API模块
</description>
<dependencies>
<!-- RuoYi Common Core -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<!-- Spring Cloud OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>
```
---
## 三、配置文件规范
### 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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.{module}.mapper.{Entity}Mapper">
<resultMap type="com.ruoyi.{module}.mapper.entity.{Entity}Entity" id="{Entity}Result">
<result property="{entity}Id" column="{entity}_id" />
<result property="fieldName" column="field_name" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>
<sql id="select{Entity}Vo">
select {entity}_id, field_name, create_by, create_time, update_by, update_time, remark
from {module}_{table}
</sql>
<select id="select{Entity}List" parameterType="com.ruoyi.{module}.mapper.entity.{Entity}Entity" resultMap="{Entity}Result">
<include refid="select{Entity}Vo"/>
<where>
<if test="{entity}Id != null"> and {entity}_id = #{{{entity}Id}}</if>
<if test="fieldName != null and fieldName != ''"> and field_name like concat('%', #{fieldName}, '%')</if>
</where>
</select>
<select id="select{Entity}ById" parameterType="Long" resultMap="{Entity}Result">
<include refid="select{Entity}Vo"/>
where {entity}_id = #{{{entity}Id}}
</select>
2026-01-17 17:16:42 +08:00
<insert id="insert{Entity}" parameterType="com.ruoyi.{module}.mapper.entity.{Entity}Entity" useGeneratedKeys="true" keyProperty="{entity}Id" keyColumn="{entity}_id">
2026-01-17 13:05:43 +08:00
insert into {module}_{table}
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="fieldName != null and fieldName != ''">field_name,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
<if test="remark != null">remark,</if>
create_time
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="fieldName != null and fieldName != ''">#{fieldName},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
<if test="remark != null">#{remark},</if>
sysdate()
</trim>
</insert>
<update id="update{Entity}" parameterType="com.ruoyi.{module}.mapper.entity.{Entity}Entity">
update {module}_{table}
<trim prefix="SET" suffixOverrides=",">
<if test="fieldName != null and fieldName != ''">field_name = #{fieldName},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
<if test="remark != null">remark = #{remark},</if>
update_time = sysdate()
</trim>
where {entity}_id = #{{{entity}Id}}
</update>
<delete id="delete{Entity}ById" parameterType="Long">
delete from {module}_{table} where {entity}_id = #{{{entity}Id}}
</delete>
<delete id="delete{Entity}ByIds" parameterType="Long">
delete from {module}_{table} where {entity}_id in
<foreach item="{entity}Id" collection="array" open="(" separator="," close=")">
#{{{entity}Id}}
</foreach>
</delete>
</mapper>
```
**规范要点:**
- 使用 `<resultMap>` 定义结果映射
- 使用 `<sql>` 定义可复用的 SQL 片段
- 使用动态 SQL 标签:`<if>`, `<trim>`, `<foreach>`
- 插入操作使用 `useGeneratedKeys="true"` 返回主键
2026-01-17 17:16:42 +08:00
- **重要**:插入操作必须同时指定 `keyProperty``keyColumn` 属性
2026-01-17 13:05:43 +08:00
- 时间字段使用 `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});
2026-01-17 17:16:42 +08:00
int result = {entity}Mapper.insert{Entity}(entity);
// 【重要】MyBatis 会将自增主键回填到 entity 对象,需要同步回 model 对象
{entity}.set{Entity}Id(entity.get{Entity}Id());
return result;
2026-01-17 13:05:43 +08:00
}
@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 类进行对象转换
2026-01-17 17:16:42 +08:00
- **【关键】insert 方法必须将 Entity 的主键回填到 Model 对象**
**⚠️ MyBatis 主键回填注意事项:**
MyBatis 在执行 INSERT 操作后,会将数据库自动生成的主键值回填到 `Entity` 对象中,但**不会自动同步到传入的 `Model` 对象**。
**错误示例(会导致主键丢失):**
```java
@Override
public int insert{Entity}({Entity} {entity})
{
{Entity}Entity entity = {Entity}DomainConvert.toEntity({entity});
return {entity}Mapper.insert{Entity}(entity);
// ❌ 错误entity 中的主键没有同步回 {entity} 对象
}
```
**正确示例(主键正确回填):**
```java
@Override
public int insert{Entity}({Entity} {entity})
{
{Entity}Entity entity = {Entity}DomainConvert.toEntity({entity});
int result = {entity}Mapper.insert{Entity}(entity);
// ✅ 正确:将 entity 中的主键同步回 {entity} 对象
{entity}.set{Entity}Id(entity.get{Entity}Id());
return result;
}
```
**为什么需要手动同步主键?**
1. **对象转换导致的隔离**`BeanUtils.copyProperties()` 只是属性拷贝,创建了新的 Entity 对象
2. **MyBatis 只回填 Entity**MyBatis 的 `useGeneratedKeys` 只会将主键设置到 Mapper 方法参数Entity
3. **Model 对象不会自动更新**:原始的 Model 对象与 Entity 对象是两个独立的对象,需要手动同步
**不同步主键的后果:**
如果不将主键同步回 Model 对象,会导致:
- Service 层无法获取新插入记录的主键 ID
- 后续依赖主键的业务逻辑会失败(如关联表插入)
- 可能产生重复数据或数据不一致问题
**示例场景:**
```java
// Service 层调用
Device device = new Device();
device.setDeviceName("测试设备");
deviceDomain.insertDevice(device);
// 如果没有主键回填device.getDeviceId() 将返回 null
Long deviceId = device.getDeviceId(); // 期望得到新插入的 ID
// 后续业务逻辑依赖这个 ID
Dock dock = new Dock();
dock.setDeviceId(deviceId); // 如果 deviceId 为 null会导致关联失败
dockDomain.insertDock(dock);
```
2026-01-17 13:05:43 +08:00
### 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<Remote{Module}Service>
{
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<Remote{Module}Service>`
- 记录错误日志
- 返回友好的错误信息
---
## 九、依赖注入规范
### 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` (数据库) <br> `{entity}Id` (Java) | `device_id` <br> `deviceId` |
| 普通字段 | `field_name` (数据库) <br> `fieldName` (Java) | `device_name` <br> `deviceName` |
| 外键字段 | `{ref_entity}_id` (数据库) <br> `{refEntity}Id` (Java) | `dock_id` <br> `dockId` |
| 布尔字段 | `is_{field}` (数据库) <br> `is{Field}` (Java) | `is_active` <br> `isActive` |
| 时间字段 | `{action}_time` (数据库) <br> `{action}Time` (Java) | `create_time` <br> `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`BIGINTAUTO_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 ServiceFeign 客户端)
- [ ] 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开始递增不能跳号描述使用下划线分隔的英文首字母大写。
---
**文档结束**
> 本文档将持续更新,如有疑问或建议,请联系架构团队。