## 架构设计规范 > 本文档基于参考文档 `参考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 # Nacos / 注册中心 NACOS_SERVER_ADDR=localhost:8848 NACOS_NAMESPACE=project-namespace NACOS_GROUP=DEFAULT_GROUP # Redis REDIS_HOST=localhost REDIS_PORT=6379 REDIS_PASSWORD=your-password # 网关限流 GATEWAY_RATE_LIMIT_REPLENISH_RATE=100 GATEWAY_RATE_LIMIT_BURST_CAPACITY=200 # 文件上传 FILE_UPLOAD_MAX_SIZE=50MB FILE_UPLOAD_MAX_REQUEST_SIZE=100MB FILE_STORAGE_PATH=./uploads # 日志 LOG_PATH=/var/logs/apps LOG_LEVEL_ROOT=INFO LOG_LEVEL_APP=DEBUG ``` 个性化配置示例: ```properties # 服务名称 APP_NAME=service-sys # 实例名称(多租户场景使用,默认与 APP_NAME 相同) INSTANCE_NAME=${APP_NAME} # 租户标识(多租户场景使用,用于路由) TENANT_ID= # 服务端口(可覆盖 application.yml 中配置) # SERVER_PORT=8100 ``` #### 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. **文档持续维护**: - 根据实际项目经验定期补充常见错误案例和最佳实践,将实践沉淀回本规范文档中。