# Java 后端开发规范

本项目 Java 后端开发规范，基于 Spring Boot + MyBatis-Plus 技术栈。

## JDK 版本要求（重要）

**必须使用 JDK 17** 进行编译和运行。

如果系统环境变量配置的是 JDK 1.8，请在编译前设置 `JAVA_HOME`：

```bash
# Windows (Git Bash) - 根据实际安装路径调整
export JAVA_HOME="/f/Java/jdk-17"
export PATH="$JAVA_HOME/bin:$PATH"

# 编译项目
mvn clean compile -DskipTests

# 或者在启动时指定
mvn spring-boot:run -Djava.home="/f/Java/jdk-17"
```

## 核心原则

1. **OpenAPI 规范驱动** - 前后端通过接口规范对齐
2. **类型安全优先** - 强制类型校验
3. **约定大于配置** - 统一代码风格
4. **自动化优先** - 能自动化的绝不手动
5. **三层架构分离** - Controller、Service、Mapper 职责清晰

## 技术栈

| 组件 | 技术选型 | 版本 |
|------|---------|------|
| 框架 | Spring Boot | 3.2+ |
| 持久层 | MyBatis-Plus | 3.5+ |
| 对象映射 | MapStruct | 1.5+ |
| 数据库 | MySQL | 8.0+ |
| 缓存 | Redis | - |
| 安全 | Spring Security + JWT | - |
| API 文档 | Knife4j | 4.x |

## 三层架构

| 层级 | 职责 | 数据接收 | 数据返回 |
|------|------|---------|---------|
| Controller | 接收请求、参数校验、返回响应 | DTO/Request | VO/Response |
| Service | 处理业务逻辑、事务控制 | DTO/Entity | Entity |
| Mapper | 数据库 CRUD 操作 | Entity/条件 | Entity |

**核心规范：**
- Service↔Mapper 之间只用 Entity，禁止 DTO/VO 转换
- 转换只在 Controller 层发生
- Service 继承 `IService<T>`
- 查询接口默认分页

## 消除魔法值规范

**禁止在代码中使用魔法值，所有状态、类型、常量必须使用枚举定义**

- 枚举类存放在 `enums` 包下
- 枚举包含 `code` 和 `desc` 字段
- 提供 `getCode()`、`getDesc()`、`valueOfCode()` 方法
- 数据库存储 `code`，代码中使用枚举

## ORM 实体类规范

### 表名命名

- `t_user_*` - 用户模块
- `t_sys_*` - 系统模块
- `t_biz_*` - 业务模块
- `t_auth_*` - 权限模块

### 审计字段（必填）

所有表必须包含审计字段：

| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键（自增） |
| create_by | VARCHAR(50) | 创建人 |
| create_time | DATETIME | 创建时间 |
| update_by | VARCHAR(50) | 更新人 |
| update_time | DATETIME | 更新时间 |
| deleted | TINYINT | 逻辑删除（0-未删除，1-已删除） |

- 审计字段通过 MyBatis-Plus `FieldFill` 自动填充
- 禁止手动设置审计字段

## 日志规范

### 日志语言

**所有日志必须使用中文**

### TraceId 链路追踪

- 使用 MDC 实现 TraceId 链路追踪
- AOP 切面在请求入口生成 TraceId
- 日志格式包含 `[%X{traceId}]`

### 环境差异化配置

| 环境 | 日志策略 |
|------|---------|
| 开发/测试 | 全量记录（DEBUG 级别） |
| 生产 | 精简记录（INFO/WARN 级别） |

### AOP 日志实现

- 拦截 Controller 层所有方法
- 请求日志：TraceId、请求方法、路径、IP、耗时
- 异常日志：TraceId、异常类型、消息、堆栈

## 统一响应格式

```java
Result<T> {
    code: Integer;     // 状态码
    message: String;   // 消息
    data: T;           // 数据
    timestamp: Long;   // 时间戳
}
```

**错误码：** 200-成功、400-参数错误、401-未授权、403-无权限、404-不存在、500-系统错误

## MapStruct 对象映射

- 使用 `@Mapper` 注解定义 Converter 接口
- Entity ↔ VO 转换在 Controller 层调用
- Service 层和 Mapper 层禁止 DTO/VO 转换
- 命名规范：`XxxConverter` 或 `XxxMapStruct`

## 工具类规范

- 工具函数集中管理，禁止在 Controller 层编写工具方法
- 工具类私有构造、静态方法、无状态、线程安全
- 命名规范：`XxxUtil`（通用）、`XxxHelper`（业务）、`XxxConverter`（转换）

## 多环境配置

| 配置项 | dev | test | prod |
|--------|-----|------|------|
| SQL 日志 | 开启 | 开启 | 关闭 |
| Swagger | 开启 | 开启 | 关闭 |
| Flyway Clean | 允许 | 禁止 | 禁止 |

## 快速参考

### Redis Key 命名

- `auth:token:{token}` - 用户 Token
- `user:info:{userId}` - 用户信息缓存
- `dict:{type}` - 数据字典
- `lock:{resource}:{id}` - 分布式锁
- `rate_limit:{key}` - 限流计数器

## 数据库索引规范（重要）

### 唯一索引处理原则

**项目使用逻辑删除，唯一索引通过应用层控制，而非数据库唯一约束。**

#### 背景问题

当表有逻辑删除字段（`deleted`）时，数据库唯一索引会导致：
1. 删除后无法重新添加相同数据
2. 错误信息不友好，直接返回数据库异常

#### 解决方案

| 层级 | 职责 | 实现方式 |
|------|------|----------|
| **数据库层** | 普通索引 | `KEY idx_xxx (xxx)` 而非 `UNIQUE KEY uk_xxx (xxx)` |
| **应用层** | 重复校验 | Service 层查询 + 悲观锁 `FOR UPDATE` |
| **异常处理** | 兜底处理 | 全局异常处理器捕获 `DuplicateKeyException` |

#### 实现示例

**1. 数据库迁移（Flyway）**
```sql
-- ❌ 错误：使用唯一索引
UNIQUE KEY `uk_username` (`username`)

-- ✅ 正确：使用普通索引
KEY `idx_username` (`username`)
```

**2. Mapper 层（悲观锁查询）**
```java
/**
 * 根据用户名查询用户（悲观锁，用于创建时防并发）
 */
@Select("SELECT * FROM t_user WHERE username = #{username} AND tenant_id = #{tenantId} AND deleted = 0 FOR UPDATE")
User getUserByUsernameForUpdate(@Param("username") String username, @Param("tenantId") Long tenantId);
```

**3. Service 层（事务 + 校验）**
```java
@Transactional(rollbackFor = Exception.class)
public UserVO createUser(CreateUserDTO dto, Long tenantId, Long operatorId) {
    log.info("开始创建用户，用户名：{}", dto.getUsername());

    // 使用悲观锁检查用户名是否已存在（防止并发）
    User existingUser = userMapper.getUserByUsernameForUpdate(dto.getUsername(), tenantId);
    if (existingUser != null) {
        throw new BusinessException("用户名已存在");
    }

    // 插入用户
    User user = convert(dto);
    userMapper.insert(user);
    return convertToVO(user);
}
```

**4. 需要应用层校验的字段**

以下字段需要添加应用层重复校验：

| 表 | 字段 | 说明 |
|-----|------|------|
| t_user | username, email, phone | 用户名、邮箱、手机号 |
| t_role | code | 角色编码 |
| t_permission | code | 权限编码 |
| t_dict | code + tenant_id | 字典编码 |
| t_tenant | code | 租户编码 |
| t_sys_config | config_key | 配置键 |

### 关联表索引规范

对于多对多关联表，使用普通索引而非唯一索引：

```sql
-- t_user_role: 用户角色关联表
KEY `idx_user_role` (`user_id`, `role_id`)

-- t_role_permission: 角色权限关联表
KEY `idx_role_permission` (`role_id`, `permission_id`)
```

**注意**：关联表的重复数据控制在应用层业务逻辑中处理。

### 核心环境变量

- `SPRING_PROFILES_ACTIVE` - 活跃环境
- `DB_HOST`、`DB_PASSWORD` - 数据库配置
- `REDIS_HOST`、`REDIS_PASSWORD` - Redis 配置
- `JWT_SECRET` - JWT 密钥
- `OSS_ACCESS_KEY_ID`、`OSS_ACCESS_KEY_SECRET` - OSS 配置
