阶段二:认证授权模块 - User实体类、Mapper、DataService - Token服务(Redis存储)、密码加密(BCrypt) - 认证拦截器、UserContext上下文 - 登录/登出接口 阶段三:核心业务模块 - 用户管理:CRUD、状态管理、密码重置 - 模板管理:CRUD、状态管理 - 工作日志:CRUD、权限控制 配置分离架构 - env.properties(环境敏感配置) - service.properties(服务配置) - logback-spring.xml更新 部署脚本 - deploy/目录(Nginx配置、启停脚本、备份脚本) 单元测试:29个测试全部通过
1338 lines
40 KiB
Markdown
1338 lines
40 KiB
Markdown
## 架构设计规范
|
||
|
||
> 本文档基于参考文档 `参考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
|
||
<!-- 在业务服务的 pom.xml 中引入基础模块 -->
|
||
<dependency>
|
||
<groupId>com.wjbl.project</groupId>
|
||
<artifactId>common</artifactId>
|
||
<version>${project.version}</version>
|
||
</dependency>
|
||
```
|
||
|
||
**优势说明**:
|
||
- 统一版本管理,避免基础代码不一致。
|
||
- 便于基础能力的统一升级和维护。
|
||
- 支持 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<Entity>`
|
||
```java
|
||
public interface CustomerDataService extends IService<Customer> {
|
||
// 数据访问相关方法
|
||
}
|
||
```
|
||
|
||
2. **数据服务实现**:命名为 `XxxDataServiceImpl`,继承 `ServiceImpl<Mapper, Entity>`
|
||
```java
|
||
@Service
|
||
public class CustomerDataServiceImpl
|
||
extends ServiceImpl<CustomerMapper, Customer>
|
||
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<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 调用规范**:
|
||
|
||
1. **定义 Feign 客户端接口**:
|
||
|
||
```java
|
||
@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);
|
||
}
|
||
```
|
||
|
||
2. **服务提供方职责**:
|
||
- 确保接口遵循 RESTful 规范
|
||
- 返回统一的 `Result<T>` 格式
|
||
- 接口文档及时更新(推荐使用 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<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 模式:
|
||
|
||
```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<T>`:
|
||
|
||
```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
|
||
<!-- 从环境变量读取配置 -->
|
||
<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 日志配置说明**:
|
||
|
||
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<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { }
|
||
}
|
||
```
|
||
|
||
#### 9.3 注册中心服务发现失败
|
||
|
||
使用注册中心(如 Nacos)时,应确保依赖与配置完整,例如:
|
||
|
||
```xml
|
||
<dependency>
|
||
<groupId>com.alibaba.cloud</groupId>
|
||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||
</dependency>
|
||
```
|
||
|
||
并检查:
|
||
|
||
- 注册中心地址、命名空间、分组等配置是否正确;
|
||
- 服务名是否唯一;
|
||
- 客户端网络是否能访问注册中心。
|
||
|
||
---
|
||
|
||
## 十、开发规范
|
||
|
||
### 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>: <subject>
|
||
|
||
<body>
|
||
```
|
||
|
||
**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<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 层聚焦业务逻辑,典型步骤:
|
||
|
||
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<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 业务异常定义
|
||
|
||
业务异常类用于表达可预期的业务错误,并携带错误码和错误信息:
|
||
|
||
```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. **文档持续维护**:
|
||
- 根据实际项目经验定期补充常见错误案例和最佳实践,将实践沉淀回本规范文档中。
|
||
|