56 KiB
RuoYi-Cloud 微服务架构开发规范
本文档定义了 RuoYi-Cloud 微服务项目的标准架构、编码规范和最佳实践。
版本: v1.0 更新日期: 2026-01-17
目录
一、项目结构规范
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 中添加:
/**
* {模块中文名}服务的serviceid
*/
public static final String {MODULE}_SERVICE = "{module-name}";
示例:
/**
* 设备服务的serviceid
*/
public static final String DEVICE_SERVICE = "tuoheng-device";
/**
* 航线服务的serviceid
*/
public static final String AIRLINE_SERVICE = "tuoheng-airline";
2.1.2 添加依赖管理
在根 pom.xml 的 <dependencyManagement> 中添加:
<!-- {模块中文名}接口 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>{module-name}-api-{submodule}</artifactId>
<version>${ruoyi.version}</version>
</dependency>
示例:
<!-- 设备接口 -->
<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> 中添加:
<module>{module-name}-api-{submodule}</module>
示例:
<modules>
<module>ruoyi-api-system</module>
<module>tuoheng-api-device</module>
<module>tuoheng-api-airline</module>
</modules>
2.3 业务模块 pom.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 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 标准配置
# 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 中添加服务配置:
{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 数据库表设计规范
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='{表中文说明}';
设计原则:
-
表名格式:
{module}_{table_name},全小写,下划线分隔- 示例:
device_device,device_dock,airline_route
- 示例:
-
主键命名:
{table}_id,类型为BIGINT,AUTO_INCREMENT- 示例:
device_id,dock_id,route_id
- 示例:
-
BaseEntity 字段: 所有表必须包含以下5个字段
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 '备注' -
字符集: 统一使用
utf8mb4 -
存储引擎: 统一使用
InnoDB -
注释: 必须添加表注释和字段注释
-
外键: 不使用数据库外键约束,通过应用层维护关联关系
4.3 索引设计规范
-- 外键索引
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}
索引设计原则:
- 所有外键字段必须添加索引
- 频繁查询的字段添加索引
- 复合索引遵循最左前缀原则
- 避免过多索引影响写入性能
示例:
-- 外键索引
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
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
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 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>
<insert id="insert{Entity}" parameterType="com.ruoyi.{module}.mapper.entity.{Entity}Entity" useGeneratedKeys="true" keyProperty="{entity}Id" keyColumn="{entity}_id">
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"返回主键 - 重要:插入操作必须同时指定
keyProperty和keyColumn属性 - 时间字段使用
sysdate()函数
五、领域层(Domain)规范
5.1 Model 领域模型规范
位置: src/main/java/com/ruoyi/{module}/domain/model/{Entity}.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
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
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});
int result = {entity}Mapper.insert{Entity}(entity);
// 【重要】MyBatis 会将自增主键回填到 entity 对象,需要同步回 model 对象
{entity}.set{Entity}Id(entity.get{Entity}Id());
return result;
}
@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 类进行对象转换
- 【关键】insert 方法必须将 Entity 的主键回填到 Model 对象
⚠️ MyBatis 主键回填注意事项:
MyBatis 在执行 INSERT 操作后,会将数据库自动生成的主键值回填到 Entity 对象中,但不会自动同步到传入的 Model 对象。
错误示例(会导致主键丢失):
@Override
public int insert{Entity}({Entity} {entity})
{
{Entity}Entity entity = {Entity}DomainConvert.toEntity({entity});
return {entity}Mapper.insert{Entity}(entity);
// ❌ 错误:entity 中的主键没有同步回 {entity} 对象
}
正确示例(主键正确回填):
@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;
}
为什么需要手动同步主键?
- 对象转换导致的隔离:
BeanUtils.copyProperties()只是属性拷贝,创建了新的 Entity 对象 - MyBatis 只回填 Entity:MyBatis 的
useGeneratedKeys只会将主键设置到 Mapper 方法参数(Entity)中 - Model 对象不会自动更新:原始的 Model 对象与 Entity 对象是两个独立的对象,需要手动同步
不同步主键的后果:
如果不将主键同步回 Model 对象,会导致:
- Service 层无法获取新插入记录的主键 ID
- 后续依赖主键的业务逻辑会失败(如关联表插入)
- 可能产生重复数据或数据不一致问题
示例场景:
// 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);
5.4 Domain Convert 转换类规范
位置: src/main/java/com/ruoyi/{module}/domain/convert/{Entity}DomainConvert.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
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
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
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
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
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
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
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
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}Servicevalue使用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
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)
@Component
public class DeviceDomainImpl implements IDeviceDomain
{
private final DeviceMapper deviceMapper;
public DeviceDomainImpl(DeviceMapper deviceMapper)
{
this.deviceMapper = deviceMapper;
}
}
优点:
- 依赖不可变(final 字段)
- 更好的可测试性
- 避免循环依赖
- 符合 Spring 官方推荐
Service 层和 Controller 层:使用字段注入(Field Injection)
@Service
public class DeviceServiceImpl implements IDeviceService
{
@Autowired
private IDeviceDomain deviceDomain;
}
@RestController
public class DeviceController extends BaseController
{
@Autowired
private IDeviceService deviceService;
}
原因:
- 代码简洁
- 符合项目现有风格
- 减少样板代码
9.2 依赖注入最佳实践
- Domain 层必须使用构造器注入
- Service 层和 Controller 层使用字段注入
- 避免循环依赖
- 依赖接口而非实现
- 使用
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 代码规范
- 分层清晰:严格遵守 Controller → Service → Domain → Mapper 分层
- 单一职责:每个类只负责一个职责
- 依赖倒置:依赖接口而非实现
- 对象转换:使用专门的 Convert 类进行对象转换
- 异常处理:使用统一的异常处理机制
- 日志记录:关键操作必须记录日志
- 权限控制:Controller 方法必须添加权限注解
12.2 数据库规范
- 表名规范:
{module}_{table}格式 - 主键规范:
{table}_id,BIGINT,AUTO_INCREMENT - BaseEntity字段:所有表必须包含 create_by, create_time, update_by, update_time, remark
- 索引规范:外键字段必须添加索引
- 字符集:统一使用 utf8mb4
- 存储引擎:统一使用 InnoDB
- Flyway迁移:所有数据库变更必须通过 Flyway 脚本
12.3 配置规范
-
端口分配:
- 基础服务:9200-9209
- 业务服务:9210-9299
- 监控服务:9100
- 特殊服务:9300+
-
配置同步:
- bootstrap.yml 的 server.port 必须与 docker-compose.yml 的容器端口一致
- Flyway 历史表名格式:
flyway_{module}_schema_history
-
服务命名:
- 服务名称格式:
{module-name} - 在 ServiceNameConstants 中定义常量
- 服务名称格式:
12.4 API 规范
-
Feign 客户端:
- 接口命名:
Remote{Module}Service - contextId:
remote{Module}Service - 必须配置 fallbackFactory
- 接口命名:
-
降级处理:
- 工厂命名:
Remote{Module}FallbackFactory - 必须记录错误日志
- 返回友好的错误信息
- 工厂命名:
-
安全头:
- 所有 Feign 方法必须包含
@RequestHeader(SecurityConstants.FROM_SOURCE) String source
- 所有 Feign 方法必须包含
12.5 Maven 规范
-
新增服务步骤:
- 在 ServiceNameConstants 中添加服务名常量
- 在根 pom.xml 的 dependencyManagement 中添加 API 依赖
- 在 ruoyi-api/pom.xml 中添加模块声明
- 创建业务模块和 API 模块
-
依赖管理:
- 版本号统一在根 pom.xml 管理
- 使用
${ruoyi.version}引用版本
12.6 开发流程
- 数据库设计 → 编写 Flyway 迁移脚本
- Mapper 层 → Entity + Mapper 接口 + Mapper XML
- Domain 层 → Model + Domain 接口 + Domain 实现 + Domain Convert
- Service 层 → DTO + Service 接口 + Service 实现 + Service Convert
- Controller 层 → Controller + Controller Convert
- API 层 → VO + Remote Service + Fallback Factory
- 配置 → bootstrap.yml + docker-compose.yml
- 测试 → 单元测试 + 集成测试
附录:快速参考
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开始递增不能跳号,描述使用下划线分隔的英文,首字母大写。
文档结束
本文档将持续更新,如有疑问或建议,请联系架构团队。