阶段二:认证授权模块 - User实体类、Mapper、DataService - Token服务(Redis存储)、密码加密(BCrypt) - 认证拦截器、UserContext上下文 - 登录/登出接口 阶段三:核心业务模块 - 用户管理:CRUD、状态管理、密码重置 - 模板管理:CRUD、状态管理 - 工作日志:CRUD、权限控制 配置分离架构 - env.properties(环境敏感配置) - service.properties(服务配置) - logback-spring.xml更新 部署脚本 - deploy/目录(Nginx配置、启停脚本、备份脚本) 单元测试:29个测试全部通过
40 KiB
架构设计规范
本文档基于参考文档
参考Agents.md中的内容,抽取并整理出与具体业务项目无关的通用架构设计约定,可复用到其它类似的微服务项目中。
1. 后端模块结构规范
1.1 服务模块目录结构
建议单体或微服务项目的后端服务模块遵循统一的目录结构,便于团队协作和代码治理:
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 模块,供各业务服务复用:
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 包方式使用。
<!-- 在业务服务的 pom.xml 中引入基础模块 -->
<dependency>
<groupId>com.wjbl.project</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
优势说明:
- 统一版本管理,避免基础代码不一致。
- 便于基础能力的统一升级和维护。
- 支持 Maven 的传递依赖管理。
- 确保各业务服务使用的基础模块版本一致。
1.3 启动类与组件扫描
所有业务服务建议显式配置组件扫描路径,以确保能扫描到业务模块和公共模块:
@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 审计字段
所有业务表建议统一包含以下审计字段,用于记录数据的创建与修改信息:
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 主键策略
强制要求:
-
数据库主键字段类型:
- 使用
VARCHAR(20)或CHAR(19)字符串类型存储主键。 - 存储内容为雪花算法(Snowflake)生成的 19 位数字 ID。
- 使用
-
实体类主键配置:
@TableId(type = IdType.ASSIGN_ID) private String id; // 注意:使用 String 类型 -
雪花算法配置:
- 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 目录下,实现数据访问与业务逻辑的明确分离:
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 数据服务命名规范
强制要求:
-
数据服务接口:命名为
XxxDataService,继承IService<Entity>public interface CustomerDataService extends IService<Customer> { // 数据访问相关方法 } -
数据服务实现:命名为
XxxDataServiceImpl,继承ServiceImpl<Mapper, Entity>@Service public class CustomerDataServiceImpl extends ServiceImpl<CustomerMapper, Customer> implements CustomerDataService { // 数据访问实现 } -
命名原因:
- 避免与业务 Service(如
CustomerService)产生命名冲突。 - 清晰区分数据访问层(DataService)和业务逻辑层(Service)。
- 符合单一职责原则,DataService 只负责数据的 CRUD 操作。
- 避免与业务 Service(如
3.3 业务服务与数据服务的协作规范
强制要求:
-
业务 Service 必须通过 DataService 访问数据:
@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); } } -
禁止业务 Service 直接注入 Mapper:
// ❌ 错误:业务层直接使用 Mapper @Service public class CustomerServiceImpl implements CustomerService { @Autowired private CustomerMapper customerMapper; // 不允许 } -
DataService 职责:
- 封装所有数据库查询和操作。
- 提供基础的 CRUD 方法(继承自 MyBatis-Plus 的 IService)。
- 提供复杂查询的封装方法(如多表关联、统计等)。
-
业务 Service 职责:
- 实现业务逻辑和流程控制。
- 调用一个或多个 DataService 完成数据操作。
- 处理 DTO 到实体、实体到 VO 的转换。
- 处理事务管理(@Transactional)。
3.4 分层示例
完整的分层调用示例:
// 1. Controller 层
@RestController
@RequestMapping("/api/v1/customer")
@RequiredArgsConstructor
public class CustomerController {
private final CustomerService customerService;
@GetMapping("/{id}")
public Result<CustomerVO> 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<Customer> {
// 可添加自定义数据访问方法
List<Customer> listByStatus(Integer status);
}
@Service
public class CustomerDataServiceImpl
extends ServiceImpl<CustomerMapper, Customer>
implements CustomerDataService {
@Override
public List<Customer> listByStatus(Integer status) {
return lambdaQuery()
.eq(Customer::getStatus, status)
.list();
}
}
// 4. Mapper 层
public interface CustomerMapper extends BaseMapper<Customer> {
// 复杂SQL可在此定义
}
4. API 设计规范
4.1 服务间调用规范
强制要求:服务模块(对外提供 API 服务)之间的调用必须采用 OpenFeign 进行,禁止以下方式:
- ❌ 禁止通过 RestTemplate 直接调用其他服务
- ❌ 禁止通过 HttpClient 等工具类直接调用其他服务
- ❌ 禁止通过共享数据库方式进行服务间通信
OpenFeign 调用规范:
- 定义 Feign 客户端接口:
@FeignClient(name = "service-customer", path = "/cust/api/v1")
public interface CustomerFeignClient {
@GetMapping("/customer/{id}")
Result<CustomerVO> getCustomer(@PathVariable("id") String id);
@PostMapping("/customer")
Result<String> createCustomer(@RequestBody CustomerCreateDTO dto);
}
-
服务提供方职责:
- 确保接口遵循 RESTful 规范
- 返回统一的
Result<T>格式 - 接口文档及时更新(推荐使用 Swagger/OpenAPI)
-
服务调用方职责:
- 通过
@Autowired或@RequiredArgsConstructor注入 Feign 客户端 - 做好异常处理和降级策略(可选配置 Fallback)
- 避免循环调用和过深的调用链
- 通过
-
Feign 配置建议:
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
- 示例:服务间调用:
@Service
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final CustomerFeignClient customerFeignClient;
@Override
public OrderVO createOrder(OrderCreateDTO dto) {
// 通过 Feign 调用客户服务获取客户信息
Result<CustomerVO> 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 模式:
# 基础格式
/{模块}/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<T>:
{
"code": 200,
"message": "success",
"data": { "...": "..." },
"success": true
}
错误响应示例:
{
"code": 400,
"message": "参数错误:xxx 不能为空",
"data": null,
"success": false
}
4.5 分页数据格式
{
"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:
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:
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId:-}][%X{spanId:-}] %-5level %logger{50} - %msg%n
日志示例:
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 响应头返回
服务在响应中也应返回追踪信息,便于排查问题:
X-Trace-Id: a1b2c3d4e5f67890a1b2c3d4e5f67890
X-Span-Id: 1234567890abcdef
6. Token 认证规范
6.1 Token 机制
可采用 随机 Token + 缓存(如 Redis) 的会话机制(非 JWT):
// 生成 Token
String token = tokenService.generateToken(userId, username, tenantId);
// Token 存储到 Redis
Key: auth:token:{token}
Value: TokenInfo(userId, username, tenantId, expireTime)
TTL: 24 小时(或配置项)
6.2 请求认证
Authorization: Bearer {token}
6.3 白名单路径
可将登录、健康检查等接口配置为无需 Token 验证,例如:
/sys/api/v1/auth/login
/sys/api/v1/sys/health
7. 配置与部署规范
7.1 配置文件分离架构
推荐采用「统一配置 + 服务个性化配置」分离的方式:
project-root/
├── scripts/
│ └── env.properties # 统一配置(所有服务共用)
├── service-a/src/main/resources/
│ └── service.properties # 个性化配置(单服务独立)
├── service-b/src/main/resources/
│ └── service.properties
└── ... 其他服务
env.properties:环境级通用配置(注册中心、缓存、限流、文件上传、日志等)。- 各服务
service.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
个性化配置示例:
# 服务名称
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 配置加载顺序
启动脚本中建议按以下顺序加载配置(后加载覆盖先加载):
# 1. 加载统一配置
load_properties "${APP_HOME}/conf/env.properties"
# 2. 加载个性化配置(覆盖同名参数)
load_properties "${APP_HOME}/conf/service.properties"
7.3 打包目录结构
通过打包插件(如 Maven Assembly)时,推荐输出统一的目录结构:
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 日志配置集中化
强制要求:
-
日志文件命名:
- 主日志文件统一命名为
app.log。 - 所有日志文件(包括不同级别)统一输出到
logs/目录下,不再创建子目录。
- 主日志文件统一命名为
-
日志目录结构:
logs/ ├── app.log # 应用主日志(所有级别) ├── error.log # ERROR 级别日志(可选) ├── sql.log # MyBatis-Plus SQL 日志(强制) └── aop.log # API 请求日志(可选)
日志管理建议统一由 logback-spring.xml 等日志框架配置文件控制,从环境变量读取关键参数:
<!-- 从环境变量读取配置 -->
<springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
<springProperty scope="context" name="LOG_PATH" source="logging.file.path" defaultValue="/var/logs/apps"/>
<springProperty scope="context" name="LOG_LEVEL_ROOT" source="logging.level.root" defaultValue="INFO"/>
<springProperty scope="context" name="LOG_LEVEL_APP" source="logging.level.app" defaultValue="DEBUG"/>
<!-- 日志格式:强制包含 traceId 和 spanId -->
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId:-}][%X{spanId:-}] %-5level %logger{50} - %msg%n"/>
<!-- 主日志文件 appender - 直接输出到 logs/ 目录 -->
<appender name="FILE_APP" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/app.log</file>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/app-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<!-- ERROR 日志文件 appender(可选)- 直接输出到 logs/ 目录 -->
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/error.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<!-- SQL 日志文件 appender(强制)- MyBatis-Plus SQL 日志独立输出 -->
<appender name="FILE_SQL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/sql.log</file>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/sql-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<!-- MyBatis-Plus SQL 日志配置 -->
<logger name="com.baomidou.mybatisplus" level="DEBUG" additivity="false">
<appender-ref ref="FILE_SQL"/>
<appender-ref ref="CONSOLE"/>
</logger>
<!-- 项目 Mapper 日志配置 - 根据实际包名调整 -->
<logger name="com.wjbl.project.*.data.mapper" level="DEBUG" additivity="false">
<appender-ref ref="FILE_SQL"/>
<appender-ref ref="CONSOLE"/>
</logger>
SQL 日志配置说明:
- 独立文件:MyBatis-Plus 的 SQL 日志单独输出到
sql.log,便于 SQL 分析和问题排查。 - 日志级别:SQL 日志级别设置为
DEBUG,确保能记录完整的 SQL 语句和参数。 - 包名配置:
com.baomidou.mybatisplus:MyBatis-Plus 框架的 SQL 日志com.wjbl.project.*.data.mapper:项目 Mapper 的 SQL 日志(需根据实际包名调整)
- additivity="false":防止 SQL 日志重复输出到主日志文件。
- CONSOLE 输出:开发环境可以同时输出到控制台,生产环境可移除。
与应用配置文件的职责划分建议为:业务配置写在 application.yml,日志输出规则集中在 logback-spring.xml 管理。
7.5 Redis 与注册中心配置示例
Redis 配置(示例):
spring:
data:
redis:
host: localhost
port: 6379
password: your-password
database: 0
注册中心配置(以 Nacos 为例):
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: project-namespace
group: DEFAULT_GROUP
username: nacos
password: nacos
8. 多租户架构规范(可选)
说明:本章节内容适用于需要支持多租户的项目。如果项目不涉及多租户场景,可跳过本章节。
8.1 数据库多租户字段
支持多租户的业务表必须增加 tenant_id 字段:
tenant_id VARCHAR(12) NOT NULL COMMENT '租户ID'
字段说明:
- 数据类型:
VARCHAR(12)字符串类型 - 长度限制:12 个字符
- 非空约束:必填字段
8.2 租户 ID 传递
多租户系统中,推荐采用以下方式透传租户信息:
- 前端请求:通过 HTTP Header 传递
X-Tenant-Id。 - 网关层:网关过滤器提取并透传租户 ID。
- 后端服务:使用
TenantContextHolder或等价机制将租户 ID 存入上下文(如 ThreadLocal)。 - 数据访问层:ORM / MyBatis 拦截器自动注入
tenant_id条件,实现数据隔离。
8.3 租户字段自动填充
// 实体类字段 - 使用 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 租户上下文操作
// 获取当前租户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 多租户部署结构
多实例多租户部署时,可通过目录和配置分离不同实例:
/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
└── ...
个性化配置示例(多租户场景):
# 服务名称
APP_NAME=service-sys
# 实例名称(多租户场景使用,默认与 APP_NAME 相同)
INSTANCE_NAME=service-sys-vip001
# 租户标识(多租户场景使用,用于路由)
TENANT_ID=vip001
# 服务端口(可覆盖 application.yml 中配置)
# SERVER_PORT=8100
9. 常见架构问题与处理建议
9.1 Bean 名称冲突
多模块项目中,配置类命名需避免冲突:
// 不推荐:多个模块都存在同名配置类
@Configuration
public class RedisConfig { }
// 推荐:添加模块前缀或语义前缀
@Configuration
public class GatewayRedisConfig { } // 网关专用配置
9.2 WebFlux 与 Servlet 冲突
基于 WebFlux 的网关或服务不能使用 Servlet API:
// 错误:在 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<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { }
}
9.3 注册中心服务发现失败
使用注册中心(如 Nacos)时,应确保依赖与配置完整,例如:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
并检查:
- 注册中心地址、命名空间、分组等配置是否正确;
- 服务名是否唯一;
- 客户端网络是否能访问注册中心。
十、开发规范
10.1 命名规范
10.1.1 包命名
- 全部小写。
- 建议格式:
com.{公司或组织}.{项目或域}.{模块}。
示例:
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 分支命名
推荐的分支模型:
master # 主分支(稳定版本)
develop # 开发分支(集成测试)
feature/xxx # 功能分支
bugfix/xxx # Bug 修复分支
hotfix/xxx # 紧急线上修复分支
- 新功能从
develop或master拉取对应的feature/*分支。 - Bug 修复从对应问题所在分支拉取
bugfix/*或hotfix/*分支。
10.2.2 提交信息格式
推荐使用简洁且结构化的提交信息格式:
<type>: <subject>
<body>
type 类型约定:
| 类型 | 说明 |
|---|---|
| feat | 新功能 |
| fix | Bug 修复 |
| docs | 文档更新 |
| style | 代码格式调整 |
| refactor | 重构 |
| test | 测试相关 |
| chore | 构建/工具相关 |
示例 1:功能提交
feat: 新增客户管理分页查询接口
- 支持按客户名称模糊查询
- 支持按状态筛选
- 完成分页参数校验
示例 2:缺陷修复
fix: 修复路由配置错误
xxx 服务路由路径配置错误,修正为 /cust/**
要求:
- subject 尽量 50 字以内,描述清晰、可读。
- body 用于说明设计思路、变更范围、注意事项等,可选但推荐完整编写。
10.3 代码分层与编写规范
10.3.1 Controller 层
Controller 仅负责:
- 请求参数接收与基础校验(可结合
@Valid)。 - 调用 Service 层完成业务处理。
- 将结果封装为统一返回对象输出。
示例:
@RestController
@RequestMapping("/api/v1/customer")
@RequiredArgsConstructor
public class CustomerController {
private final CustomerService customerService;
@PostMapping
public Result<String> createCustomer(@Valid @RequestBody CustomerCreateDTO dto) {
String id = customerService.createCustomer(dto);
return Result.success(id);
}
@GetMapping("/page")
public Result<PageResult<CustomerVO>> pageCustomers(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String keyword) {
Page<CustomerVO> page = customerService.pageCustomers(pageNum, pageSize, keyword);
return Result.success(PageResult.of(page));
}
}
10.3.2 Service 层
Service 层聚焦业务逻辑,典型步骤:
- 参数及前置条件校验。
- 业务规则处理。
- 调用 DataService 进行数据操作。
- 组装并返回结果。
示例:
@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 字段,确保基本数据正确性。
示例:
@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 方法。
- 请求参数(适当脱敏)。
- 响应时间。
- 结果状态(成功/失败)。
示例切面:
@Around("execution(* com.wjbl.project..controller..*(..))")
public Object logApiRequest(ProceedingJoinPoint joinPoint) {
// 记录:请求方法、参数、响应时间、结果状态
// 日志中必须包含 traceId 和 spanId
return null;
}
10.5 异常处理规范
10.5.1 统一异常处理
建议在网关或应用入口层统一捕获并处理异常,输出统一格式的错误响应:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
return Result.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("系统异常", e);
return Result.error(500, "系统异常,请稍后重试");
}
}
10.5.2 业务异常定义
业务异常类用于表达可预期的业务错误,并携带错误码和错误信息:
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、第三方服务异常),在适当位置记录详细日志并抛出统一异常,让全局处理器输出通用提示。
示例:
// 参数校验
if (customerName == null || customerName.isEmpty()) {
throw new BusinessException("客户名称不能为空");
}
// 业务校验
Customer customer = customerDataService.getById(id);
if (customer == null) {
throw new BusinessException(404, "客户不存在");
}
10.6 规范落地建议
为确保上述规范真正落地,建议在团队内按以下步骤推进:
- 纳入项目模板:
- 将本架构设计规范纳入项目脚手架或模板仓库。
- 代码评审检查:
- 在 Code Review 中重点检查命名、分层、日志、异常处理是否符合规范。
- 自动化校验:
- 结合静态检查工具、提交钩子等,对提交信息格式、基础代码规范进行自动化校验。
- 文档持续维护:
- 根据实际项目经验定期补充常见错误案例和最佳实践,将实践沉淀回本规范文档中。