# RULE.md - 哈哈零售系统智能体代码生成规则

> 本文件为 AI Agent 代码生成提供强制规则约束，定义生成边界与规范。
> 所有规则均为强制性，Agent 生成代码时必须严格遵守。违反规则的代码不可提交。

---

## 1. 项目边界

### 1.1 技术栈版本锁定

| 技术 | 版本 | 禁止变更 |
|------|------|----------|
| Java | 21 | 禁止使用 Java 22+ 特性 |
| Spring Boot | 4.0.3 | 禁止降级或升级 |
| MyBatis-Plus | 3.5.16 | 使用 mybatis-plus-spring-boot4-starter |
| Sa-Token | 1.45.0 | 使用 sa-token-spring-boot3-starter |
| Hutool | 5.8.40 | 禁止使用 hutool 6.x API |
| FastJson2 | 2.0.53 | 禁止使用 fastjson (v1) |
| Lombok | 1.18.36 | -- |
| Jakarta EE | Jakarta (非 Javax) | Spring Boot 4 使用 jakarta 命名空间 |

### 1.2 模块依赖方向（严格单向）

```
haha-admin  -->  haha-service  -->  haha-mapper  -->  haha-entity
haha-miniapp -->  haha-service  -->  haha-mapper  -->  haha-entity
                                        |
haha-common <---------------------------+（所有模块均可依赖）
haha-sdk（独立模块，不依赖其他内部模块）
```

**禁止**：
- haha-mapper 依赖 haha-service（反向依赖）
- haha-entity 依赖 haha-mapper 或 haha-service（反向依赖）
- haha-common 依赖任何其他内部模块
- haha-admin 与 haha-miniapp 互相依赖

### 1.3 双应用架构

| 应用 | 模块 | 端口 | Context-Path | 用途 |
|------|------|------|--------------|------|
| AdminApplication | haha-admin | 7070 | /admin | 运营平台后端 |
| MiniappApplication | haha-miniapp | 7077 | /api | 小程序后端 |

两个应用共享 haha-common / haha-entity / haha-mapper / haha-service，共享 `@ComponentScan(basePackages = {"com.haha"})` 和 `@MapperScan("com.haha.mapper")`。

---

## 2. 代码生成规则

### 2.1 实体类（Entity）

**位置**: `haha-entity/src/main/java/com/haha/entity/`

**必须遵守**:

```java
package com.haha.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@TableName("t_xxx")  // 表名必须 t_ 前缀
public class Xxx implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.ASSIGN_ID)  // 雪花算法
    @JsonSerialize(using = ToStringSerializer.class)  // Long -> String 防精度丢失
    private Long id;

    // 日期字段必须加格式注解
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
    private LocalDateTime createTime;

    // 非数据库字段必须标记
    @TableField(exist = false)
    private String statusLabel;

    // 金额字段使用 BigDecimal，禁止使用 double/float
    // private BigDecimal amount;
}
```

**规则**：
- `id` 字段必须添加 `@JsonSerialize(using = ToStringSerializer.class)`，禁止遗漏
- 所有 Long 类型外键字段（如 userId, orderId, shopId）也必须添加 `@JsonSerialize(using = ToStringSerializer.class)`
- 日期字段必须添加 `@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")`
- 金额字段必须使用 `BigDecimal`，禁止使用 `double` 或 `float`
- 实现 `Serializable` 接口
- 非 DB 字段必须用 `@TableField(exist = false)` 标注
- 数据库字段命名用下划线（`create_time`），Java 字段用驼峰（`createTime`），MyBatis-Plus 自动映射

### 2.2 DTO 类

**位置**: `haha-entity/src/main/java/com/haha/entity/dto/` 或 `haha-admin/src/main/java/com/haha/admin/dto/`

```java
package com.haha.entity.dto;

import lombok.Data;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class XxxCreateDTO {
    // 使用 Jakarta Validation 注解
    // @NotNull(message = "xxx不能为空")
    // @NotEmpty(message = "xxx不能为空")
    // @Size(max = 50, message = "xxx长度不能超过50")
    private String name;
}
```

**规则**：
- DTO 按操作分：`XxxCreateDTO`、`XxxUpdateDTO`、`XxxQueryDTO`
- 查询 DTO 继承 `BasePageQueryDTO`（来自 haha-common）并调用 `validate()` 方法校验分页参数
- 使用 Jakarta Validation 注解（`jakarta.validation.constraints.*`），禁止使用 `javax.validation.*`
- haha-common 模块使用 Validation 注解时，必须在 pom.xml 中显式添加 `jakarta.validation-api` 依赖

### 2.3 VO 类

**位置**: `haha-common/src/main/java/com/haha/common/vo/`

**规则**：
- 统一返回使用 `Result<T>`（`Result.success()` / `Result.error()`）
- 分页返回使用 `PageResult<T>`，通过 `PageResult.of(IPage<T>)` 转换
- VO 类用 `@Data` 注解，实现 `Serializable`

### 2.4 Mapper 接口

**位置**: `haha-mapper/src/main/java/com/haha/mapper/`

```java
package com.haha.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.haha.entity.Xxx;
import org.apache.ibatis.annotations.Param;

public interface XxxMapper extends BaseMapper<Xxx> {
    // 简单查询：使用注解式 SQL
    // @Select("SELECT ... FROM t_xxx WHERE ...")
    // List<Xxx> findByCondition(@Param("field") String field);

    // 复杂查询：使用 XML 映射文件
    // 对应文件：haha-mapper/src/main/resources/mapper/XxxMapper.xml
}
```

**规则**：
- 必须继承 `BaseMapper<Entity>`
- 简单 SQL 用 `@Select` / `@Update` / `@Insert` 注解，动态 SQL 用 `<script>` 标签
- 复杂 SQL（多表关联、动态条件>3个）必须使用 XML 映射文件
- XML 映射文件位置：`haha-mapper/src/main/resources/mapper/XxxMapper.xml`
- XML 中必须定义 `resultMap`，禁止使用 `resultType` 自动映射（保证字段可控）
- 查询方法参数使用 `@Param` 注解

### 2.5 Service 接口

**位置**: `haha-service/src/main/java/com/haha/service/`

```java
package com.haha.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.haha.entity.Xxx;

public interface XxxService extends IService<Xxx> {
    /**
     * 方法说明
     *
     * @param param 参数说明
     * @return 返回说明
     */
    Xxx getByField(String param);
}
```

**规则**：
- 必须继承 `IService<Entity>`
- 接口方法必须添加 Javadoc 注释
- 返回值类型优先使用实体类或 VO，禁止返回 `Map<String, Object>`（已有代码除外，新代码禁止）

### 2.6 Service 实现

**位置**: `haha-service/src/main/java/com/haha/service/impl/`

```java
package com.haha.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.haha.service.XxxService;
import com.haha.mapper.XxxMapper;
import com.haha.entity.Xxx;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@RequiredArgsConstructor
public class XxxServiceImpl extends ServiceImpl<XxxMapper, Xxx> implements XxxService {

    // 依赖注入：构造器注入（通过 @RequiredArgsConstructor）
    private final OtherMapper otherMapper;

    // 循环依赖时使用 @Autowired @Lazy
    // @Autowired
    // @Lazy
    // private CircularService circularService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Xxx getByField(String param) {
        log.info("查询xxx: param={}", param);
        return lambdaQuery().eq(Xxx::getField, param).one();
    }
}
```

**规则**：
- 必须继承 `ServiceImpl<Mapper, Entity>`
- 类注解固定：`@Slf4j` + `@Service` + `@RequiredArgsConstructor`
- 依赖注入优先使用构造器注入（`final` 字段 + `@RequiredArgsConstructor`）
- 仅在循环依赖时才允许使用 `@Autowired @Lazy`
- 事务方法必须添加 `@Transactional(rollbackFor = Exception.class)`
- 查询优先使用 MyBatis-Plus Lambda 链式：`lambdaQuery()` / `lambdaUpdate()`
- 分页参数校验：`page < 1` 时设为 1，`pageSize > 100` 时设为 10

### 2.7 Controller 层

**位置**：
- 运营平台：`haha-admin/src/main/java/com/haha/admin/controller/`
- 小程序：`haha-miniapp/src/main/java/com/haha/miniapp/controller/`

```java
package com.haha.admin.controller;

import com.haha.admin.annotation.RequirePermission;
import com.haha.common.annotation.Log;
import com.haha.common.enums.OperationType;
import com.haha.common.vo.PageResult;
import com.haha.common.vo.Result;
import com.haha.entity.Xxx;
import com.haha.service.XxxService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequestMapping("/xxx")
@RequiredArgsConstructor
public class XxxController {

    private final XxxService xxxService;

    @RequirePermission("xxx:read")
    @GetMapping("/list")
    public Result<PageResult<Xxx>> list(XxxQueryDTO queryDTO) {
        queryDTO.validate();
        // ... 分页查询
        return Result.success("查询成功", PageResult.of(page));
    }

    @RequirePermission("xxx:read")
    @GetMapping("/{id}")
    public Result<Xxx> getById(@PathVariable Long id) {
        Xxx xxx = xxxService.getDetailById(id);
        if (xxx == null) {
            return Result.error(404, "xxx不存在");
        }
        return Result.success("查询成功", xxx);
    }

    @RequirePermission("xxx:create")
    @Log(module = "xxx管理", operation = OperationType.CREATE, summary = "创建xxx")
    @PostMapping
    public Result<Void> create(@RequestBody XxxCreateDTO dto) {
        // ...
        return Result.success("创建成功", null);
    }

    @RequirePermission("xxx:update")
    @Log(module = "xxx管理", operation = OperationType.UPDATE, summary = "更新xxx")
    @PutMapping("/{id}")
    public Result<Void> update(@PathVariable Long id, @RequestBody XxxUpdateDTO dto) {
        // ...
        return Result.success("更新成功", null);
    }

    @RequirePermission("xxx:delete")
    @Log(module = "xxx管理", operation = OperationType.DELETE, summary = "删除xxx")
    @DeleteMapping("/{id}")
    public Result<Void> delete(@PathVariable Long id) {
        // ...
        return Result.success("删除成功", null);
    }
}
```

**规则**：
- 类注解固定：`@Slf4j` + `@RestController` + `@RequestMapping` + `@RequiredArgsConstructor`
- 所有接口必须添加 `@RequirePermission` 权限注解
- 写操作（增/删/改）必须添加 `@Log` 操作日志注解
- 统一返回 `Result<T>`，禁止直接返回实体或 Map
- 分页统一返回 `Result<PageResult<T>>`
- 资源不存在返回 `Result.error(404, "xxx不存在")`
- `@RequestMapping` 路径使用小写 kebab-case（如 `/timed-discount`），禁止驼峰
- 请求参数：查询用 `@RequestParam` 或 DTO 接收，路径参数用 `@PathVariable`，请求体用 `@RequestBody`

### 2.8 枚举类

**位置**: `haha-common/src/main/java/com/haha/common/enums/`

```java
package com.haha.common.enums;

import lombok.Getter;

@Getter
public enum XxxStatus {

    ACTIVE(1, "活跃"),
    INACTIVE(0, "未激活");

    private final int code;          // 数据库存储值
    private final String description; // 中文描述

    XxxStatus(int code, String description) {
        this.code = code;
        this.description = description;
    }

    public static XxxStatus fromCode(Integer code) {
        if (code == null) return null;
        for (XxxStatus s : values()) {
            if (s.code == code) return s;
        }
        return null;
    }

    public static String getDescription(Integer code) {
        XxxStatus s = fromCode(code);
        return s != null ? s.description : "未知";
    }
}
```

**规则**：
- 枚举只包含 `code` 和 `description` 两个字段，禁止添加 `status` 等冗余字段
- 字符串枚举的 `code` 类型为 `String`（如 `DeviceDoorStatus`），数字枚举的 `code` 类型为 `int`（如 `OrderStatus`）
- 必须提供 `fromCode()` 静态方法
- 数据库存储 `code` 值，禁止存储 `description` 或数字序号

### 2.9 常量类

**位置**: `haha-common/src/main/java/com/haha/common/constant/`

```java
package com.haha.common.constant;

/**
 * xxx相关常量
 */
public final class XxxConstants {

    private XxxConstants() {}  // 禁止实例化

    public static final int STATUS_ACTIVE = 1;
    public static final String PAY_STATUS_PAID = "PAID";
}
```

**规则**：
- `public final class`，私有构造
- 常量命名全大写下划线分隔
- 数字状态码用 `int`，字符串状态码用 `String`
- 常量类不与枚举重复定义相同的状态码

---

## 3. API 生成规则

### 3.1 RESTful 设计

| 操作 | HTTP Method | 路径 | 示例 |
|------|-------------|------|------|
| 分页列表 | GET | /xxx/list | `/order/list` |
| 详情 | GET | /xxx/{id} | `/order/{id}` |
| 统计 | GET | /xxx/statistics | `/order/statistics` |
| 创建 | POST | /xxx | `/order` |
| 更新 | PUT | /xxx/{id} | `/order/{id}` |
| 删除 | DELETE | /xxx/{id} | `/order/{id}` |
| 特殊操作 | POST | /xxx/{id}/action | `/order/{id}/refund` |

### 3.2 权限注解

- 格式：`@RequirePermission("module:operation")`
- operation 标准值：`read` / `create` / `update` / `delete` / `other`
- 读操作用 `:read`，写操作用对应 operation
- 小程序端 Controller 不需要 `@RequirePermission`（用户通过 accessToken 鉴权）

### 3.3 操作日志注解

- 写操作必须添加：`@Log(module = "模块名", operation = OperationType.XXX, summary = "操作描述")`
- OperationType 标准值：`CREATE` / `UPDATE` / `DELETE` / `OTHER`
- 读操作不加 `@Log`

### 3.4 统一返回格式

```json
{
  "code": 200,
  "message": "操作成功",
  "data": { ... }
}
```

| 场景 | code | message |
|------|------|---------|
| 成功 | 200 | "操作成功" 或具体描述 |
| 参数错误 | 400 | 具体字段错误描述 |
| 未登录 | 401 | "未登录" / "token已过期" 等 |
| 无权限 | 403 | "无访问权限" |
| 资源不存在 | 404 | "xxx不存在" |
| 业务异常 | 自定义 | BusinessException 消息 |
| 系统异常 | 500 | "系统繁忙，请稍后重试" |

**禁止**：
- 禁止在 Controller 中抛出异常后不做处理，统一由 `GlobalExceptionHandler` 捕获
- 禁止返回 `code=200` 但 data 中包含错误信息

### 3.5 业务异常抛出

```java
// 使用 ResponseEnum 枚举
throw new BusinessException(ResponseEnum.ORDER_NOT_FOUND);

// 自定义消息
throw new BusinessException(404, "订单不存在");

// 仅消息（默认500）
throw new BusinessException("操作失败");
```

---

## 4. 数据库规则

### 4.1 表命名

- 前缀 `t_`，如 `t_order`、`t_device`、`t_door_record`
- 多词用下划线分隔，如 `t_timed_discount_activity`
- 关联表命名：`t_{主实体}_{关联实体}`，如 `t_activity_device`、`t_coupon_shop`

### 4.2 ID 策略

- 全局使用雪花算法：`@TableId(type = IdType.ASSIGN_ID)`
- MyBatis-Plus 全局配置已设 `IdType.ASSIGN_ID`
- 数据库列类型为 `BIGINT`
- 禁止使用自增 ID（`IdType.AUTO`）

### 4.3 字段命名

- 数据库列：下划线命名（`create_time`、`pay_status`、`order_no`）
- Java 字段：驼峰命名（`createTime`、`payStatus`、`orderNo`）
- MyBatis-Plus 已开启 `map-underscore-to-camel-case: true` 自动映射
- 金额字段：`BigDecimal` 类型，数据库 `DECIMAL(10,2)`
- 状态字段：数字状态用 `INT`，字符串状态用 `VARCHAR`（如 pay_status 存 "PAID"）
- 时间字段：`DATETIME`，对应 Java `LocalDateTime`

### 4.4 XML 映射文件

- 位置：`haha-mapper/src/main/resources/mapper/XxxMapper.xml`
- 必须定义 `resultMap`，显式映射所有列
- SQL 中引用表名必须使用 `t_` 前缀
- 动态条件使用 `<if>` / `<where>` / `<set>` 标签
- SQL 片段使用 `<sql>` 标签复用

### 4.5 逻辑删除

- MyBatis-Plus 逻辑删除条件**不会自动添加**到手写 SQL 中
- 使用 `@TableLogic` 注解的实体，`BaseMapper` 自带方法自动处理
- 手写 SQL（注解或 XML）必须手动添加逻辑删除条件：`AND deleted = 0`

---

## 5. 序列化与精度规则

### 5.1 雪花 ID 精度（最高优先级）

JavaScript `Number.MAX_SAFE_INTEGER` = 9007199254740991（16位），雪花 ID 为 19 位数字，超出安全范围导致精度丢失。

**强制规则**：
- 所有实体的 `id` 字段（`Long` 类型）必须添加 `@JsonSerialize(using = ToStringSerializer.class)`
- 所有 `Long` 类型外键字段（userId, orderId, shopId, templateId 等）同样必须添加
- 禁止依赖全局 Jackson 配置（`JacksonConfig` 中的 Long->String 模块）替代字段级注解
- 前端接收 ID 必须为字符串格式

```java
// 正确
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;

// 错误 - 缺少 @JsonSerialize
@TableId(type = IdType.ASSIGN_ID)
private Long id;
```

### 5.2 日期序列化

- 日期格式统一：`yyyy-MM-dd HH:mm:ss`
- 时区统一：`Asia/Shanghai`
- 实体字段必须加：`@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")`
- Jackson 全局配置已注册 `JavaTimeModule`，禁用 `WRITE_DATES_AS_TIMESTAMPS`

### 5.3 图片 URL 容错

处理图片 URL 时必须进行容错判断：

```java
private String normalizeImageUrl(String picUrl) {
    if (picUrl == null || picUrl.isEmpty()) {
        return picUrl;
    }
    if (picUrl.startsWith("http://") || picUrl.startsWith("https://")) {
        return picUrl;
    }
    return commonConfig.getImageDomainPrefix() + picUrl;
}
```

---

## 6. 依赖注入规则

### 6.1 构造器注入优先

```java
// 正确 - 构造器注入（推荐）
@Slf4j
@Service
@RequiredArgsConstructor
public class XxxServiceImpl {
    private final XxxMapper xxxMapper;
    private final OtherService otherService;
}
```

### 6.2 循环依赖处理

仅在出现循环依赖时使用 `@Autowired @Lazy`：

```java
// 仅循环依赖时允许
@Autowired
@Lazy
private PaymentService paymentService;
```

**禁止**：无循环依赖时使用 `@Autowired`，应一律使用构造器注入。

### 6.3 @Configuration 规则

Spring Boot 4.0.3 要求 `@Configuration` 必须显式指定 `proxyBeanMethods = false`：

```java
// 正确
@Configuration(proxyBeanMethods = false)
public class XxxConfig {
    // ...
}

// 错误 - 缺少 proxyBeanMethods = false
@Configuration
public class XxxConfig {
    // ...
}
```

---

## 7. 业务规则约束

### 7.1 订单金额取值

- **实付金额必须从 `order.paidAmount` 取值**，禁止使用 `order.totalAmount`
- `paidAmount` 为空时回退到 `totalAmount`，确保向前兼容
- 三字段关系：`paidAmount = totalAmount - discountAmount`
- `totalAmount`：订单总金额（原价）
- `discountAmount`：优惠金额（营销活动优惠 + 优惠券优惠）
- `paidAmount`：实付总金额

### 7.2 优惠券优先级

- 多张可用优惠券时，按**到期时间升序**排序，最先到期的优先使用
- 适用条件检查：门店匹配、商品匹配、最低消费金额
- 优惠券自动扣减在订单回调（`handleOrderCallback`）中执行
- 扣减后更新 `discountAmount` 和 `paidAmount`

### 7.3 设备门状态枚举

- `DeviceDoorStatus` 枚举仅含 `code`（String）和 `description`（String），禁止冗余字段
- 数据库 `door_status` 字段存储枚举 `code` 值（如 `OPENED`、`CLOSED`、`ERROR`、`ANOTHER`）
- 禁止存储数字、JSON 对象或非规范字符串
- 使用 `DeviceDoorStatus.convertToDbValue()` 写入数据库
- 使用 `DeviceDoorStatus.fromDbValue()` 从数据库读取

### 7.4 设备告警

- 采用回调实时预警 + 定时巡检兜底的混合方案
- 告警通知通过企业微信群机器人 Webhook
- 配置项：开关（`device.alert.enabled`）、冷却期、巡检间隔、信号阈值
- Redis 存储告警冷却状态，防止重复告警

### 7.5 订单售后时效

- 退款申请有效期：支付完成后 7 天内
- 前后端均需校验 7 天时效限制
- 超期订单前端应给出明确提示

### 7.6 退款管理

- 退款数据使用独立的 `t_refund`（退款记录）和 `t_refund_item`（退款商品明细）表管理
- `t_order` 上的 `refund_status`、`refund_amount` 等字段保留作为汇总冗余，但数据源头在 `t_refund`
- 退款金额支持三种模式（优先级）：选择商品计算 > 自定义金额 > 全额
- 退款金额校验：累计退款金额 ≤ `paidAmount`（实付金额）；逐商品校验退款数量 ≤ 剩余可退数量
- `t_order.current_refund_id` 关联当前退款记录，便于快速查询
- 支付状态新增 `PARTIAL_REFUND`（部分退款），与 `REFUND`（全额退款）区分
- 退款服务：`RefundService` / `RefundServiceImpl`，实体：`Refund` / `RefundItem`
- **用户端退款**：提交后创建 PENDING 记录，需管理员审核。审核通过后执行第三方退款，拒绝后用户可重新提交
- **管理后台直接退款**：传入 `immediate=true`，即时执行第三方退款
- **多次退款**：支持同一订单多次部分退款，累计不超过实付金额。已退商品禁用勾选
- **退款追溯**：`t_refund_item` 记录每笔退款的商品明细；小程序订单详情展示退款时间线
- **支付分退款**：每次退款生成唯一 `out_refund_no`（含时间戳），避免重试参数不一致
- 管理后台退款审核入口：`RefundApplicationController`，基于 `t_refund` 查询

### 7.7 微信支付分集成

- 扫码开门流程强制检查支付分开通状态
- 订单创建后初始化支付分服务订单
- 订单回调后完结支付分订单并扣费
- 支付分状态：CREATED -> DOING -> USER_PAYING -> DONE

---

## 8. 常见陷阱（禁止违反）

### 8.1 雪花 ID 精度丢失

- 前端 JavaScript 无法安全表示 19 位数字
- **必须**在每个 Long 类型 ID 字段添加 `@JsonSerialize(using = ToStringSerializer.class)`
- 已有全局 Jackson Long->String 配置作为兜底，但**禁止依赖全局配置**替代字段注解

### 8.2 jakarta.validation 依赖

- haha-common 模块使用 `@NotNull` / `@NotEmpty` 等 Validation 注解时
- **必须**在 `haha-common/pom.xml` 中显式添加 `jakarta.validation-api` 依赖
- 否则编译报错：`import jakarta.validation cannot be resolved`

### 8.3 @Configuration 的 proxyBeanMethods

- Spring Boot 4.0.3 中，`@Configuration` 不指定 `proxyBeanMethods = false` 会导致 CGLIB 增强失败
- **必须**所有 `@Configuration` 类添加 `(proxyBeanMethods = false)`

### 8.4 MyBatis-Plus 逻辑删除

- 逻辑删除条件**不会自动添加**到手写 SQL（注解或 XML）中
- 手写 SQL 查询**必须手动添加** `AND deleted = 0` 条件
- `BaseMapper` 自带方法（selectById, selectList 等）自动处理

### 8.5 循环依赖

- Service 间循环引用必须使用 `@Autowired @Lazy`，禁止使用 `@RequiredArgsConstructor` 注入循环依赖
- 新增 Service 依赖时优先检查是否形成循环

### 8.6 @Transactional 与 @Async

- `@Async` 方法不在调用方事务上下文中执行
- 异步方法内部如需事务，必须单独添加 `@Transactional`
- 异步方法异常不应影响主流程，必须 try-catch 并记录日志

### 8.7 金额计算

- 禁止使用 `double` / `float` 进行金额计算
- 必须使用 `BigDecimal`，指定 `RoundingMode.HALF_UP`
- 除法必须指定 scale：`amount.divide(divisor, 2, RoundingMode.HALF_UP)`

### 8.8 PowerShell 反引号

- 在 Windows PowerShell 环境下生成 Java 代码时
- 禁止在字符串中使用反引号（`` ` ``），PowerShell 会将反引号解释为转义字符
- 使用 `System.out.println()` 或日志输出时避免反引号

---

## 9. 代码模板速查

### 9.1 新增业务模块检查清单

1. Entity 类（haha-entity）-> DTO 类（haha-entity/dto）-> Mapper 接口（haha-mapper）
2. 如需 XML 映射 -> `haha-mapper/src/main/resources/mapper/XxxMapper.xml`
3. Service 接口（haha-service）-> Service 实现（haha-service/impl）
4. Controller（haha-admin/controller 或 haha-miniapp/controller）
5. 常量类（haha-common/constant）-> 枚举类（haha-common/enums）
6. 确认所有 Long ID 字段添加 `@JsonSerialize(using = ToStringSerializer.class)`
7. 确认日期字段添加 `@JsonFormat`
8. 运营端 Controller 添加 `@RequirePermission`
9. 写操作添加 `@Log` 注解

### 9.2 数据库变更检查清单

1. SQL 迁移脚本放在 `docs/database/` 目录
2. 表名 `t_` 前缀
3. ID 列 `BIGINT`，雪花算法
4. 金额列 `DECIMAL(10,2)`
5. 时间列 `DATETIME`
6. Entity 类同步更新
7. `@JsonSerialize` 和 `@JsonFormat` 注解不可遗漏
8. 如使用逻辑删除，添加 `deleted` 列和 `@TableLogic` 注解
