AGENTS.md 23 KB

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/

必须遵守:

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,禁止使用 doublefloat
  • 实现 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/

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 按操作分:XxxCreateDTOXxxUpdateDTOXxxQueryDTO
  • 查询 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/

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/

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/

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/

    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/

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 : "未知";
    }
}

规则

  • 枚举只包含 codedescription 两个字段,禁止添加 status 等冗余字段
  • 字符串枚举的 code 类型为 String(如 DeviceDoorStatus),数字枚举的 code 类型为 int(如 OrderStatus
  • 必须提供 fromCode() 静态方法
  • 数据库存储 code 值,禁止存储 description 或数字序号

2.9 常量类

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

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 统一返回格式

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

禁止

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

3.5 业务异常抛出

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

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

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

4. 数据库规则

4.1 表命名

  • 前缀 t_,如 t_ordert_devicet_door_record
  • 多词用下划线分隔,如 t_timed_discount_activity
  • 关联表命名:t_{主实体}_{关联实体},如 t_activity_devicet_coupon_shop

4.2 ID 策略

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

4.3 字段命名

  • 数据库列:下划线命名(create_timepay_statusorder_no
  • Java 字段:驼峰命名(createTimepayStatusorderNo
  • 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 必须为字符串格式

    // 正确
    @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 时必须进行容错判断:

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 构造器注入优先

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

6.2 循环依赖处理

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

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

禁止:无循环依赖时使用 @Autowired,应一律使用构造器注入。

6.3 @Configuration 规则

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

// 正确
@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)中执行
  • 扣减后更新 discountAmountpaidAmount

7.3 设备门状态枚举

  • DeviceDoorStatus 枚举仅含 code(String)和 description(String),禁止冗余字段
  • 数据库 door_status 字段存储枚举 code 值(如 OPENEDCLOSEDERRORANOTHER
  • 禁止存储数字、JSON 对象或非规范字符串
  • 使用 DeviceDoorStatus.convertToDbValue() 写入数据库
  • 使用 DeviceDoorStatus.fromDbValue() 从数据库读取

7.4 设备告警

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

7.5 订单售后时效

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

7.6 微信支付分集成

  • 扫码开门流程强制检查支付分开通状态
  • 订单创建后初始化支付分服务订单
  • 订单回调后完结支付分订单并扣费
  • 支付分状态: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 注解