## 架构设计规范
> 本文档基于参考文档 `参考Agents.md` 中的内容,抽取并整理出**与具体业务项目无关**的通用架构设计约定,可复用到其它类似的微服务项目中。
---
### 1. 后端模块结构规范
#### 1.1 服务模块目录结构
建议单体或微服务项目的后端服务模块遵循统一的目录结构,便于团队协作和代码治理:
```text
service-xxx/
├── src/main/java/com/company/project/xxx/
│ ├── XxxApplication.java # 启动类
│ ├── config/ # 配置类
│ ├── controller/ # 控制器层
│ ├── service/ # 业务服务层
│ │ └── impl/ # 业务服务实现
│ ├── data/ # 数据访问层(MyBatis-Plus相关)
│ │ ├── entity/ # 实体类
│ │ ├── mapper/ # Mapper接口
│ │ ├── service/ # XxxDataService(数据服务)
│ │ │ └── impl/ # XxxDataServiceImpl(数据服务实现)
│ │ └── xml/ # Mapper XML文件(可选)
│ ├── dto/ # 数据传输对象
│ ├── vo/ # 视图对象
│ └── enums/ # 枚举类
├── src/main/resources/
│ ├── application.yml # 主配置
│ ├── application-docker.yml # 容器环境配置(可选)
│ └── logback-spring.xml # 日志配置
└── pom.xml
```
#### 1.2 公共模块结构
对于多服务项目,建议抽取公共能力到独立的 `common` 模块,供各业务服务复用:
```text
common/
├── src/main/java/com/company/project/common/
│ ├── auth/ # 认证相关
│ ├── cache/ # 缓存服务
│ ├── config/ # 通用配置
│ ├── context/ # 上下文管理
│ ├── core/ # 核心类(Result、PageResult 等)
│ ├── exception/ # 异常处理
│ ├── feign/ # 远程调用拦截器
│ ├── loadbalancer/ # 负载均衡
│ ├── mybatis/ # ORM / 数据访问配置
│ ├── nacos/ # 注册配置(如使用 Nacos)
│ └── web/ # Web 拦截器
```
**基础模块依赖方式**:
**强制要求**:基础模块(如 `common` 模块)必须采用 Maven/Gradle 项目依赖方式引入,不允许通过拷贝代码或 JAR 包方式使用。
```xml
com.wjbl.project
common
${project.version}
```
**优势说明**:
- 统一版本管理,避免基础代码不一致。
- 便于基础能力的统一升级和维护。
- 支持 Maven 的传递依赖管理。
- 确保各业务服务使用的基础模块版本一致。
#### 1.3 启动类与组件扫描
所有业务服务建议显式配置组件扫描路径,以确保能扫描到业务模块和公共模块:
```java
@SpringBootApplication(scanBasePackages = {
"com.wjbl.project.xxx",
"com.wjbl.project.common"
})
@EnableDiscoveryClient
public class XxxApplication {
public static void main(String[] args) {
SpringApplication.run(XxxApplication.class, args);
}
}
```
**注意**:基于 WebFlux 的网关类服务不应扫描包含 Servlet 相关依赖的公共模块包,以避免 WebFlux 与 Servlet 依赖冲突。
---
### 2. 数据库设计规范
#### 2.1 审计字段
所有业务表建议统一包含以下审计字段,用于记录数据的创建与修改信息:
```sql
created_by BIGINT NOT NULL COMMENT '创建人ID',
created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_by BIGINT NOT NULL COMMENT '更新人ID',
updated_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除(0-未删除,1-已删除)'
```
#### 2.2 主键策略
**强制要求**:
1. **数据库主键字段类型**:
- 使用 `VARCHAR(20)` 或 `CHAR(19)` 字符串类型存储主键。
- 存储内容为雪花算法(Snowflake)生成的 19 位数字 ID。
2. **实体类主键配置**:
```java
@TableId(type = IdType.ASSIGN_ID)
private String id; // 注意:使用 String 类型
```
3. **雪花算法配置**:
- MyBatis-Plus 默认支持雪花算法生成 Long 类型 ID。
- 通过自定义 IdentifierGenerator 或在插入前转换为字符串存储。
**优势说明**:
- 字符串存储避免前端 JavaScript 处理长整型精度丢失问题(JS Number 最大安全整数为 2^53-1)。
- 保持雪花算法的分布式唯一性和时间有序性优势。
- 便于跨语言系统集成(如 Python、Go 等)。
#### 2.3 逻辑删除
- 统一使用 `deleted` 字段表示逻辑删除状态。
- 0 = 未删除,1 = 已删除。
- 在 ORM 框架中统一配置逻辑删除注解(例如:`@TableLogic`)。
---
### 3. MyBatis-Plus 架构规范
#### 3.1 数据访问层目录结构
MyBatis-Plus 相关的所有文件统一存放在 `data` 目录下,实现数据访问与业务逻辑的明确分离:
```text
src/main/java/com/company/project/xxx/data/
├── entity/ # 实体类(对应数据库表)
│ ├── Customer.java
│ └── Project.java
├── mapper/ # Mapper 接口
│ ├── CustomerMapper.java
│ └── ProjectMapper.java
├── service/ # 数据服务接口(XxxDataService)
│ ├── CustomerDataService.java
│ └── ProjectDataService.java
└── impl/ # 数据服务实现(XxxDataServiceImpl)
├── CustomerDataServiceImpl.java
└── ProjectDataServiceImpl.java
```
#### 3.2 数据服务命名规范
**强制要求**:
1. **数据服务接口**:命名为 `XxxDataService`,继承 `IService`
```java
public interface CustomerDataService extends IService {
// 数据访问相关方法
}
```
2. **数据服务实现**:命名为 `XxxDataServiceImpl`,继承 `ServiceImpl`
```java
@Service
public class CustomerDataServiceImpl
extends ServiceImpl
implements CustomerDataService {
// 数据访问实现
}
```
3. **命名原因**:
- 避免与业务 Service(如 `CustomerService`)产生命名冲突。
- 清晰区分数据访问层(DataService)和业务逻辑层(Service)。
- 符合单一职责原则,DataService 只负责数据的 CRUD 操作。
#### 3.3 业务服务与数据服务的协作规范
**强制要求**:
1. **业务 Service 必须通过 DataService 访问数据**:
```java
@Service
@RequiredArgsConstructor
public class CustomerServiceImpl implements CustomerService {
private final CustomerDataService customerDataService; // 注入 DataService
@Override
public CustomerVO getCustomerById(String id) {
// 通过 DataService 查询数据
Customer customer = customerDataService.getById(id);
// 业务逻辑处理和转换
return convertToVO(customer);
}
}
```
2. **禁止业务 Service 直接注入 Mapper**:
```java
// ❌ 错误:业务层直接使用 Mapper
@Service
public class CustomerServiceImpl implements CustomerService {
@Autowired
private CustomerMapper customerMapper; // 不允许
}
```
3. **DataService 职责**:
- 封装所有数据库查询和操作。
- 提供基础的 CRUD 方法(继承自 MyBatis-Plus 的 IService)。
- 提供复杂查询的封装方法(如多表关联、统计等)。
4. **业务 Service 职责**:
- 实现业务逻辑和流程控制。
- 调用一个或多个 DataService 完成数据操作。
- 处理 DTO 到实体、实体到 VO 的转换。
- 处理事务管理(@Transactional)。
#### 3.4 分层示例
完整的分层调用示例:
```java
// 1. Controller 层
@RestController
@RequestMapping("/api/v1/customer")
@RequiredArgsConstructor
public class CustomerController {
private final CustomerService customerService;
@GetMapping("/{id}")
public Result getCustomer(@PathVariable String id) {
return Result.success(customerService.getCustomerById(id));
}
}
// 2. 业务 Service 层
@Service
@RequiredArgsConstructor
public class CustomerServiceImpl implements CustomerService {
private final CustomerDataService customerDataService;
@Override
public CustomerVO getCustomerById(String id) {
Customer customer = customerDataService.getById(id);
if (customer == null) {
throw new BusinessException("客户不存在");
}
return BeanUtil.copyProperties(customer, CustomerVO.class);
}
}
// 3. 数据 Service 层
public interface CustomerDataService extends IService {
// 可添加自定义数据访问方法
List listByStatus(Integer status);
}
@Service
public class CustomerDataServiceImpl
extends ServiceImpl
implements CustomerDataService {
@Override
public List listByStatus(Integer status) {
return lambdaQuery()
.eq(Customer::getStatus, status)
.list();
}
}
// 4. Mapper 层
public interface CustomerMapper extends BaseMapper {
// 复杂SQL可在此定义
}
```
---
### 4. API 设计规范
#### 4.1 服务间调用规范
**强制要求**:服务模块(对外提供 API 服务)之间的调用必须采用 OpenFeign 进行,禁止以下方式:
- ❌ 禁止通过 RestTemplate 直接调用其他服务
- ❌ 禁止通过 HttpClient 等工具类直接调用其他服务
- ❌ 禁止通过共享数据库方式进行服务间通信
**OpenFeign 调用规范**:
1. **定义 Feign 客户端接口**:
```java
@FeignClient(name = "service-customer", path = "/cust/api/v1")
public interface CustomerFeignClient {
@GetMapping("/customer/{id}")
Result getCustomer(@PathVariable("id") String id);
@PostMapping("/customer")
Result createCustomer(@RequestBody CustomerCreateDTO dto);
}
```
2. **服务提供方职责**:
- 确保接口遵循 RESTful 规范
- 返回统一的 `Result` 格式
- 接口文档及时更新(推荐使用 Swagger/OpenAPI)
3. **服务调用方职责**:
- 通过 `@Autowired` 或 `@RequiredArgsConstructor` 注入 Feign 客户端
- 做好异常处理和降级策略(可选配置 Fallback)
- 避免循环调用和过深的调用链
4. **Feign 配置建议**:
```yaml
feign:
client:
config:
default:
connectTimeout: 5000 # 连接超时 5 秒
readTimeout: 10000 # 读取超时 10 秒
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
response:
enabled: true
```
5. **示例:服务间调用**:
```java
@Service
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final CustomerFeignClient customerFeignClient;
@Override
public OrderVO createOrder(OrderCreateDTO dto) {
// 通过 Feign 调用客户服务获取客户信息
Result customerResult = customerFeignClient.getCustomer(dto.getCustomerId());
if (!customerResult.isSuccess()) {
throw new BusinessException("客户信息获取失败:" + customerResult.getMessage());
}
CustomerVO customer = customerResult.getData();
// 业务逻辑处理...
return null;
}
}
```
**优势说明**:
- 统一服务调用方式,便于管理和监控
- 自动集成负载均衡(结合注册中心)
- 支持熔断降级(结合 Sentinel 或 Hystrix)
- 便于链路追踪(自动传递 TraceId、SpanId 等)
- 接口调用类型安全,编译期检查
#### 4.2 URL 设计
RESTful 风格接口推荐使用统一的 URL 模式:
```text
# 基础格式
/{模块}/api/v1/{资源}/{动作}
# 示例
POST /sys/api/v1/auth/login # 登录
GET /cust/api/v1/customer/{id} # 查询客户
POST /cust/api/v1/customer # 创建客户
PUT /cust/api/v1/customer/{id} # 更新客户
DELETE /cust/api/v1/customer/{id} # 删除客户
GET /cust/api/v1/customer/page # 分页查询
```
#### 4.3 HTTP 方法语义
| 方法 | 用途 | 示例 |
|--------|------|--------------------------|
| GET | 查询 | 查询列表、详情 |
| POST | 创建 | 新增资源 |
| PUT | 更新 | 修改资源信息 |
| DELETE | 删除 | 删除资源 |
#### 4.4 统一响应格式
后端服务建议统一使用泛型包装类 `Result`:
```json
{
"code": 200,
"message": "success",
"data": { "...": "..." },
"success": true
}
```
错误响应示例:
```json
{
"code": 400,
"message": "参数错误:xxx 不能为空",
"data": null,
"success": false
}
```
#### 4.5 分页数据格式
```json
{
"code": 200,
"message": "success",
"data": {
"pageNum": 1,
"pageSize": 10,
"total": 100,
"list": []
},
"success": true
}
```
#### 4.6 状态码规范
| 状态码 | 说明 |
|--------|----------------|
| 200 | 成功 |
| 400 | 参数错误 |
| 401 | 未授权 / Token 无效 |
| 403 | 权限不足 |
| 404 | 资源不存在 |
| 500 | 服务器内部错误 |
#### 4.7 前后端 API 管理
前端项目应采用**独立目录或文件集中管理**与后端交互的 API,避免在页面组件中硬编码 URL:
```text
src/api/
├── index.ts # API 定义入口,统一导出
├── request.ts # Axios 实例配置、拦截器
└── modules/ # 按业务模块拆分(可选)
├── auth.ts # 认证相关 API
├── customer.ts # 客户管理 API
└── expense.ts # 其它业务 API
```
**规范要求:**
- **禁止硬编码**:组件中禁止直接调用 `request.get('/xxx/xxx')`。
- **统一入口**:所有 API 函数从 `@/api` 统一导出,按模块分组。
- **路径简化**:API 内部使用简化路径,由 `baseURL` 或拦截器统一添加前缀。
- **便于维护**:后端接口变更时,只需修改 API 定义位置即可。
---
### 5. 链路追踪规范
#### 5.1 基本概念
- **TraceId**:全链路唯一标识,一次请求从入口到各个下游服务共享同一个 TraceId。
- **SpanId**:链路中单个服务或调用节点的唯一标识,用于区分各个调用段。
#### 5.2 日志格式
**强制要求**:所有日志必须包含 TraceId 和 SpanId,便于进行日志跟踪和问题排查。
推荐在日志格式中统一输出 TraceId 与 SpanId:
```text
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId:-}][%X{spanId:-}] %-5level %logger{50} - %msg%n
```
日志示例:
```text
2026-02-20 18:10:25.240 [http-nio-8100-exec-1] [a1b2c3d4e5f6...][1234567890abcdef] INFO c.c.p.controller.CustomerController - 查询客户列表
```
**配置要点**:
- 使用 `%X{traceId:-}` 和 `%X{spanId:-}` 从 MDC(Mapped Diagnostic Context)中提取追踪信息。
- `-` 作为默认值,当上下文中不存在追踪信息时显示。
- 需配合拦截器或过滤器在请求入口将 TraceId 和 SpanId 放入 MDC。
#### 5.3 HTTP Header 透传
| Header | 说明 |
|------------------|----------------|
| `X-Trace-Id` | 全链路追踪 ID |
| `X-Span-Id` | 当前服务 Span |
| `X-Source-Service` | 来源服务名 |
| `X-Request-Time` | 请求发起时间戳 |
| `X-User-Id` | 用户 ID |
| `X-Username` | 用户名 |
#### 5.4 响应头返回
服务在响应中也应返回追踪信息,便于排查问题:
```text
X-Trace-Id: a1b2c3d4e5f67890a1b2c3d4e5f67890
X-Span-Id: 1234567890abcdef
```
---
### 6. Token 认证规范
#### 6.1 Token 机制
可采用 **随机 Token + 缓存(如 Redis)** 的会话机制(非 JWT):
```java
// 生成 Token
String token = tokenService.generateToken(userId, username, tenantId);
// Token 存储到 Redis
Key: auth:token:{token}
Value: TokenInfo(userId, username, tenantId, expireTime)
TTL: 24 小时(或配置项)
```
#### 6.2 请求认证
```text
Authorization: Bearer {token}
```
#### 6.3 白名单路径
可将登录、健康检查等接口配置为无需 Token 验证,例如:
```text
/sys/api/v1/auth/login
/sys/api/v1/sys/health
```
---
### 7. 配置与部署规范
#### 7.1 配置文件分离架构
推荐采用「统一配置 + 服务个性化配置」分离的方式:
```text
project-root/
├── scripts/
│ └── env.properties # 统一配置(所有服务共用)
├── service-a/src/main/resources/
│ └── service.properties # 个性化配置(单服务独立)
├── service-b/src/main/resources/
│ └── service.properties
└── ... 其他服务
```
- `env.properties`:环境级通用配置(注册中心、缓存、限流、文件上传、日志等)。
- 各服务 `service.properties`:服务名、实例名、多租户路由、端口等个性化配置。
统一配置示例:
```properties
# ==================== 数据库配置 ====================
DB_HOST=localhost
DB_PORT=3306
DB_NAME=worklog
DB_USERNAME=worklog
DB_PASSWORD=Wlog@123
DB_URL=jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
# 连接池配置
DB_POOL_MIN_IDLE=5
DB_POOL_MAX_SIZE=20
DB_POOL_CONNECTION_TIMEOUT=30000
DB_POOL_IDLE_TIMEOUT=600000
DB_POOL_MAX_LIFETIME=1800000
# ==================== Redis 配置 ====================
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=zjf@123456
REDIS_DATABASE=0
REDIS_TIMEOUT=5000
# Redis 连接池配置
REDIS_POOL_MAX_ACTIVE=8
REDIS_POOL_MAX_WAIT=-1
REDIS_POOL_MAX_IDLE=8
REDIS_POOL_MIN_IDLE=0
# ==================== Nacos 配置 ====================
NACOS_SERVER_ADDR=localhost:8848
NACOS_NAMESPACE=worklog-dev
NACOS_GROUP=DEFAULT_GROUP
NACOS_USERNAME=nacos
NACOS_PASSWORD=nacos
# ==================== 文件上传配置 ====================
FILE_UPLOAD_MAX_SIZE=50MB
FILE_UPLOAD_MAX_REQUEST_SIZE=100MB
FILE_STORAGE_PATH=./uploads
# ==================== 日志配置 ====================
# 日志路径(扁平目录结构,无子目录)
LOG_PATH=./logs
# 日志级别
LOG_LEVEL_ROOT=INFO
LOG_LEVEL_APP=DEBUG
# 日志文件配置
LOG_FILE_MAX_SIZE=100MB
LOG_FILE_MAX_HISTORY=30
# ==================== JVM 配置 ====================
JVM_XMS=512m
JVM_XMX=1024m
JVM_METASPACE_SIZE=128m
JVM_MAX_METASPACE_SIZE=256m
# GC 配置
JVM_GC_TYPE=G1GC
JVM_MAX_GC_PAUSE_MILLIS=200
# ==================== 业务配置 ====================
# Token 配置
TOKEN_EXPIRE_TIME=86400
TOKEN_PREFIX=auth:token:
# 日志内容限制
WORKLOG_MAX_CONTENT_LENGTH=2000
# 允许的上传文件扩展名
UPLOAD_ALLOWED_EXTENSIONS=jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx
```
个性化配置示例:
```properties
# 服务名称
APP_NAME=service-sys
# 实例名称(多租户场景使用,默认与 APP_NAME 相同)
INSTANCE_NAME=${APP_NAME}
# 租户标识(多租户场景使用,用于路由)
TENANT_ID=
# ==================== 服务端口配置 ====================
# 服务端口(可覆盖 application.yml 中配置)
SERVER_PORT=8080
# ==================== 环境标识 ====================
# 运行环境:dev-开发, test-测试, prod-生产
SPRING_PROFILES_ACTIVE=prod
# ==================== 个性化覆盖配置(可选) ====================
# 如果当前服务需要使用不同的日志路径,可在此覆盖
# LOG_PATH=/var/logs/worklog-api
# 如果当前服务需要使用不同的日志级别,可在此覆盖
# LOG_LEVEL_APP=INFO
```
#### 7.2 配置加载顺序
启动脚本中建议按以下顺序加载配置(后加载覆盖先加载):
```bash
# 1. 加载统一配置
load_properties "${APP_HOME}/conf/env.properties"
# 2. 加载个性化配置(覆盖同名参数)
load_properties "${APP_HOME}/conf/service.properties"
```
#### 7.3 打包目录结构
通过打包插件(如 Maven Assembly)时,推荐输出统一的目录结构:
```text
service-sys/ # 服务根目录
├── bin/ # 脚本目录
│ ├── start.sh # 启动脚本
│ ├── stop.sh # 停止脚本
│ ├── restart.sh # 重启脚本
│ └── status.sh # 状态查看脚本
├── lib/ # 依赖 JAR 目录
│ └── service-sys.jar # 服务 JAR 包
└── conf/ # 配置文件目录
├── env.properties # 统一配置
├── service.properties # 个性化配置
├── application.yml # 主配置
├── bootstrap.yml # 启动配置
└── logback-spring.xml # 日志配置
```
#### 7.4 日志配置集中化
**强制要求**:
1. **日志文件命名**:
- 主日志文件统一命名为 `app.log`。
- 所有日志文件(包括不同级别)统一输出到 `logs/` 目录下,不再创建子目录。
2. **日志目录结构**:
```text
logs/
├── app.log # 应用主日志(所有级别)
├── error.log # ERROR 级别日志(可选)
├── sql.log # MyBatis-Plus SQL 日志(强制)
└── aop.log # API 请求日志(可选)
```
日志管理建议统一由 `logback-spring.xml` 等日志框架配置文件控制,从环境变量读取关键参数:
```xml
${LOG_PATH}/app.log
${LOG_PATTERN}
${LOG_PATH}/app-%d{yyyy-MM-dd}.%i.log
100MB
30
${LOG_PATH}/error.log
ERROR
ACCEPT
DENY
${LOG_PATTERN}
${LOG_PATH}/error-%d{yyyy-MM-dd}.%i.log
100MB
30
${LOG_PATH}/sql.log
${LOG_PATTERN}
${LOG_PATH}/sql-%d{yyyy-MM-dd}.%i.log
100MB
30
```
**SQL 日志配置说明**:
1. **独立文件**:MyBatis-Plus 的 SQL 日志单独输出到 `sql.log`,便于 SQL 分析和问题排查。
2. **日志级别**:SQL 日志级别设置为 `DEBUG`,确保能记录完整的 SQL 语句和参数。
3. **包名配置**:
- `com.baomidou.mybatisplus`:MyBatis-Plus 框架的 SQL 日志
- `com.wjbl.project.*.data.mapper`:项目 Mapper 的 SQL 日志(需根据实际包名调整)
4. **additivity="false"**:防止 SQL 日志重复输出到主日志文件。
5. **CONSOLE 输出**:开发环境可以同时输出到控制台,生产环境可移除。
与应用配置文件的职责划分建议为:**业务配置写在 application.yml,日志输出规则集中在 logback-spring.xml 管理**。
#### 7.5 Redis 与注册中心配置示例
Redis 配置(示例):
```yaml
spring:
data:
redis:
host: localhost
port: 6379
password: your-password
database: 0
```
注册中心配置(以 Nacos 为例):
```yaml
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: project-namespace
group: DEFAULT_GROUP
username: nacos
password: nacos
```
---
### 8. 多租户架构规范(可选)
> **说明**:本章节内容适用于需要支持多租户的项目。如果项目不涉及多租户场景,可跳过本章节。
#### 8.1 数据库多租户字段
支持多租户的业务表必须增加 `tenant_id` 字段:
```sql
tenant_id VARCHAR(12) NOT NULL COMMENT '租户ID'
```
**字段说明**:
- 数据类型:`VARCHAR(12)` 字符串类型
- 长度限制:12 个字符
- 非空约束:必填字段
#### 8.2 租户 ID 传递
多租户系统中,推荐采用以下方式透传租户信息:
1. **前端请求**:通过 HTTP Header 传递 `X-Tenant-Id`。
2. **网关层**:网关过滤器提取并透传租户 ID。
3. **后端服务**:使用 `TenantContextHolder` 或等价机制将租户 ID 存入上下文(如 ThreadLocal)。
4. **数据访问层**:ORM / MyBatis 拦截器自动注入 `tenant_id` 条件,实现数据隔离。
#### 8.3 租户字段自动填充
```java
// 实体类字段 - 使用 String 类型
@TableField(fill = FieldFill.INSERT)
private String tenantId; // 注意:使用 String 类型,长度最大 12 字符
// 元数据填充处理
public class TenantMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "tenantId", String.class,
TenantContextHolder.getTenantId());
}
}
```
#### 8.4 租户上下文操作
```java
// 获取当前租户ID(返回 String 类型)
String tenantId = TenantContextHolder.getTenantId();
// 设置租户ID(通常由拦截器自动设置)
TenantContextHolder.setTenantId(tenantId);
// 清除上下文(请求结束时)
TenantContextHolder.clear();
```
**注意事项**:
- 租户 ID 统一使用 String 类型,最大长度 12 字符。
- TenantContextHolder 需要对应调整为存储 String 类型。
#### 8.5 HTTP Header 透传
多租户场景下,需要在链路追踪 Header 基础上增加租户信息:
| Header | 说明 |
|------------------|----------------|
| `X-Tenant-Id` | 租户 ID |
#### 8.6 多租户部署结构
多实例多租户部署时,可通过目录和配置分离不同实例:
```text
/opt/project/deploy/
├── service-sys/ # 共享实例
│ ├── bin/
│ ├── lib/
│ └── conf/
│ └── service.properties # INSTANCE_NAME=service-sys, TENANT_ID=
├── service-sys-vip001/ # VIP 实例 1
│ ├── bin/
│ ├── lib/
│ └── conf/
│ └── service.properties # INSTANCE_NAME=service-sys-vip001, TENANT_ID=vip001
└── service-sys-vip002/ # VIP 实例 2
└── ...
```
**个性化配置示例(多租户场景)**:
```properties
# 服务名称
APP_NAME=service-sys
# 实例名称(多租户场景使用,默认与 APP_NAME 相同)
INSTANCE_NAME=service-sys-vip001
# 租户标识(多租户场景使用,用于路由)
TENANT_ID=vip001
# 服务端口(可覆盖 application.yml 中配置)
# SERVER_PORT=8100
```
---
### 9. 常见架构问题与处理建议
#### 9.1 Bean 名称冲突
多模块项目中,配置类命名需避免冲突:
```java
// 不推荐:多个模块都存在同名配置类
@Configuration
public class RedisConfig { }
// 推荐:添加模块前缀或语义前缀
@Configuration
public class GatewayRedisConfig { } // 网关专用配置
```
#### 9.2 WebFlux 与 Servlet 冲突
基于 WebFlux 的网关或服务不能使用 Servlet API:
```java
// 错误:在 WebFlux 项目中使用 Servlet API
public class TokenAuthFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response) { }
}
// 正确:WebFlux 环境下使用 Reactive API
public class TokenAuthFilter implements GlobalFilter {
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { }
}
```
#### 9.3 注册中心服务发现失败
使用注册中心(如 Nacos)时,应确保依赖与配置完整,例如:
```xml
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
```
并检查:
- 注册中心地址、命名空间、分组等配置是否正确;
- 服务名是否唯一;
- 客户端网络是否能访问注册中心。
---
## 十、开发规范
### 10.1 命名规范
#### 10.1.1 包命名
- **全部小写**。
- 建议格式:`com.{公司或组织}.{项目或域}.{模块}`。
示例:
```text
com.wjbl.project.customer.controller
com.wjbl.project.customer.service
com.wjbl.project.customer.service.impl
com.wjbl.project.customer.data.mapper
com.wjbl.project.customer.data.entity
com.wjbl.project.customer.dto
com.wjbl.project.customer.vo
```
#### 10.1.2 类命名
| 类型 | 命名规则 | 示例 |
|------------|-----------------------|---------------------------------------|
| 实体类 | 名词,PascalCase | `Customer`, `Project` |
| Controller | 名词 + Controller | `CustomerController` |
| Service 接口 | 名词 + Service | `CustomerService` |
| Service 实现 | 名词 + ServiceImpl | `CustomerServiceImpl` |
| DataService 接口 | 名词 + DataService | `CustomerDataService` |
| DataService 实现 | 名词 + DataServiceImpl | `CustomerDataServiceImpl` |
| Mapper | 名词 + Mapper | `CustomerMapper` |
| DTO | 动作 + 名词 + DTO | `CustomerCreateDTO`, `CustomerUpdateDTO` |
| VO | 名词 + VO | `CustomerVO`, `ProjectVO` |
| 配置类 | 功能 + Config | `RedisConfig`, `MybatisConfig` |
#### 10.1.3 方法命名
| 操作 | Service 方法 | Controller 方法 |
|------------|----------------------|-----------------|
| 创建 | `createXxx()` | `createXxx()` |
| 更新 | `updateXxx()` | `updateXxx()` |
| 删除 | `deleteXxx()` | `deleteXxx()` |
| 查询单个 | `getXxxById()` | `getXxx()` |
| 分页查询 | `pageXxxs()` | `pageXxxs()` |
| 列表查询 | `listXxxs()` | `listXxxs()` |
#### 10.1.4 数据库命名
- **表名**:小写,下划线分隔,如 `customer`, `project_member`。
- **字段名**:小写,下划线分隔,如 `customer_name`, `created_time`。
- **索引名**:`idx_表名_字段名`,如 `idx_customer_status`。
- **主键名**:`表名_id`,如 `customer_id`(数据库中使用字符串类型存储)。
---
### 10.2 Git 分支与提交规范
#### 10.2.1 分支命名
推荐的分支模型:
```text
master # 主分支(稳定版本)
develop # 开发分支(集成测试)
feature/xxx # 功能分支
bugfix/xxx # Bug 修复分支
hotfix/xxx # 紧急线上修复分支
```
- 新功能从 `develop` 或 `master` 拉取对应的 `feature/*` 分支。
- Bug 修复从对应问题所在分支拉取 `bugfix/*` 或 `hotfix/*` 分支。
#### 10.2.2 提交信息格式
推荐使用简洁且结构化的提交信息格式:
```text
:
```
**type 类型约定**:
| 类型 | 说明 |
|---------|----------------|
| feat | 新功能 |
| fix | Bug 修复 |
| docs | 文档更新 |
| style | 代码格式调整 |
| refactor| 重构 |
| test | 测试相关 |
| chore | 构建/工具相关 |
**示例 1:功能提交**
```text
feat: 新增客户管理分页查询接口
- 支持按客户名称模糊查询
- 支持按状态筛选
- 完成分页参数校验
```
**示例 2:缺陷修复**
```text
fix: 修复路由配置错误
xxx 服务路由路径配置错误,修正为 /cust/**
```
**要求**:
- **subject** 尽量 50 字以内,描述清晰、可读。
- **body** 用于说明设计思路、变更范围、注意事项等,可选但推荐完整编写。
---
### 10.3 代码分层与编写规范
#### 10.3.1 Controller 层
Controller 仅负责:
- 请求参数接收与基础校验(可结合 `@Valid`)。
- 调用 Service 层完成业务处理。
- 将结果封装为统一返回对象输出。
示例:
```java
@RestController
@RequestMapping("/api/v1/customer")
@RequiredArgsConstructor
public class CustomerController {
private final CustomerService customerService;
@PostMapping
public Result createCustomer(@Valid @RequestBody CustomerCreateDTO dto) {
String id = customerService.createCustomer(dto);
return Result.success(id);
}
@GetMapping("/page")
public Result> pageCustomers(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String keyword) {
Page page = customerService.pageCustomers(pageNum, pageSize, keyword);
return Result.success(PageResult.of(page));
}
}
```
#### 10.3.2 Service 层
Service 层聚焦业务逻辑,典型步骤:
1. 参数及前置条件校验。
2. 业务规则处理。
3. 调用 DataService 进行数据操作。
4. 组装并返回结果。
示例:
```java
@Service
@RequiredArgsConstructor
public class CustomerServiceImpl implements CustomerService {
private final CustomerDataService customerDataService;
@Override
@Transactional(rollbackFor = Exception.class)
public String createCustomer(CustomerCreateDTO dto) {
// 1. 参数校验
// 2. 业务逻辑
// 3. 通过 DataService 进行数据持久化
// 4. 返回结果
return null;
}
}
```
**重要**:业务 Service 必须通过 DataService 访问数据,禁止直接注入 Mapper。
#### 10.3.3 DTO / VO 规范
- **DTO(Data Transfer Object)**:用于接收请求参数或封装服务间调用参数,只包含与接口输入相关的字段。
- **VO(View Object)**:用于接口响应展示,只包含与前端展示相关的字段。
- 使用校验注解约束 DTO 字段,确保基本数据正确性。
示例:
```java
@Data
public class CustomerCreateDTO {
@NotBlank(message = "客户名称不能为空")
@Size(max = 200, message = "客户名称最长 200 字符")
private String customerName;
@NotBlank(message = "联系人不能为空")
private String contact;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
}
```
---
### 10.4 日志使用规范
#### 10.4.1 日志级别
| 级别 | 使用场景 |
|-------|--------------------------------------|
| ERROR | 异常、错误、需要立即处理的问题 |
| WARN | 警告、潜在问题 |
| INFO | 关键业务流程、重要操作 |
| DEBUG | 调试信息、详细流程(仅在调试环境启用)|
**要求**:
- 线上默认不开启 DEBUG,避免大量无效日志。
- 禁止在日志中输出敏感信息(密码、密钥、完整身份证号等)。
- **所有日志必须包含 traceId 和 spanId**,便于链路追踪。
#### 10.4.2 API 请求日志
推荐通过 AOP 统一记录 API 请求日志,内容包括:
- 请求路径、HTTP 方法。
- 请求参数(适当脱敏)。
- 响应时间。
- 结果状态(成功/失败)。
示例切面:
```java
@Around("execution(* com.wjbl.project..controller..*(..))")
public Object logApiRequest(ProceedingJoinPoint joinPoint) {
// 记录:请求方法、参数、响应时间、结果状态
// 日志中必须包含 traceId 和 spanId
return null;
}
```
---
### 10.5 异常处理规范
#### 10.5.1 统一异常处理
建议在网关或应用入口层统一捕获并处理异常,输出统一格式的错误响应:
```java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result handleBusinessException(BusinessException e) {
return Result.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
log.error("系统异常", e);
return Result.error(500, "系统异常,请稍后重试");
}
}
```
#### 10.5.2 业务异常定义
业务异常类用于表达可预期的业务错误,并携带错误码和错误信息:
```java
public class BusinessException extends RuntimeException {
private final int code;
public BusinessException(String message) {
super(message);
this.code = 400;
}
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
}
```
#### 10.5.3 业务代码中使用规范
- 对于**参数错误、状态不合法**等可预期问题,直接抛出 `BusinessException`,携带明确的提示信息。
- 对于**系统级异常**(如网络、IO、第三方服务异常),在适当位置记录详细日志并抛出统一异常,让全局处理器输出通用提示。
示例:
```java
// 参数校验
if (customerName == null || customerName.isEmpty()) {
throw new BusinessException("客户名称不能为空");
}
// 业务校验
Customer customer = customerDataService.getById(id);
if (customer == null) {
throw new BusinessException(404, "客户不存在");
}
```
---
### 10.6 规范落地建议
为确保上述规范真正落地,建议在团队内按以下步骤推进:
1. **纳入项目模板**:
- 将本架构设计规范纳入项目脚手架或模板仓库。
2. **代码评审检查**:
- 在 Code Review 中重点检查命名、分层、日志、异常处理是否符合规范。
3. **自动化校验**:
- 结合静态检查工具、提交钩子等,对提交信息格式、基础代码规范进行自动化校验。
4. **文档持续维护**:
- 根据实际项目经验定期补充常见错误案例和最佳实践,将实践沉淀回本规范文档中。