|
|
@@ -0,0 +1,723 @@
|
|
|
+# 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 微信支付分集成
|
|
|
+
|
|
|
+- 扫码开门流程强制检查支付分开通状态
|
|
|
+- 订单创建后初始化支付分服务订单
|
|
|
+- 订单回调后完结支付分订单并扣费
|
|
|
+- 支付分状态: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` 注解
|