forked from wangziqi/ruoyi-vue-pro
fix(education): align Flyway delivery review
This commit is contained in:
@@ -1,709 +0,0 @@
|
||||
# API 设计规范知识库
|
||||
|
||||
convention:
|
||||
id: "api-designer"
|
||||
name: "API 设计规范"
|
||||
category: "设计规范"
|
||||
description: "定义项目 REST API 的设计规范,包括接口命名、请求响应格式、权限控制、文档注解等,确保 API 设计的统一性和规范性"
|
||||
|
||||
# ============================================
|
||||
# 第一部分:设计原则
|
||||
# ============================================
|
||||
philosophy:
|
||||
design_principles:
|
||||
- principle: "操作式 URL 设计"
|
||||
description: "使用动词表达操作意图,而非 RESTful 资源式命名"
|
||||
example: "/system/user/create 而非 POST /users"
|
||||
- principle: "统一响应格式"
|
||||
description: "所有接口使用 CommonResult 包装,确保响应格式一致"
|
||||
- principle: "权限分级控制"
|
||||
description: "通过 @PermitAll 和 @PreAuthorize 实现公开接口与权限接口的分离"
|
||||
- principle: "文档即代码"
|
||||
description: "使用 OpenAPI 3.0 注解,接口文档与代码同步维护"
|
||||
|
||||
# ============================================
|
||||
# 第二部分:接口规范
|
||||
# ============================================
|
||||
interface_conventions:
|
||||
# URL 命名规范
|
||||
url_naming:
|
||||
format: "/{模块}/{功能}/{操作}"
|
||||
rules:
|
||||
- "模块名与功能名使用小写字母"
|
||||
- "操作名使用动词或动词短语"
|
||||
- "多词使用连字符分隔(如 update-status)"
|
||||
examples:
|
||||
- path: "/system/user/create"
|
||||
description: "创建用户"
|
||||
- path: "/system/user/page"
|
||||
description: "用户分页列表"
|
||||
- path: "/pay/order/get"
|
||||
description: "获取订单详情"
|
||||
- path: "/system/user/update-status"
|
||||
description: "更新用户状态"
|
||||
|
||||
# HTTP 方法映射
|
||||
http_methods:
|
||||
- method: "POST"
|
||||
operations: ["create", "import", "send", "login", "logout"]
|
||||
description: "创建类操作"
|
||||
idempotent: false
|
||||
- method: "PUT"
|
||||
operations: ["update", "update-status", "update-password"]
|
||||
description: "更新类操作"
|
||||
idempotent: true
|
||||
- method: "DELETE"
|
||||
operations: ["delete", "delete-list"]
|
||||
description: "删除类操作"
|
||||
idempotent: true
|
||||
- method: "GET"
|
||||
operations: ["get", "page", "list", "export-excel", "get-import-template"]
|
||||
description: "查询类操作"
|
||||
idempotent: true
|
||||
safe: true
|
||||
|
||||
# 标准端点定义
|
||||
standard_endpoints:
|
||||
- endpoint: "/create"
|
||||
method: "POST"
|
||||
purpose: "创建单个实体"
|
||||
request: "@RequestBody @Valid XxxSaveReqVO"
|
||||
response: "CommonResult<Long>"
|
||||
permission: "{模块}:{功能}:create"
|
||||
|
||||
- endpoint: "/update"
|
||||
method: "PUT"
|
||||
purpose: "更新单个实体"
|
||||
request: "@RequestBody @Valid XxxSaveReqVO"
|
||||
response: "CommonResult<Boolean>"
|
||||
permission: "{模块}:{功能}:update"
|
||||
|
||||
- endpoint: "/delete"
|
||||
method: "DELETE"
|
||||
purpose: "删除单个实体"
|
||||
request: "@RequestParam(\"id\") Long id"
|
||||
response: "CommonResult<Boolean>"
|
||||
permission: "{模块}:{功能}:delete"
|
||||
parameter_annotation: "@Parameter(name = \"id\", description = \"编号\", required = true)"
|
||||
|
||||
- endpoint: "/delete-list"
|
||||
method: "DELETE"
|
||||
purpose: "批量删除实体"
|
||||
request: "@RequestParam(\"ids\") List<Long> ids"
|
||||
response: "CommonResult<Boolean>"
|
||||
permission: "{模块}:{功能}:delete"
|
||||
parameter_annotation: "@Parameter(name = \"ids\", description = \"编号列表\", required = true)"
|
||||
|
||||
- endpoint: "/get"
|
||||
method: "GET"
|
||||
purpose: "获取单个实体详情"
|
||||
request: "@RequestParam(\"id\") Long id"
|
||||
response: "CommonResult<XxxRespVO>"
|
||||
permission: "{模块}:{功能}:query"
|
||||
parameter_annotation: "@Parameter(name = \"id\", description = \"编号\", required = true)"
|
||||
|
||||
- endpoint: "/page"
|
||||
method: "GET"
|
||||
purpose: "分页查询列表"
|
||||
request: "@Valid XxxPageReqVO"
|
||||
response: "CommonResult<PageResult<XxxRespVO>>"
|
||||
permission: "{模块}:{功能}:query"
|
||||
|
||||
- endpoint: "/list"
|
||||
method: "GET"
|
||||
purpose: "列表查询(不分页)"
|
||||
request: "@RequestParam(\"ids\") List<Long> ids 或无参数"
|
||||
response: "CommonResult<List<XxxRespVO>>"
|
||||
permission: "{模块}:{功能}:query"
|
||||
|
||||
- endpoint: "/export-excel"
|
||||
method: "GET"
|
||||
purpose: "导出 Excel"
|
||||
request: "@Valid XxxPageReqVO"
|
||||
response: "void(直接写入 HttpServletResponse)"
|
||||
permission: "{模块}:{功能}:export"
|
||||
special_annotation: "@ApiAccessLog(operateType = EXPORT)"
|
||||
|
||||
- endpoint: "/get-import-template"
|
||||
method: "GET"
|
||||
purpose: "获取导入模板"
|
||||
request: "无参数"
|
||||
response: "void(直接写入 HttpServletResponse)"
|
||||
permission: "通常无需权限或 {模块}:{功能}:import"
|
||||
|
||||
- endpoint: "/import"
|
||||
method: "POST"
|
||||
purpose: "导入 Excel"
|
||||
request: "@RequestParam(\"file\") MultipartFile file, @RequestParam(\"updateSupport\") Boolean updateSupport"
|
||||
response: "CommonResult<XxxImportRespVO>"
|
||||
permission: "{模块}:{功能}:import"
|
||||
parameter_annotation: "@Parameters({...})"
|
||||
|
||||
# ============================================
|
||||
# 第三部分:注解规范
|
||||
# ============================================
|
||||
annotation_standards:
|
||||
# Controller 类注解
|
||||
class_annotations:
|
||||
- annotation: "@Tag"
|
||||
format: "@Tag(name = \"管理后台 - {模块名}\")"
|
||||
purpose: "OpenAPI 3.0 文档分组"
|
||||
import: "io.swagger.v3.oas.annotations.tags.Tag"
|
||||
- annotation: "@RestController"
|
||||
purpose: "REST 控制器"
|
||||
import: "org.springframework.web.bind.annotation.RestController"
|
||||
- annotation: "@RequestMapping"
|
||||
format: "@RequestMapping(\"/{模块}/{功能}\")"
|
||||
purpose: "路由前缀"
|
||||
import: "org.springframework.web.bind.annotation.RequestMapping"
|
||||
- annotation: "@Validated"
|
||||
purpose: "参数校验支持"
|
||||
import: "org.springframework.validation.annotation.Validated"
|
||||
|
||||
# 方法注解
|
||||
method_annotations:
|
||||
- annotation: "@Operation"
|
||||
format: "@Operation(summary = \"{操作描述}\")"
|
||||
purpose: "接口文档说明"
|
||||
import: "io.swagger.v3.oas.annotations.Operation"
|
||||
examples:
|
||||
- "@Operation(summary = \"创建用户\")"
|
||||
- "@Operation(summary = \"获得用户分页列表\")"
|
||||
- "@Operation(summary = \"导出用户 Excel\")"
|
||||
|
||||
- annotation: "@PermitAll"
|
||||
purpose: "公开接口(无需登录)"
|
||||
import: "cn.iocoder.yudao.framework.security.core.annotations.PermitAll"
|
||||
when_to_use: "登录、登出、注册、验证码等无需认证的接口"
|
||||
|
||||
- annotation: "@PreAuthorize"
|
||||
format: "@PreAuthorize(\"@ss.hasPermission('{模块}:{功能}:{操作}')\")"
|
||||
purpose: "权限控制"
|
||||
import: "org.springframework.security.access.prepost.PreAuthorize"
|
||||
examples:
|
||||
- "@PreAuthorize(\"@ss.hasPermission('system:user:create')\")"
|
||||
- "@PreAuthorize(\"@ss.hasPermission('pay:order:query')\")"
|
||||
|
||||
- annotation: "@Parameter"
|
||||
format: "@Parameter(name = \"{参数名}\", description = \"{描述}\", required = {是否必填}, example = \"{示例}\")"
|
||||
purpose: "单参数文档"
|
||||
import: "io.swagger.v3.oas.annotations.Parameter"
|
||||
when_to_use: "单个 @RequestParam 参数时使用"
|
||||
|
||||
- annotation: "@Parameters"
|
||||
format: "@Parameters({ @Parameter(name = \"xxx\", ...), @Parameter(name = \"yyy\", ...) })"
|
||||
purpose: "多参数文档"
|
||||
import: "io.swagger.v3.oas.annotations.Parameters"
|
||||
when_to_use: "多个 @RequestParam 参数时使用"
|
||||
|
||||
- annotation: "@ApiAccessLog"
|
||||
format: "@ApiAccessLog(operateType = EXPORT)"
|
||||
purpose: "操作日志记录"
|
||||
import: "cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog"
|
||||
when_to_use: "导出、导入等需要记录日志的操作"
|
||||
constants_import: "static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT"
|
||||
|
||||
# 参数注解
|
||||
parameter_annotations:
|
||||
- annotation: "@RequestBody"
|
||||
purpose: "接收 JSON 请求体"
|
||||
import: "org.springframework.web.bind.annotation.RequestBody"
|
||||
usage: "@RequestBody @Valid XxxReqVO reqVO"
|
||||
- annotation: "@RequestParam"
|
||||
purpose: "接收 URL 查询参数"
|
||||
import: "org.springframework.web.bind.annotation.RequestParam"
|
||||
usage: "@RequestParam(\"id\") Long id"
|
||||
- annotation: "@Valid"
|
||||
purpose: "参数校验"
|
||||
import: "javax.validation.Valid"
|
||||
usage: "@Valid @RequestBody XxxReqVO reqVO"
|
||||
|
||||
# ============================================
|
||||
# 第四部分:请求响应规范
|
||||
# ============================================
|
||||
request_response:
|
||||
# 统一响应结构
|
||||
common_result:
|
||||
class: "cn.iocoder.yudao.framework.common.pojo.CommonResult<T>"
|
||||
structure:
|
||||
code: "Integer(0 表示成功)"
|
||||
msg: "String(错误提示信息)"
|
||||
data: "T(返回数据)"
|
||||
usage:
|
||||
success: "return success(data)"
|
||||
success_boolean: "return success(true)"
|
||||
success_id: "return success(id)"
|
||||
import: "static cn.iocoder.yudao.framework.common.pojo.CommonResult.success"
|
||||
|
||||
# 分页响应结构
|
||||
page_result:
|
||||
class: "cn.iocoder.yudao.framework.common.pojo.PageResult<T>"
|
||||
structure:
|
||||
total: "Long(总记录数)"
|
||||
list: "List<T>(当前页数据列表)"
|
||||
usage: "CommonResult<PageResult<XxxRespVO>> getXxxPage(...)"
|
||||
empty: "new PageResult<>(pageResult.getTotal())"
|
||||
|
||||
# 分页请求参数
|
||||
page_param:
|
||||
class: "cn.iocoder.yudao.framework.common.pojo.PageParam"
|
||||
fields:
|
||||
pageNo: "Integer(页码,从 1 开始,默认 1,最小值 1)"
|
||||
pageSize: "Integer(每页条数,默认 10,范围 1-200)"
|
||||
constants:
|
||||
PAGE_NO: "1"
|
||||
PAGE_SIZE: "10"
|
||||
PAGE_SIZE_NONE: "-1(不分页,用于导出等场景)"
|
||||
usage: "分页查询 VO 继承 PageParam"
|
||||
|
||||
# 请求 VO 类型
|
||||
request_vo_types:
|
||||
- type: "SaveReqVO"
|
||||
purpose: "新增/修改请求"
|
||||
naming: "XxxSaveReqVO"
|
||||
features:
|
||||
- "修改时包含 id 字段"
|
||||
- "包含 @NotBlank/@NotNull 校验注解"
|
||||
- "使用 @Schema 注解描述字段"
|
||||
- type: "PageReqVO"
|
||||
purpose: "分页查询请求"
|
||||
naming: "XxxPageReqVO"
|
||||
features:
|
||||
- "继承 PageParam"
|
||||
- "包含查询条件字段"
|
||||
- "时间字段使用 @DateTimeFormat"
|
||||
- type: "ImportReqVO"
|
||||
purpose: "导入请求"
|
||||
naming: "XxxImportReqVO 或使用 @RequestParam 接收 file"
|
||||
features:
|
||||
- "包含 file 和 updateSupport 参数"
|
||||
- type: "RespVO"
|
||||
purpose: "响应对象"
|
||||
naming: "XxxRespVO"
|
||||
features:
|
||||
- "包含 @Schema 注解描述字段"
|
||||
- "包含 createTime 等只读字段"
|
||||
|
||||
# VO 注解规范
|
||||
vo_annotations:
|
||||
- annotation: "@Schema"
|
||||
purpose: "OpenAPI 3.0 字段描述"
|
||||
format: "@Schema(description = \"{字段描述}\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"{示例}\")"
|
||||
import: "io.swagger.v3.oas.annotations.media.Schema"
|
||||
- annotation: "@ExcelProperty"
|
||||
purpose: "Excel 导出字段"
|
||||
format: "@ExcelProperty(\"{列名}\")"
|
||||
import: "com.alibaba.excel.annotation.ExcelProperty"
|
||||
- annotation: "@DictFormat"
|
||||
purpose: "字典转换"
|
||||
format: "@DictFormat(\"{字典类型}\")"
|
||||
|
||||
# ============================================
|
||||
# 第五部分:权限标识规范
|
||||
# ============================================
|
||||
permission_naming:
|
||||
format: "{模块}:{功能}:{操作}"
|
||||
rules:
|
||||
- "模块名与功能名使用小写字母"
|
||||
- "操作名使用标准动词"
|
||||
standard_operations:
|
||||
- operation: "query"
|
||||
description: "查询权限(包括 get、page、list)"
|
||||
- operation: "create"
|
||||
description: "新增权限"
|
||||
- operation: "update"
|
||||
description: "修改权限"
|
||||
- operation: "delete"
|
||||
description: "删除权限"
|
||||
- operation: "export"
|
||||
description: "导出权限"
|
||||
- operation: "import"
|
||||
description: "导入权限"
|
||||
examples:
|
||||
- "system:user:query"
|
||||
- "system:user:create"
|
||||
- "system:user:update"
|
||||
- "system:user:delete"
|
||||
- "system:user:export"
|
||||
- "system:user:import"
|
||||
- "pay:order:query"
|
||||
- "pay:order:create"
|
||||
|
||||
# 权限 SQL 配置
|
||||
permission_sql:
|
||||
menu_format: |
|
||||
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
|
||||
VALUES ('{功能名称}管理', '', 2, 0, {父菜单ID}, '{功能}', 'ep:document', '{模块}/{功能}/index', 0);
|
||||
|
||||
button_format: |
|
||||
INSERT INTO system_menu (name, permission, type, sort, parent_id, status) VALUES
|
||||
('{功能名称}查询', '{模块}:{功能}:query', 3, 1, @menuId, 0),
|
||||
('{功能名称}新增', '{模块}:{功能}:create', 3, 2, @menuId, 0),
|
||||
('{功能名称}修改', '{模块}:{功能}:update', 3, 3, @menuId, 0),
|
||||
('{功能名称}删除', '{模块}:{功能}:delete', 3, 4, @menuId, 0),
|
||||
('{功能名称}导出', '{模块}:{功能}:export', 3, 5, @menuId, 0);
|
||||
|
||||
# ============================================
|
||||
# 第六部分:错误码体系
|
||||
# ============================================
|
||||
error_codes:
|
||||
format: "1_模块编号_功能编号_错误序号"
|
||||
success_code: 0
|
||||
|
||||
# 全局错误码
|
||||
global_errors:
|
||||
- code: 0
|
||||
message: "成功"
|
||||
- code: 400
|
||||
message: "请求参数不正确"
|
||||
- code: 401
|
||||
message: "账号未登录"
|
||||
- code: 403
|
||||
message: "没有该操作权限"
|
||||
- code: 404
|
||||
message: "请求未找到"
|
||||
- code: 405
|
||||
message: "请求方法不正确"
|
||||
- code: 423
|
||||
message: "请求失败,请稍后重试"
|
||||
- code: 429
|
||||
message: "请求过于频繁"
|
||||
- code: 500
|
||||
message: "系统异常"
|
||||
- code: 501
|
||||
message: "功能未实现/未开启"
|
||||
- code: 502
|
||||
message: "错误的配置项"
|
||||
- code: 900
|
||||
message: "重复请求"
|
||||
- code: 901
|
||||
message: "演示模式,禁止写操作"
|
||||
- code: 999
|
||||
message: "未知错误"
|
||||
|
||||
# 模块编号速查
|
||||
module_codes:
|
||||
- module: "infra"
|
||||
code_prefix: "1_001"
|
||||
description: "基础设施模块"
|
||||
- module: "system"
|
||||
code_prefix: "1_002"
|
||||
description: "系统管理模块"
|
||||
- module: "member"
|
||||
code_prefix: "1_003"
|
||||
description: "会员模块"
|
||||
- module: "pay"
|
||||
code_prefix: "1_007"
|
||||
description: "支付模块"
|
||||
- module: "product"
|
||||
code_prefix: "1_008"
|
||||
description: "商品模块"
|
||||
- module: "trade"
|
||||
code_prefix: "1_011"
|
||||
description: "交易模块"
|
||||
- module: "promotion"
|
||||
code_prefix: "1_013"
|
||||
description: "促销模块"
|
||||
|
||||
# 错误码定义模板
|
||||
error_code_template: |
|
||||
// ========== {实体名称} 相关错误码 1_XXX_XXX_XXX ==========
|
||||
ErrorCode XXX_NOT_EXISTS = new ErrorCode(1_002_001_000, "{实体名称}不存在");
|
||||
ErrorCode XXX_CODE_DUPLICATE = new ErrorCode(1_002_001_001, "已存在该编码的{实体名称}");
|
||||
ErrorCode XXX_NAME_DUPLICATE = new ErrorCode(1_002_001_002, "已存在该名称的{实体名称}");
|
||||
ErrorCode XXX_CAN_NOT_DELETE = new ErrorCode(1_002_001_003, "{实体名称}不能删除,原因:{}");
|
||||
ErrorCode XXX_STATUS_ERROR = new ErrorCode(1_002_001_004, "{实体名称}状态不正确");
|
||||
|
||||
# ============================================
|
||||
# 第七部分:Controller 模板
|
||||
# ============================================
|
||||
controller_template:
|
||||
# 基础 CRUD 模板
|
||||
basic_crud: |
|
||||
package cn.iocoder.yudao.module.{模块}.controller.admin.{功能};
|
||||
|
||||
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
import cn.iocoder.yudao.module.{模块}.controller.admin.{功能}.vo.*;
|
||||
import cn.iocoder.yudao.module.{模块}.dal.dataobject.{功能}.XxxDO;
|
||||
import cn.iocoder.yudao.module.{模块}.service.{功能}.XxxService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - {实体名称}")
|
||||
@RestController
|
||||
@RequestMapping("/{模块}/{功能}")
|
||||
@Validated
|
||||
public class XxxController {
|
||||
|
||||
@Resource
|
||||
private XxxService xxxService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建{实体名称}")
|
||||
@PreAuthorize("@ss.hasPermission('{模块}:{功能}:create')")
|
||||
public CommonResult<Long> createXxx(@Valid @RequestBody XxxSaveReqVO createReqVO) {
|
||||
return success(xxxService.createXxx(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新{实体名称}")
|
||||
@PreAuthorize("@ss.hasPermission('{模块}:{功能}:update')")
|
||||
public CommonResult<Boolean> updateXxx(@Valid @RequestBody XxxSaveReqVO updateReqVO) {
|
||||
xxxService.updateXxx(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除{实体名称}")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('{模块}:{功能}:delete')")
|
||||
public CommonResult<Boolean> deleteXxx(@RequestParam("id") Long id) {
|
||||
xxxService.deleteXxx(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得{实体名称}")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('{模块}:{功能}:query')")
|
||||
public CommonResult<XxxRespVO> getXxx(@RequestParam("id") Long id) {
|
||||
XxxDO xxx = xxxService.getXxx(id);
|
||||
return success(BeanUtils.toBean(xxx, XxxRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得{实体名称}分页")
|
||||
@PreAuthorize("@ss.hasPermission('{模块}:{功能}:query')")
|
||||
public CommonResult<PageResult<XxxRespVO>> getXxxPage(@Valid XxxPageReqVO pageReqVO) {
|
||||
PageResult<XxxDO> pageResult = xxxService.getXxxPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, XxxRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出{实体名称} Excel")
|
||||
@PreAuthorize("@ss.hasPermission('{模块}:{功能}:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportXxxExcel(@Valid XxxPageReqVO pageReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<XxxDO> list = xxxService.getXxxPage(pageReqVO).getList();
|
||||
List<XxxExcelVO> excelList = BeanUtils.toBean(list, XxxExcelVO.class);
|
||||
ExcelUtils.write(response, "{实体名称}.xls", "数据", XxxExcelVO.class, excelList);
|
||||
}
|
||||
}
|
||||
|
||||
# 公开接口模板
|
||||
public_endpoint: |
|
||||
@PostMapping("/login")
|
||||
@PermitAll
|
||||
@Operation(summary = "使用账号密码登录")
|
||||
public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
|
||||
return success(authService.login(reqVO));
|
||||
}
|
||||
|
||||
# 批量操作模板
|
||||
batch_operations: |
|
||||
@DeleteMapping("/delete-list")
|
||||
@Parameter(name = "ids", description = "编号列表", required = true)
|
||||
@Operation(summary = "批量删除{实体名称}")
|
||||
@PreAuthorize("@ss.hasPermission('{模块}:{功能}:delete')")
|
||||
public CommonResult<Boolean> deleteXxxList(@RequestParam("ids") List<Long> ids) {
|
||||
xxxService.deleteXxxList(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
# 导入导出模板
|
||||
import_export: |
|
||||
@GetMapping("/get-import-template")
|
||||
@Operation(summary = "获得导入{实体名称}模板")
|
||||
public void importTemplate(HttpServletResponse response) throws IOException {
|
||||
// 手动创建导出 demo
|
||||
List<XxxImportExcelVO> list = Arrays.asList(
|
||||
XxxImportExcelVO.builder().name("示例1").code("TEST1").build(),
|
||||
XxxImportExcelVO.builder().name("示例2").code("TEST2").build()
|
||||
);
|
||||
ExcelUtils.write(response, "{实体名称}导入模板.xls", "数据", XxxImportExcelVO.class, list);
|
||||
}
|
||||
|
||||
@PostMapping("/import")
|
||||
@Operation(summary = "导入{实体名称}")
|
||||
@Parameters({
|
||||
@Parameter(name = "file", description = "Excel 文件", required = true),
|
||||
@Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true")
|
||||
})
|
||||
@PreAuthorize("@ss.hasPermission('{模块}:{功能}:import')")
|
||||
public CommonResult<XxxImportRespVO> importExcel(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception {
|
||||
List<XxxImportExcelVO> list = ExcelUtils.read(file, XxxImportExcelVO.class);
|
||||
return success(xxxService.importXxxList(list, updateSupport));
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# 第八部分:设计流程
|
||||
# ============================================
|
||||
design_workflow:
|
||||
steps:
|
||||
- step: 1
|
||||
name: "实体分析"
|
||||
action: "分析数据库设计或 PRD,确定实体字段和业务含义"
|
||||
input: ["db_design_file", "prd_file", "模块 skill 文档"]
|
||||
output: "实体字段清单"
|
||||
checklist:
|
||||
- "确认实体所属模块"
|
||||
- "阅读对应模块的 skill 文档"
|
||||
- "确定需要哪些标准端点"
|
||||
|
||||
- step: 2
|
||||
name: "端点设计"
|
||||
action: "根据业务需求确定需要的 API 端点"
|
||||
considerations:
|
||||
- "是否需要完整 CRUD(create/update/delete/get/page)"
|
||||
- "是否需要批量操作(delete-list)"
|
||||
- "是否需要导入导出(export-excel/import)"
|
||||
- "是否需要状态切换(update-status)"
|
||||
- "是否有公开接口需求(使用 @PermitAll)"
|
||||
|
||||
- step: 3
|
||||
name: "请求响应设计"
|
||||
action: "设计各端点的请求参数和响应格式"
|
||||
templates:
|
||||
- "使用 SaveReqVO 处理新增/修改"
|
||||
- "使用 PageReqVO 处理分页查询(继承 PageParam)"
|
||||
- "使用 RespVO 处理响应"
|
||||
- "使用 CommonResult 包装响应"
|
||||
|
||||
- step: 4
|
||||
name: "权限设计"
|
||||
action: "确定各端点的权限标识"
|
||||
rules:
|
||||
- "公开接口使用 @PermitAll"
|
||||
- "权限接口使用 @PreAuthorize"
|
||||
- "权限标识格式:模块:功能:操作"
|
||||
- "查询类接口统一使用 query 权限"
|
||||
|
||||
- step: 5
|
||||
name: "错误码设计"
|
||||
action: "定义业务错误码"
|
||||
rules:
|
||||
- "格式:1_模块编号_功能编号_错误序号"
|
||||
- "常见错误:不存在、重复、状态错误、操作限制"
|
||||
|
||||
- step: 6
|
||||
name: "文档生成"
|
||||
action: "生成 Controller 代码和 API 文档"
|
||||
output: ["Controller.java", "VO classes", "错误码定义", "权限 SQL"]
|
||||
|
||||
# ============================================
|
||||
# 第九部分:检查清单
|
||||
# ============================================
|
||||
checklist:
|
||||
before_design:
|
||||
- "确认实体所属模块"
|
||||
- "阅读对应模块的 skill 文档"
|
||||
- "了解现有类似接口的设计模式"
|
||||
- "准备表结构设计"
|
||||
|
||||
during_design:
|
||||
- "URL 命名符合规范(/{模块}/{功能}/{操作})"
|
||||
- "HTTP 方法与操作类型匹配"
|
||||
- "请求 VO 类型选择正确"
|
||||
- "响应格式使用 CommonResult"
|
||||
- "权限标识命名正确"
|
||||
- "注解使用完整"
|
||||
|
||||
after_design:
|
||||
- "所有接口添加 @Operation 注解"
|
||||
- "参数添加 @Parameter 或 @Parameters 注解"
|
||||
- "权限接口添加 @PreAuthorize 注解"
|
||||
- "公开接口添加 @PermitAll 注解"
|
||||
- "导出接口添加 @ApiAccessLog 注解"
|
||||
- "错误码定义完整"
|
||||
|
||||
# ============================================
|
||||
# 第十部分:与 entity-implementation.md 协同
|
||||
# ============================================
|
||||
collaboration:
|
||||
relationship:
|
||||
- doc: "api-designer.yaml"
|
||||
role: "API 设计阶段规范指导"
|
||||
focus: "设计决策、端点规划、权限标识"
|
||||
- doc: "entity-implementation.md"
|
||||
role: "完整实现流程指导"
|
||||
focus: "实现细节、模板代码、各层规范"
|
||||
usage_flow:
|
||||
- "1. 使用 api-designer.yaml 进行 API 设计决策"
|
||||
- "2. 参考 entity-implementation.md 进行完整实现"
|
||||
- "3. 两者 Controller 模板保持一致"
|
||||
|
||||
# ============================================
|
||||
# 快速参考
|
||||
# ============================================
|
||||
quick_reference:
|
||||
http_methods:
|
||||
POST: "创建类操作(create, import, send)"
|
||||
PUT: "更新类操作(update, update-status)"
|
||||
DELETE: "删除类操作(delete, delete-list)"
|
||||
GET: "查询类操作(get, page, list, export-excel)"
|
||||
|
||||
standard_endpoints:
|
||||
/create: "创建 → CommonResult<Long>"
|
||||
/update: "更新 → CommonResult<Boolean>"
|
||||
/delete: "删除 → CommonResult<Boolean>"
|
||||
/delete-list: "批量删除 → CommonResult<Boolean>"
|
||||
/get: "详情 → CommonResult<XxxRespVO>"
|
||||
/page: "分页 → CommonResult<PageResult<XxxRespVO>>"
|
||||
/list: "列表 → CommonResult<List<XxxRespVO>>"
|
||||
/export-excel: "导出 → void"
|
||||
/import: "导入 → CommonResult<XxxImportRespVO>"
|
||||
|
||||
permission_operations:
|
||||
query: "查询(get/page/list)"
|
||||
create: "新增"
|
||||
update: "修改"
|
||||
delete: "删除"
|
||||
export: "导出"
|
||||
import: "导入"
|
||||
|
||||
response_types:
|
||||
CommonResult<Long>: "创建类响应"
|
||||
CommonResult<Boolean>: "操作类响应"
|
||||
CommonResult<XxxRespVO>: "详情类响应"
|
||||
CommonResult<PageResult<XxxRespVO>>: "分页类响应"
|
||||
CommonResult<List<XxxRespVO>>: "列表类响应"
|
||||
|
||||
vo_types:
|
||||
XxxSaveReqVO: "新增/修改请求"
|
||||
XxxPageReqVO: "分页查询请求(继承 PageParam)"
|
||||
XxxRespVO: "响应对象"
|
||||
XxxExcelVO: "Excel 导出对象"
|
||||
XxxImportExcelVO: "Excel 导入对象"
|
||||
XxxImportRespVO: "导入结果响应"
|
||||
|
||||
imports:
|
||||
controller_common:
|
||||
- "io.swagger.v3.oas.annotations.tags.Tag"
|
||||
- "io.swagger.v3.oas.annotations.Operation"
|
||||
- "io.swagger.v3.oas.annotations.Parameter"
|
||||
- "io.swagger.v3.oas.annotations.Parameters"
|
||||
- "org.springframework.security.access.prepost.PreAuthorize"
|
||||
- "cn.iocoder.yudao.framework.common.pojo.CommonResult"
|
||||
- "cn.iocoder.yudao.framework.common.pojo.PageResult"
|
||||
- "static cn.iocoder.yudao.framework.common.pojo.CommonResult.success"
|
||||
@@ -1,518 +0,0 @@
|
||||
# CRUD 代码生成 Skill
|
||||
# 整合性入口指南 - 引用已有的模板文档
|
||||
|
||||
skill:
|
||||
id: "crud-generator"
|
||||
name: "CRUD 代码生成"
|
||||
version: "1.0.0"
|
||||
category: "design"
|
||||
description: "根据数据库表结构自动生成符合yudao项目规范的完整CRUD功能代码"
|
||||
created_at: "2026-03-31"
|
||||
updated_at: "2026-03-31"
|
||||
|
||||
# ============================================
|
||||
# 第一部分:触发条件
|
||||
# ============================================
|
||||
trigger:
|
||||
commands:
|
||||
- "/crud-gen"
|
||||
- "/generate-crud"
|
||||
keywords:
|
||||
- "生成CRUD"
|
||||
- "创建实体代码"
|
||||
- "生成增删改查"
|
||||
- "实体类实现"
|
||||
events:
|
||||
- name: "table_created"
|
||||
condition: "数据库表创建完成"
|
||||
|
||||
# ============================================
|
||||
# 第二部分:输入参数
|
||||
# ============================================
|
||||
input:
|
||||
parameters:
|
||||
- name: "module"
|
||||
type: "string"
|
||||
required: true
|
||||
description: "模块编码(system/infra/pay/mes等)"
|
||||
examples: ["system", "infra", "pay", "mes"]
|
||||
|
||||
- name: "feature"
|
||||
type: "string"
|
||||
required: true
|
||||
description: "功能编码(user/order/process等)"
|
||||
examples: ["user", "order", "process"]
|
||||
|
||||
- name: "entity_name"
|
||||
type: "string"
|
||||
required: true
|
||||
description: "实体名称(中文)"
|
||||
examples: ["用户", "订单", "工序"]
|
||||
|
||||
- name: "entity_class"
|
||||
type: "string"
|
||||
required: true
|
||||
description: "实体类名(英文,首字母大写)"
|
||||
examples: ["User", "Order", "Process"]
|
||||
|
||||
- name: "table_name"
|
||||
type: "string"
|
||||
required: true
|
||||
description: "数据库表名"
|
||||
examples: ["system_user", "pay_order", "mes_process"]
|
||||
|
||||
- name: "is_tenant"
|
||||
type: "boolean"
|
||||
required: false
|
||||
default: true
|
||||
description: "是否多租户实体(true使用TenantBaseDO,false使用BaseDO)"
|
||||
|
||||
- name: "fields"
|
||||
type: "array"
|
||||
required: true
|
||||
description: "业务字段列表"
|
||||
item_schema:
|
||||
name: "string"
|
||||
type: "string"
|
||||
required: "boolean"
|
||||
description: "string"
|
||||
|
||||
- name: "enable_export"
|
||||
type: "boolean"
|
||||
required: false
|
||||
default: false
|
||||
description: "是否启用Excel导出功能"
|
||||
|
||||
- name: "enable_import"
|
||||
type: "boolean"
|
||||
required: false
|
||||
default: false
|
||||
description: "是否启用Excel导入功能"
|
||||
|
||||
- name: "enable_batch"
|
||||
type: "boolean"
|
||||
required: false
|
||||
default: true
|
||||
description: "是否启用批量删除功能"
|
||||
|
||||
# ============================================
|
||||
# 第三部分:执行流程(5阶段)
|
||||
# ============================================
|
||||
workflow:
|
||||
description: "CRUD代码生成按以下5个阶段顺序执行"
|
||||
|
||||
phases:
|
||||
# 阶段1:结构分析
|
||||
- name: "结构分析"
|
||||
description: "分析表结构,确定实体属性和生成范围"
|
||||
duration: "5min"
|
||||
steps:
|
||||
- step: "解析表名和字段定义"
|
||||
action: "读取表结构信息,提取字段列表"
|
||||
- step: "确定DO基类"
|
||||
action: "根据is_tenant参数选择BaseDO或TenantBaseDO"
|
||||
rules:
|
||||
- "is_tenant=true → TenantBaseDO(含tenantId)"
|
||||
- "is_tenant=false → BaseDO"
|
||||
- step: "生成文件清单"
|
||||
action: "根据参数确定需要创建的文件列表"
|
||||
decision_table:
|
||||
- condition: "默认"
|
||||
files: ["DO", "Mapper", "Service", "ServiceImpl", "Controller", "SaveReqVO", "PageReqVO", "RespVO"]
|
||||
- condition: "enable_export=true"
|
||||
add_files: ["ExcelVO", "export-excel接口"]
|
||||
- condition: "enable_import=true"
|
||||
add_files: ["ImportExcelVO", "ImportRespVO", "import接口"]
|
||||
- condition: "enable_batch=true"
|
||||
add_files: ["delete-list接口"]
|
||||
- step: "分配错误码编号"
|
||||
action: "根据模块编号分配错误码段"
|
||||
reference: "api-designer.yaml 第六部分 module_codes"
|
||||
|
||||
# 阶段2:VO生成
|
||||
- name: "VO生成"
|
||||
description: "生成请求和响应VO类"
|
||||
duration: "5min"
|
||||
reference: "entity-implementation.md 第3.6节"
|
||||
steps:
|
||||
- step: "生成 SaveReqVO"
|
||||
description: "新增/修改共用请求对象"
|
||||
template_path: "entity-implementation.md 第621-657行"
|
||||
rules:
|
||||
- "包含id字段(修改时必填)"
|
||||
- "包含所有可创建/更新字段"
|
||||
- "排除:creator, createTime, updater, updateTime, deleted, tenantId"
|
||||
- "添加校验注解(@NotBlank/@NotNull/@Size)"
|
||||
- step: "生成 PageReqVO"
|
||||
description: "分页查询请求对象"
|
||||
template_path: "entity-implementation.md 第661-695行"
|
||||
rules:
|
||||
- "继承 PageParam"
|
||||
- "包含常用查询条件字段"
|
||||
- "时间字段使用 @DateTimeFormat"
|
||||
- step: "生成 RespVO"
|
||||
description: "响应对象"
|
||||
template_path: "entity-implementation.md 第699-733行"
|
||||
rules:
|
||||
- "包含所有展示字段"
|
||||
- "包含 createTime 等只读字段"
|
||||
- step: "生成 ExcelVO(可选)"
|
||||
condition: "enable_export=true 或 enable_import=true"
|
||||
template_path: "entity-implementation.md 第737-773行"
|
||||
|
||||
# 阶段3:DAL生成
|
||||
- name: "DAL生成"
|
||||
description: "生成数据访问层代码"
|
||||
duration: "3min"
|
||||
reference: "entity-implementation.md 第3.2-3.3节"
|
||||
steps:
|
||||
- step: "生成 DO实体类"
|
||||
template_path: "entity-implementation.md 第128-180行"
|
||||
rules:
|
||||
- "@TableName 指定表名"
|
||||
- "@TableId 标注主键"
|
||||
- "继承正确基类"
|
||||
- "枚举字段添加引用注释"
|
||||
- step: "生成 Mapper接口"
|
||||
template_path: "entity-implementation.md 第234-281行"
|
||||
rules:
|
||||
- "继承 BaseMapperX<EntityDO>"
|
||||
- "定义 selectPage 分页查询方法"
|
||||
- "使用 LambdaQueryWrapperX 构建条件"
|
||||
- "使用 xxxIfPresent() 方法链"
|
||||
|
||||
# 阶段4:Service生成
|
||||
- name: "Service生成"
|
||||
description: "生成业务逻辑层代码"
|
||||
duration: "5min"
|
||||
reference: "entity-implementation.md 第3.4节"
|
||||
steps:
|
||||
- step: "生成 Service接口"
|
||||
template_path: "entity-implementation.md 第313-377行"
|
||||
methods:
|
||||
- "createXxx(SaveReqVO) → Long"
|
||||
- "updateXxx(SaveReqVO) → void"
|
||||
- "deleteXxx(Long id) → void"
|
||||
- "getXxx(Long id) → XxxDO"
|
||||
- "getXxxPage(PageReqVO) → PageResult<XxxDO>"
|
||||
- step: "生成 ServiceImpl实现"
|
||||
template_path: "entity-implementation.md 第382-485行"
|
||||
rules:
|
||||
- "@Service + @Validated 注解"
|
||||
- "新增方法校验唯一性"
|
||||
- "更新/删除方法校验存在性"
|
||||
- "使用 exception(ERROR_CODE) 抛异常"
|
||||
- "使用 BeanUtils.toBean() 转换对象"
|
||||
|
||||
# 阶段5:Controller生成
|
||||
- name: "Controller生成"
|
||||
description: "生成HTTP接口层代码"
|
||||
duration: "7min"
|
||||
reference: "api-designer.yaml 第七部分"
|
||||
steps:
|
||||
- step: "生成 Controller类"
|
||||
template_path: "api-designer.yaml 第420-511行"
|
||||
annotations:
|
||||
- "@Tag(name = '管理后台 - {实体名称}')"
|
||||
- "@RestController"
|
||||
- "@RequestMapping('/{module}/{feature}')"
|
||||
- "@Validated"
|
||||
- step: "生成标准端点"
|
||||
endpoints:
|
||||
- endpoint: "/create"
|
||||
method: "POST"
|
||||
template_path: "api-designer.yaml 第67-72行"
|
||||
- endpoint: "/update"
|
||||
method: "PUT"
|
||||
template_path: "api-designer.yaml 第74-79行"
|
||||
- endpoint: "/delete"
|
||||
method: "DELETE"
|
||||
template_path: "api-designer.yaml 第81-87行"
|
||||
- endpoint: "/get"
|
||||
method: "GET"
|
||||
template_path: "api-designer.yaml 第97-103行"
|
||||
- endpoint: "/page"
|
||||
method: "GET"
|
||||
template_path: "api-designer.yaml 第105-110行"
|
||||
- step: "生成扩展端点(可选)"
|
||||
conditionals:
|
||||
- condition: "enable_batch=true"
|
||||
endpoint: "/delete-list"
|
||||
template_path: "api-designer.yaml 第523-531行"
|
||||
- condition: "enable_export=true"
|
||||
endpoint: "/export-excel"
|
||||
template_path: "api-designer.yaml 第119-126行"
|
||||
- condition: "enable_import=true"
|
||||
endpoints: ["/get-import-template", "/import"]
|
||||
template_path: "api-designer.yaml 第534-557行"
|
||||
- step: "生成权限SQL"
|
||||
template_path: "api-designer.yaml 第333-344行"
|
||||
|
||||
# ============================================
|
||||
# 第四部分:输出产物
|
||||
# ============================================
|
||||
output:
|
||||
base_path: "yudao-module-{module}-biz/src/main/java/cn/iocoder/yudao/module/{module}"
|
||||
|
||||
artifacts:
|
||||
# DAL层
|
||||
- name: "DO实体类"
|
||||
path: "dal/dataobject/{feature}/{Entity}DO.java"
|
||||
description: "数据库实体映射类"
|
||||
- name: "Mapper接口"
|
||||
path: "dal/mysql/{feature}/{Entity}Mapper.java"
|
||||
description: "数据访问接口"
|
||||
|
||||
# Service层
|
||||
- name: "Service接口"
|
||||
path: "service/{feature}/{Entity}Service.java"
|
||||
description: "业务服务接口"
|
||||
- name: "Service实现"
|
||||
path: "service/{feature}/{Entity}ServiceImpl.java"
|
||||
description: "业务服务实现类"
|
||||
|
||||
# Controller层
|
||||
- name: "Controller"
|
||||
path: "controller/admin/{feature}/{Entity}Controller.java"
|
||||
description: "HTTP API控制器"
|
||||
|
||||
# VO类
|
||||
- name: "SaveReqVO"
|
||||
path: "controller/admin/{feature}/vo/{Entity}SaveReqVO.java"
|
||||
description: "新增/修改请求对象"
|
||||
- name: "PageReqVO"
|
||||
path: "controller/admin/{feature}/vo/{Entity}PageReqVO.java"
|
||||
description: "分页查询请求对象"
|
||||
- name: "RespVO"
|
||||
path: "controller/admin/{feature}/vo/{Entity}RespVO.java"
|
||||
description: "响应对象"
|
||||
|
||||
# 可选产物
|
||||
- name: "ExcelVO"
|
||||
path: "controller/admin/{feature}/vo/{Entity}ExcelVO.java"
|
||||
condition: "enable_export=true"
|
||||
description: "Excel导出对象"
|
||||
- name: "ImportExcelVO"
|
||||
path: "controller/admin/{feature}/vo/{Entity}ImportExcelVO.java"
|
||||
condition: "enable_import=true"
|
||||
description: "Excel导入对象"
|
||||
- name: "ImportRespVO"
|
||||
path: "controller/admin/{feature}/vo/{Entity}ImportRespVO.java"
|
||||
condition: "enable_import=true"
|
||||
description: "导入结果响应"
|
||||
|
||||
# 配置文件
|
||||
- name: "错误码定义"
|
||||
path: "enums/ErrorCodeConstants.java"
|
||||
action: "追加"
|
||||
description: "业务错误码定义"
|
||||
- name: "权限配置SQL"
|
||||
path: "sql/menu/{module}_{feature}_menu.sql"
|
||||
description: "菜单和按钮权限SQL"
|
||||
|
||||
# ============================================
|
||||
# 第五部分:命名规范速查
|
||||
# ============================================
|
||||
naming_standards:
|
||||
# 文件命名
|
||||
files:
|
||||
do: "{Entity}DO"
|
||||
mapper: "{Entity}Mapper"
|
||||
service: "{Entity}Service"
|
||||
service_impl: "{Entity}ServiceImpl"
|
||||
controller: "{Entity}Controller"
|
||||
vo_save: "{Entity}SaveReqVO"
|
||||
vo_page: "{Entity}PageReqVO"
|
||||
vo_resp: "{Entity}RespVO"
|
||||
vo_excel: "{Entity}ExcelVO"
|
||||
vo_import: "{Entity}ImportExcelVO"
|
||||
|
||||
# 权限标识
|
||||
permission:
|
||||
format: "{module}:{feature}:{operation}"
|
||||
operations:
|
||||
- "query"
|
||||
- "create"
|
||||
- "update"
|
||||
- "delete"
|
||||
- "export"
|
||||
- "import"
|
||||
example: "system:user:create"
|
||||
|
||||
# 错误码
|
||||
error_code:
|
||||
format: "1_{module_code}_{feature_code}_{seq}"
|
||||
module_codes:
|
||||
infra: "001"
|
||||
system: "002"
|
||||
member: "003"
|
||||
pay: "007"
|
||||
product: "008"
|
||||
trade: "011"
|
||||
promotion: "013"
|
||||
example: "1_002_001_000"
|
||||
|
||||
# 数据库表名
|
||||
table:
|
||||
format: "{module}_{feature}"
|
||||
example: "system_user"
|
||||
|
||||
# ============================================
|
||||
# 第六部分:关联文档引用
|
||||
# ============================================
|
||||
references:
|
||||
primary:
|
||||
- path: "skills/design/api-designer.yaml"
|
||||
description: "API设计规范、Controller模板、权限标识、错误码体系"
|
||||
sections:
|
||||
- "第二部分:接口规范(标准端点定义)"
|
||||
- "第五部分:权限标识规范"
|
||||
- "第六部分:错误码体系"
|
||||
- "第七部分:Controller模板"
|
||||
|
||||
- path: "skills/usage/entity-implementation.md"
|
||||
description: "实体类实现完整流程、各层代码模板"
|
||||
sections:
|
||||
- "3.2节:DO实体类模板"
|
||||
- "3.3节:Mapper层模板"
|
||||
- "3.4节:Service层模板"
|
||||
- "3.5节:Controller层模板"
|
||||
- "3.6节:VO类模板"
|
||||
- "3.7节:错误码定义"
|
||||
- "3.8节:权限配置SQL"
|
||||
|
||||
- path: "skills/design/db-designer.yaml"
|
||||
relationship: "前置依赖"
|
||||
description: "数据库表结构设计规范"
|
||||
|
||||
secondary:
|
||||
- path: "skills/modules/{module}/skill-{module}.yaml"
|
||||
relationship: "模块参考"
|
||||
description: "具体模块的技术规范和扩展指南"
|
||||
example: "skills/modules/system/skill-system.yaml"
|
||||
|
||||
# ============================================
|
||||
# 第七部分:质量检查清单
|
||||
# ============================================
|
||||
checklist:
|
||||
before_generate:
|
||||
- item: "确认模块编码正确"
|
||||
check: "module参数在module_codes中有对应编号"
|
||||
- item: "确认表名符合命名规范"
|
||||
check: "表名格式为 {module}_{feature}"
|
||||
- item: "阅读对应模块的skill文档"
|
||||
reference: "skills/modules/{module}/skill-{module}.yaml"
|
||||
- item: "确认字段类型映射正确"
|
||||
check: "数据库类型与Java类型对应"
|
||||
|
||||
during_generate:
|
||||
- item: "DO类继承正确的基类"
|
||||
check: "TenantBaseDO(多租户)或 BaseDO(单租户)"
|
||||
- item: "Mapper使用BaseMapperX和LambdaQueryWrapperX"
|
||||
check: "继承BaseMapperX,条件构建使用xxxIfPresent()"
|
||||
- item: "Service包含业务校验方法"
|
||||
check: "新增校验唯一性,更新/删除校验存在性"
|
||||
- item: "Controller注解完整"
|
||||
check: "@Tag/@RestController/@RequestMapping/@Validated"
|
||||
- item: "接口方法注解完整"
|
||||
check: "@Operation/@PreAuthorize/@Parameter"
|
||||
|
||||
after_generate:
|
||||
- item: "错误码编号符合规范"
|
||||
format: "1_{module_code}_{feature_code}_{seq}"
|
||||
- item: "权限标识格式正确"
|
||||
format: "{module}:{feature}:{operation}"
|
||||
- item: "所有文件包路径正确"
|
||||
base: "cn.iocoder.yudao.module.{module}"
|
||||
- item: "VO类添加@Schema注解"
|
||||
check: "所有字段有description"
|
||||
- item: "导入语句无缺失"
|
||||
check: "所有注解有对应import"
|
||||
|
||||
# ============================================
|
||||
# 第八部分:与原skill的差异调整
|
||||
# ============================================
|
||||
migration_notes:
|
||||
# 命名规范调整
|
||||
naming_adjustments:
|
||||
- original: "CreateDTO"
|
||||
target: "XxxSaveReqVO"
|
||||
reason: "yudao系统新增/修改共用一个VO"
|
||||
- original: "UpdateDTO"
|
||||
target: "XxxSaveReqVO"
|
||||
reason: "与CreateDTO合并,通过id字段区分"
|
||||
- original: "QueryDTO"
|
||||
target: "XxxPageReqVO"
|
||||
reason: "yudao系统分页查询VO继承PageParam"
|
||||
- original: "VO"
|
||||
target: "XxxRespVO"
|
||||
reason: "响应对象命名规范"
|
||||
- original: "ListVO"
|
||||
target: "XxxRespVO"
|
||||
reason: "分页列表使用同一响应VO"
|
||||
|
||||
# 技术栈调整
|
||||
tech_adjustments:
|
||||
- original: "BaseMapper<Entity>"
|
||||
target: "BaseMapperX<XxxDO>"
|
||||
reason: "yudao扩展的Mapper基类"
|
||||
- original: "LambdaQueryWrapper"
|
||||
target: "LambdaQueryWrapperX"
|
||||
reason: "支持xxxIfPresent()简化条件构建"
|
||||
- original: "Result<T>"
|
||||
target: "CommonResult<T>"
|
||||
reason: "yudao统一响应包装类"
|
||||
- original: "Page<T>"
|
||||
target: "PageResult<T>"
|
||||
reason: "yudao分页结果类"
|
||||
- original: "@Api (Swagger 2)"
|
||||
target: "@Tag (OpenAPI 3)"
|
||||
reason: "Swagger版本升级"
|
||||
- original: "@ApiOperation"
|
||||
target: "@Operation"
|
||||
reason: "OpenAPI 3.0注解"
|
||||
|
||||
# 目录结构调整
|
||||
path_adjustments:
|
||||
- original: "controller/{Entity}Controller.java"
|
||||
target: "controller/admin/{feature}/{Entity}Controller.java"
|
||||
- original: "dto/{Entity}DTO.java"
|
||||
target: "controller/admin/{feature}/vo/{Entity}VO.java"
|
||||
- original: "mapper/{Entity}Mapper.java"
|
||||
target: "dal/mysql/{feature}/{Entity}Mapper.java"
|
||||
- original: "entity/{Entity}.java"
|
||||
target: "dal/dataobject/{feature}/{Entity}DO.java"
|
||||
|
||||
# ============================================
|
||||
# 快速参考
|
||||
# ============================================
|
||||
quick_reference:
|
||||
standard_endpoints:
|
||||
POST_create: "创建 → CommonResult<Long>"
|
||||
PUT_update: "更新 → CommonResult<Boolean>"
|
||||
DELETE_delete: "删除 → CommonResult<Boolean>"
|
||||
GET_get: "详情 → CommonResult<XxxRespVO>"
|
||||
GET_page: "分页 → CommonResult<PageResult<XxxRespVO>>"
|
||||
DELETE_delete-list: "批量删除 → CommonResult<Boolean>"
|
||||
GET_export-excel: "导出 → void"
|
||||
POST_import: "导入 → CommonResult<XxxImportRespVO>"
|
||||
|
||||
permission_operations:
|
||||
query: "查询(get/page/list)"
|
||||
create: "新增"
|
||||
update: "修改"
|
||||
delete: "删除"
|
||||
export: "导出"
|
||||
import: "导入"
|
||||
|
||||
base_classes:
|
||||
BaseDO: "单租户场景 - creator/createTime/updater/updateTime/deleted"
|
||||
TenantBaseDO: "多租户场景 - 额外含tenantId"
|
||||
|
||||
mapper_methods:
|
||||
selectPage: "分页查询"
|
||||
selectOne: "查询单条"
|
||||
selectList: "查询列表"
|
||||
insert: "插入"
|
||||
updateById: "更新"
|
||||
deleteById: "删除"
|
||||
@@ -1,654 +0,0 @@
|
||||
# 数据库设计 Skill
|
||||
# 根据业务需求生成符合项目规范的 PostgreSQL 表结构
|
||||
# 本项目默认且唯一首选数据库方言为 PostgreSQL;除非用户明确指定,否则禁止生成 MySQL 语法。
|
||||
|
||||
skill:
|
||||
id: "db-designer"
|
||||
name: "数据库设计 Skill"
|
||||
version: "1.0.0"
|
||||
category: "design"
|
||||
description: "根据业务需求自动生成符合项目规范的 PostgreSQL 数据库设计,包括表结构、索引、ER图等"
|
||||
created_at: "2026-03-31"
|
||||
updated_at: "2026-03-31"
|
||||
|
||||
# ============================================
|
||||
# 触发条件
|
||||
# ============================================
|
||||
trigger:
|
||||
commands:
|
||||
- "/db-design"
|
||||
- "/db-designer"
|
||||
|
||||
keywords:
|
||||
- "设计数据库"
|
||||
- "生成表结构"
|
||||
- "创建数据模型"
|
||||
- "建表"
|
||||
- "DDL"
|
||||
|
||||
events:
|
||||
- name: "prd_approved"
|
||||
condition: "PRD文档审核通过"
|
||||
- name: "architecture_defined"
|
||||
condition: "系统架构设计完成"
|
||||
|
||||
# ============================================
|
||||
# 输入参数
|
||||
# ============================================
|
||||
input:
|
||||
parameters:
|
||||
- name: "module_code"
|
||||
type: "string"
|
||||
required: true
|
||||
description: "模块编码(如 mes、erp、crm、bpm 等)"
|
||||
examples: ["mes", "erp", "crm", "bpm", "pay", "fz"]
|
||||
|
||||
- name: "table_name"
|
||||
type: "string"
|
||||
required: true
|
||||
description: "表名(不含前缀)"
|
||||
examples: ["work_order", "product", "customer", "letter"]
|
||||
|
||||
- name: "business_fields"
|
||||
type: "array"
|
||||
required: true
|
||||
description: "业务字段列表"
|
||||
|
||||
- name: "indexes"
|
||||
type: "array"
|
||||
required: false
|
||||
default: []
|
||||
description: "额外索引定义"
|
||||
|
||||
- name: "create_date"
|
||||
type: "string"
|
||||
required: false
|
||||
default: "today"
|
||||
description: "创建日期(用于文件命名)"
|
||||
|
||||
# ============================================
|
||||
# 设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
business_position: "数据库设计是业务建模的核心环节,将业务需求转化为可存储的数据结构"
|
||||
|
||||
design_principles:
|
||||
- "多租户隔离:所有业务表包含 tenant_id 字段,索引必须包含 tenant_id"
|
||||
- "审计追踪:标准化审计字段,支持数据变更追踪"
|
||||
- "逻辑删除:使用 deleted 字段实现软删除,避免数据物理删除"
|
||||
- "索引优化:根据查询场景设计索引,遵循最左匹配原则"
|
||||
- "命名规范:统一的表名、字段名、索引名命名规则"
|
||||
|
||||
# ============================================
|
||||
# 目录结构规范
|
||||
# ============================================
|
||||
directory_structure:
|
||||
base_path: "sql/postgresql"
|
||||
layout: |
|
||||
sql/postgresql/
|
||||
├── {module}/ # 按模块组织 PostgreSQL 迁移脚本
|
||||
├── table/ # 通用表结构定义
|
||||
│ ├── create/ # 建表脚本
|
||||
│ └── update/ # 表结构更新脚本
|
||||
├── index/ # 索引定义
|
||||
├── view/ # 视图定义
|
||||
├── data/ # 初始数据
|
||||
└── init/ # 模块初始化脚本
|
||||
|
||||
directories:
|
||||
- name: "table/create"
|
||||
purpose: "存放建表 SQL 脚本"
|
||||
note: "每个表一个文件,按日期命名"
|
||||
- name: "table/update"
|
||||
purpose: "存放表结构更新 SQL 脚本"
|
||||
note: "ALTER TABLE、ADD COLUMN 等增量变更"
|
||||
- name: "sequence"
|
||||
purpose: "需要显式序列时存放序列脚本"
|
||||
note: "默认优先使用 GENERATED BY DEFAULT AS IDENTITY;仅兼容既有 @KeySequence 约定时显式建序列"
|
||||
- name: "index"
|
||||
purpose: "存放索引创建脚本"
|
||||
note: "可按表名组织子目录"
|
||||
- name: "view"
|
||||
purpose: "存放视图创建脚本"
|
||||
note: "复杂查询可封装为视图"
|
||||
- name: "data"
|
||||
purpose: "存放初始数据 SQL 脚本"
|
||||
note: "INSERT 语句,初始化字典、菜单等"
|
||||
- name: "init"
|
||||
purpose: "存放模块初始化脚本"
|
||||
note: "按模块组织,如 bpm.sql, quartz.sql"
|
||||
|
||||
# ============================================
|
||||
# 文件命名规范
|
||||
# ============================================
|
||||
file_naming:
|
||||
# 建表文件命名
|
||||
create_table:
|
||||
format: "YYYY_MM_DD_{表名}_create.sql"
|
||||
examples:
|
||||
- "2025_08_09_fz_letter_create.sql"
|
||||
- "2026_03_25_mes_work_order_create.sql"
|
||||
rules:
|
||||
- "日期前缀使用实际创建日期"
|
||||
- "表名使用小写字母和下划线"
|
||||
- "统一使用 _create.sql 后缀"
|
||||
|
||||
# 更新文件命名
|
||||
update_table:
|
||||
format: "YYYY_MM_DD_{表名}_update.sql"
|
||||
examples:
|
||||
- "2025_08_15_fz_metaletter_update.sql"
|
||||
- "2026_01_07_fz_attachment_rel_update.sql"
|
||||
rules:
|
||||
- "日期前缀使用实际更新日期"
|
||||
- "同一表多次更新使用不同日期"
|
||||
- "统一使用 _update.sql 后缀"
|
||||
special_cases:
|
||||
- pattern: "YYYY_MM_DD_{表名}_index_update.sql"
|
||||
purpose: "专门用于索引更新"
|
||||
|
||||
# 序列文件命名
|
||||
sequence:
|
||||
format: "YYYY_MM_DD_{表名}_seq_create.sql"
|
||||
examples:
|
||||
- "2025_07_24_fz_metaletter_seq_create.sql"
|
||||
rules:
|
||||
- "与建表日期保持一致"
|
||||
- "序列名通常为 表名_seq"
|
||||
|
||||
# 初始数据文件命名
|
||||
initial_data:
|
||||
format: "模块名.sql"
|
||||
examples:
|
||||
- "fazhi.sql"
|
||||
- "bpm.sql"
|
||||
- "quartz.sql"
|
||||
- "ruoyi-vue-pro.sql"
|
||||
rules:
|
||||
- "按模块或功能命名"
|
||||
- "包含该模块的初始数据"
|
||||
|
||||
# ============================================
|
||||
# 表命名规范
|
||||
# ============================================
|
||||
table_naming:
|
||||
# 表名前缀规范
|
||||
prefixes:
|
||||
- prefix: "sys_"
|
||||
usage: "系统核心表"
|
||||
examples: ["sys_user", "sys_role", "sys_menu", "sys_dict_data"]
|
||||
- prefix: "mes_"
|
||||
usage: "制造执行系统表"
|
||||
examples: ["mes_work_order", "mes_product", "mes_workshop"]
|
||||
- prefix: "erp_"
|
||||
usage: "企业资源计划表"
|
||||
examples: ["erp_purchase", "erp_sale", "erp_inventory"]
|
||||
- prefix: "crm_"
|
||||
usage: "客户关系管理表"
|
||||
examples: ["crm_customer", "crm_contract", "crm_clue"]
|
||||
- prefix: "bpm_"
|
||||
usage: "工作流表"
|
||||
examples: ["bpm_process_definition", "bpm_process_instance"]
|
||||
- prefix: "pay_"
|
||||
usage: "支付模块表"
|
||||
examples: ["pay_order", "pay_refund", "pay_channel"]
|
||||
- prefix: "member_"
|
||||
usage: "会员模块表"
|
||||
examples: ["member_user", "member_level", "member_address"]
|
||||
- prefix: "infra_"
|
||||
usage: "基础设施表"
|
||||
examples: ["infra_file", "infra_config"]
|
||||
- prefix: "fz_"
|
||||
usage: "法制业务表"
|
||||
examples: ["fz_letter", "fz_replay", "fz_handle_unit"]
|
||||
|
||||
# 表名规则
|
||||
rules:
|
||||
- "使用小写字母,单词间用下划线分隔"
|
||||
- "使用名词,表示实体或关系"
|
||||
- "避免使用 PostgreSQL 保留字"
|
||||
- "长度不超过 64 个字符"
|
||||
- "关联表命名:主表_关联表_rel,如 fz_letter_label_rel"
|
||||
|
||||
# 字段命名规则
|
||||
column_rules:
|
||||
- "使用小写字母和下划线"
|
||||
- "布尔类型使用 is_ 前缀,如 is_deleted, is_enabled"
|
||||
- "时间类型使用 _time 或 _date 后缀"
|
||||
- "外键使用 关联表_id 格式,如 user_id, dept_id"
|
||||
- "主键统一命名为 id"
|
||||
|
||||
# ============================================
|
||||
# 必需字段规范
|
||||
# ============================================
|
||||
required_columns:
|
||||
# 字段定义
|
||||
columns:
|
||||
- name: "id"
|
||||
type: "BIGINT"
|
||||
constraint: "GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY"
|
||||
comment: "主键ID"
|
||||
position: 1
|
||||
|
||||
- name: "tenant_id"
|
||||
type: "BIGINT"
|
||||
default: "0"
|
||||
constraint: "NOT NULL"
|
||||
comment: "租户编号"
|
||||
position: 2
|
||||
note: "所有业务表必须包含,位于 id 之后"
|
||||
|
||||
- name: "creator"
|
||||
type: "VARCHAR(64)"
|
||||
default: "''"
|
||||
comment: "创建者"
|
||||
|
||||
- name: "create_time"
|
||||
type: "TIMESTAMP"
|
||||
default: "CURRENT_TIMESTAMP"
|
||||
constraint: "NOT NULL"
|
||||
comment: "创建时间"
|
||||
|
||||
- name: "updater"
|
||||
type: "VARCHAR(64)"
|
||||
default: "''"
|
||||
comment: "更新者"
|
||||
|
||||
- name: "update_time"
|
||||
type: "TIMESTAMP"
|
||||
default: "CURRENT_TIMESTAMP"
|
||||
constraint: "NOT NULL"
|
||||
comment: "更新时间"
|
||||
note: "PostgreSQL 不支持 ON UPDATE CURRENT_TIMESTAMP,由应用层或触发器维护"
|
||||
|
||||
- name: "deleted"
|
||||
type: "BOOLEAN"
|
||||
default: "false"
|
||||
constraint: "NOT NULL"
|
||||
comment: "是否删除"
|
||||
|
||||
# 可选备注字段
|
||||
remark:
|
||||
name: "remark"
|
||||
type: "VARCHAR(500)"
|
||||
default: "NULL"
|
||||
comment: "备注"
|
||||
position: "before_audit"
|
||||
|
||||
# ============================================
|
||||
# 索引设计规范
|
||||
# ============================================
|
||||
index_standards:
|
||||
# 索引命名规则
|
||||
naming:
|
||||
primary_key: "PRIMARY KEY (id)"
|
||||
unique_key: "uk_{字段名} 或 uk_{字段1_字段2}"
|
||||
normal_index: "idx_{字段名} 或 idx_{字段1_字段2}"
|
||||
|
||||
# 索引设计原则
|
||||
principles:
|
||||
- "所有索引必须包含 tenant_id 作为第一列"
|
||||
- "唯一索引必须包含 deleted 字段(软删除场景)"
|
||||
- "外键字段必须建立索引"
|
||||
- "经常用于查询条件的字段建立索引"
|
||||
- "唯一约束字段建立唯一索引"
|
||||
- "组合索引注意字段顺序(最左匹配)"
|
||||
- "避免过多索引,影响写入性能"
|
||||
|
||||
# 索引模板
|
||||
templates:
|
||||
unique_with_tenant:
|
||||
pattern: "CREATE UNIQUE INDEX uk_tenant_{col} ON {table} (tenant_id, {col}) WHERE deleted = false"
|
||||
example: "CREATE UNIQUE INDEX uk_tenant_order ON mes_work_order (tenant_id, order_no) WHERE deleted = false"
|
||||
|
||||
normal_with_tenant:
|
||||
pattern: "CREATE INDEX idx_tenant_{col} ON {table} (tenant_id, {col})"
|
||||
example: "CREATE INDEX idx_tenant_status ON mes_work_order (tenant_id, status)"
|
||||
|
||||
foreign_key:
|
||||
pattern: "CREATE INDEX idx_tenant_{fk} ON {table} (tenant_id, {fk})"
|
||||
example: "CREATE INDEX idx_tenant_line ON mes_work_order (tenant_id, line_id)"
|
||||
|
||||
# ============================================
|
||||
# 建表模板
|
||||
# ============================================
|
||||
create_table_template:
|
||||
structure: |
|
||||
-- ============================================
|
||||
-- 文件名:{filename}
|
||||
-- 描述:{table_comment}
|
||||
-- 作者:{author}
|
||||
-- 日期:{date}
|
||||
-- ============================================
|
||||
|
||||
CREATE TABLE {table_name} (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL DEFAULT 0,
|
||||
-- 业务字段
|
||||
{business_columns}
|
||||
-- 通用字段
|
||||
remark VARCHAR(500) DEFAULT NULL,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
COMMENT ON TABLE {table_name} IS '{table_comment}';
|
||||
{column_comments}
|
||||
{indexes}
|
||||
|
||||
# 字段类型推荐
|
||||
column_types:
|
||||
string_short: "VARCHAR(64)"
|
||||
string_medium: "VARCHAR(255)"
|
||||
string_long: "VARCHAR(500)"
|
||||
text: "TEXT"
|
||||
integer: "INT"
|
||||
bigint: "BIGINT"
|
||||
decimal_amount: "DECIMAL(18,2)"
|
||||
decimal_quantity: "DECIMAL(10,4)"
|
||||
boolean: "BOOLEAN"
|
||||
datetime: "TIMESTAMP"
|
||||
date: "DATE"
|
||||
status: "SMALLINT"
|
||||
|
||||
# ============================================
|
||||
# 更新脚本规范
|
||||
# ============================================
|
||||
update_standards:
|
||||
# 更新文件命名
|
||||
naming: "YYYY_MM_DD_{表名}_update.sql"
|
||||
|
||||
# 字段操作模板
|
||||
operations:
|
||||
add_column: |
|
||||
-- 新增字段并添加字段注释
|
||||
ALTER TABLE {table} ADD COLUMN {column} {type};
|
||||
COMMENT ON COLUMN {table}.{column} IS '{comment}';
|
||||
-- 示例
|
||||
ALTER TABLE fz_letter ADD COLUMN priority SMALLINT DEFAULT 0;
|
||||
COMMENT ON COLUMN fz_letter.priority IS '优先级';
|
||||
|
||||
modify_column: |
|
||||
-- 修改字段类型
|
||||
ALTER TABLE {table} ALTER COLUMN {column} TYPE {new_type} USING {column}::{new_type};
|
||||
COMMENT ON COLUMN {table}.{column} IS '{comment}';
|
||||
-- 示例
|
||||
ALTER TABLE fz_letter ALTER COLUMN content TYPE TEXT USING content::TEXT;
|
||||
COMMENT ON COLUMN fz_letter.content IS '信件内容';
|
||||
|
||||
drop_column: |
|
||||
-- 删除字段(确保无业务依赖)
|
||||
ALTER TABLE {table} DROP COLUMN {column};
|
||||
|
||||
add_index: |
|
||||
-- 添加普通索引
|
||||
CREATE INDEX idx_tenant_{col} ON {table} (tenant_id, {col});
|
||||
-- 添加软删除唯一索引
|
||||
CREATE UNIQUE INDEX uk_tenant_{col} ON {table} (tenant_id, {col}) WHERE deleted = false;
|
||||
|
||||
# 文件头部模板
|
||||
header: |
|
||||
-- ============================================
|
||||
-- 更新说明:{更新目的}
|
||||
-- 更新日期:{YYYY-MM-DD}
|
||||
-- 更新人:{姓名}
|
||||
-- ============================================
|
||||
|
||||
# 注释规范
|
||||
comment_standards:
|
||||
- "文件头部说明更新目的"
|
||||
- "每个变更语句添加注释"
|
||||
- "危险操作(删除字段、删除数据)添加警告注释"
|
||||
|
||||
# ============================================
|
||||
# 数据初始化规范
|
||||
# ============================================
|
||||
data_standards:
|
||||
# INSERT 语句格式
|
||||
insert_format: |
|
||||
-- 插入菜单数据
|
||||
INSERT INTO `sys_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
|
||||
VALUES
|
||||
(1001, '信件管理', '', 2, 1, 0, 'letter', 'ep:document', 'fazhi/letter/index', 0),
|
||||
(1002, '信件查询', 'fz:letter:query', 3, 1, 1001, '', '', '', 0);
|
||||
|
||||
# 字典数据
|
||||
dict_data: |
|
||||
-- 字典类型
|
||||
INSERT INTO `sys_dict_type` (`name`, `type`, `status`, `remark`)
|
||||
VALUES ('信件状态', 'fz_letter_status', 0, '信件流转状态');
|
||||
|
||||
-- 字典数据
|
||||
INSERT INTO `sys_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`)
|
||||
VALUES
|
||||
(1, '待处理', '0', 'fz_letter_status', 0),
|
||||
(2, '处理中', '1', 'fz_letter_status', 0),
|
||||
(3, '已完成', '2', 'fz_letter_status', 0);
|
||||
|
||||
# ============================================
|
||||
# 编写规范
|
||||
# ============================================
|
||||
coding_standards:
|
||||
# SQL 格式
|
||||
formatting:
|
||||
- "关键字使用大写:SELECT, FROM, WHERE, JOIN, ORDER BY"
|
||||
- "表名、字段名使用小写;仅在名称冲突或保留字场景使用双引号"
|
||||
- "每个字段占一行,逗号放在行尾"
|
||||
- "复杂查询使用缩进和换行提高可读性"
|
||||
|
||||
# 注释规范
|
||||
comments:
|
||||
file_header: |
|
||||
-- ============================================
|
||||
-- 文件名:{文件名}
|
||||
-- 描述:{功能描述}
|
||||
-- 作者:{作者}
|
||||
-- 日期:{YYYY-MM-DD}
|
||||
-- ============================================
|
||||
table_comment: "CREATE TABLE 后必须使用 COMMENT ON TABLE 添加表注释"
|
||||
column_comment: "每个字段必须使用 COMMENT ON COLUMN 添加字段注释"
|
||||
index_comment: "复杂索引添加注释说明用途"
|
||||
|
||||
# 事务处理
|
||||
transaction:
|
||||
- "多条关联语句使用事务包裹"
|
||||
- "DDL 语句(部分数据库)自动提交,注意顺序"
|
||||
- "大数据量操作分批执行"
|
||||
|
||||
# ============================================
|
||||
# 版本管理
|
||||
# ============================================
|
||||
version_control:
|
||||
principles:
|
||||
- "建表后表结构变更使用 update 脚本,不修改原文件"
|
||||
- "每个变更独立文件,便于追踪和回滚"
|
||||
- "日期前缀确保文件顺序和变更时间线"
|
||||
- "版本升级时按日期顺序执行所有脚本"
|
||||
|
||||
change_log:
|
||||
format: |
|
||||
-- 变更记录:
|
||||
-- 2025-08-09:创建表
|
||||
-- 2025-11-11:新增 priority 字段
|
||||
-- 2026-01-07:新增 category 字段,添加索引
|
||||
|
||||
# ============================================
|
||||
# 执行流程
|
||||
# ============================================
|
||||
workflow:
|
||||
phases:
|
||||
- name: "需求分析"
|
||||
description: "解析业务需求,提取实体和关系"
|
||||
steps:
|
||||
- "识别核心业务实体"
|
||||
- "分析实体间关系(一对多、多对多)"
|
||||
- "确定实体属性列表"
|
||||
- "确定表名前缀(模块编码)"
|
||||
|
||||
- name: "实体设计"
|
||||
description: "设计实体属性和字段"
|
||||
steps:
|
||||
- "设计业务属性字段"
|
||||
- "添加系统必需字段(审计字段)"
|
||||
- "定义主键策略(BIGINT GENERATED BY DEFAULT AS IDENTITY;兼容既有代码时配合 @KeySequence)"
|
||||
- "设置外键关系"
|
||||
|
||||
- name: "多租户设计"
|
||||
description: "添加租户隔离字段和索引"
|
||||
steps:
|
||||
- "添加 tenant_id 字段(第二位置)"
|
||||
- "确保所有索引包含 tenant_id"
|
||||
- "唯一索引包含 deleted 字段"
|
||||
|
||||
- name: "索引设计"
|
||||
description: "根据查询场景设计索引"
|
||||
steps:
|
||||
- "分析查询场景(列表、详情、关联、搜索)"
|
||||
- "设计主键索引"
|
||||
- "设计唯一索引(业务唯一键)"
|
||||
- "设计普通索引(查询优化)"
|
||||
- "设计外键索引"
|
||||
|
||||
- name: "文件生成"
|
||||
description: "生成符合规范的 SQL 文件"
|
||||
steps:
|
||||
- "确定文件命名(日期前缀)"
|
||||
- "生成文件头部注释"
|
||||
- "生成 CREATE TABLE 语句"
|
||||
- "生成索引定义"
|
||||
- "添加表注释"
|
||||
|
||||
# ============================================
|
||||
# 质量检查清单
|
||||
# ============================================
|
||||
quality_check:
|
||||
before_create:
|
||||
- "确认表名符合命名规范"
|
||||
- "确认包含所有必需字段"
|
||||
- "确认字段类型和长度合理"
|
||||
- "确认添加了必要的索引"
|
||||
- "确认添加了表注释和字段注释"
|
||||
- "确认文件命名符合规范"
|
||||
|
||||
after_create:
|
||||
- "测试建表脚本可正常执行"
|
||||
- "确认所有索引包含 tenant_id"
|
||||
- "确认唯一索引包含 deleted"
|
||||
- "确认外键字段有索引"
|
||||
- "检查索引是否生效"
|
||||
- "更新相关文档"
|
||||
|
||||
before_update:
|
||||
- "确认变更的必要性"
|
||||
- "评估对现有数据的影响"
|
||||
- "准备回滚方案"
|
||||
- "选择低峰期执行"
|
||||
|
||||
# ============================================
|
||||
# 快速参考
|
||||
# ============================================
|
||||
quick_reference:
|
||||
table_prefixes:
|
||||
sys_: "系统表"
|
||||
mes_: "制造执行系统表"
|
||||
erp_: "企业资源计划表"
|
||||
crm_: "客户关系管理表"
|
||||
bpm_: "工作流表"
|
||||
pay_: "支付表"
|
||||
member_: "会员表"
|
||||
infra_: "基础设施表"
|
||||
fz_: "法制业务表"
|
||||
|
||||
file_suffixes:
|
||||
_create.sql: "建表脚本"
|
||||
_update.sql: "更新脚本"
|
||||
_seq_create.sql: "序列脚本"
|
||||
|
||||
common_columns:
|
||||
id: "主键ID(BIGINT)"
|
||||
tenant_id: "租户编号(BIGINT)"
|
||||
creator: "创建者(VARCHAR(64))"
|
||||
create_time: "创建时间(TIMESTAMP)"
|
||||
updater: "更新者(VARCHAR(64))"
|
||||
update_time: "更新时间(TIMESTAMP)"
|
||||
deleted: "逻辑删除(BOOLEAN)"
|
||||
remark: "备注(VARCHAR(500))"
|
||||
|
||||
# ============================================
|
||||
# 示例
|
||||
# ============================================
|
||||
examples:
|
||||
work_order:
|
||||
input:
|
||||
module_code: "mes"
|
||||
table_name: "mes_work_order"
|
||||
table_comment: "生产工单表"
|
||||
business_fields:
|
||||
- name: "order_no"
|
||||
type: "VARCHAR(64)"
|
||||
required: true
|
||||
comment: "工单编号"
|
||||
unique: true
|
||||
- name: "product_id"
|
||||
type: "BIGINT"
|
||||
required: true
|
||||
comment: "产品ID"
|
||||
- name: "status"
|
||||
type: "TINYINT"
|
||||
required: true
|
||||
default: "0"
|
||||
comment: "状态:0-待下发,1-已下发,2-生产中,3-已完成,4-已关闭"
|
||||
|
||||
output_file: "2026_03_25_mes_work_order_create.sql"
|
||||
|
||||
output_sql: |
|
||||
-- PostgreSQL 示例(字段注释通过 COMMENT ON COLUMN 补充)
|
||||
CREATE TABLE mes_work_order (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL DEFAULT 0,
|
||||
order_no VARCHAR(64) NOT NULL,
|
||||
product_id BIGINT NOT NULL,
|
||||
status SMALLINT NOT NULL DEFAULT 0,
|
||||
plan_start_time TIMESTAMP NOT NULL,
|
||||
plan_end_time TIMESTAMP NOT NULL,
|
||||
remark VARCHAR(500) DEFAULT NULL,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
COMMENT ON TABLE mes_work_order IS '生产工单表';
|
||||
CREATE UNIQUE INDEX uk_tenant_order
|
||||
ON mes_work_order (tenant_id, order_no) WHERE deleted = false;
|
||||
CREATE INDEX idx_tenant_status ON mes_work_order (tenant_id, status);
|
||||
CREATE INDEX idx_tenant_time ON mes_work_order (tenant_id, plan_start_time);
|
||||
|
||||
# ============================================
|
||||
# 错误处理
|
||||
# ============================================
|
||||
error_handling:
|
||||
errors:
|
||||
- code: "DB001"
|
||||
message: "表名不符合命名规范"
|
||||
solution: "检查表名前缀和格式"
|
||||
|
||||
- code: "DB002"
|
||||
message: "缺少必需字段"
|
||||
solution: "确保包含 id, tenant_id, 审计字段"
|
||||
|
||||
- code: "DB003"
|
||||
message: "索引缺少 tenant_id"
|
||||
solution: "所有索引必须包含 tenant_id 作为第一列"
|
||||
|
||||
- code: "DB004"
|
||||
message: "文件命名不符合规范"
|
||||
solution: "使用 YYYY_MM_DD_{表名}_create.sql 格式"
|
||||
|
||||
- code: "DB005"
|
||||
message: "唯一索引缺少 deleted"
|
||||
solution: "唯一索引必须包含 deleted 字段"
|
||||
@@ -1,836 +0,0 @@
|
||||
# 实体类设计 Skill
|
||||
# 根据 PostgreSQL 表结构生成符合 yudao-vue-pro 规范的实体类(DO)
|
||||
# 本项目默认数据库方言为 PostgreSQL,类型映射不得回退为 MySQL 默认。
|
||||
|
||||
skill:
|
||||
id: "design-entity"
|
||||
name: "实体类设计 Skill"
|
||||
version: "1.0.0"
|
||||
category: "design"
|
||||
description: "根据 PostgreSQL 表结构自动生成符合 yudao 项目规范的实体类(DO),支持多租户、审计字段、JSONB 字段等特性"
|
||||
created_at: "2026-03-31"
|
||||
updated_at: "2026-03-31"
|
||||
|
||||
# ============================================
|
||||
# 触发条件
|
||||
# ============================================
|
||||
trigger:
|
||||
# 命令触发
|
||||
commands:
|
||||
- "/entity-design"
|
||||
- "/design-entity"
|
||||
- "/entity-generator"
|
||||
|
||||
# 自然语言触发
|
||||
keywords:
|
||||
- "生成实体类"
|
||||
- "创建DO"
|
||||
- "生成DO实体"
|
||||
- "实体类设计"
|
||||
- "数据对象"
|
||||
|
||||
# 事件触发
|
||||
events:
|
||||
- name: "db_design_completed"
|
||||
condition: "数据库表结构设计完成"
|
||||
- name: "sql_created"
|
||||
condition: "建表 SQL 文件创建完成"
|
||||
|
||||
# ============================================
|
||||
# 输入参数
|
||||
# ============================================
|
||||
input:
|
||||
parameters:
|
||||
- name: "module_code"
|
||||
type: "string"
|
||||
required: true
|
||||
description: "模块编码(如 mes、erp、crm、system)"
|
||||
examples: ["mes", "erp", "crm", "system", "pay"]
|
||||
|
||||
- name: "table_name"
|
||||
type: "string"
|
||||
required: true
|
||||
description: "表名(含前缀,如 mes_work_order)"
|
||||
examples: ["mes_work_order", "erp_product", "system_user"]
|
||||
|
||||
- name: "table_comment"
|
||||
type: "string"
|
||||
required: true
|
||||
description: "表注释/描述"
|
||||
|
||||
- name: "business_fields"
|
||||
type: "array"
|
||||
required: true
|
||||
description: "业务字段列表(不含通用字段)"
|
||||
schema:
|
||||
- name: "字段名"
|
||||
type: "数据类型"
|
||||
required: "是否必填"
|
||||
comment: "字段注释"
|
||||
enum_ref: "枚举引用(可选)"
|
||||
json_field: "是否JSON字段(可选)"
|
||||
java_type: "JSON字段的Java类型(可选,如 Set<Long>)"
|
||||
|
||||
- name: "tenant_type"
|
||||
type: "string"
|
||||
required: false
|
||||
default: "multi"
|
||||
description: "租户类型:single-单租户,multi-多租户,ignore-忽略租户"
|
||||
examples: ["single", "multi", "ignore"]
|
||||
|
||||
- name: "entity_name"
|
||||
type: "string"
|
||||
required: false
|
||||
default: "auto"
|
||||
description: "实体类名称(默认根据表名自动生成)"
|
||||
|
||||
- name: "feature"
|
||||
type: "string"
|
||||
required: false
|
||||
default: "auto"
|
||||
description: "功能子目录名(默认根据实体名推断)"
|
||||
|
||||
# ============================================
|
||||
# 设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
business_position: "实体类(DO)是数据库表与业务逻辑之间的桥梁,将关系型数据映射为对象"
|
||||
|
||||
design_principles:
|
||||
- "继承体系:多租户继承 TenantBaseDO,单租户继承 BaseDO"
|
||||
- "审计字段:由基类提供,业务实体不重复定义"
|
||||
- "字段注释:使用 JavaDoc 格式,不使用 Swagger 注解"
|
||||
- "JSON 字段:使用 JacksonTypeHandler 处理复杂类型"
|
||||
- "命名规范:实体类以 DO 结尾,与表名保持映射关系"
|
||||
- "枚举引用:在注释中说明枚举类,字段使用 Integer 类型"
|
||||
|
||||
inheritance_model:
|
||||
base_hierarchy:
|
||||
BaseDO:
|
||||
package: "cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO"
|
||||
fields: ["createTime", "updateTime", "creator", "updater", "deleted"]
|
||||
use_case: "单租户业务表"
|
||||
note: "deleted 字段为 Boolean 类型"
|
||||
TenantBaseDO:
|
||||
package: "cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO"
|
||||
parent: "BaseDO"
|
||||
extra_fields: ["tenantId"]
|
||||
use_case: "多租户业务表(绝大多数场景)"
|
||||
special_annotation:
|
||||
TenantIgnore:
|
||||
package: "cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore"
|
||||
use_case: "租户表、套餐表等需要忽略多租户过滤的实体"
|
||||
|
||||
# ============================================
|
||||
# 命名转换规则
|
||||
# ============================================
|
||||
naming:
|
||||
# 表名 -> 实体类名转换
|
||||
table_to_class:
|
||||
rules:
|
||||
- "去除表前缀(如 mes_、erp_、system_、crm_、bpm_、pay_)"
|
||||
- "snake_case 转 PascalCase"
|
||||
- "添加 DO 后缀"
|
||||
- "特殊表名特殊处理"
|
||||
examples:
|
||||
- table: "mes_work_order"
|
||||
class: "WorkOrderDO"
|
||||
- table: "erp_product"
|
||||
class: "ProductDO"
|
||||
- table: "system_users"
|
||||
class: "AdminUserDO"
|
||||
- table: "system_role"
|
||||
class: "RoleDO"
|
||||
- table: "crm_customer"
|
||||
class: "CustomerDO"
|
||||
|
||||
# 字段名 -> 属性名转换
|
||||
column_to_field:
|
||||
rules:
|
||||
- "snake_case 转 camelCase"
|
||||
- "保持与数据库字段映射"
|
||||
examples:
|
||||
- column: "work_order_no"
|
||||
field: "workOrderNo"
|
||||
- column: "create_time"
|
||||
field: "createTime"
|
||||
- column: "tenant_id"
|
||||
field: "tenantId"
|
||||
|
||||
# 特殊表名映射
|
||||
special_mappings:
|
||||
system_users: "AdminUserDO"
|
||||
system_menu: "MenuDO"
|
||||
system_tenant: "TenantDO"
|
||||
system_dict_data: "DictDataDO"
|
||||
system_dict_type: "DictTypeDO"
|
||||
|
||||
# 表前缀映射(模块编码)
|
||||
prefix_mapping:
|
||||
sys: "system"
|
||||
mes: "mes"
|
||||
erp: "erp"
|
||||
crm: "crm"
|
||||
bpm: "bpm"
|
||||
pay: "pay"
|
||||
member: "member"
|
||||
infra: "infra"
|
||||
|
||||
# ============================================
|
||||
# 类型映射规则
|
||||
# ============================================
|
||||
type_mapping:
|
||||
postgresql_to_java:
|
||||
- postgresql: "BIGINT"
|
||||
java: "Long"
|
||||
note: "主键、外键、数量类字段"
|
||||
|
||||
- postgresql: "INT / INTEGER"
|
||||
java: "Integer"
|
||||
note: "状态、排序、计数类字段"
|
||||
|
||||
- postgresql: "TINYINT"
|
||||
java: "Integer"
|
||||
note: "枚举字段、开关字段"
|
||||
|
||||
- postgresql: "SMALLINT"
|
||||
java: "Integer"
|
||||
note: "小范围数值"
|
||||
|
||||
- postgresql: "DECIMAL(p,s)"
|
||||
java: "BigDecimal"
|
||||
note: "金额、精确数值"
|
||||
import: "java.math.BigDecimal"
|
||||
|
||||
- postgresql: "FLOAT / DOUBLE"
|
||||
java: "Double"
|
||||
note: "浮点数"
|
||||
|
||||
- postgresql: "VARCHAR / CHAR"
|
||||
java: "String"
|
||||
note: "字符串"
|
||||
|
||||
- postgresql: "TEXT / LONGTEXT"
|
||||
java: "String"
|
||||
note: "大文本"
|
||||
|
||||
- postgresql: "TIMESTAMP / TIMESTAMPTZ"
|
||||
java: "LocalDateTime"
|
||||
note: "日期时间"
|
||||
import: "java.time.LocalDateTime"
|
||||
|
||||
- postgresql: "DATE"
|
||||
java: "LocalDate"
|
||||
note: "日期"
|
||||
import: "java.time.LocalDate"
|
||||
|
||||
- postgresql: "TIME"
|
||||
java: "LocalTime"
|
||||
note: "时间"
|
||||
import: "java.time.LocalTime"
|
||||
|
||||
- postgresql: "BOOLEAN"
|
||||
java: "Boolean"
|
||||
note: "布尔值"
|
||||
|
||||
- postgresql: "BLOB / LONGBLOB"
|
||||
java: "byte[]"
|
||||
note: "二进制数据"
|
||||
|
||||
- postgresql: "JSON"
|
||||
java: "复杂类型"
|
||||
handler: "JacksonTypeHandler"
|
||||
note: "JSON 数据,需特殊处理"
|
||||
|
||||
# JSON 字段类型映射
|
||||
json_type_mapping:
|
||||
- pattern: "ID数组"
|
||||
java_type: "Set<Long>"
|
||||
import: "java.util.Set"
|
||||
- pattern: "字符串数组"
|
||||
java_type: "List<String>"
|
||||
import: "java.util.List"
|
||||
- pattern: "键值对对象"
|
||||
java_type: "Map<String, Object>"
|
||||
import: "java.util.Map"
|
||||
|
||||
# ============================================
|
||||
# 注解规范
|
||||
# ============================================
|
||||
annotations:
|
||||
# 类级别注解
|
||||
class_annotations:
|
||||
required:
|
||||
- annotation: "@TableName"
|
||||
format: "@TableName(value = \"{table_name}\", autoResultMap = true)"
|
||||
import: "com.baomidou.mybatisplus.annotation.TableName"
|
||||
note: "autoResultMap = true 用于支持 TypeHandler"
|
||||
|
||||
- annotation: "@KeySequence"
|
||||
format: "@KeySequence(\"{table_name}_seq\")"
|
||||
import: "com.baomidou.mybatisplus.annotation.KeySequence"
|
||||
note: "PostgreSQL 主键约定;与项目既有 @KeySequence 规范保持一致"
|
||||
|
||||
- annotation: "@Data"
|
||||
import: "lombok.Data"
|
||||
note: "Lombok getter/setter"
|
||||
|
||||
- annotation: "@EqualsAndHashCode"
|
||||
format: "@EqualsAndHashCode(callSuper = true)"
|
||||
import: "lombok.EqualsAndHashCode"
|
||||
note: "必须 callSuper = true 以包含基类字段"
|
||||
|
||||
optional:
|
||||
- annotation: "@Builder"
|
||||
import: "lombok.Builder"
|
||||
note: "构建器模式"
|
||||
|
||||
- annotation: "@NoArgsConstructor"
|
||||
import: "lombok.NoArgsConstructor"
|
||||
note: "无参构造(与 Builder 配合)"
|
||||
|
||||
- annotation: "@AllArgsConstructor"
|
||||
import: "lombok.AllArgsConstructor"
|
||||
note: "全参构造(与 Builder 配合)"
|
||||
|
||||
- annotation: "@TenantIgnore"
|
||||
import: "cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore"
|
||||
note: "忽略多租户过滤(仅用于租户表等特殊场景)"
|
||||
|
||||
# 字段级别注解
|
||||
field_annotations:
|
||||
primary_key:
|
||||
- annotation: "@TableId"
|
||||
import: "com.baomidou.mybatisplus.annotation.TableId"
|
||||
note: "主键字段,默认使用雪花算法"
|
||||
|
||||
json_field:
|
||||
- annotation: "@TableField"
|
||||
format: "@TableField(typeHandler = JacksonTypeHandler.class)"
|
||||
import: "com.baomidou.mybatisplus.annotation.TableField"
|
||||
handler_import: "com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"
|
||||
note: "JSON 类型字段,需要 @TableName 包含 autoResultMap = true"
|
||||
|
||||
# ============================================
|
||||
# 代码模板
|
||||
# ============================================
|
||||
templates:
|
||||
# 多租户实体模板(最常用)
|
||||
tenant_entity:
|
||||
description: "多租户业务表实体类"
|
||||
base_class: "TenantBaseDO"
|
||||
code: |
|
||||
package cn.iocoder.yudao.module.{module}.dal.dataobject.{feature};
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
{additional_imports}
|
||||
|
||||
/**
|
||||
* {table_comment} DO
|
||||
*
|
||||
* @author {author}
|
||||
*/
|
||||
@TableName(value = "{table_name}", autoResultMap = true)
|
||||
@KeySequence("{table_name}_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class {EntityName}DO extends TenantBaseDO {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
{business_fields}
|
||||
|
||||
}
|
||||
|
||||
# 单租户实体模板
|
||||
single_entity:
|
||||
description: "单租户业务表实体类"
|
||||
base_class: "BaseDO"
|
||||
code: |
|
||||
package cn.iocoder.yudao.module.{module}.dal.dataobject.{feature};
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
{additional_imports}
|
||||
|
||||
/**
|
||||
* {table_comment} DO
|
||||
*
|
||||
* @author {author}
|
||||
*/
|
||||
@TableName(value = "{table_name}", autoResultMap = true)
|
||||
@KeySequence("{table_name}_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class {EntityName}DO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
{business_fields}
|
||||
|
||||
}
|
||||
|
||||
# 忽略多租户实体模板
|
||||
tenant_ignore_entity:
|
||||
description: "需要忽略多租户过滤的实体类(如租户表、套餐表)"
|
||||
base_class: "BaseDO"
|
||||
extra_annotation: "@TenantIgnore"
|
||||
code: |
|
||||
package cn.iocoder.yudao.module.{module}.dal.dataobject.{feature};
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
{additional_imports}
|
||||
|
||||
/**
|
||||
* {table_comment} DO
|
||||
*
|
||||
* @author {author}
|
||||
*/
|
||||
@TenantIgnore
|
||||
@TableName(value = "{table_name}", autoResultMap = true)
|
||||
@KeySequence("{table_name}_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class {EntityName}DO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
{business_fields}
|
||||
|
||||
}
|
||||
|
||||
# 业务字段模板
|
||||
business_field:
|
||||
normal: |
|
||||
/**
|
||||
* {字段注释}
|
||||
{enum_reference}
|
||||
*/
|
||||
private {JavaType} {fieldName};
|
||||
|
||||
json_field: |
|
||||
/**
|
||||
* {字段注释}
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private {JavaType} {fieldName};
|
||||
|
||||
# ============================================
|
||||
# 输出规范
|
||||
# ============================================
|
||||
output:
|
||||
# 文件输出位置
|
||||
file_location:
|
||||
path_pattern: "yudao-module-{module}/src/main/java/cn/iocoder/yudao/module/{module}/dal/dataobject/{feature}/{EntityName}DO.java"
|
||||
example: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/workorder/WorkOrderDO.java"
|
||||
|
||||
# 导入语句规范
|
||||
imports:
|
||||
base_imports:
|
||||
- "com.baomidou.mybatisplus.annotation.TableName"
|
||||
- "com.baomidou.mybatisplus.annotation.TableId"
|
||||
- "com.baomidou.mybatisplus.annotation.KeySequence"
|
||||
- "lombok.Data"
|
||||
- "lombok.EqualsAndHashCode"
|
||||
|
||||
tenant_imports:
|
||||
- "cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO"
|
||||
|
||||
single_imports:
|
||||
- "cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO"
|
||||
|
||||
json_handler_import:
|
||||
- "com.baomidou.mybatisplus.annotation.TableField"
|
||||
- "com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"
|
||||
|
||||
builder_imports:
|
||||
- "lombok.Builder"
|
||||
- "lombok.NoArgsConstructor"
|
||||
- "lombok.AllArgsConstructor"
|
||||
|
||||
# 产物清单
|
||||
artifacts:
|
||||
- name: "实体类文件"
|
||||
type: "code"
|
||||
path: "dal/dataobject/{feature}/{EntityName}DO.java"
|
||||
|
||||
# ============================================
|
||||
# 执行流程
|
||||
# ============================================
|
||||
workflow:
|
||||
phases:
|
||||
- name: "表结构解析"
|
||||
description: "解析数据库表结构,提取元数据"
|
||||
steps:
|
||||
- "识别表名、表前缀、表注释"
|
||||
- "提取业务字段列表(排除通用字段:id, tenant_id, creator, create_time, updater, update_time, deleted)"
|
||||
- "识别主键、索引信息"
|
||||
- "识别 JSON 字段、枚举字段"
|
||||
|
||||
- name: "命名转换"
|
||||
description: "将数据库命名转换为 Java 命名"
|
||||
steps:
|
||||
- "表名 -> 实体类名(去除前缀 + PascalCase + DO 后缀)"
|
||||
- "字段名 -> 属性名(camelCase)"
|
||||
- "处理特殊表名映射"
|
||||
|
||||
- name: "类型映射"
|
||||
description: "将 MySQL 类型映射为 Java 类型"
|
||||
steps:
|
||||
- "标准类型映射(BIGINT -> Long 等)"
|
||||
- "JSON 字段识别和处理"
|
||||
- "枚举字段处理(Integer + 注释引用)"
|
||||
|
||||
- name: "继承选择"
|
||||
description: "确定实体类继承的基类"
|
||||
steps:
|
||||
- "判断租户类型(multi/single/ignore)"
|
||||
- "选择对应基类(TenantBaseDO/BaseDO)"
|
||||
- "判断是否需要 @TenantIgnore"
|
||||
|
||||
- name: "代码生成"
|
||||
description: "生成实体类代码"
|
||||
steps:
|
||||
- "生成类注释和注解"
|
||||
- "生成主键字段"
|
||||
- "生成业务字段(含 JavaDoc 注释)"
|
||||
- "生成导入语句(按规范分组排序)"
|
||||
- "格式化代码"
|
||||
|
||||
- name: "质量检查"
|
||||
description: "验证生成的实体类"
|
||||
steps:
|
||||
- "检查注解完整性"
|
||||
- "检查字段注释格式"
|
||||
- "检查继承正确性"
|
||||
- "检查命名规范"
|
||||
|
||||
# ============================================
|
||||
# 质量检查清单
|
||||
# ============================================
|
||||
quality_check:
|
||||
before_generate:
|
||||
- "确认表名符合命名规范"
|
||||
- "确认业务字段列表完整"
|
||||
- "确认租户类型选择正确"
|
||||
- "确认字段类型映射正确"
|
||||
|
||||
after_generate:
|
||||
- "实体类名称以 DO 结尾"
|
||||
- "@TableName 注解包含 autoResultMap = true"
|
||||
- "@EqualsAndHashCode 包含 callSuper = true"
|
||||
- "继承正确的基类"
|
||||
- "主键字段使用 @TableId 注解"
|
||||
- "所有字段使用 JavaDoc 注释格式"
|
||||
- "JSON 字段使用 JacksonTypeHandler"
|
||||
- "枚举字段注释包含枚举引用"
|
||||
- "不包含 Swagger @Schema 注解"
|
||||
- "不重复定义审计字段(已在基类中)"
|
||||
|
||||
# ============================================
|
||||
# 示例
|
||||
# ============================================
|
||||
examples:
|
||||
# 多租户实体示例
|
||||
work_order:
|
||||
input:
|
||||
module_code: "mes"
|
||||
table_name: "mes_work_order"
|
||||
table_comment: "生产工单"
|
||||
tenant_type: "multi"
|
||||
business_fields:
|
||||
- name: "order_no"
|
||||
type: "VARCHAR(64)"
|
||||
comment: "工单编号"
|
||||
unique: true
|
||||
- name: "product_id"
|
||||
type: "BIGINT"
|
||||
comment: "产品ID"
|
||||
- name: "status"
|
||||
type: "TINYINT"
|
||||
comment: "状态"
|
||||
enum_ref: "WorkOrderStatusEnum"
|
||||
- name: "plan_qty"
|
||||
type: "INT"
|
||||
comment: "计划数量"
|
||||
|
||||
output_class_name: "WorkOrderDO"
|
||||
output_file: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/workorder/WorkOrderDO.java"
|
||||
|
||||
output_code: |
|
||||
package cn.iocoder.yudao.module.mes.dal.dataobject.workorder;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import cn.iocoder.yudao.module.mes.enums.WorkOrderStatusEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 生产工单 DO
|
||||
*
|
||||
* @author yudao
|
||||
*/
|
||||
@TableName(value = "mes_work_order", autoResultMap = true)
|
||||
@KeySequence("mes_work_order_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class WorkOrderDO extends TenantBaseDO {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 工单编号
|
||||
*/
|
||||
private String orderNo;
|
||||
|
||||
/**
|
||||
* 产品ID
|
||||
*/
|
||||
private Long productId;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*
|
||||
* 枚举 {@link WorkOrderStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 计划数量
|
||||
*/
|
||||
private Integer planQty;
|
||||
|
||||
}
|
||||
|
||||
# JSON 字段实体示例
|
||||
role_entity:
|
||||
input:
|
||||
module_code: "system"
|
||||
table_name: "system_role"
|
||||
table_comment: "角色"
|
||||
tenant_type: "multi"
|
||||
business_fields:
|
||||
- name: "name"
|
||||
type: "VARCHAR(100)"
|
||||
comment: "角色名称"
|
||||
- name: "code"
|
||||
type: "VARCHAR(50)"
|
||||
comment: "角色标识"
|
||||
- name: "data_scope_dept_ids"
|
||||
type: "JSON"
|
||||
comment: "数据范围部门ID数组"
|
||||
json_field: true
|
||||
java_type: "Set<Long>"
|
||||
|
||||
output_code: |
|
||||
package cn.iocoder.yudao.module.system.dal.dataobject.permission;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 角色 DO
|
||||
*
|
||||
* @author yudao
|
||||
*/
|
||||
@TableName(value = "system_role", autoResultMap = true)
|
||||
@KeySequence("system_role_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RoleDO extends TenantBaseDO {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 角色名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 角色标识
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 数据范围(指定部门数组)
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private Set<Long> dataScopeDeptIds;
|
||||
|
||||
}
|
||||
|
||||
# 忽略租户实体示例
|
||||
tenant_entity:
|
||||
input:
|
||||
module_code: "system"
|
||||
table_name: "system_tenant"
|
||||
table_comment: "租户"
|
||||
tenant_type: "ignore"
|
||||
business_fields:
|
||||
- name: "name"
|
||||
type: "VARCHAR(100)"
|
||||
comment: "租户名称"
|
||||
- name: "contact_name"
|
||||
type: "VARCHAR(50)"
|
||||
comment: "联系人"
|
||||
- name: "status"
|
||||
type: "TINYINT"
|
||||
comment: "状态"
|
||||
enum_ref: "CommonStatusEnum"
|
||||
|
||||
output_code: |
|
||||
package cn.iocoder.yudao.module.system.dal.dataobject.tenant;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 租户 DO
|
||||
*
|
||||
* @author yudao
|
||||
*/
|
||||
@TenantIgnore
|
||||
@TableName(value = "system_tenant", autoResultMap = true)
|
||||
@KeySequence("system_tenant_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TenantDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 租户名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 联系人
|
||||
*/
|
||||
private String contactName;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*
|
||||
* 枚举 {@link CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# 与其他 Skill 的关系
|
||||
# ============================================
|
||||
related_skills:
|
||||
upstream:
|
||||
- skill: "db-designer"
|
||||
relationship: "依赖"
|
||||
description: "依赖数据库表结构设计,从 SQL 解析表信息"
|
||||
path: "skills/design/db-designer.yaml"
|
||||
|
||||
downstream:
|
||||
- skill: "api-designer"
|
||||
relationship: "输出"
|
||||
description: "实体类作为 API 设计的输入"
|
||||
path: "skills/design/api-designer.yaml"
|
||||
|
||||
collaboration:
|
||||
- path: "skills/usage/entity-implementation.md"
|
||||
relationship: "协同"
|
||||
description: "entity-designer 专注实体类设计规范,entity-implementation 提供完整实现流程(含 Mapper、Service 等)"
|
||||
|
||||
# ============================================
|
||||
# 错误处理
|
||||
# ============================================
|
||||
error_handling:
|
||||
errors:
|
||||
- code: "ENT001"
|
||||
message: "表名不符合命名规范"
|
||||
solution: "检查表名前缀和格式,确保包含模块前缀(如 mes_、erp_)"
|
||||
|
||||
- code: "ENT002"
|
||||
message: "缺少必需的表注释"
|
||||
solution: "提供 table_comment 参数"
|
||||
|
||||
- code: "ENT003"
|
||||
message: "字段类型无法映射"
|
||||
solution: "使用默认 String 类型,手动调整"
|
||||
|
||||
- code: "ENT004"
|
||||
message: "JSON 字段缺少 java_type"
|
||||
solution: "为 JSON 字段指定 java_type,如 Set<Long>"
|
||||
|
||||
- code: "ENT005"
|
||||
message: "租户类型选择错误"
|
||||
solution: "multi 使用 TenantBaseDO,single 使用 BaseDO,ignore 使用 @TenantIgnore"
|
||||
|
||||
- code: "ENT006"
|
||||
message: "枚举字段缺少 enum_ref"
|
||||
solution: "为枚举字段提供 enum_ref 参数,在注释中引用枚举类"
|
||||
@@ -1,152 +0,0 @@
|
||||
# Skill 索引文件
|
||||
# 记录所有模块的Skill文档位置和状态
|
||||
|
||||
skills:
|
||||
# 核心模块
|
||||
system:
|
||||
name: "系统管理模块"
|
||||
path: "skills/modules/system/skill-system.yaml"
|
||||
status: "completed"
|
||||
priority: 1
|
||||
lines: 839
|
||||
|
||||
infra:
|
||||
name: "基础设施模块"
|
||||
path: "skills/modules/infra/skill-infra.yaml"
|
||||
status: "completed"
|
||||
priority: 1
|
||||
lines: 700+
|
||||
|
||||
pay:
|
||||
name: "支付模块"
|
||||
path: "skills/modules/pay/skill-pay.yaml"
|
||||
status: "completed"
|
||||
priority: 1
|
||||
lines: 654
|
||||
|
||||
# 业务模块
|
||||
member:
|
||||
name: "会员模块"
|
||||
path: "skills/modules/member/skill-member.yaml"
|
||||
status: "completed"
|
||||
priority: 2
|
||||
lines: 608
|
||||
|
||||
mall:
|
||||
name: "商城模块"
|
||||
path: "skills/modules/mall/skill-mall.yaml"
|
||||
status: "completed"
|
||||
priority: 2
|
||||
lines: 650+
|
||||
|
||||
crm:
|
||||
name: "CRM模块"
|
||||
path: "skills/modules/crm/skill-crm.yaml"
|
||||
status: "completed"
|
||||
priority: 2
|
||||
lines: 850+
|
||||
|
||||
erp:
|
||||
name: "ERP模块"
|
||||
path: "skills/modules/erp/skill-erp.yaml"
|
||||
status: "completed"
|
||||
priority: 3
|
||||
lines: 900+
|
||||
|
||||
# 技术/平台模块
|
||||
bpm:
|
||||
name: "工作流模块"
|
||||
path: "skills/modules/bpm/skill-bpm.yaml"
|
||||
status: "completed"
|
||||
priority: 2
|
||||
lines: 650+
|
||||
|
||||
ai:
|
||||
name: "AI模块"
|
||||
path: "skills/modules/ai/skill-ai.yaml"
|
||||
status: "completed"
|
||||
priority: 3
|
||||
lines: 670+
|
||||
|
||||
iot:
|
||||
name: "物联网模块"
|
||||
path: "skills/modules/iot/skill-iot.yaml"
|
||||
status: "completed"
|
||||
priority: 3
|
||||
lines: 721
|
||||
|
||||
mp:
|
||||
name: "微信公众号模块"
|
||||
path: "skills/modules/mp/skill-mp.yaml"
|
||||
status: "completed"
|
||||
priority: 3
|
||||
lines: 660+
|
||||
|
||||
report:
|
||||
name: "报表模块"
|
||||
path: "skills/modules/report/skill-report.yaml"
|
||||
status: "completed"
|
||||
priority: 3
|
||||
lines: 320+
|
||||
|
||||
# 设计规范
|
||||
design:
|
||||
db:
|
||||
name: "数据库设计"
|
||||
path: "skills/design/db-designer.yaml"
|
||||
status: "completed"
|
||||
description: "数据库表结构设计规范、SQL文件规范、编写规范"
|
||||
lines: 450
|
||||
entity:
|
||||
name: "实体类设计"
|
||||
path: "skills/design/entity-designer.yaml"
|
||||
status: "completed"
|
||||
description: "yudao 实体类(DO)设计规范,包括继承体系、注解规范、命名转换"
|
||||
lines: 520
|
||||
api:
|
||||
name: "API 设计"
|
||||
path: "skills/design/api-designer.yaml"
|
||||
status: "completed"
|
||||
description: "REST API 设计规范、注解、请求响应格式、权限标识"
|
||||
crud:
|
||||
name: "CRUD 代码生成"
|
||||
path: "skills/design/crud-designer.yaml"
|
||||
status: "completed"
|
||||
description: "根据数据库表结构自动生成完整CRUD功能代码"
|
||||
lines: 517
|
||||
|
||||
# 使用样例自动引用说明
|
||||
# 使用样例文档(usage/目录)通过 YAML front matter 声明需要引用的规范文件
|
||||
# AI 读取使用样例时会自动加载相关规范,确保生成的代码符合项目标准
|
||||
usage_auto_reference:
|
||||
description: "使用样例通过 YAML front matter 自动引用设计规范"
|
||||
mechanism: |
|
||||
每个使用样例文档头部包含 references 配置,声明该场景需要引用的规范文件:
|
||||
- design: 设计规范(db-designer, entity-designer, api-designer, crud-designer)
|
||||
- module_guide: 模块引用提示(用户指定模块后加载对应 skill)
|
||||
- patterns: 设计模式引用(factory, strategy, template-method)
|
||||
- templates: 模板文件引用
|
||||
example: |
|
||||
---
|
||||
references:
|
||||
design:
|
||||
- skills/design/db-designer.yaml
|
||||
- skills/design/entity-designer.yaml
|
||||
module_guide:
|
||||
prompt: "请指定目标模块"
|
||||
mapping:
|
||||
mes: skills/modules/mes/skill-mes.yaml
|
||||
---
|
||||
|
||||
# 提取进度
|
||||
progress:
|
||||
total: 12
|
||||
completed: 12
|
||||
in_progress: 0
|
||||
pending: 0
|
||||
|
||||
# 最后更新时间
|
||||
metadata:
|
||||
created_at: "2026-03-18"
|
||||
updated_at: "2026-03-31"
|
||||
completed_at: "2026-03-18"
|
||||
@@ -1,706 +0,0 @@
|
||||
# Skill 文件 - AI 模块
|
||||
# 用于提取模块知识的标准格式
|
||||
|
||||
skill:
|
||||
id: "skill-ai"
|
||||
name: "AI Skill"
|
||||
version: "1.0.0"
|
||||
module_path: "yudao-module-ai"
|
||||
created_at: "2026-03-18"
|
||||
updated_at: "2026-03-18"
|
||||
|
||||
# ============================================
|
||||
# 第一阶段:设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
# 业务定位:模块解决什么业务问题?在整个系统中的定位?
|
||||
business_position: |
|
||||
AI 模块是系统的智能化核心,提供大模型集成能力,支持多种 AI 能力:
|
||||
1. 聊天对话:支持多模型切换、流式响应、知识库增强、联网搜索
|
||||
2. 图像生成:支持 OpenAI DALL-E、Stable Diffusion、Midjourney、硅基流动等
|
||||
3. 音乐生成:支持 Suno AI 音乐创作
|
||||
4. 知识库:RAG 检索增强生成,支持文档分片、向量存储、语义检索
|
||||
5. 写作助手:AI 辅助写作
|
||||
6. 思维导图:AI 生成思维导图
|
||||
7. 工作流:可视化 AI 工作流编排
|
||||
|
||||
# 设计原则:遵循了哪些设计原则(SOLID、DDD)?有哪些架构决策?
|
||||
design_principles:
|
||||
- "策略模式:通过 AiModelFactory 统一创建不同平台的 AI 模型实例"
|
||||
- "工厂模式:AiModelFactoryImpl 负责创建 ChatModel、ImageModel、EmbeddingModel 等"
|
||||
- "单例缓存:使用 Hutool Singleton 缓存 AI 客户端实例,避免重复创建"
|
||||
- "Spring AI 集成:基于 Spring AI 框架,实现多模型统一抽象"
|
||||
- "开放-封闭原则:新增 AI 平台只需扩展 AiPlatformEnum 和工厂方法"
|
||||
- "接口隔离:Service 层接口按领域划分,职责单一"
|
||||
|
||||
# 领域模型:核心领域对象有哪些?领域对象之间的关系?聚合根是什么?
|
||||
domain_model:
|
||||
aggregates:
|
||||
- name: "聊天会话"
|
||||
type: "聚合根"
|
||||
entities: ["AiChatMessageDO"]
|
||||
description: "用户发起的对话,包含多条消息"
|
||||
- name: "知识库"
|
||||
type: "聚合根"
|
||||
entities: ["AiKnowledgeDocumentDO", "AiKnowledgeSegmentDO"]
|
||||
description: "知识库包含多个文档,文档包含多个分段"
|
||||
- name: "AI 模型配置"
|
||||
type: "聚合根"
|
||||
entities: ["AiApiKeyDO"]
|
||||
description: "模型配置关联 API 密钥"
|
||||
|
||||
value_objects:
|
||||
- name: "AiPlatformEnum"
|
||||
description: "AI 平台枚举,定义支持的 AI 服务商"
|
||||
- name: "AiModelTypeEnum"
|
||||
description: "模型类型枚举:对话、图像、语音、视频、向量、重排序"
|
||||
|
||||
services:
|
||||
- name: "AiChatMessageService"
|
||||
description: "聊天消息服务,处理对话生成"
|
||||
- name: "AiImageService"
|
||||
description: "图像生成服务"
|
||||
- name: "AiKnowledgeService"
|
||||
description: "知识库管理服务"
|
||||
- name: "AiModelService"
|
||||
description: "模型配置管理服务"
|
||||
- name: "AiMusicService"
|
||||
description: "音乐生成服务"
|
||||
|
||||
# ============================================
|
||||
# 第二阶段:架构设计
|
||||
# ============================================
|
||||
architecture:
|
||||
# 分层架构
|
||||
layers:
|
||||
- name: "controller"
|
||||
purpose: "HTTP 接口层"
|
||||
components:
|
||||
- "AiChatMessageController: 聊天消息接口"
|
||||
- "AiChatConversationController: 对话管理接口"
|
||||
- "AiImageController: 图像生成接口"
|
||||
- "AiKnowledgeController: 知识库管理接口"
|
||||
- "AiModelController: 模型配置接口"
|
||||
- "AiMusicController: 音乐生成接口"
|
||||
- "AiWriteController: 写作助手接口"
|
||||
- "AiMindMapController: 思维导图接口"
|
||||
- "AiWorkflowController: 工作流接口"
|
||||
|
||||
- name: "service"
|
||||
purpose: "业务逻辑层"
|
||||
components:
|
||||
- "chat/: 聊天相关服务"
|
||||
- "image/: 图像生成服务"
|
||||
- "knowledge/: 知识库服务"
|
||||
- "model/: 模型配置服务"
|
||||
- "music/: 音乐生成服务"
|
||||
- "write/: 写作服务"
|
||||
- "mindmap/: 思维导图服务"
|
||||
- "workflow/: 工作流服务"
|
||||
|
||||
- name: "dal"
|
||||
purpose: "数据访问层"
|
||||
components:
|
||||
- "dataobject/: DO 实体类"
|
||||
- "mysql/: MyBatis Mapper 接口"
|
||||
|
||||
- name: "framework/ai"
|
||||
purpose: "AI 框架集成层"
|
||||
components:
|
||||
- "core/model/: 模型工厂和多平台适配"
|
||||
- "core/webserch/: 联网搜索客户端"
|
||||
- "config/: 自动配置类"
|
||||
|
||||
# 设计模式应用
|
||||
design_patterns:
|
||||
- pattern: "工厂模式"
|
||||
location: "framework/ai/core/model/AiModelFactory.java"
|
||||
purpose: "统一创建各平台 AI 模型实例,屏蔽创建细节"
|
||||
- pattern: "策略模式"
|
||||
location: "framework/ai/core/model/AiModelFactoryImpl.java"
|
||||
purpose: "根据平台类型选择不同的模型创建策略"
|
||||
- pattern: "单例模式"
|
||||
location: "framework/ai/core/model/AiModelFactoryImpl.java"
|
||||
purpose: "使用 Hutool Singleton 缓存 AI 客户端,避免重复创建连接"
|
||||
- pattern: "模板方法"
|
||||
location: "service/chat/AiChatMessageServiceImpl.java"
|
||||
purpose: "聊天消息处理的统一流程"
|
||||
|
||||
# 模块间通信
|
||||
communication:
|
||||
apis: []
|
||||
# 暂无对外暴露的 RPC API
|
||||
consumers:
|
||||
- module: "system"
|
||||
api: "AdminUserApi"
|
||||
purpose: "获取用户信息"
|
||||
mq: []
|
||||
# 暂未使用消息队列
|
||||
|
||||
# ============================================
|
||||
# 第三阶段:数据表设计
|
||||
# ============================================
|
||||
data_model:
|
||||
# 实体继承体系
|
||||
entity_hierarchy:
|
||||
base: "BaseDO"
|
||||
description: "所有实体继承 BaseDO,包含 id、creator、createTime、updater、updateTime、deleted 字段"
|
||||
|
||||
# 核心数据表
|
||||
tables:
|
||||
# ========== 模型配置 ==========
|
||||
- name: "ai_api_key"
|
||||
comment: "AI API 密钥表"
|
||||
entity: "AiApiKeyDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "name", type: "String", comment: "名称" }
|
||||
- { name: "api_key", type: "String", comment: "密钥" }
|
||||
- { name: "platform", type: "String", comment: "平台,枚举 AiPlatformEnum" }
|
||||
- { name: "url", type: "String", comment: "API 地址" }
|
||||
- { name: "status", type: "Integer", comment: "状态" }
|
||||
|
||||
- name: "ai_model"
|
||||
comment: "AI 模型配置表"
|
||||
entity: "AiModelDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "key_id", type: "Long", comment: "API 密钥编号" }
|
||||
- { name: "name", type: "String", comment: "模型名称" }
|
||||
- { name: "model", type: "String", comment: "模型标识" }
|
||||
- { name: "platform", type: "String", comment: "平台" }
|
||||
- { name: "type", type: "Integer", comment: "类型:1对话 2图片 3语音 4视频 5向量 6重排序" }
|
||||
- { name: "sort", type: "Integer", comment: "排序值" }
|
||||
- { name: "status", type: "Integer", comment: "状态" }
|
||||
- { name: "temperature", type: "Double", comment: "温度参数" }
|
||||
- { name: "max_tokens", type: "Integer", comment: "最大 Token 数" }
|
||||
- { name: "max_contexts", type: "Integer", comment: "最大上下文数" }
|
||||
|
||||
- name: "ai_chat_role"
|
||||
comment: "AI 聊天角色表"
|
||||
entity: "AiChatRoleDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "name", type: "String", comment: "角色名称" }
|
||||
- { name: "avatar", type: "String", comment: "角色头像" }
|
||||
- { name: "category", type: "String", comment: "角色分类" }
|
||||
- { name: "description", type: "String", comment: "角色描述" }
|
||||
- { name: "system_message", type: "String", comment: "角色设定" }
|
||||
- { name: "user_id", type: "Long", comment: "用户编号" }
|
||||
- { name: "model_id", type: "Long", comment: "模型编号" }
|
||||
- { name: "knowledge_ids", type: "List<Long>", comment: "知识库编号列表" }
|
||||
- { name: "tool_ids", type: "List<Long>", comment: "工具编号列表" }
|
||||
- { name: "mcp_client_names", type: "List<String>", comment: "MCP Client 名字列表" }
|
||||
- { name: "public_status", type: "Boolean", comment: "是否公开" }
|
||||
- { name: "sort", type: "Integer", comment: "排序值" }
|
||||
- { name: "status", type: "Integer", comment: "状态" }
|
||||
|
||||
- name: "ai_tool"
|
||||
comment: "AI 工具表"
|
||||
entity: "AiToolDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "name", type: "String", comment: "工具名称(Bean 名字)" }
|
||||
- { name: "description", type: "String", comment: "工具描述" }
|
||||
- { name: "status", type: "Integer", comment: "状态" }
|
||||
|
||||
# ========== 聊天对话 ==========
|
||||
- name: "ai_chat_conversation"
|
||||
comment: "AI 聊天对话表"
|
||||
entity: "AiChatConversationDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "user_id", type: "Long", comment: "用户编号" }
|
||||
- { name: "title", type: "String", comment: "对话标题" }
|
||||
- { name: "pinned", type: "Boolean", comment: "是否置顶" }
|
||||
- { name: "pinned_time", type: "LocalDateTime", comment: "置顶时间" }
|
||||
- { name: "role_id", type: "Long", comment: "角色编号" }
|
||||
- { name: "model_id", type: "Long", comment: "模型编号" }
|
||||
- { name: "model", type: "String", comment: "模型标识" }
|
||||
- { name: "system_message", type: "String", comment: "角色设定" }
|
||||
- { name: "temperature", type: "Double", comment: "温度参数" }
|
||||
- { name: "max_tokens", type: "Integer", comment: "最大 Token 数" }
|
||||
- { name: "max_contexts", type: "Integer", comment: "最大上下文数" }
|
||||
|
||||
- name: "ai_chat_message"
|
||||
comment: "AI 聊天消息表"
|
||||
entity: "AiChatMessageDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "conversation_id", type: "Long", comment: "对话编号" }
|
||||
- { name: "reply_id", type: "Long", comment: "回复消息编号" }
|
||||
- { name: "type", type: "String", comment: "消息类型(USER/ASSISTANT/SYSTEM)" }
|
||||
- { name: "user_id", type: "Long", comment: "用户编号" }
|
||||
- { name: "role_id", type: "Long", comment: "角色编号" }
|
||||
- { name: "model", type: "String", comment: "模型标识" }
|
||||
- { name: "model_id", type: "Long", comment: "模型编号" }
|
||||
- { name: "content", type: "String", comment: "聊天内容" }
|
||||
- { name: "reasoning_content", type: "String", comment: "推理内容" }
|
||||
- { name: "use_context", type: "Boolean", comment: "是否携带上下文" }
|
||||
- { name: "segment_ids", type: "List<Long>", comment: "知识库段落编号数组" }
|
||||
- { name: "web_search_pages", type: "List<WebPage>", comment: "联网搜索网页内容" }
|
||||
- { name: "attachment_urls", type: "List<String>", comment: "附件 URL 数组" }
|
||||
|
||||
# ========== 图像生成 ==========
|
||||
- name: "ai_image"
|
||||
comment: "AI 绘画表"
|
||||
entity: "AiImageDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "user_id", type: "Long", comment: "用户编号" }
|
||||
- { name: "prompt", type: "String", comment: "提示词" }
|
||||
- { name: "platform", type: "String", comment: "平台" }
|
||||
- { name: "model_id", type: "Long", comment: "模型编号" }
|
||||
- { name: "model", type: "String", comment: "模型标识" }
|
||||
- { name: "width", type: "Integer", comment: "图片宽度" }
|
||||
- { name: "height", type: "Integer", comment: "图片高度" }
|
||||
- { name: "status", type: "Integer", comment: "生成状态" }
|
||||
- { name: "finish_time", type: "LocalDateTime", comment: "完成时间" }
|
||||
- { name: "error_message", type: "String", comment: "错误信息" }
|
||||
- { name: "pic_url", type: "String", comment: "图片地址" }
|
||||
- { name: "public_status", type: "Boolean", comment: "是否公开" }
|
||||
- { name: "options", type: "Map<String,Object>", comment: "绘制参数" }
|
||||
- { name: "buttons", type: "List<Button>", comment: "MJ 按钮" }
|
||||
- { name: "task_id", type: "String", comment: "任务编号" }
|
||||
|
||||
# ========== 知识库 ==========
|
||||
- name: "ai_knowledge"
|
||||
comment: "AI 知识库表"
|
||||
entity: "AiKnowledgeDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "name", type: "String", comment: "知识库名称" }
|
||||
- { name: "description", type: "String", comment: "知识库描述" }
|
||||
- { name: "embedding_model_id", type: "Long", comment: "向量模型编号" }
|
||||
- { name: "embedding_model", type: "String", comment: "模型标识" }
|
||||
- { name: "top_k", type: "Integer", comment: "TopK" }
|
||||
- { name: "similarity_threshold", type: "Double", comment: "相似度阈值" }
|
||||
- { name: "status", type: "Integer", comment: "状态" }
|
||||
|
||||
- name: "ai_knowledge_document"
|
||||
comment: "AI 知识库文档表"
|
||||
entity: "AiKnowledgeDocumentDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "knowledge_id", type: "Long", comment: "知识库编号" }
|
||||
- { name: "name", type: "String", comment: "文档名称" }
|
||||
- { name: "url", type: "String", comment: "文件 URL" }
|
||||
- { name: "content", type: "String", comment: "内容" }
|
||||
- { name: "content_length", type: "Integer", comment: "文档长度" }
|
||||
- { name: "tokens", type: "Integer", comment: "Token 数量" }
|
||||
- { name: "segment_max_tokens", type: "Integer", comment: "分片最大 Token 数" }
|
||||
- { name: "retrieval_count", type: "Integer", comment: "召回次数" }
|
||||
- { name: "status", type: "Integer", comment: "状态" }
|
||||
|
||||
- name: "ai_knowledge_segment"
|
||||
comment: "AI 知识库分段表"
|
||||
entity: "AiKnowledgeSegmentDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "knowledge_id", type: "Long", comment: "知识库编号" }
|
||||
- { name: "document_id", type: "Long", comment: "文档编号" }
|
||||
- { name: "content", type: "String", comment: "切片内容" }
|
||||
- { name: "content_length", type: "Integer", comment: "内容长度" }
|
||||
- { name: "vector_id", type: "String", comment: "向量库编号" }
|
||||
- { name: "tokens", type: "Integer", comment: "Token 数量" }
|
||||
- { name: "retrieval_count", type: "Integer", comment: "召回次数" }
|
||||
- { name: "status", type: "Integer", comment: "状态" }
|
||||
|
||||
# ========== 音乐生成 ==========
|
||||
- name: "ai_music"
|
||||
comment: "AI 音乐表"
|
||||
entity: "AiMusicDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "user_id", type: "Long", comment: "用户编号" }
|
||||
- { name: "title", type: "String", comment: "音乐名称" }
|
||||
- { name: "lyric", type: "String", comment: "歌词" }
|
||||
- { name: "image_url", type: "String", comment: "图片地址" }
|
||||
- { name: "audio_url", type: "String", comment: "音频地址" }
|
||||
- { name: "video_url", type: "String", comment: "视频地址" }
|
||||
- { name: "status", type: "Integer", comment: "音乐状态" }
|
||||
- { name: "generate_mode", type: "Integer", comment: "生成模式" }
|
||||
- { name: "description", type: "String", comment: "描述词" }
|
||||
- { name: "platform", type: "String", comment: "平台" }
|
||||
- { name: "model", type: "String", comment: "模型" }
|
||||
- { name: "tags", type: "List<String>", comment: "音乐风格标签" }
|
||||
- { name: "duration", type: "Double", comment: "音乐时长" }
|
||||
- { name: "public_status", type: "Boolean", comment: "是否公开" }
|
||||
- { name: "task_id", type: "String", comment: "任务编号" }
|
||||
- { name: "error_message", type: "String", comment: "错误信息" }
|
||||
|
||||
# ========== 写作 ==========
|
||||
- name: "ai_write"
|
||||
comment: "AI 写作表"
|
||||
entity: "AiWriteDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "user_id", type: "Long", comment: "用户编号" }
|
||||
- { name: "type", type: "Integer", comment: "写作类型" }
|
||||
- { name: "platform", type: "String", comment: "平台" }
|
||||
- { name: "model_id", type: "Long", comment: "模型编号" }
|
||||
- { name: "model", type: "String", comment: "模型" }
|
||||
- { name: "prompt", type: "String", comment: "生成内容提示" }
|
||||
- { name: "generated_content", type: "String", comment: "生成的内容" }
|
||||
- { name: "original_content", type: "String", comment: "原文" }
|
||||
- { name: "length", type: "Integer", comment: "长度提示词" }
|
||||
- { name: "format", type: "Integer", comment: "格式提示词" }
|
||||
- { name: "tone", type: "Integer", comment: "语气提示词" }
|
||||
- { name: "language", type: "Integer", comment: "语言提示词" }
|
||||
- { name: "error_message", type: "String", comment: "错误信息" }
|
||||
|
||||
# ========== 思维导图 ==========
|
||||
- name: "ai_mind_map"
|
||||
comment: "AI 思维导图表"
|
||||
entity: "AiMindMapDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "user_id", type: "Long", comment: "用户编号" }
|
||||
- { name: "platform", type: "String", comment: "平台" }
|
||||
- { name: "model_id", type: "Long", comment: "模型编号" }
|
||||
- { name: "model", type: "String", comment: "模型" }
|
||||
- { name: "prompt", type: "String", comment: "生成内容提示" }
|
||||
- { name: "generated_content", type: "String", comment: "生成的内容" }
|
||||
- { name: "error_message", type: "String", comment: "错误信息" }
|
||||
|
||||
# ========== 工作流 ==========
|
||||
- name: "ai_workflow"
|
||||
comment: "AI 工作流表"
|
||||
entity: "AiWorkflowDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "name", type: "String", comment: "工作流名称" }
|
||||
- { name: "code", type: "String", comment: "工作流标识" }
|
||||
- { name: "graph", type: "String", comment: "工作流模型 JSON 数据" }
|
||||
- { name: "remark", type: "String", comment: "备注" }
|
||||
- { name: "status", type: "Integer", comment: "状态" }
|
||||
|
||||
# 表关系(ER关系)
|
||||
relationships:
|
||||
- from: "ai_chat_conversation"
|
||||
to: "ai_chat_message"
|
||||
type: "1:N"
|
||||
foreign_key: "conversation_id"
|
||||
- from: "ai_knowledge"
|
||||
to: "ai_knowledge_document"
|
||||
type: "1:N"
|
||||
foreign_key: "knowledge_id"
|
||||
- from: "ai_knowledge_document"
|
||||
to: "ai_knowledge_segment"
|
||||
type: "1:N"
|
||||
foreign_key: "document_id"
|
||||
- from: "ai_model"
|
||||
to: "ai_api_key"
|
||||
type: "N:1"
|
||||
foreign_key: "key_id"
|
||||
- from: "ai_chat_role"
|
||||
to: "ai_model"
|
||||
type: "N:1"
|
||||
foreign_key: "model_id"
|
||||
|
||||
# ============================================
|
||||
# 第四阶段:代码使用设计
|
||||
# ============================================
|
||||
code_patterns:
|
||||
# Controller层规范
|
||||
controller:
|
||||
annotations:
|
||||
- "@Tag(name = '管理后台 - XXX')"
|
||||
- "@RestController"
|
||||
- "@RequestMapping('/ai/xxx')"
|
||||
- "@Operation(summary = 'xxx')"
|
||||
example: |
|
||||
@Tag(name = "管理后台 - 聊天消息")
|
||||
@RestController
|
||||
@RequestMapping("/ai/chat/message")
|
||||
public class AiChatMessageController {
|
||||
|
||||
@Resource
|
||||
private AiChatMessageService chatMessageService;
|
||||
|
||||
@Operation(summary = "发送消息(流式)")
|
||||
@PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(
|
||||
@Valid @RequestBody AiChatMessageSendReqVO sendReqVO) {
|
||||
return chatMessageService.sendChatMessageStream(sendReqVO, getLoginUserId());
|
||||
}
|
||||
}
|
||||
|
||||
# Service层规范
|
||||
service:
|
||||
interface_pattern: "定义接口 + Impl 实现类,接口用于解耦"
|
||||
impl_pattern: "@Service 注解,使用 @Resource 注入依赖"
|
||||
example: |
|
||||
public interface AiChatMessageService {
|
||||
// 同步发送消息
|
||||
AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId);
|
||||
// 流式发送消息
|
||||
Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(
|
||||
AiChatMessageSendReqVO sendReqVO, Long userId);
|
||||
}
|
||||
|
||||
@Service
|
||||
public class AiChatMessageServiceImpl implements AiChatMessageService {
|
||||
@Resource
|
||||
private AiModelService modelService;
|
||||
|
||||
@Override
|
||||
public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(
|
||||
AiChatMessageSendReqVO sendReqVO, Long userId) {
|
||||
// 1. 获取对话和模型配置
|
||||
// 2. 构建消息上下文
|
||||
// 3. 调用 ChatModel 流式生成
|
||||
// 4. 保存消息记录
|
||||
}
|
||||
}
|
||||
|
||||
# 数据访问规范
|
||||
dal:
|
||||
mapper_pattern: "继承 BaseMapperX,使用 LambdaQueryWrapper 查询"
|
||||
example: |
|
||||
@Mapper
|
||||
public interface AiChatMessageMapper extends BaseMapperX<AiChatMessageDO> {
|
||||
|
||||
default List<AiChatMessageDO> selectListByConversationId(Long conversationId) {
|
||||
return selectList(new LambdaQueryWrapper<AiChatMessageDO>()
|
||||
.eq(AiChatMessageDO::getConversationId, conversationId)
|
||||
.orderByAsc(AiChatMessageDO::getId));
|
||||
}
|
||||
}
|
||||
|
||||
# AI 模型调用规范
|
||||
ai_model_usage:
|
||||
chat_model: |
|
||||
// 获取 ChatModel
|
||||
ChatModel chatModel = modelService.getChatModel(modelId);
|
||||
|
||||
// 同步调用
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
|
||||
// 流式调用
|
||||
Flux<ChatResponse> stream = chatModel.stream(new Prompt(messages));
|
||||
|
||||
image_model: |
|
||||
// 获取 ImageModel
|
||||
ImageModel imageModel = modelService.getImageModel(modelId);
|
||||
|
||||
// 生成图片
|
||||
ImageResponse response = imageModel.call(new ImagePrompt(prompt, options));
|
||||
|
||||
embedding_model: |
|
||||
// 获取 EmbeddingModel
|
||||
EmbeddingModel embeddingModel = modelService.getOrCreateVectorStore(modelId, metadataFields);
|
||||
|
||||
// 生成向量
|
||||
EmbeddingResponse response = embeddingModel.embedForResponse(texts);
|
||||
|
||||
# 异常处理
|
||||
error_handling:
|
||||
code_prefix: "1-040-XXX-XXX"
|
||||
examples:
|
||||
- code: "1_040_000_000"
|
||||
message: "API 密钥不存在"
|
||||
- code: "1_040_001_000"
|
||||
message: "模型不存在"
|
||||
- code: "1_040_003_000"
|
||||
message: "对话不存在"
|
||||
- code: "1_040_004_001"
|
||||
message: "对话生成异常"
|
||||
- code: "1_040_009_000"
|
||||
message: "知识库不存在"
|
||||
|
||||
# 统一响应
|
||||
response:
|
||||
success_pattern: "CommonResult.success(data)"
|
||||
error_pattern: "CommonResult.error(ErrorCode)"
|
||||
|
||||
# ============================================
|
||||
# 第五阶段:扩展指南
|
||||
# ============================================
|
||||
extension_guide:
|
||||
# 新增 AI 平台提供商
|
||||
new_platform:
|
||||
title: "新增 AI 平台提供商"
|
||||
steps:
|
||||
- step: 1
|
||||
action: "在 AiPlatformEnum 中添加新平台枚举值"
|
||||
example: |
|
||||
MY_PLATFORM("MyPlatform", "我的平台"),
|
||||
|
||||
- step: 2
|
||||
action: "在 AiModelFactoryImpl 中添加模型创建方法"
|
||||
example: |
|
||||
// 在 getOrCreateChatModel 方法的 switch 中添加
|
||||
case MY_PLATFORM:
|
||||
return buildMyPlatformChatModel(apiKey, url);
|
||||
|
||||
// 新增构建方法
|
||||
private static ChatModel buildMyPlatformChatModel(String apiKey, String url) {
|
||||
// 参考 Spring AI 对应平台的 AutoConfiguration 实现
|
||||
MyPlatformApi api = MyPlatformApi.builder()
|
||||
.apiKey(apiKey)
|
||||
.baseUrl(url)
|
||||
.build();
|
||||
return MyPlatformChatModel.builder()
|
||||
.myPlatformApi(api)
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.build();
|
||||
}
|
||||
|
||||
- step: 3
|
||||
action: "如需自定义模型类,在 framework/ai/core/model/ 下创建"
|
||||
example: |
|
||||
// 参考 BaiChuanChatModel、GeminiChatModel 等
|
||||
|
||||
- step: 4
|
||||
action: "在 YudaoAiProperties 中添加平台配置属性"
|
||||
example: |
|
||||
@Data
|
||||
public static class MyPlatform {
|
||||
private String apiKey;
|
||||
private String baseUrl;
|
||||
}
|
||||
|
||||
- step: 5
|
||||
action: "在 AiAutoConfiguration 中添加自动配置"
|
||||
example: |
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "yudao.ai.my-platform", name = "api-key")
|
||||
public ChatModel myPlatformChatModel(YudaoAiProperties properties) {
|
||||
return buildMyPlatformChatModel(properties.getMyPlatform());
|
||||
}
|
||||
|
||||
# 新增 AI 能力类型
|
||||
new_capability:
|
||||
title: "新增 AI 能力类型"
|
||||
steps:
|
||||
- step: 1
|
||||
action: "在 AiModelTypeEnum 中添加新类型"
|
||||
example: |
|
||||
MY_CAPABILITY(7, "新能力"),
|
||||
|
||||
- step: 2
|
||||
action: "创建对应的 DO 实体类"
|
||||
example: |
|
||||
@TableName("ai_my_capability")
|
||||
@Data
|
||||
public class AiMyCapabilityDO extends BaseDO {
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private String prompt;
|
||||
private String result;
|
||||
// ...
|
||||
}
|
||||
|
||||
- step: 3
|
||||
action: "创建 Controller、Service、Mapper"
|
||||
example: |
|
||||
// Controller
|
||||
@Tag(name = "管理后台 - 新能力")
|
||||
@RestController
|
||||
@RequestMapping("/ai/my-capability")
|
||||
public class AiMyCapabilityController { ... }
|
||||
|
||||
// Service
|
||||
public interface AiMyCapabilityService { ... }
|
||||
|
||||
// Mapper
|
||||
public interface AiMyCapabilityMapper extends BaseMapperX<AiMyCapabilityDO> { ... }
|
||||
|
||||
- step: 4
|
||||
action: "在 AiModelFactory 中添加对应的模型获取方法"
|
||||
example: |
|
||||
MyCapabilityModel getOrCreateMyCapabilityModel(AiPlatformEnum platform,
|
||||
String apiKey, String url);
|
||||
|
||||
# 最佳实践
|
||||
best_practices:
|
||||
- "模型实例缓存:使用 Singleton.get() 缓存,避免重复创建连接"
|
||||
- "流式响应优先:聊天推荐使用流式接口,用户体验更好"
|
||||
- "错误处理:捕获 AI 调用异常,记录 errorMessage 字段"
|
||||
- "向量存储选择:生产环境推荐 Milvus、Qdrant,测试可用 SimpleVectorStore"
|
||||
- "知识库分片:根据文档特点选择分片策略(语义分片、Markdown QA 分片)"
|
||||
- "多模态支持:ChatMessage 支持携带 attachmentUrls 实现多模态对话"
|
||||
|
||||
# ============================================
|
||||
# 依赖关系
|
||||
# ============================================
|
||||
dependencies:
|
||||
# 内部依赖(其他模块)
|
||||
internal:
|
||||
- module: "system"
|
||||
api: "AdminUserApi"
|
||||
purpose: "获取用户信息"
|
||||
|
||||
# 外部依赖(第三方库)
|
||||
external:
|
||||
- name: "spring-ai-core"
|
||||
version: "1.0.x"
|
||||
purpose: "Spring AI 核心框架,提供统一的 AI 模型抽象"
|
||||
- name: "spring-ai-openai"
|
||||
version: "1.0.x"
|
||||
purpose: "OpenAI 模型集成"
|
||||
- name: "spring-ai-anthropic"
|
||||
version: "1.0.x"
|
||||
purpose: "Anthropic Claude 模型集成"
|
||||
- name: "spring-ai-ollama"
|
||||
version: "1.0.x"
|
||||
purpose: "Ollama 本地模型集成"
|
||||
- name: "spring-ai-zhipuai"
|
||||
version: "1.0.x"
|
||||
purpose: "智谱 AI 模型集成"
|
||||
- name: "spring-ai-deepseek"
|
||||
version: "1.0.x"
|
||||
purpose: "DeepSeek 模型集成"
|
||||
- name: "spring-ai-minimax"
|
||||
version: "1.0.x"
|
||||
purpose: "MiniMax 模型集成"
|
||||
- name: "spring-ai-milvus"
|
||||
version: "1.0.x"
|
||||
purpose: "Milvus 向量数据库集成"
|
||||
- name: "spring-ai-qdrant"
|
||||
version: "1.0.x"
|
||||
purpose: "Qdrant 向量数据库集成"
|
||||
- name: "spring-ai-redis"
|
||||
version: "1.0.x"
|
||||
purpose: "Redis 向量存储集成"
|
||||
- name: "hutool"
|
||||
version: "5.x"
|
||||
purpose: "Singleton 缓存、工具类"
|
||||
|
||||
# ============================================
|
||||
# 关键文件清单
|
||||
# ============================================
|
||||
key_files:
|
||||
- path: "yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactory.java"
|
||||
purpose: "AI 模型工厂接口,定义统一的模型创建方法"
|
||||
- path: "yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactoryImpl.java"
|
||||
purpose: "AI 模型工厂实现,包含各平台模型创建逻辑"
|
||||
- path: "yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiPlatformEnum.java"
|
||||
purpose: "AI 平台枚举,定义支持的 20+ AI 服务商"
|
||||
- path: "yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelTypeEnum.java"
|
||||
purpose: "AI 模型类型枚举"
|
||||
- path: "yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelService.java"
|
||||
purpose: "模型配置服务,管理 API Key、模型配置"
|
||||
- path: "yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java"
|
||||
purpose: "聊天消息服务,核心业务逻辑"
|
||||
- path: "yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java"
|
||||
purpose: "知识库服务"
|
||||
- path: "yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java"
|
||||
purpose: "聊天消息 Controller,提供 HTTP 接口"
|
||||
- path: "yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java"
|
||||
purpose: "AI 自动配置类"
|
||||
@@ -1,719 +0,0 @@
|
||||
# Skill 文件 - BPM 工作流模块
|
||||
# 用于提取模块知识的标准格式
|
||||
|
||||
skill:
|
||||
id: "skill-bpm"
|
||||
name: "BPM 工作流 Skill"
|
||||
version: "1.0.0"
|
||||
module_path: "yudao-module-bpm"
|
||||
created_at: "2026-03-18"
|
||||
updated_at: "2026-03-18"
|
||||
|
||||
# ============================================
|
||||
# 第一阶段:设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
# 业务定位:模块解决什么业务问题?在整个系统中的定位?
|
||||
business_position: |
|
||||
BPM (Business Process Management) 模块是工作流引擎核心模块,基于 Flowable 6 实现完整的业务流程管理功能。
|
||||
主要解决企业内部审批流程、业务流程自动化等问题。
|
||||
|
||||
核心功能包括:
|
||||
1. 流程定义管理 - 支持 BPMN 2.0 标准设计器和仿钉钉/飞书的 Simple 设计器
|
||||
2. 流程表单管理 - 动态表单配置,支持与流程绑定
|
||||
3. 流程实例管理 - 发起、取消、查询流程实例
|
||||
4. 任务审批管理 - 待办、已办、审批通过/拒绝、退回、委派、转办、加签/减签
|
||||
5. 流程抄送 - 支持流程抄送功能
|
||||
6. 流程监听器 - 支持执行监听器和任务监听器扩展
|
||||
|
||||
# 设计原则:遵循了哪些设计原则(SOLID、DDD)?有哪些架构决策?
|
||||
design_principles:
|
||||
- "领域驱动设计 (DDD): 按流程定义(Definition)、流程实例(Instance)、任务(Task)划分限界上下文"
|
||||
- "策略模式: 审批人策略(BpmTaskCandidateStrategy)使用策略模式,支持多种审批人分配方式"
|
||||
- "事件驱动: 流程状态变更通过 Spring ApplicationEvent 发布事件,实现解耦"
|
||||
- "模板方法模式: BpmTaskCandidateStrategy 接口定义审批人计算模板"
|
||||
- "适配器模式: 封装 Flowable API,隔离底层工作流引擎实现"
|
||||
- "监听器模式: 通过 Flowable Listener 扩展流程行为"
|
||||
|
||||
# 领域模型:核心领域对象有哪些?领域对象之间的关系?聚合根是什么?
|
||||
domain_model:
|
||||
aggregates:
|
||||
- name: "流程定义 (ProcessDefinition)"
|
||||
type: "聚合根"
|
||||
entities:
|
||||
- "BpmFormDO - 流程表单"
|
||||
- "BpmCategoryDO - 流程分类"
|
||||
- "BpmUserGroupDO - 审批用户组"
|
||||
- "BpmProcessDefinitionInfoDO - 流程定义扩展信息"
|
||||
- "BpmProcessListenerDO - 流程监听器"
|
||||
- "BpmProcessExpressionDO - 流程表达式"
|
||||
description: "流程定义是流程的模板,包含流程结构、表单配置、审批规则等元数据"
|
||||
|
||||
- name: "流程实例 (ProcessInstance)"
|
||||
type: "聚合根"
|
||||
entities:
|
||||
- "BpmProcessInstanceCopyDO - 流程抄送记录"
|
||||
description: "流程实例是流程定义的一次执行,由 Flowable 原生表管理,扩展信息存储在 DO 中"
|
||||
|
||||
- name: "流程任务 (Task)"
|
||||
type: "实体"
|
||||
entities: []
|
||||
description: "流程任务是流程实例中的审批节点,由 Flowable 原生表管理"
|
||||
|
||||
value_objects:
|
||||
- "BpmProcessInstanceCreateReqDTO - 流程实例创建请求"
|
||||
- "BpmModelMetaInfoVO - 流程模型元信息"
|
||||
- "BpmSimpleModelNodeVO - Simple 设计器节点定义"
|
||||
|
||||
services:
|
||||
- "BpmModelService - 流程模型管理服务"
|
||||
- "BpmProcessDefinitionService - 流程定义管理服务"
|
||||
- "BpmProcessInstanceService - 流程实例管理服务"
|
||||
- "BpmTaskService - 流程任务管理服务"
|
||||
- "BpmFormService - 表单管理服务"
|
||||
- "BpmMessageService - 流程消息服务"
|
||||
|
||||
# ============================================
|
||||
# 第二阶段:架构设计
|
||||
# ============================================
|
||||
architecture:
|
||||
# 分层架构
|
||||
layers:
|
||||
- name: "api"
|
||||
purpose: "模块间API接口,供其他模块调用"
|
||||
components:
|
||||
- "BpmProcessInstanceApi - 流程实例创建接口"
|
||||
- "BpmProcessTaskApi - 流程任务操作接口"
|
||||
- "BpmProcessInstanceStatusEvent - 流程状态变更事件"
|
||||
|
||||
- name: "controller"
|
||||
purpose: "HTTP接口,供前端调用"
|
||||
components:
|
||||
- "BpmModelController - 流程模型管理"
|
||||
- "BpmProcessDefinitionController - 流程定义管理"
|
||||
- "BpmProcessInstanceController - 流程实例管理"
|
||||
- "BpmTaskController - 流程任务管理"
|
||||
- "BpmFormController - 表单管理"
|
||||
- "BpmCategoryController - 分类管理"
|
||||
- "BpmUserGroupController - 用户组管理"
|
||||
- "BpmOALeaveController - 请假申请示例"
|
||||
|
||||
- name: "service"
|
||||
purpose: "业务逻辑层,封装核心业务"
|
||||
components:
|
||||
- "definition/ - 流程定义相关服务"
|
||||
- "task/ - 流程任务相关服务"
|
||||
- "message/ - 消息通知服务"
|
||||
- "oa/ - OA 业务示例(请假)"
|
||||
|
||||
- name: "dal"
|
||||
purpose: "数据访问层,与数据库交互"
|
||||
components:
|
||||
- "dataobject/definition/ - 定义相关 DO"
|
||||
- "dataobject/task/ - 任务相关 DO"
|
||||
- "dataobject/oa/ - OA 相关 DO"
|
||||
- "mysql/ - MyBatis Mapper"
|
||||
- "redis/ - Redis 缓存"
|
||||
|
||||
- name: "framework/flowable"
|
||||
purpose: "Flowable 框架集成层"
|
||||
components:
|
||||
- "config/BpmFlowableConfiguration - Flowable 配置"
|
||||
- "core/candidate/ - 审批人策略实现"
|
||||
- "core/listener/ - 流程监听器"
|
||||
- "core/behavior/ - 自定义行为"
|
||||
- "core/util/ - 工具类"
|
||||
|
||||
# 设计模式应用
|
||||
design_patterns:
|
||||
- pattern: "策略模式 (Strategy Pattern)"
|
||||
location: "framework/flowable/core/candidate/BpmTaskCandidateStrategy.java"
|
||||
purpose: "审批人分配策略,支持角色、部门、用户、表达式等多种分配方式"
|
||||
|
||||
- pattern: "事件驱动 (Event-Driven)"
|
||||
location: "api/event/BpmProcessInstanceStatusEvent.java"
|
||||
purpose: "流程状态变更事件,通知其他模块流程结果"
|
||||
|
||||
- pattern: "监听器模式 (Listener Pattern)"
|
||||
location: "framework/flowable/core/listener/BpmTaskEventListener.java"
|
||||
purpose: "监听 Flowable 任务事件,执行自定义逻辑"
|
||||
|
||||
- pattern: "工厂模式 (Factory Pattern)"
|
||||
location: "framework/flowable/core/behavior/BpmActivityBehaviorFactory.java"
|
||||
purpose: "创建自定义 ActivityBehavior,扩展 Flowable 行为"
|
||||
|
||||
- pattern: "模板方法模式 (Template Method)"
|
||||
location: "service/task/trigger/BpmTrigger.java"
|
||||
purpose: "流程触发器抽象,支持 HTTP 请求、表单操作等触发类型"
|
||||
|
||||
# 模块间通信
|
||||
communication:
|
||||
apis:
|
||||
- name: "BpmProcessInstanceApi"
|
||||
method: "createProcessInstance"
|
||||
purpose: "供其他模块发起流程实例"
|
||||
- name: "BpmProcessTaskApi"
|
||||
method: "triggerTask"
|
||||
purpose: "触发流程任务执行"
|
||||
- name: "BpmProcessInstanceStatusEvent"
|
||||
purpose: "流程状态变更事件,供其他模块监听"
|
||||
|
||||
consumers:
|
||||
- module: "system"
|
||||
api: "AdminUserApi"
|
||||
purpose: "获取用户信息"
|
||||
- module: "system"
|
||||
api: "DeptApi"
|
||||
purpose: "获取部门信息"
|
||||
- module: "system"
|
||||
api: "PermissionApi"
|
||||
purpose: "获取用户角色、权限"
|
||||
|
||||
mq: []
|
||||
|
||||
# ============================================
|
||||
# 第三阶段:数据表设计
|
||||
# ============================================
|
||||
data_model:
|
||||
# 实体继承体系
|
||||
entity_hierarchy:
|
||||
base: "BaseDO"
|
||||
description: "所有 DO 继承 BaseDO,包含 creator、createTime、updater、updateTime、deleted 字段"
|
||||
|
||||
# 核心数据表
|
||||
tables:
|
||||
- name: "bpm_category"
|
||||
comment: "流程分类表"
|
||||
entity: "BpmCategoryDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "分类编号" }
|
||||
- { name: "name", type: "String", comment: "分类名" }
|
||||
- { name: "code", type: "String", comment: "分类标志" }
|
||||
- { name: "description", type: "String", comment: "分类描述" }
|
||||
- { name: "status", type: "Integer", comment: "分类状态" }
|
||||
- { name: "sort", type: "Integer", comment: "分类排序" }
|
||||
|
||||
- name: "bpm_form"
|
||||
comment: "流程表单表"
|
||||
entity: "BpmFormDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "name", type: "String", comment: "表单名" }
|
||||
- { name: "status", type: "Integer", comment: "状态" }
|
||||
- { name: "conf", type: "String", comment: "表单配置 JSON" }
|
||||
- { name: "fields", type: "List<String>", comment: "表单项数组" }
|
||||
- { name: "remark", type: "String", comment: "备注" }
|
||||
|
||||
- name: "bpm_user_group"
|
||||
comment: "审批用户组表"
|
||||
entity: "BpmUserGroupDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "name", type: "String", comment: "组名" }
|
||||
- { name: "description", type: "String", comment: "描述" }
|
||||
- { name: "status", type: "Integer", comment: "状态" }
|
||||
- { name: "user_ids", type: "Set<Long>", comment: "成员用户编号数组" }
|
||||
|
||||
- name: "bpm_process_definition_info"
|
||||
comment: "流程定义扩展信息表"
|
||||
entity: "BpmProcessDefinitionInfoDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "process_definition_id", type: "String", comment: "流程定义编号" }
|
||||
- { name: "model_id", type: "String", comment: "流程模型编号" }
|
||||
- { name: "model_type", type: "Integer", comment: "模型类型(10:BPMN, 20:Simple)" }
|
||||
- { name: "category", type: "String", comment: "流程分类编码" }
|
||||
- { name: "form_type", type: "Integer", comment: "表单类型" }
|
||||
- { name: "form_id", type: "Long", comment: "动态表单编号" }
|
||||
- { name: "form_conf", type: "String", comment: "表单配置" }
|
||||
- { name: "form_fields", type: "List<String>", comment: "表单字段" }
|
||||
- { name: "start_user_ids", type: "List<Long>", comment: "可发起用户" }
|
||||
- { name: "manager_user_ids", type: "List<Long>", comment: "可管理用户" }
|
||||
- { name: "simple_model", type: "String", comment: "Simple设计器模型数据" }
|
||||
|
||||
- name: "bpm_process_listener"
|
||||
comment: "流程监听器表"
|
||||
entity: "BpmProcessListenerDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "主键ID" }
|
||||
- { name: "name", type: "String", comment: "监听器名字" }
|
||||
- { name: "status", type: "Integer", comment: "状态" }
|
||||
- { name: "type", type: "String", comment: "监听类型(execution/task)" }
|
||||
- { name: "event", type: "String", comment: "监听事件" }
|
||||
- { name: "value_type", type: "String", comment: "值类型(class/delegateExpression/expression)" }
|
||||
- { name: "value", type: "String", comment: "值" }
|
||||
|
||||
- name: "bpm_process_expression"
|
||||
comment: "流程表达式表"
|
||||
entity: "BpmProcessExpressionDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "name", type: "String", comment: "表达式名字" }
|
||||
- { name: "status", type: "Integer", comment: "状态" }
|
||||
- { name: "expression", type: "String", comment: "表达式" }
|
||||
|
||||
- name: "bpm_process_instance_copy"
|
||||
comment: "流程抄送表"
|
||||
entity: "BpmProcessInstanceCopyDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "编号" }
|
||||
- { name: "start_user_id", type: "Long", comment: "发起人ID" }
|
||||
- { name: "process_instance_id", type: "String", comment: "流程实例编号" }
|
||||
- { name: "process_definition_id", type: "String", comment: "流程定义编号" }
|
||||
- { name: "activity_id", type: "String", comment: "流程活动编号" }
|
||||
- { name: "task_id", type: "String", comment: "任务编号" }
|
||||
- { name: "user_id", type: "Long", comment: "被抄送用户编号" }
|
||||
- { name: "reason", type: "String", comment: "抄送意见" }
|
||||
|
||||
- name: "bpm_oa_leave"
|
||||
comment: "OA请假申请表(示例业务表)"
|
||||
entity: "BpmOALeaveDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "请假表单主键" }
|
||||
- { name: "user_id", type: "Long", comment: "申请人用户编号" }
|
||||
- { name: "type", type: "String", comment: "请假类型" }
|
||||
- { name: "reason", type: "String", comment: "原因" }
|
||||
- { name: "start_time", type: "LocalDateTime", comment: "开始时间" }
|
||||
- { name: "end_time", type: "LocalDateTime", comment: "结束时间" }
|
||||
- { name: "day", type: "Long", comment: "请假天数" }
|
||||
- { name: "status", type: "Integer", comment: "审批结果" }
|
||||
- { name: "process_instance_id", type: "String", comment: "流程编号" }
|
||||
|
||||
# 表关系(ER关系)
|
||||
relationships:
|
||||
- from: "bpm_process_definition_info"
|
||||
to: "bpm_category"
|
||||
type: "N:1"
|
||||
foreign_key: "category -> code"
|
||||
- from: "bpm_process_definition_info"
|
||||
to: "bpm_form"
|
||||
type: "N:1"
|
||||
foreign_key: "form_id -> id"
|
||||
- from: "bpm_process_instance_copy"
|
||||
to: "Flowable ProcessInstance"
|
||||
type: "N:1"
|
||||
foreign_key: "process_instance_id -> id"
|
||||
- from: "bpm_oa_leave"
|
||||
to: "Flowable ProcessInstance"
|
||||
type: "1:1"
|
||||
foreign_key: "process_instance_id -> id"
|
||||
|
||||
# Flowable 原生表说明
|
||||
flowable_tables:
|
||||
- name: "ACT_RE_DEPLOYMENT"
|
||||
comment: "部署信息表"
|
||||
- name: "ACT_RE_PROCDEF"
|
||||
comment: "流程定义表"
|
||||
- name: "ACT_RE_MODEL"
|
||||
comment: "流程模型表"
|
||||
- name: "ACT_RU_EXECUTION"
|
||||
comment: "运行时执行实例表"
|
||||
- name: "ACT_RU_TASK"
|
||||
comment: "运行时任务表"
|
||||
- name: "ACT_HI_PROCINST"
|
||||
comment: "历史流程实例表"
|
||||
- name: "ACT_HI_TASKINST"
|
||||
comment: "历史任务实例表"
|
||||
- name: "ACT_HI_VARINST"
|
||||
comment: "历史变量表"
|
||||
|
||||
# ============================================
|
||||
# 第四阶段:代码使用设计
|
||||
# ============================================
|
||||
code_patterns:
|
||||
# Controller层规范
|
||||
controller:
|
||||
annotations:
|
||||
- "@Tag(name = '管理后台 - XXX')"
|
||||
- "@RestController"
|
||||
- "@RequestMapping('/bpm/xxx')"
|
||||
- "@Validated"
|
||||
- "@PreAuthorize('@ss.hasPermission(xxx)')"
|
||||
example: |
|
||||
@Tag(name = "管理后台 - 流程任务实例")
|
||||
@RestController
|
||||
@RequestMapping("/bpm/task")
|
||||
@Validated
|
||||
public class BpmTaskController {
|
||||
|
||||
@Resource
|
||||
private BpmTaskService taskService;
|
||||
|
||||
@PutMapping("/approve")
|
||||
@Operation(summary = "通过任务")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task:update')")
|
||||
public CommonResult<Boolean> approveTask(@Valid @RequestBody BpmTaskApproveReqVO reqVO) {
|
||||
taskService.approveTask(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
}
|
||||
|
||||
# Service层规范
|
||||
service:
|
||||
interface_pattern: |
|
||||
接口定义在 service/ 目录下,命名规则:Bpm{Entity}Service
|
||||
|
||||
核心服务接口:
|
||||
- BpmModelService: 流程模型管理
|
||||
- BpmProcessDefinitionService: 流程定义管理
|
||||
- BpmProcessInstanceService: 流程实例管理
|
||||
- BpmTaskService: 流程任务管理
|
||||
|
||||
方法分类:
|
||||
1. Query 查询相关方法 - 查询操作
|
||||
2. Update 写入相关方法 - 修改操作
|
||||
3. Event 事件相关方法 - 事件处理
|
||||
|
||||
impl_pattern: |
|
||||
实现类放在同目录下,命名规则:Bpm{Entity}ServiceImpl
|
||||
使用 @Service 和 @Validated 注解
|
||||
|
||||
注意事项:
|
||||
1. 循环依赖使用 @Lazy 注解
|
||||
2. 事务操作使用 @Transactional
|
||||
3. 数据权限使用 @DataPermission
|
||||
|
||||
example: |
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService {
|
||||
|
||||
@Resource
|
||||
private RuntimeService runtimeService; // Flowable 运行时服务
|
||||
@Resource
|
||||
private HistoryService historyService; // Flowable 历史服务
|
||||
|
||||
@Override
|
||||
public String createProcessInstance(Long userId, BpmProcessInstanceCreateReqDTO createReqDTO) {
|
||||
// 1. 获取流程定义
|
||||
ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(
|
||||
createReqDTO.getProcessDefinitionKey());
|
||||
// 2. 构建流程实例
|
||||
ProcessInstanceBuilder builder = runtimeService.createProcessInstanceBuilder()
|
||||
.processDefinitionKey(createReqDTO.getProcessDefinitionKey())
|
||||
.businessKey(createReqDTO.getBusinessKey())
|
||||
.variables(createReqDTO.getVariables());
|
||||
// 3. 启动流程实例
|
||||
return builder.start().getId();
|
||||
}
|
||||
}
|
||||
|
||||
# 数据访问规范
|
||||
dal:
|
||||
mapper_pattern: |
|
||||
Mapper 继承 BaseMapperX,提供基础 CRUD 操作
|
||||
使用 @Mapper 注解
|
||||
|
||||
特殊查询:
|
||||
- 使用 MyBatis-Plus 的 LambdaQueryWrapper
|
||||
- 复杂查询使用自定义 XML 或 @Select 注解
|
||||
|
||||
example: |
|
||||
@Mapper
|
||||
public interface BpmFormMapper extends BaseMapperX<BpmFormDO> {
|
||||
|
||||
default PageResult<BpmFormDO> selectPage(BpmFormPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<BpmFormDO>()
|
||||
.likeIfPresent(BpmFormDO::getName, reqVO.getName())
|
||||
.orderByDesc(BpmFormDO::getId));
|
||||
}
|
||||
}
|
||||
|
||||
# 异常处理
|
||||
error_handling:
|
||||
code_prefix: "1-009-XXX-XXX"
|
||||
examples:
|
||||
- code: "1_009_002_000"
|
||||
message: "已经存在流程标识为【{}】的流程"
|
||||
- code: "1_009_003_002"
|
||||
message: "流程定义不存在"
|
||||
- code: "1_009_004_000"
|
||||
message: "流程实例不存在"
|
||||
- code: "1_009_005_001"
|
||||
message: "操作失败,原因:该任务的审批人不是你"
|
||||
- code: "1_009_005_002"
|
||||
message: "流程任务不存在"
|
||||
|
||||
# 统一响应
|
||||
response:
|
||||
success_pattern: "CommonResult.success(data)"
|
||||
error_pattern: "throw exception(ERROR_CODE, params...)"
|
||||
|
||||
# 流程发起代码示例
|
||||
process_start_example: |
|
||||
// 方式一:通过 API 接口发起流程(推荐)
|
||||
@Resource
|
||||
private BpmProcessInstanceApi processInstanceApi;
|
||||
|
||||
public void submitLeave(LeaveCreateReqVO createReqVO) {
|
||||
// 1. 创建业务数据
|
||||
BpmOALeaveDO leave = new BpmOALeaveDO();
|
||||
// ... 设置属性
|
||||
leaveMapper.insert(leave);
|
||||
|
||||
// 2. 发起流程
|
||||
BpmProcessInstanceCreateReqDTO reqDTO = new BpmProcessInstanceCreateReqDTO();
|
||||
reqDTO.setProcessDefinitionKey("oa_leave"); // 流程定义 Key
|
||||
reqDTO.setBusinessKey(leave.getId().toString()); // 业务主键
|
||||
reqDTO.setVariables(BeanUtil.beanToMap(createReqVO)); // 流程变量
|
||||
String processInstanceId = processInstanceApi.createProcessInstance(userId, reqDTO);
|
||||
|
||||
// 3. 关联流程实例
|
||||
leave.setProcessInstanceId(processInstanceId);
|
||||
leaveMapper.updateById(leave);
|
||||
}
|
||||
|
||||
// 方式二:通过 Controller 接口发起流程(前端调用)
|
||||
@PostMapping("/create")
|
||||
public CommonResult<String> createProcessInstance(@Valid @RequestBody BpmProcessInstanceCreateReqVO createReqVO) {
|
||||
return success(processInstanceService.createProcessInstance(getLoginUserId(), createReqVO));
|
||||
}
|
||||
|
||||
# 任务审批代码示例
|
||||
task_approve_example: |
|
||||
// 审批通过
|
||||
@PutMapping("/approve")
|
||||
public CommonResult<Boolean> approveTask(@Valid @RequestBody BpmTaskApproveReqVO reqVO) {
|
||||
taskService.approveTask(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// 审批拒绝
|
||||
@PutMapping("/reject")
|
||||
public CommonResult<Boolean> rejectTask(@Valid @RequestBody BpmTaskRejectReqVO reqVO) {
|
||||
taskService.rejectTask(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// 任务退回
|
||||
@PutMapping("/return")
|
||||
public CommonResult<Boolean> returnTask(@Valid @RequestBody BpmTaskReturnReqVO reqVO) {
|
||||
taskService.returnTask(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// 任务委派
|
||||
@PutMapping("/delegate")
|
||||
public CommonResult<Boolean> delegateTask(@Valid @RequestBody BpmTaskDelegateReqVO reqVO) {
|
||||
taskService.delegateTask(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# 第五阶段:扩展指南
|
||||
# ============================================
|
||||
extension_guide:
|
||||
# 新增审批类型
|
||||
new_approval_type:
|
||||
title: "新增审批类型(如报销、采购等)"
|
||||
steps:
|
||||
- step: 1
|
||||
action: "创建业务 DO 实体"
|
||||
description: |
|
||||
在 dal/dataobject/ 下创建业务实体类,如 BpmExpenseDO
|
||||
需要包含 processInstanceId 字段关联流程实例
|
||||
|
||||
- step: 2
|
||||
action: "创建业务表"
|
||||
description: |
|
||||
创建对应数据库表,包含业务字段和 process_instance_id 字段
|
||||
|
||||
- step: 3
|
||||
action: "创建业务服务"
|
||||
description: |
|
||||
创建 Service 接口和实现类
|
||||
实现业务逻辑,调用 BpmProcessInstanceApi 发起流程
|
||||
|
||||
- step: 4
|
||||
action: "实现流程状态监听"
|
||||
description: |
|
||||
实现 BpmProcessInstanceStatusEventListener
|
||||
监听流程状态变更,更新业务表状态
|
||||
|
||||
示例:
|
||||
@Component
|
||||
public class BpmExpenseStatusListener extends BpmProcessInstanceStatusEventListener {
|
||||
@Override
|
||||
protected void onEvent(BpmProcessInstanceStatusEvent event) {
|
||||
if ("expense".equals(event.getProcessDefinitionKey())) {
|
||||
// 更新报销单状态
|
||||
expenseMapper.updateStatus(event.getBusinessKey(), event.getStatus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- step: 5
|
||||
action: "配置流程定义"
|
||||
description: |
|
||||
在管理后台配置流程定义
|
||||
1. 创建流程分类
|
||||
2. 创建流程表单
|
||||
3. 设计流程图(BPMN 或 Simple)
|
||||
4. 配置审批人策略
|
||||
|
||||
# 自定义审批人策略
|
||||
new_candidate_strategy:
|
||||
title: "自定义审批人策略"
|
||||
steps:
|
||||
- step: 1
|
||||
action: "添加策略枚举"
|
||||
description: |
|
||||
在 BpmTaskCandidateStrategyEnum 中添加新的策略类型
|
||||
|
||||
- step: 2
|
||||
action: "实现策略接口"
|
||||
description: |
|
||||
实现 BpmTaskCandidateStrategy 接口
|
||||
|
||||
示例:
|
||||
@Component
|
||||
public class BpmTaskCandidateCustomStrategy implements BpmTaskCandidateStrategy {
|
||||
|
||||
@Override
|
||||
public BpmTaskCandidateStrategyEnum getStrategy() {
|
||||
return BpmTaskCandidateStrategyEnum.CUSTOM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateParam(String param) {
|
||||
// 校验参数
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
|
||||
// 计算审批人
|
||||
return calculateUsers(param);
|
||||
}
|
||||
}
|
||||
|
||||
- step: 3
|
||||
action: "注册策略 Bean"
|
||||
description: |
|
||||
使用 @Component 注解,Spring 自动注册到策略工厂
|
||||
|
||||
# 自定义流程监听器
|
||||
new_process_listener:
|
||||
title: "自定义流程监听器"
|
||||
steps:
|
||||
- step: 1
|
||||
action: "创建监听器类"
|
||||
description: |
|
||||
实现 ExecutionListener 或 TaskListener 接口
|
||||
|
||||
示例(执行监听器):
|
||||
@Component
|
||||
public class CustomExecutionListener implements ExecutionListener {
|
||||
@Override
|
||||
public void notify(DelegateExecution execution) {
|
||||
// 监听逻辑
|
||||
}
|
||||
}
|
||||
|
||||
示例(任务监听器):
|
||||
@Component
|
||||
public class CustomTaskListener implements TaskListener {
|
||||
@Override
|
||||
public void notify(DelegateTask delegateTask) {
|
||||
// 监听逻辑
|
||||
}
|
||||
}
|
||||
|
||||
- step: 2
|
||||
action: "注册监听器"
|
||||
description: |
|
||||
方式一:通过数据库配置(推荐)
|
||||
在 bpm_process_listener 表中添加监听器配置
|
||||
|
||||
方式二:在 BPMN XML 中配置
|
||||
<extensionElements>
|
||||
<flowable:executionListener event="start" delegateExpression="${customExecutionListener}"/>
|
||||
</extensionElements>
|
||||
|
||||
# 最佳实践
|
||||
best_practices:
|
||||
- "流程设计原则:流程定义和业务数据分离,通过 businessKey 关联"
|
||||
- "审批人策略:优先使用已有的审批人策略,如角色、部门负责人、用户组等"
|
||||
- "流程变量:使用流程变量传递表单数据,避免过度依赖业务查询"
|
||||
- "事件监听:通过监听流程状态变更事件更新业务状态,实现解耦"
|
||||
- "Simple 设计器:对于简单审批流程,推荐使用 Simple 设计器,降低学习成本"
|
||||
- "流程版本:同一流程定义可以部署多个版本,已运行的实例使用旧版本"
|
||||
- "权限控制:通过 managerUserIds 配置流程管理员,实现流程管理权限分离"
|
||||
|
||||
# ============================================
|
||||
# 依赖关系
|
||||
# ============================================
|
||||
dependencies:
|
||||
# 内部依赖(其他模块)
|
||||
internal:
|
||||
- module: "yudao-module-system"
|
||||
api: "AdminUserApi"
|
||||
purpose: "获取用户信息,用于审批人计算和流程发起人信息"
|
||||
- module: "yudao-module-system"
|
||||
api: "DeptApi"
|
||||
purpose: "获取部门信息,用于部门负责人审批策略"
|
||||
- module: "yudao-module-system"
|
||||
api: "PermissionApi"
|
||||
purpose: "获取用户角色,用于角色审批策略"
|
||||
- module: "yudao-module-system"
|
||||
api: "PostApi"
|
||||
purpose: "获取岗位信息,用于岗位审批策略"
|
||||
|
||||
# 外部依赖(第三方库)
|
||||
external:
|
||||
- name: "flowable-spring-boot-starter-process"
|
||||
version: "6.x"
|
||||
purpose: "Flowable 工作流引擎核心"
|
||||
- name: "flowable-spring-boot-starter-actuator"
|
||||
version: "6.x"
|
||||
purpose: "Flowable 监控端点"
|
||||
- name: "yudao-spring-boot-starter-mybatis"
|
||||
version: "${revision}"
|
||||
purpose: "MyBatis-Plus 数据访问"
|
||||
- name: "yudao-spring-boot-starter-biz-tenant"
|
||||
version: "${revision}"
|
||||
purpose: "多租户支持"
|
||||
- name: "yudao-spring-boot-starter-biz-data-permission"
|
||||
version: "${revision}"
|
||||
purpose: "数据权限控制"
|
||||
|
||||
# ============================================
|
||||
# 关键文件清单
|
||||
# ============================================
|
||||
key_files:
|
||||
- path: "yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java"
|
||||
purpose: "流程实例服务接口,定义流程实例的核心操作"
|
||||
- path: "yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java"
|
||||
purpose: "流程任务服务接口,定义任务审批的核心操作"
|
||||
- path: "yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java"
|
||||
purpose: "流程模型服务接口,定义流程模型管理操作"
|
||||
- path: "yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApi.java"
|
||||
purpose: "流程实例 API 接口,供其他模块调用发起流程"
|
||||
- path: "yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/api/event/BpmProcessInstanceStatusEvent.java"
|
||||
purpose: "流程状态变更事件,实现模块间解耦"
|
||||
- path: "yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java"
|
||||
purpose: "审批人策略接口,定义审批人计算的标准"
|
||||
- path: "yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java"
|
||||
purpose: "审批人策略枚举,列出所有支持的审批人分配方式"
|
||||
- path: "yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java"
|
||||
purpose: "错误码常量,定义 BPM 模块所有错误码"
|
||||
- path: "yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmModelTypeEnum.java"
|
||||
purpose: "流程模型类型枚举,区分 BPMN 和 Simple 设计器"
|
||||
- path: "yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java"
|
||||
purpose: "流程实例状态枚举,定义流程的所有状态"
|
||||
- path: "yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java"
|
||||
purpose: "任务管理 Controller,提供任务审批相关 API"
|
||||
- path: "yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java"
|
||||
purpose: "流程实例 Controller,提供流程实例管理 API"
|
||||
- path: "yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/listener/BpmOALeaveStatusListener.java"
|
||||
purpose: "请假流程状态监听示例,展示如何监听流程状态变更"
|
||||
- path: "yudao-module-bpm/pom.xml"
|
||||
purpose: "Maven 依赖配置,包含 Flowable 等核心依赖"
|
||||
@@ -1,861 +0,0 @@
|
||||
# Skill 文档 - yudao-module-crm 模块
|
||||
# 客户关系管理模块完整技术文档
|
||||
|
||||
skill:
|
||||
id: "skill-crm"
|
||||
name: "CRM 客户关系管理 Skill"
|
||||
version: "1.0.0"
|
||||
module_path: "yudao-module-crm"
|
||||
created_at: "2026-03-18"
|
||||
updated_at: "2026-03-18"
|
||||
|
||||
# ============================================
|
||||
# 第一阶段:设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
# 业务定位:模块解决什么业务问题?在整个系统中的定位?
|
||||
business_position: |
|
||||
CRM(Customer Relationship Management)模块是客户关系管理系统的核心,解决企业销售过程中的客户管理、商机跟进、合同签订、回款管理等全流程业务问题。
|
||||
|
||||
核心业务场景:
|
||||
1. 线索管理:潜在客户信息的录入与转化
|
||||
2. 客户管理:客户信息维护、公海池机制、客户分配与转移
|
||||
3. 联系人管理:客户关键联系人信息管理
|
||||
4. 商机管理:销售机会的全生命周期管理(销售漏斗)
|
||||
5. 合同管理:合同签订与审批流程
|
||||
6. 回款管理:回款计划与实际回款跟踪
|
||||
7. 产品管理:产品信息与分类管理
|
||||
8. 数据统计:销售漏斗、业绩排名等统计分析
|
||||
|
||||
在整个系统中的定位:
|
||||
- 作为独立的业务模块,提供完整的 CRM 业务功能
|
||||
- 依赖 System 模块(用户、部门)和 BPM 模块(审批流程)
|
||||
- 不对外暴露 API,通过 Controller 层提供 HTTP 接口
|
||||
|
||||
# 设计原则:遵循了哪些设计原则(SOLID、DDD)?有哪些架构决策?
|
||||
design_principles:
|
||||
- name: "领域驱动设计(DDD)"
|
||||
description: "以客户为聚合根,商机、联系人、合同等为关联实体,形成清晰的领域模型边界"
|
||||
- name: "数据权限控制"
|
||||
description: "基于 CrmPermissionDO 实现细粒度的数据权限控制,支持负责人、只读、读写三级权限"
|
||||
- name: "分层架构"
|
||||
description: "严格的 Controller -> Service -> DAL 三层架构,职责清晰"
|
||||
- name: "销售漏斗模型"
|
||||
description: "商机状态组(CrmBusinessStatusTypeDO)+ 商机状态(CrmBusinessStatusDO)实现可配置的销售漏斗"
|
||||
- name: "公海池机制"
|
||||
description: "客户公海池配置(CrmCustomerPoolConfigDO)实现客户自动回收与分配机制"
|
||||
- name: "AOP 权限校验"
|
||||
description: "通过 @CrmPermission 注解实现声明式的数据权限校验"
|
||||
- name: "操作日志记录"
|
||||
description: "通过 @LogRecord 注解记录关键业务操作日志"
|
||||
|
||||
# 领域模型:核心领域对象有哪些?领域对象之间的关系?聚合根是什么?
|
||||
domain_model:
|
||||
aggregates:
|
||||
- name: "客户(Customer)"
|
||||
type: "聚合根"
|
||||
description: "CRM 系统的核心聚合根,关联商机、联系人、合同、回款等所有业务对象"
|
||||
entities:
|
||||
- "CrmBusinessDO(商机)"
|
||||
- "CrmContactDO(联系人)"
|
||||
- "CrmContractDO(合同)"
|
||||
- "CrmReceivableDO(回款)"
|
||||
- "CrmReceivablePlanDO(回款计划)"
|
||||
- "CrmFollowUpRecordDO(跟进记录)"
|
||||
|
||||
- name: "线索(Clue)"
|
||||
type: "聚合根"
|
||||
description: "潜在客户信息,可转化为正式客户"
|
||||
entities: []
|
||||
|
||||
- name: "产品(Product)"
|
||||
type: "聚合根"
|
||||
description: "产品信息管理,被商机和合同引用"
|
||||
entities:
|
||||
- "CrmProductCategoryDO(产品分类)"
|
||||
|
||||
value_objects:
|
||||
- name: "CrmPermissionDO"
|
||||
description: "数据权限值对象,关联业务类型与权限级别"
|
||||
- name: "CrmBusinessStatusDO"
|
||||
description: "商机状态值对象,属于商机状态组"
|
||||
- name: "CrmBusinessStatusTypeDO"
|
||||
description: "商机状态组配置"
|
||||
- name: "CrmCustomerPoolConfigDO"
|
||||
description: "客户公海池配置"
|
||||
- name: "CrmCustomerLimitConfigDO"
|
||||
description: "客户限制配置(拥有上限、锁定上限)"
|
||||
|
||||
services:
|
||||
- name: "CrmCustomerService"
|
||||
description: "客户管理领域服务,处理客户创建、转移、公海等核心业务"
|
||||
- name: "CrmBusinessService"
|
||||
description: "商机管理领域服务,处理商机创建、状态变更等业务"
|
||||
- name: "CrmPermissionService"
|
||||
description: "数据权限领域服务,处理权限创建、转移、校验等业务"
|
||||
- name: "CrmContractService"
|
||||
description: "合同管理领域服务,处理合同签订、审批等业务"
|
||||
- name: "CrmReceivableService"
|
||||
description: "回款管理领域服务,处理回款录入、审批等业务"
|
||||
|
||||
# ============================================
|
||||
# 第二阶段:架构设计
|
||||
# ============================================
|
||||
architecture:
|
||||
# 分层架构
|
||||
layers:
|
||||
- name: "api"
|
||||
purpose: "模块间API接口(当前模块未对外暴露API)"
|
||||
components:
|
||||
- "package-info.java"
|
||||
|
||||
- name: "controller"
|
||||
purpose: "HTTP接口层,处理请求响应"
|
||||
components:
|
||||
- "CrmCustomerController - 客户管理接口"
|
||||
- "CrmClueController - 线索管理接口"
|
||||
- "CrmContactController - 联系人管理接口"
|
||||
- "CrmBusinessController - 商机管理接口"
|
||||
- "CrmBusinessStatusController - 商机状态管理接口"
|
||||
- "CrmContractController - 合同管理接口"
|
||||
- "CrmContractConfigController - 合同配置接口"
|
||||
- "CrmReceivableController - 回款管理接口"
|
||||
- "CrmReceivablePlanController - 回款计划接口"
|
||||
- "CrmProductController - 产品管理接口"
|
||||
- "CrmProductCategoryController - 产品分类接口"
|
||||
- "CrmPermissionController - 数据权限接口"
|
||||
- "CrmFollowUpRecordController - 跟进记录接口"
|
||||
- "CrmOperateLogController - 操作日志接口"
|
||||
- "CrmCustomerLimitConfigController - 客户限制配置接口"
|
||||
- "CrmCustomerPoolConfigController - 公海池配置接口"
|
||||
- "CrmStatisticsCustomerController - 客户统计接口"
|
||||
- "CrmStatisticsFunnelController - 漏斗统计接口"
|
||||
- "CrmStatisticsPerformanceController - 业绩统计接口"
|
||||
- "CrmStatisticsPortraitController - 客户画像统计接口"
|
||||
- "CrmStatisticsRankController - 排行榜统计接口"
|
||||
|
||||
- name: "service"
|
||||
purpose: "业务逻辑层,处理核心业务"
|
||||
components:
|
||||
- "customer/ - 客户相关服务"
|
||||
- "clue/ - 线索相关服务"
|
||||
- "contact/ - 联系人相关服务"
|
||||
- "business/ - 商机相关服务"
|
||||
- "contract/ - 合同相关服务"
|
||||
- "receivable/ - 回款相关服务"
|
||||
- "product/ - 产品相关服务"
|
||||
- "permission/ - 权限相关服务"
|
||||
- "followup/ - 跟进记录服务"
|
||||
- "statistics/ - 统计服务"
|
||||
|
||||
- name: "dal"
|
||||
purpose: "数据访问层,处理数据库操作"
|
||||
components:
|
||||
- "dataobject/ - DO 实体类"
|
||||
- "mysql/ - MyBatis Mapper 接口"
|
||||
|
||||
# 设计模式应用
|
||||
design_patterns:
|
||||
- pattern: "AOP 权限校验"
|
||||
location: "CrmPermission 注解 + CrmPermissionAspect"
|
||||
purpose: "通过注解声明数据权限要求,AOP 自动校验"
|
||||
- pattern: "策略模式"
|
||||
location: "CrmBizTypeEnum 业务类型枚举"
|
||||
purpose: "统一处理不同业务类型的权限校验逻辑"
|
||||
- pattern: "模板方法"
|
||||
location: "各 Service 实现类的校验方法"
|
||||
purpose: "统一的业务校验流程"
|
||||
- pattern: "观察者模式"
|
||||
location: "CrmContractStatusListener、CrmReceivableStatusListener"
|
||||
purpose: "监听 BPM 审批状态变更,更新业务状态"
|
||||
- pattern: "Builder 模式"
|
||||
location: "所有 DO 实体类使用 @Builder"
|
||||
purpose: "简化复杂对象的构建"
|
||||
- pattern: "代理模式"
|
||||
location: "CrmCustomerServiceImpl.getSelf()"
|
||||
purpose: "解决 AOP 事务代理问题"
|
||||
|
||||
# 模块间通信
|
||||
communication:
|
||||
apis: [] # 当前模块未对外暴露 API
|
||||
|
||||
consumers:
|
||||
- module: "yudao-module-system"
|
||||
api: "AdminUserApi"
|
||||
purpose: "获取用户信息、校验用户"
|
||||
- module: "yudao-module-system"
|
||||
api: "DeptApi"
|
||||
purpose: "获取部门信息"
|
||||
- module: "yudao-module-bpm"
|
||||
api: "流程审批"
|
||||
purpose: "合同、回款的审批流程"
|
||||
|
||||
mq: [] # 当前模块未使用消息队列
|
||||
|
||||
# ============================================
|
||||
# 第三阶段:数据表设计
|
||||
# ============================================
|
||||
data_model:
|
||||
# 实体继承体系
|
||||
entity_hierarchy:
|
||||
base: "BaseDO"
|
||||
description: "所有 CRM 实体继承 BaseDO,包含 id、creator、create_time、updater、update_time、deleted 字段。注意:CRM 模块未使用 TenantBaseDO,不涉及多租户"
|
||||
|
||||
# 核心数据表
|
||||
tables:
|
||||
# ==================== 客户相关 ====================
|
||||
- name: "crm_customer"
|
||||
comment: "客户表"
|
||||
entity: "CrmCustomerDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "name", type: "VARCHAR", comment: "客户名称" }
|
||||
- { name: "follow_up_status", type: "TINYINT", comment: "跟进状态" }
|
||||
- { name: "contact_last_time", type: "DATETIME", comment: "最后跟进时间" }
|
||||
- { name: "contact_last_content", type: "VARCHAR", comment: "最后跟进内容" }
|
||||
- { name: "contact_next_time", type: "DATETIME", comment: "下次联系时间" }
|
||||
- { name: "owner_user_id", type: "BIGINT", comment: "负责人用户编号" }
|
||||
- { name: "owner_time", type: "DATETIME", comment: "成为负责人时间" }
|
||||
- { name: "lock_status", type: "TINYINT", comment: "锁定状态" }
|
||||
- { name: "deal_status", type: "TINYINT", comment: "成交状态" }
|
||||
- { name: "mobile", type: "VARCHAR", comment: "手机号" }
|
||||
- { name: "telephone", type: "VARCHAR", comment: "电话" }
|
||||
- { name: "qq", type: "VARCHAR", comment: "QQ" }
|
||||
- { name: "wechat", type: "VARCHAR", comment: "微信" }
|
||||
- { name: "email", type: "VARCHAR", comment: "邮箱" }
|
||||
- { name: "area_id", type: "INT", comment: "地区ID" }
|
||||
- { name: "detail_address", type: "VARCHAR", comment: "详细地址" }
|
||||
- { name: "industry_id", type: "INT", comment: "所属行业" }
|
||||
- { name: "level", type: "INT", comment: "客户等级" }
|
||||
- { name: "source", type: "INT", comment: "客户来源" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
indexes:
|
||||
- { name: "idx_owner_user_id", columns: ["owner_user_id"] }
|
||||
- { name: "idx_name", columns: ["name"] }
|
||||
|
||||
- name: "crm_customer_pool_config"
|
||||
comment: "客户公海池配置表"
|
||||
entity: "CrmCustomerPoolConfigDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "enabled", type: "TINYINT", comment: "是否启用客户公海" }
|
||||
- { name: "contact_expire_days", type: "INT", comment: "未跟进放入公海天数" }
|
||||
- { name: "deal_expire_days", type: "INT", comment: "未成交放入公海天数" }
|
||||
- { name: "notify_enabled", type: "TINYINT", comment: "是否开启提前提醒" }
|
||||
- { name: "notify_days", type: "INT", comment: "提前提醒天数" }
|
||||
|
||||
- name: "crm_customer_limit_config"
|
||||
comment: "客户限制配置表"
|
||||
entity: "CrmCustomerLimitConfigDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "type", type: "INT", comment: "限制类型(拥有上限/锁定上限)" }
|
||||
- { name: "max_count", type: "INT", comment: "最大数量" }
|
||||
- { name: "deal_count_enabled", type: "TINYINT", comment: "是否计入成交客户" }
|
||||
- { name: "user_ids", type: "VARCHAR", comment: "适用用户ID列表" }
|
||||
- { name: "dept_ids", type: "VARCHAR", comment: "适用部门ID列表" }
|
||||
|
||||
# ==================== 线索相关 ====================
|
||||
- name: "crm_clue"
|
||||
comment: "线索表"
|
||||
entity: "CrmClueDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "name", type: "VARCHAR", comment: "线索名称" }
|
||||
- { name: "follow_up_status", type: "TINYINT", comment: "跟进状态" }
|
||||
- { name: "contact_last_time", type: "DATETIME", comment: "最后跟进时间" }
|
||||
- { name: "contact_last_content", type: "VARCHAR", comment: "最后跟进内容" }
|
||||
- { name: "contact_next_time", type: "DATETIME", comment: "下次联系时间" }
|
||||
- { name: "owner_user_id", type: "BIGINT", comment: "负责人用户编号" }
|
||||
- { name: "transform_status", type: "TINYINT", comment: "转化状态" }
|
||||
- { name: "customer_id", type: "BIGINT", comment: "转化后的客户ID" }
|
||||
- { name: "mobile", type: "VARCHAR", comment: "手机号" }
|
||||
- { name: "telephone", type: "VARCHAR", comment: "电话" }
|
||||
- { name: "qq", type: "VARCHAR", comment: "QQ" }
|
||||
- { name: "wechat", type: "VARCHAR", comment: "微信" }
|
||||
- { name: "email", type: "VARCHAR", comment: "邮箱" }
|
||||
- { name: "area_id", type: "INT", comment: "地区ID" }
|
||||
- { name: "detail_address", type: "VARCHAR", comment: "详细地址" }
|
||||
- { name: "industry_id", type: "INT", comment: "所属行业" }
|
||||
- { name: "level", type: "INT", comment: "客户等级" }
|
||||
- { name: "source", type: "INT", comment: "客户来源" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
|
||||
# ==================== 联系人相关 ====================
|
||||
- name: "crm_contact"
|
||||
comment: "联系人表"
|
||||
entity: "CrmContactDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "name", type: "VARCHAR", comment: "联系人姓名" }
|
||||
- { name: "customer_id", type: "BIGINT", comment: "客户ID" }
|
||||
- { name: "contact_last_time", type: "DATETIME", comment: "最后跟进时间" }
|
||||
- { name: "contact_last_content", type: "VARCHAR", comment: "最后跟进内容" }
|
||||
- { name: "contact_next_time", type: "DATETIME", comment: "下次联系时间" }
|
||||
- { name: "owner_user_id", type: "BIGINT", comment: "负责人用户编号" }
|
||||
- { name: "mobile", type: "VARCHAR", comment: "手机号" }
|
||||
- { name: "telephone", type: "VARCHAR", comment: "电话" }
|
||||
- { name: "email", type: "VARCHAR", comment: "邮箱" }
|
||||
- { name: "qq", type: "BIGINT", comment: "QQ" }
|
||||
- { name: "wechat", type: "VARCHAR", comment: "微信" }
|
||||
- { name: "area_id", type: "INT", comment: "地区ID" }
|
||||
- { name: "detail_address", type: "VARCHAR", comment: "详细地址" }
|
||||
- { name: "sex", type: "INT", comment: "性别" }
|
||||
- { name: "master", type: "TINYINT", comment: "是否关键决策人" }
|
||||
- { name: "post", type: "VARCHAR", comment: "职位" }
|
||||
- { name: "parent_id", type: "BIGINT", comment: "直属上级ID" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
|
||||
- name: "crm_contact_business"
|
||||
comment: "联系人商机关联表"
|
||||
entity: "CrmContactBusinessDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "contact_id", type: "BIGINT", comment: "联系人ID" }
|
||||
- { name: "business_id", type: "BIGINT", comment: "商机ID" }
|
||||
|
||||
# ==================== 商机相关 ====================
|
||||
- name: "crm_business"
|
||||
comment: "商机表"
|
||||
entity: "CrmBusinessDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "name", type: "VARCHAR", comment: "商机名称" }
|
||||
- { name: "customer_id", type: "BIGINT", comment: "客户ID" }
|
||||
- { name: "follow_up_status", type: "TINYINT", comment: "跟进状态" }
|
||||
- { name: "contact_last_time", type: "DATETIME", comment: "最后跟进时间" }
|
||||
- { name: "contact_next_time", type: "DATETIME", comment: "下次联系时间" }
|
||||
- { name: "owner_user_id", type: "BIGINT", comment: "负责人用户编号" }
|
||||
- { name: "status_type_id", type: "BIGINT", comment: "商机状态组ID" }
|
||||
- { name: "status_id", type: "BIGINT", comment: "商机状态ID" }
|
||||
- { name: "end_status", type: "INT", comment: "结束状态(赢单/输单/无效)" }
|
||||
- { name: "end_remark", type: "VARCHAR", comment: "结束备注" }
|
||||
- { name: "deal_time", type: "DATETIME", comment: "预计成交日期" }
|
||||
- { name: "total_product_price", type: "DECIMAL", comment: "产品总金额" }
|
||||
- { name: "discount_percent", type: "DECIMAL", comment: "整单折扣" }
|
||||
- { name: "total_price", type: "DECIMAL", comment: "商机总金额" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
|
||||
- name: "crm_business_status_type"
|
||||
comment: "商机状态组表(配置表)"
|
||||
entity: "CrmBusinessStatusTypeDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "name", type: "VARCHAR", comment: "状态组名称" }
|
||||
- { name: "dept_ids", type: "VARCHAR", comment: "使用的部门ID列表" }
|
||||
|
||||
- name: "crm_business_status"
|
||||
comment: "商机状态表(配置表)"
|
||||
entity: "CrmBusinessStatusDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "type_id", type: "BIGINT", comment: "状态组ID" }
|
||||
- { name: "name", type: "VARCHAR", comment: "状态名称" }
|
||||
- { name: "percent", type: "INT", comment: "赢单率百分比" }
|
||||
- { name: "sort", type: "INT", comment: "排序" }
|
||||
|
||||
- name: "crm_business_product"
|
||||
comment: "商机产品关联表"
|
||||
entity: "CrmBusinessProductDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "business_id", type: "BIGINT", comment: "商机ID" }
|
||||
- { name: "product_id", type: "BIGINT", comment: "产品ID" }
|
||||
- { name: "count", type: "DECIMAL", comment: "数量" }
|
||||
- { name: "price", type: "DECIMAL", comment: "单价" }
|
||||
|
||||
# ==================== 合同相关 ====================
|
||||
- name: "crm_contract"
|
||||
comment: "合同表"
|
||||
entity: "CrmContractDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "name", type: "VARCHAR", comment: "合同名称" }
|
||||
- { name: "no", type: "VARCHAR", comment: "合同编号" }
|
||||
- { name: "customer_id", type: "BIGINT", comment: "客户ID" }
|
||||
- { name: "business_id", type: "BIGINT", comment: "商机ID" }
|
||||
- { name: "contact_last_time", type: "DATETIME", comment: "最后跟进时间" }
|
||||
- { name: "owner_user_id", type: "BIGINT", comment: "负责人用户编号" }
|
||||
- { name: "process_instance_id", type: "VARCHAR", comment: "工作流实例ID" }
|
||||
- { name: "audit_status", type: "INT", comment: "审批状态" }
|
||||
- { name: "order_date", type: "DATETIME", comment: "下单日期" }
|
||||
- { name: "start_time", type: "DATETIME", comment: "开始时间" }
|
||||
- { name: "end_time", type: "DATETIME", comment: "结束时间" }
|
||||
- { name: "total_product_price", type: "DECIMAL", comment: "产品总金额" }
|
||||
- { name: "discount_percent", type: "DECIMAL", comment: "整单折扣" }
|
||||
- { name: "total_price", type: "DECIMAL", comment: "合同总金额" }
|
||||
- { name: "sign_contact_id", type: "BIGINT", comment: "客户签约人ID" }
|
||||
- { name: "sign_user_id", type: "BIGINT", comment: "公司签约人ID" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
|
||||
- name: "crm_contract_config"
|
||||
comment: "合同配置表"
|
||||
entity: "CrmContractConfigDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "deposit_enabled", type: "TINYINT", comment: "是否开启押金" }
|
||||
- { name: "deposit_percent", type: "DECIMAL", comment: "押金比例" }
|
||||
|
||||
- name: "crm_contract_product"
|
||||
comment: "合同产品关联表"
|
||||
entity: "CrmContractProductDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "contract_id", type: "BIGINT", comment: "合同ID" }
|
||||
- { name: "product_id", type: "BIGINT", comment: "产品ID" }
|
||||
- { name: "count", type: "DECIMAL", comment: "数量" }
|
||||
- { name: "price", type: "DECIMAL", comment: "单价" }
|
||||
|
||||
# ==================== 回款相关 ====================
|
||||
- name: "crm_receivable"
|
||||
comment: "回款表"
|
||||
entity: "CrmReceivableDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "no", type: "VARCHAR", comment: "回款编号" }
|
||||
- { name: "plan_id", type: "BIGINT", comment: "回款计划ID" }
|
||||
- { name: "customer_id", type: "BIGINT", comment: "客户ID" }
|
||||
- { name: "contract_id", type: "BIGINT", comment: "合同ID" }
|
||||
- { name: "owner_user_id", type: "BIGINT", comment: "负责人用户编号" }
|
||||
- { name: "return_time", type: "DATETIME", comment: "回款日期" }
|
||||
- { name: "return_type", type: "INT", comment: "回款方式" }
|
||||
- { name: "price", type: "DECIMAL", comment: "回款金额" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
- { name: "process_instance_id", type: "VARCHAR", comment: "工作流实例ID" }
|
||||
- { name: "audit_status", type: "INT", comment: "审批状态" }
|
||||
|
||||
- name: "crm_receivable_plan"
|
||||
comment: "回款计划表"
|
||||
entity: "CrmReceivablePlanDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "period", type: "INT", comment: "期数" }
|
||||
- { name: "customer_id", type: "BIGINT", comment: "客户ID" }
|
||||
- { name: "contract_id", type: "BIGINT", comment: "合同ID" }
|
||||
- { name: "owner_user_id", type: "BIGINT", comment: "负责人用户编号" }
|
||||
- { name: "return_time", type: "DATETIME", comment: "计划回款日期" }
|
||||
- { name: "return_type", type: "INT", comment: "计划回款类型" }
|
||||
- { name: "price", type: "DECIMAL", comment: "计划回款金额" }
|
||||
- { name: "receivable_id", type: "BIGINT", comment: "实际回款ID" }
|
||||
- { name: "remind_days", type: "INT", comment: "提前提醒天数" }
|
||||
- { name: "remind_time", type: "DATETIME", comment: "提醒日期" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
|
||||
# ==================== 产品相关 ====================
|
||||
- name: "crm_product"
|
||||
comment: "产品表"
|
||||
entity: "CrmProductDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "name", type: "VARCHAR", comment: "产品名称" }
|
||||
- { name: "no", type: "VARCHAR", comment: "产品编码" }
|
||||
- { name: "unit", type: "INT", comment: "单位" }
|
||||
- { name: "price", type: "DECIMAL", comment: "价格" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
- { name: "category_id", type: "BIGINT", comment: "分类ID" }
|
||||
- { name: "description", type: "VARCHAR", comment: "产品描述" }
|
||||
- { name: "owner_user_id", type: "BIGINT", comment: "负责人用户编号" }
|
||||
|
||||
- name: "crm_product_category"
|
||||
comment: "产品分类表"
|
||||
entity: "CrmProductCategoryDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "name", type: "VARCHAR", comment: "分类名称" }
|
||||
- { name: "parent_id", type: "BIGINT", comment: "父分类ID" }
|
||||
- { name: "sort", type: "INT", comment: "排序" }
|
||||
|
||||
# ==================== 权限与跟进 ====================
|
||||
- name: "crm_permission"
|
||||
comment: "数据权限表"
|
||||
entity: "CrmPermissionDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "biz_type", type: "INT", comment: "业务类型" }
|
||||
- { name: "biz_id", type: "BIGINT", comment: "业务ID" }
|
||||
- { name: "user_id", type: "BIGINT", comment: "用户ID" }
|
||||
- { name: "level", type: "INT", comment: "权限级别" }
|
||||
indexes:
|
||||
- { name: "idx_biz", columns: ["biz_type", "biz_id"] }
|
||||
- { name: "idx_user", columns: ["user_id"] }
|
||||
|
||||
- name: "crm_follow_up_record"
|
||||
comment: "跟进记录表"
|
||||
entity: "CrmFollowUpRecordDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID" }
|
||||
- { name: "biz_type", type: "INT", comment: "业务类型" }
|
||||
- { name: "biz_id", type: "BIGINT", comment: "业务ID" }
|
||||
- { name: "type", type: "INT", comment: "跟进类型" }
|
||||
- { name: "content", type: "VARCHAR", comment: "跟进内容" }
|
||||
- { name: "next_time", type: "DATETIME", comment: "下次联系时间" }
|
||||
- { name: "pic_urls", type: "VARCHAR", comment: "图片URL列表" }
|
||||
- { name: "file_urls", type: "VARCHAR", comment: "附件URL列表" }
|
||||
- { name: "business_ids", type: "VARCHAR", comment: "关联商机ID列表" }
|
||||
- { name: "contact_ids", type: "VARCHAR", comment: "关联联系人ID列表" }
|
||||
|
||||
# 表关系(ER关系)
|
||||
relationships:
|
||||
- from: "crm_customer"
|
||||
to: "crm_business"
|
||||
type: "1:N"
|
||||
foreign_key: "customer_id"
|
||||
description: "一个客户可以有多个商机"
|
||||
- from: "crm_customer"
|
||||
to: "crm_contact"
|
||||
type: "1:N"
|
||||
foreign_key: "customer_id"
|
||||
description: "一个客户可以有多个联系人"
|
||||
- from: "crm_customer"
|
||||
to: "crm_contract"
|
||||
type: "1:N"
|
||||
foreign_key: "customer_id"
|
||||
description: "一个客户可以签订多个合同"
|
||||
- from: "crm_customer"
|
||||
to: "crm_receivable"
|
||||
type: "1:N"
|
||||
foreign_key: "customer_id"
|
||||
description: "一个客户可以有多个回款"
|
||||
- from: "crm_business"
|
||||
to: "crm_contract"
|
||||
type: "1:N"
|
||||
foreign_key: "business_id"
|
||||
description: "一个商机可以签订多个合同"
|
||||
- from: "crm_contract"
|
||||
to: "crm_receivable"
|
||||
type: "1:N"
|
||||
foreign_key: "contract_id"
|
||||
description: "一个合同可以有多个回款"
|
||||
- from: "crm_contract"
|
||||
to: "crm_receivable_plan"
|
||||
type: "1:N"
|
||||
foreign_key: "contract_id"
|
||||
description: "一个合同可以有多个回款计划"
|
||||
- from: "crm_receivable_plan"
|
||||
to: "crm_receivable"
|
||||
type: "1:1"
|
||||
foreign_key: "receivable_id"
|
||||
description: "一个回款计划对应一个实际回款"
|
||||
- from: "crm_business_status_type"
|
||||
to: "crm_business_status"
|
||||
type: "1:N"
|
||||
foreign_key: "type_id"
|
||||
description: "一个状态组包含多个状态"
|
||||
- from: "crm_product_category"
|
||||
to: "crm_product"
|
||||
type: "1:N"
|
||||
foreign_key: "category_id"
|
||||
description: "一个分类下有多个产品"
|
||||
- from: "crm_clue"
|
||||
to: "crm_customer"
|
||||
type: "N:1"
|
||||
foreign_key: "customer_id"
|
||||
description: "线索转化为客户"
|
||||
|
||||
# ============================================
|
||||
# 第四阶段:代码使用设计
|
||||
# ============================================
|
||||
code_patterns:
|
||||
# Controller层规范
|
||||
controller:
|
||||
annotations:
|
||||
- "@Tag(name = '管理后台 - CRM xxx')"
|
||||
- "@RestController"
|
||||
- "@RequestMapping('/crm/xxx')"
|
||||
- "@Validated"
|
||||
- "@PreAuthorize('@ss.hasPermission('crm:xxx:action')')"
|
||||
- "@Operation(summary = 'xxx')"
|
||||
- "@Parameter(name = 'id', description = '编号', required = true)"
|
||||
example: |
|
||||
@Tag(name = "管理后台 - CRM 客户")
|
||||
@RestController
|
||||
@RequestMapping("/crm/customer")
|
||||
@Validated
|
||||
public class CrmCustomerController {
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建客户")
|
||||
@PreAuthorize("@ss.hasPermission('crm:customer:create')")
|
||||
public CommonResult<Long> createCustomer(@Valid @RequestBody CrmCustomerSaveReqVO createReqVO) {
|
||||
return success(customerService.createCustomer(createReqVO, getLoginUserId()));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新客户")
|
||||
@PreAuthorize("@ss.hasPermission('crm:customer:update')")
|
||||
public CommonResult<Boolean> updateCustomer(@Valid @RequestBody CrmCustomerSaveReqVO updateReqVO) {
|
||||
customerService.updateCustomer(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得客户分页")
|
||||
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
|
||||
public CommonResult<PageResult<CrmCustomerRespVO>> getCustomerPage(@Valid CrmCustomerPageReqVO pageVO) {
|
||||
PageResult<CrmCustomerDO> pageResult = customerService.getCustomerPage(pageVO, getLoginUserId());
|
||||
return success(new PageResult<>(buildCustomerDetailList(pageResult.getList()), pageResult.getTotal()));
|
||||
}
|
||||
}
|
||||
|
||||
# Service层规范
|
||||
service:
|
||||
interface_pattern: "接口定义业务方法,使用 @Valid 注解校验参数"
|
||||
impl_pattern: |
|
||||
- @Service 注解标记服务类
|
||||
- @Validated 启用方法参数校验
|
||||
- @Transactional(rollbackFor = Exception.class) 声明事务
|
||||
- @LogRecord 记录操作日志
|
||||
- @CrmPermission 声明数据权限要求
|
||||
example: |
|
||||
@Service
|
||||
@Validated
|
||||
public class CrmCustomerServiceImpl implements CrmCustomerService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_CREATE_SUB_TYPE,
|
||||
bizNo = "{{#customer.id}}", success = CRM_CUSTOMER_CREATE_SUCCESS)
|
||||
public Long createCustomer(CrmCustomerSaveReqVO createReqVO, Long userId) {
|
||||
// 1. 校验拥有客户是否到达上限
|
||||
validateCustomerExceedOwnerLimit(createReqVO.getOwnerUserId(), 1);
|
||||
|
||||
// 2. 插入客户
|
||||
CrmCustomerDO customer = initCustomer(createReqVO, userId);
|
||||
customerMapper.insert(customer);
|
||||
|
||||
// 3. 创建数据权限
|
||||
permissionService.createPermission(new CrmPermissionCreateReqBO()
|
||||
.setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
|
||||
.setBizId(customer.getId())
|
||||
.setUserId(userId)
|
||||
.setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
|
||||
|
||||
// 4. 记录操作日志上下文
|
||||
LogRecordContext.putVariable("customer", customer);
|
||||
return customer.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id",
|
||||
level = CrmPermissionLevelEnum.READ)
|
||||
public CrmCustomerDO getCustomer(Long id) {
|
||||
return customerMapper.selectById(id);
|
||||
}
|
||||
}
|
||||
|
||||
# 数据访问规范
|
||||
dal:
|
||||
mapper_pattern: |
|
||||
- 继承 BaseMapperX<T> 获得通用 CRUD 方法
|
||||
- 使用 @Mapper 注解标记
|
||||
- 复杂查询使用 LambdaQueryWrapperX
|
||||
example: |
|
||||
@Mapper
|
||||
public interface CrmCustomerMapper extends BaseMapperX<CrmCustomerDO> {
|
||||
|
||||
default PageResult<CrmCustomerDO> selectPage(CrmCustomerPageReqVO pageReqVO, Long userId) {
|
||||
return selectPage(pageReqVO, new LambdaQueryWrapperX<CrmCustomerDO>()
|
||||
.likeIfPresent(CrmCustomerDO::getName, pageReqVO.getName())
|
||||
.eqIfPresent(CrmCustomerDO::getOwnerUserId, pageReqVO.getOwnerUserId())
|
||||
.orderByDesc(CrmCustomerDO::getId));
|
||||
}
|
||||
}
|
||||
|
||||
# 数据权限控制
|
||||
permission:
|
||||
annotation_pattern: "@CrmPermission(bizType = CrmBizTypeEnum.XXX, bizId = '#id', level = CrmPermissionLevelEnum.XXX)"
|
||||
levels:
|
||||
- level: "OWNER"
|
||||
code: 1
|
||||
description: "负责人权限,拥有所有操作权限,可以删除、转移"
|
||||
- level: "READ"
|
||||
code: 2
|
||||
description: "只读权限,只能查看数据"
|
||||
- level: "WRITE"
|
||||
code: 3
|
||||
description: "读写权限,可以编辑数据但不能删除"
|
||||
example: |
|
||||
// 查询需要 READ 权限
|
||||
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.READ)
|
||||
public CrmCustomerDO getCustomer(Long id)
|
||||
|
||||
// 更新需要 WRITE 权限
|
||||
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
|
||||
public void updateCustomer(CrmCustomerSaveReqVO updateReqVO)
|
||||
|
||||
// 删除需要 OWNER 权限
|
||||
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
|
||||
public void deleteCustomer(Long id)
|
||||
|
||||
# 异常处理
|
||||
error_handling:
|
||||
code_prefix: "1-020-xxx-xxx"
|
||||
examples:
|
||||
- code: "1_020_006_000"
|
||||
message: "客户不存在"
|
||||
constant: "CUSTOMER_NOT_EXISTS"
|
||||
- code: "1_020_002_000"
|
||||
message: "商机不存在"
|
||||
constant: "BUSINESS_NOT_EXISTS"
|
||||
- code: "1_020_007_001"
|
||||
message: "{}操作失败,原因:没有权限"
|
||||
constant: "CRM_PERMISSION_DENIED"
|
||||
usage: |
|
||||
// 抛出业务异常
|
||||
throw exception(CUSTOMER_NOT_EXISTS);
|
||||
throw exception(CUSTOMER_OWNER_EXISTS, customer.getName());
|
||||
|
||||
# 统一响应
|
||||
response:
|
||||
success_pattern: "CommonResult.success(data)"
|
||||
error_pattern: "CommonResult.error(errorCode)"
|
||||
|
||||
# ============================================
|
||||
# 第五阶段:扩展指南
|
||||
# ============================================
|
||||
extension_guide:
|
||||
# 新增功能步骤
|
||||
new_feature:
|
||||
title: "新增 CRM 业务功能"
|
||||
steps:
|
||||
- step: 1
|
||||
action: "创建 DO 实体类"
|
||||
description: "在 dal/dataobject 包下创建实体类,继承 BaseDO,添加 @TableName 注解"
|
||||
- step: 2
|
||||
action: "创建 Mapper 接口"
|
||||
description: "在 dal/mysql 包下创建 Mapper 接口,继承 BaseMapperX"
|
||||
- step: 3
|
||||
action: "创建 Service 接口和实现类"
|
||||
description: "在 service 包下创建接口和实现类,添加 @CrmPermission 注解控制数据权限"
|
||||
- step: 4
|
||||
action: "创建 Controller 和 VO"
|
||||
description: "在 controller 包下创建 Controller 和 VO 类,添加权限注解和 Swagger 注解"
|
||||
- step: 5
|
||||
action: "添加业务类型枚举"
|
||||
description: "在 CrmBizTypeEnum 中添加新的业务类型,确保权限系统正确识别"
|
||||
- step: 6
|
||||
action: "添加错误码"
|
||||
description: "在 ErrorCodeConstants 中添加相关错误码"
|
||||
|
||||
# 新增商机阶段示例
|
||||
new_business_stage:
|
||||
title: "自定义商机阶段"
|
||||
steps:
|
||||
- step: 1
|
||||
action: "创建商机状态组"
|
||||
description: "通过 CrmBusinessStatusService.createBusinessStatusType() 创建新的状态组"
|
||||
- step: 2
|
||||
action: "添加状态阶段"
|
||||
description: "为状态组添加多个状态,设置赢单率和排序"
|
||||
- step: 3
|
||||
action: "关联部门"
|
||||
description: "设置状态组适用的部门列表"
|
||||
- step: 4
|
||||
action: "使用状态组"
|
||||
description: "创建商机时选择对应的状态组"
|
||||
|
||||
# 新增客户类型示例
|
||||
new_customer_type:
|
||||
title: "新增客户类型/行业"
|
||||
steps:
|
||||
- step: 1
|
||||
action: "修改字典数据"
|
||||
description: "在 CRM_CUSTOMER_INDUSTRY 字典中添加新的行业类型"
|
||||
- step: 2
|
||||
action: "前端适配"
|
||||
description: "前端页面会自动读取字典数据显示下拉选项"
|
||||
|
||||
# 最佳实践
|
||||
best_practices:
|
||||
- practice: "数据权限校验"
|
||||
description: "所有涉及数据操作的方法都必须添加 @CrmPermission 注解,确保数据安全"
|
||||
- practice: "操作日志记录"
|
||||
description: "关键业务操作(创建、更新、删除、转移)使用 @LogRecord 记录操作日志"
|
||||
- practice: "事务管理"
|
||||
description: "涉及多表操作的方法添加 @Transactional 注解,确保数据一致性"
|
||||
- practice: "参数校验"
|
||||
description: "VO 类使用 @Valid 注解进行参数校验,Service 层使用 @Validated"
|
||||
- practice: "公海池机制"
|
||||
description: "客户放入公海时,需要同时更新联系人的负责人为空"
|
||||
- practice: "客户转移"
|
||||
description: "客户转移时,可选择同时转移联系人、商机、合同"
|
||||
|
||||
# ============================================
|
||||
# 依赖关系
|
||||
# ============================================
|
||||
dependencies:
|
||||
# 内部依赖(其他模块)
|
||||
internal:
|
||||
- module: "yudao-module-system"
|
||||
api: "AdminUserApi"
|
||||
purpose: "获取用户信息、校验用户存在性"
|
||||
- module: "yudao-module-system"
|
||||
api: "DeptApi"
|
||||
purpose: "获取部门信息,用于商机状态组关联"
|
||||
- module: "yudao-module-bpm"
|
||||
api: "流程审批"
|
||||
purpose: "合同、回款的审批流程管理"
|
||||
|
||||
# 外部依赖(第三方库)
|
||||
external:
|
||||
- name: "MyBatis-Plus"
|
||||
version: "3.x"
|
||||
purpose: "ORM 框架,提供 CRUD 操作"
|
||||
- name: "Spring Security"
|
||||
version: "5.x"
|
||||
purpose: "权限校验"
|
||||
- name: "Swagger/OpenAPI"
|
||||
version: "3.x"
|
||||
purpose: "API 文档生成"
|
||||
- name: "EasyExcel"
|
||||
version: "3.x"
|
||||
purpose: "Excel 导入导出"
|
||||
- name: "LogRecord"
|
||||
version: "mzt-biz-log"
|
||||
purpose: "操作日志记录"
|
||||
|
||||
# ============================================
|
||||
# 关键文件清单
|
||||
# ============================================
|
||||
key_files:
|
||||
- path: "yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java"
|
||||
purpose: "客户实体类,CRM 核心聚合根"
|
||||
- path: "yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java"
|
||||
purpose: "商机实体类,销售漏斗核心"
|
||||
- path: "yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/permission/CrmPermissionDO.java"
|
||||
purpose: "数据权限实体,权限控制核心"
|
||||
- path: "yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java"
|
||||
purpose: "客户服务接口,定义客户核心业务方法"
|
||||
- path: "yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java"
|
||||
purpose: "客户服务实现,包含公海池、转移等复杂业务逻辑"
|
||||
- path: "yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java"
|
||||
purpose: "数据权限服务接口"
|
||||
- path: "yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java"
|
||||
purpose: "客户控制器,HTTP 接口入口"
|
||||
- path: "yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java"
|
||||
purpose: "业务类型枚举,权限控制的基础"
|
||||
- path: "yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionLevelEnum.java"
|
||||
purpose: "权限级别枚举"
|
||||
- path: "yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java"
|
||||
purpose: "错误码常量定义"
|
||||
- path: "yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/annotations/CrmPermission.java"
|
||||
purpose: "数据权限注解定义"
|
||||
@@ -1,968 +0,0 @@
|
||||
# ERP 模块 Skill 文档
|
||||
# 企业资源计划模块,涵盖采购、销售、库存、财务等核心业务
|
||||
|
||||
skill:
|
||||
id: "skill-erp"
|
||||
name: "ERP Skill"
|
||||
version: "1.0.0"
|
||||
module_path: "yudao-module-erp"
|
||||
created_at: "2026-03-18"
|
||||
updated_at: "2026-03-18"
|
||||
|
||||
# ============================================
|
||||
# 第一阶段:设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
# 业务定位:模块解决什么业务问题?在整个系统中的定位?
|
||||
business_position: |
|
||||
ERP(企业资源计划)模块是企业管理核心系统,实现进销存一体化管理。
|
||||
主要业务场景:
|
||||
1. 采购管理:采购订单 -> 采购入库 -> 采购退货 -> 付款
|
||||
2. 销售管理:销售订单 -> 销售出库 -> 销售退货 -> 收款
|
||||
3. 库存管理:其它入库/出库、库存调拨、库存盘点、库存查询
|
||||
4. 财务管理:结算账户、付款单、收款单
|
||||
5. 基础资料:产品、产品分类、产品单位、供应商、客户、仓库
|
||||
|
||||
# 设计原则:遵循了哪些设计原则(SOLID、DDD)?有哪些架构决策?
|
||||
design_principles:
|
||||
- "单据状态机:所有业务单据采用 PROCESS(未审核) -> APPROVE(已审核) 状态流转"
|
||||
- "主子表设计:订单类单据采用 Order + OrderItem 主子表结构,支持明细项增删改差量更新"
|
||||
- "库存增量更新:库存变动采用 updateCountIncrement 增量更新,避免并发问题"
|
||||
- "单据号生成器:基于 Redis 的分布式单据号生成,格式:前缀 + yyyyMMdd + 6位序号"
|
||||
- "审核权限控制:已审核单据不可修改/删除,需反审核后才能操作"
|
||||
- "业务关联校验:单据之间存在关联关系,删除/反审核时需检查关联单据"
|
||||
|
||||
# 领域模型:核心领域对象有哪些?领域对象之间的关系?聚合根是什么?
|
||||
domain_model:
|
||||
aggregates:
|
||||
# 采购聚合
|
||||
- name: "采购订单 (ErpPurchaseOrderDO)"
|
||||
type: "聚合根"
|
||||
entities:
|
||||
- "采购订单项 (ErpPurchaseOrderItemDO)"
|
||||
- "供应商 (ErpSupplierDO)"
|
||||
description: "采购域核心聚合,管理采购订单及其明细项"
|
||||
|
||||
- name: "采购入库 (ErpPurchaseInDO)"
|
||||
type: "聚合根"
|
||||
entities:
|
||||
- "采购入库项 (ErpPurchaseInItemDO)"
|
||||
description: "关联采购订单,记录采购入库明细"
|
||||
|
||||
- name: "采购退货 (ErpPurchaseReturnDO)"
|
||||
type: "聚合根"
|
||||
entities:
|
||||
- "采购退货项 (ErpPurchaseReturnItemDO)"
|
||||
description: "关联采购订单,记录采购退货明细"
|
||||
|
||||
# 销售聚合
|
||||
- name: "销售订单 (ErpSaleOrderDO)"
|
||||
type: "聚合根"
|
||||
entities:
|
||||
- "销售订单项 (ErpSaleOrderItemDO)"
|
||||
- "客户 (ErpCustomerDO)"
|
||||
description: "销售域核心聚合,管理销售订单及其明细项"
|
||||
|
||||
- name: "销售出库 (ErpSaleOutDO)"
|
||||
type: "聚合根"
|
||||
entities:
|
||||
- "销售出库项 (ErpSaleOutItemDO)"
|
||||
description: "关联销售订单,记录销售出库明细"
|
||||
|
||||
- name: "销售退货 (ErpSaleReturnDO)"
|
||||
type: "聚合根"
|
||||
entities:
|
||||
- "销售退货项 (ErpSaleReturnItemDO)"
|
||||
description: "关联销售订单,记录销售退货明细"
|
||||
|
||||
# 库存聚合
|
||||
- name: "库存 (ErpStockDO)"
|
||||
type: "聚合根"
|
||||
entities:
|
||||
- "仓库 (ErpWarehouseDO)"
|
||||
- "产品 (ErpProductDO)"
|
||||
description: "库存域核心,记录产品在各仓库的实时库存"
|
||||
|
||||
- name: "库存记录 (ErpStockRecordDO)"
|
||||
type: "实体"
|
||||
description: "记录所有库存变动明细,支持追溯"
|
||||
|
||||
# 财务聚合
|
||||
- name: "付款单 (ErpFinancePaymentDO)"
|
||||
type: "聚合根"
|
||||
entities:
|
||||
- "付款单项 (ErpFinancePaymentItemDO)"
|
||||
- "结算账户 (ErpAccountDO)"
|
||||
description: "财务域付款管理"
|
||||
|
||||
- name: "收款单 (ErpFinanceReceiptDO)"
|
||||
type: "聚合根"
|
||||
entities:
|
||||
- "收款单项 (ErpFinanceReceiptItemDO)"
|
||||
- "结算账户 (ErpAccountDO)"
|
||||
description: "财务域收款管理"
|
||||
|
||||
value_objects:
|
||||
- name: "ErpStockRecordCreateReqBO"
|
||||
type: "值对象"
|
||||
description: "库存记录创建请求BO,封装库存变动所需的业务信息"
|
||||
|
||||
services:
|
||||
- name: "ErpPurchaseOrderService"
|
||||
type: "领域服务"
|
||||
description: "采购订单领域服务,管理采购订单生命周期"
|
||||
- name: "ErpSaleOrderService"
|
||||
type: "领域服务"
|
||||
description: "销售订单领域服务,管理销售订单生命周期"
|
||||
- name: "ErpStockService"
|
||||
type: "领域服务"
|
||||
description: "库存领域服务,管理库存变动"
|
||||
- name: "ErpStockRecordService"
|
||||
type: "领域服务"
|
||||
description: "库存记录领域服务,记录库存变动明细"
|
||||
|
||||
# ============================================
|
||||
# 第二阶段:架构设计
|
||||
# ============================================
|
||||
architecture:
|
||||
# 分层架构
|
||||
layers:
|
||||
- name: "controller"
|
||||
purpose: "HTTP接口层,处理请求响应、参数校验、权限控制"
|
||||
components:
|
||||
- "ErpPurchaseOrderController - 采购订单接口"
|
||||
- "ErpPurchaseInController - 采购入库接口"
|
||||
- "ErpPurchaseReturnController - 采购退货接口"
|
||||
- "ErpSaleOrderController - 销售订单接口"
|
||||
- "ErpSaleOutController - 销售出库接口"
|
||||
- "ErpSaleReturnController - 销售退货接口"
|
||||
- "ErpStockController - 库存查询接口"
|
||||
- "ErpStockInController - 其它入库接口"
|
||||
- "ErpStockOutController - 其它出库接口"
|
||||
- "ErpStockMoveController - 库存调拨接口"
|
||||
- "ErpStockCheckController - 库存盘点接口"
|
||||
- "ErpFinancePaymentController - 付款单接口"
|
||||
- "ErpFinanceReceiptController - 收款单接口"
|
||||
- "ErpProductController - 产品接口"
|
||||
- "ErpSupplierController - 供应商接口"
|
||||
- "ErpCustomerController - 客户接口"
|
||||
- "ErpWarehouseController - 仓库接口"
|
||||
|
||||
- name: "service"
|
||||
purpose: "业务逻辑层,实现领域服务、事务管理、业务规则校验"
|
||||
components:
|
||||
- "purchase/ - 采购域服务(订单、入库、退货、供应商)"
|
||||
- "sale/ - 销售域服务(订单、出库、退货、客户)"
|
||||
- "stock/ - 库存域服务(库存、仓库、入库、出库、调拨、盘点、记录)"
|
||||
- "finance/ - 财务域服务(账户、付款、收款)"
|
||||
- "product/ - 产品域服务(产品、分类、单位)"
|
||||
- "statistics/ - 统计服务(采购统计、销售统计)"
|
||||
|
||||
- name: "dal"
|
||||
purpose: "数据访问层,封装数据库操作、Redis操作"
|
||||
components:
|
||||
- "dataobject/ - DO实体类(按业务域分包)"
|
||||
- "mysql/ - MyBatis Mapper接口"
|
||||
- "redis/ - Redis操作(单据号生成)"
|
||||
|
||||
# 设计模式应用
|
||||
design_patterns:
|
||||
- pattern: "状态机模式 (State Machine)"
|
||||
location: "cn.iocoder.yudao.module.erp.enums.ErpAuditStatus"
|
||||
purpose: "定义单据审核状态 PROCESS(10-未审核) -> APPROVE(20-已审核),控制单据生命周期"
|
||||
|
||||
- pattern: "主子表模式 (Master-Detail)"
|
||||
location: "purchase/ErpPurchaseOrderDO + ErpPurchaseOrderItemDO"
|
||||
purpose: "订单类单据采用主子表设计,支持明细项差量更新"
|
||||
|
||||
- pattern: "策略模式 (Strategy)"
|
||||
location: "cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum"
|
||||
purpose: "库存记录业务类型枚举,定义不同业务场景的库存变动类型"
|
||||
|
||||
- pattern: "分布式ID生成"
|
||||
location: "cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO"
|
||||
purpose: "基于Redis的分布式单据号生成,保证唯一性和有序性"
|
||||
|
||||
- pattern: "增量更新模式"
|
||||
location: "ErpStockMapper.updateCountIncrement"
|
||||
purpose: "库存增量更新,使用乐观锁避免并发问题"
|
||||
|
||||
# 模块间通信
|
||||
communication:
|
||||
apis: [] # ERP模块不对外暴露API接口
|
||||
consumers:
|
||||
- module: "system"
|
||||
api: "AdminUserApi"
|
||||
purpose: "获取用户信息,用于显示操作人姓名"
|
||||
mq: [] # 暂无消息队列使用
|
||||
|
||||
# ============================================
|
||||
# 第三阶段:数据表设计
|
||||
# ============================================
|
||||
data_model:
|
||||
# 实体继承体系
|
||||
entity_hierarchy:
|
||||
base: "BaseDO"
|
||||
description: "所有DO继承BaseDO,包含id、creator、create_time、updater、update_time、deleted字段"
|
||||
|
||||
# 核心数据表
|
||||
tables:
|
||||
# ========== 产品相关表 ==========
|
||||
- name: "erp_product"
|
||||
comment: "产品表"
|
||||
entity: "ErpProductDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "产品编号" }
|
||||
- { name: "name", type: "VARCHAR", comment: "产品名称" }
|
||||
- { name: "bar_code", type: "VARCHAR", comment: "产品条码" }
|
||||
- { name: "category_id", type: "BIGINT", comment: "产品分类ID" }
|
||||
- { name: "unit_id", type: "BIGINT", comment: "单位ID" }
|
||||
- { name: "status", type: "INT", comment: "状态(0正常 1停用)" }
|
||||
- { name: "standard", type: "VARCHAR", comment: "规格" }
|
||||
- { name: "purchase_price", type: "DECIMAL", comment: "采购价" }
|
||||
- { name: "sale_price", type: "DECIMAL", comment: "销售价" }
|
||||
- { name: "min_price", type: "DECIMAL", comment: "最低价" }
|
||||
|
||||
- name: "erp_product_category"
|
||||
comment: "产品分类表"
|
||||
entity: "ErpProductCategoryDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "分类编号" }
|
||||
- { name: "parent_id", type: "BIGINT", comment: "父分类ID" }
|
||||
- { name: "name", type: "VARCHAR", comment: "分类名称" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
|
||||
- name: "erp_product_unit"
|
||||
comment: "产品单位表"
|
||||
entity: "ErpProductUnitDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "单位编号" }
|
||||
- { name: "name", type: "VARCHAR", comment: "单位名称" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
|
||||
# ========== 采购相关表 ==========
|
||||
- name: "erp_supplier"
|
||||
comment: "供应商表"
|
||||
entity: "ErpSupplierDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "供应商编号" }
|
||||
- { name: "name", type: "VARCHAR", comment: "供应商名称" }
|
||||
- { name: "contact", type: "VARCHAR", comment: "联系人" }
|
||||
- { name: "mobile", type: "VARCHAR", comment: "手机号" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
|
||||
- name: "erp_purchase_order"
|
||||
comment: "采购订单表"
|
||||
entity: "ErpPurchaseOrderDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "no", type: "VARCHAR", comment: "采购订单号(CGDD+yyyyMMdd+6位序号)" }
|
||||
- { name: "status", type: "INT", comment: "状态(10未审核 20已审核)" }
|
||||
- { name: "supplier_id", type: "BIGINT", comment: "供应商ID" }
|
||||
- { name: "account_id", type: "BIGINT", comment: "结算账户ID" }
|
||||
- { name: "order_time", type: "DATETIME", comment: "下单时间" }
|
||||
- { name: "total_count", type: "DECIMAL", comment: "合计数量" }
|
||||
- { name: "total_price", type: "DECIMAL", comment: "最终合计价格" }
|
||||
- { name: "total_product_price", type: "DECIMAL", comment: "合计产品价格" }
|
||||
- { name: "total_tax_price", type: "DECIMAL", comment: "合计税额" }
|
||||
- { name: "discount_percent", type: "DECIMAL", comment: "优惠率(%)" }
|
||||
- { name: "discount_price", type: "DECIMAL", comment: "优惠金额" }
|
||||
- { name: "in_count", type: "DECIMAL", comment: "已入库数量" }
|
||||
- { name: "return_count", type: "DECIMAL", comment: "已退货数量" }
|
||||
|
||||
- name: "erp_purchase_order_item"
|
||||
comment: "采购订单项表"
|
||||
entity: "ErpPurchaseOrderItemDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "order_id", type: "BIGINT", comment: "采购订单ID" }
|
||||
- { name: "product_id", type: "BIGINT", comment: "产品ID" }
|
||||
- { name: "product_unit_id", type: "BIGINT", comment: "产品单位ID" }
|
||||
- { name: "count", type: "DECIMAL", comment: "数量" }
|
||||
- { name: "product_price", type: "DECIMAL", comment: "产品单价" }
|
||||
- { name: "tax_percent", type: "DECIMAL", comment: "税率(%)" }
|
||||
- { name: "in_count", type: "DECIMAL", comment: "已入库数量" }
|
||||
- { name: "return_count", type: "DECIMAL", comment: "已退货数量" }
|
||||
|
||||
- name: "erp_purchase_in"
|
||||
comment: "采购入库表"
|
||||
entity: "ErpPurchaseInDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "no", type: "VARCHAR", comment: "入库单号" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
- { name: "supplier_id", type: "BIGINT", comment: "供应商ID" }
|
||||
- { name: "order_id", type: "BIGINT", comment: "采购订单ID" }
|
||||
- { name: "warehouse_id", type: "BIGINT", comment: "仓库ID" }
|
||||
- { name: "in_time", type: "DATETIME", comment: "入库时间" }
|
||||
- { name: "total_count", type: "DECIMAL", comment: "合计数量" }
|
||||
- { name: "total_price", type: "DECIMAL", comment: "合计价格" }
|
||||
- { name: "payment_price", type: "DECIMAL", comment: "已付款金额" }
|
||||
|
||||
- name: "erp_purchase_return"
|
||||
comment: "采购退货表"
|
||||
entity: "ErpPurchaseReturnDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "no", type: "VARCHAR", comment: "退货单号" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
- { name: "supplier_id", type: "BIGINT", comment: "供应商ID" }
|
||||
- { name: "order_id", type: "BIGINT", comment: "采购订单ID" }
|
||||
- { name: "warehouse_id", type: "BIGINT", comment: "仓库ID" }
|
||||
- { name: "return_time", type: "DATETIME", comment: "退货时间" }
|
||||
- { name: "total_count", type: "DECIMAL", comment: "合计数量" }
|
||||
- { name: "total_price", type: "DECIMAL", comment: "合计价格" }
|
||||
- { name: "refund_price", type: "DECIMAL", comment: "已退款金额" }
|
||||
|
||||
# ========== 销售相关表 ==========
|
||||
- name: "erp_customer"
|
||||
comment: "客户表"
|
||||
entity: "ErpCustomerDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "客户编号" }
|
||||
- { name: "name", type: "VARCHAR", comment: "客户名称" }
|
||||
- { name: "contact", type: "VARCHAR", comment: "联系人" }
|
||||
- { name: "mobile", type: "VARCHAR", comment: "手机号" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
|
||||
- name: "erp_sale_order"
|
||||
comment: "销售订单表"
|
||||
entity: "ErpSaleOrderDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "no", type: "VARCHAR", comment: "销售订单号(XSDD+yyyyMMdd+6位序号)" }
|
||||
- { name: "status", type: "INT", comment: "状态(10未审核 20已审核)" }
|
||||
- { name: "customer_id", type: "BIGINT", comment: "客户ID" }
|
||||
- { name: "account_id", type: "BIGINT", comment: "结算账户ID" }
|
||||
- { name: "sale_user_id", type: "BIGINT", comment: "销售员ID" }
|
||||
- { name: "order_time", type: "DATETIME", comment: "下单时间" }
|
||||
- { name: "total_count", type: "DECIMAL", comment: "合计数量" }
|
||||
- { name: "total_price", type: "DECIMAL", comment: "最终合计价格" }
|
||||
- { name: "out_count", type: "DECIMAL", comment: "已出库数量" }
|
||||
- { name: "return_count", type: "DECIMAL", comment: "已退货数量" }
|
||||
|
||||
- name: "erp_sale_order_item"
|
||||
comment: "销售订单项表"
|
||||
entity: "ErpSaleOrderItemDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "order_id", type: "BIGINT", comment: "销售订单ID" }
|
||||
- { name: "product_id", type: "BIGINT", comment: "产品ID" }
|
||||
- { name: "count", type: "DECIMAL", comment: "数量" }
|
||||
- { name: "product_price", type: "DECIMAL", comment: "产品单价" }
|
||||
- { name: "out_count", type: "DECIMAL", comment: "已出库数量" }
|
||||
- { name: "return_count", type: "DECIMAL", comment: "已退货数量" }
|
||||
|
||||
- name: "erp_sale_out"
|
||||
comment: "销售出库表"
|
||||
entity: "ErpSaleOutDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "no", type: "VARCHAR", comment: "出库单号" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
- { name: "customer_id", type: "BIGINT", comment: "客户ID" }
|
||||
- { name: "order_id", type: "BIGINT", comment: "销售订单ID" }
|
||||
- { name: "warehouse_id", type: "BIGINT", comment: "仓库ID" }
|
||||
- { name: "out_time", type: "DATETIME", comment: "出库时间" }
|
||||
- { name: "total_count", type: "DECIMAL", comment: "合计数量" }
|
||||
- { name: "total_price", type: "DECIMAL", comment: "合计价格" }
|
||||
- { name: "receipt_price", type: "DECIMAL", comment: "已收款金额" }
|
||||
|
||||
- name: "erp_sale_return"
|
||||
comment: "销售退货表"
|
||||
entity: "ErpSaleReturnDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "no", type: "VARCHAR", comment: "退货单号" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
- { name: "customer_id", type: "BIGINT", comment: "客户ID" }
|
||||
- { name: "order_id", type: "BIGINT", comment: "销售订单ID" }
|
||||
- { name: "warehouse_id", type: "BIGINT", comment: "仓库ID" }
|
||||
- { name: "return_time", type: "DATETIME", comment: "退货时间" }
|
||||
- { name: "total_count", type: "DECIMAL", comment: "合计数量" }
|
||||
- { name: "total_price", type: "DECIMAL", comment: "合计价格" }
|
||||
- { name: "refund_price", type: "DECIMAL", comment: "已退款金额" }
|
||||
|
||||
# ========== 库存相关表 ==========
|
||||
- name: "erp_warehouse"
|
||||
comment: "仓库表"
|
||||
entity: "ErpWarehouseDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "仓库编号" }
|
||||
- { name: "name", type: "VARCHAR", comment: "仓库名称" }
|
||||
- { name: "address", type: "VARCHAR", comment: "仓库地址" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
|
||||
- name: "erp_stock"
|
||||
comment: "产品库存表"
|
||||
entity: "ErpStockDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "product_id", type: "BIGINT", comment: "产品ID" }
|
||||
- { name: "warehouse_id", type: "BIGINT", comment: "仓库ID" }
|
||||
- { name: "count", type: "DECIMAL", comment: "库存数量" }
|
||||
|
||||
- name: "erp_stock_record"
|
||||
comment: "库存明细表"
|
||||
entity: "ErpStockRecordDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "product_id", type: "BIGINT", comment: "产品ID" }
|
||||
- { name: "warehouse_id", type: "BIGINT", comment: "仓库ID" }
|
||||
- { name: "count", type: "DECIMAL", comment: "出入库数量(正数入库/负数出库)" }
|
||||
- { name: "total_count", type: "DECIMAL", comment: "变动后库存量" }
|
||||
- { name: "biz_type", type: "INT", comment: "业务类型" }
|
||||
- { name: "biz_id", type: "BIGINT", comment: "业务编号" }
|
||||
- { name: "biz_item_id", type: "BIGINT", comment: "业务项编号" }
|
||||
- { name: "biz_no", type: "VARCHAR", comment: "业务单号" }
|
||||
|
||||
- name: "erp_stock_in"
|
||||
comment: "其它入库表"
|
||||
entity: "ErpStockInDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "no", type: "VARCHAR", comment: "入库单号(QTRK+yyyyMMdd+6位序号)" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
- { name: "warehouse_id", type: "BIGINT", comment: "仓库ID" }
|
||||
- { name: "in_time", type: "DATETIME", comment: "入库时间" }
|
||||
- { name: "total_count", type: "DECIMAL", comment: "合计数量" }
|
||||
- { name: "total_price", type: "DECIMAL", comment: "合计价格" }
|
||||
|
||||
- name: "erp_stock_out"
|
||||
comment: "其它出库表"
|
||||
entity: "ErpStockOutDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "no", type: "VARCHAR", comment: "出库单号(QCKD+yyyyMMdd+6位序号)" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
- { name: "warehouse_id", type: "BIGINT", comment: "仓库ID" }
|
||||
- { name: "out_time", type: "DATETIME", comment: "出库时间" }
|
||||
- { name: "total_count", type: "DECIMAL", comment: "合计数量" }
|
||||
- { name: "total_price", type: "DECIMAL", comment: "合计价格" }
|
||||
|
||||
- name: "erp_stock_move"
|
||||
comment: "库存调拨表"
|
||||
entity: "ErpStockMoveDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "no", type: "VARCHAR", comment: "调拨单号(QCDB+yyyyMMdd+6位序号)" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
- { name: "from_warehouse_id", type: "BIGINT", comment: "调出仓库ID" }
|
||||
- { name: "to_warehouse_id", type: "BIGINT", comment: "调入仓库ID" }
|
||||
- { name: "move_time", type: "DATETIME", comment: "调拨时间" }
|
||||
- { name: "total_count", type: "DECIMAL", comment: "合计数量" }
|
||||
|
||||
- name: "erp_stock_check"
|
||||
comment: "库存盘点表"
|
||||
entity: "ErpStockCheckDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "no", type: "VARCHAR", comment: "盘点单号(QCPD+yyyyMMdd+6位序号)" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
- { name: "warehouse_id", type: "BIGINT", comment: "仓库ID" }
|
||||
- { name: "check_time", type: "DATETIME", comment: "盘点时间" }
|
||||
- { name: "total_count", type: "DECIMAL", comment: "合计数量" }
|
||||
|
||||
# ========== 财务相关表 ==========
|
||||
- name: "erp_account"
|
||||
comment: "结算账户表"
|
||||
entity: "ErpAccountDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "账户编号" }
|
||||
- { name: "name", type: "VARCHAR", comment: "账户名称" }
|
||||
- { name: "no", type: "VARCHAR", comment: "账户编号" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
|
||||
- name: "erp_finance_payment"
|
||||
comment: "付款单表"
|
||||
entity: "ErpFinancePaymentDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "no", type: "VARCHAR", comment: "付款单号(FKD+yyyyMMdd+6位序号)" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
- { name: "supplier_id", type: "BIGINT", comment: "供应商ID" }
|
||||
- { name: "account_id", type: "BIGINT", comment: "付款账户ID" }
|
||||
- { name: "payment_time", type: "DATETIME", comment: "付款时间" }
|
||||
- { name: "total_price", type: "DECIMAL", comment: "合计金额" }
|
||||
- { name: "discount_price", type: "DECIMAL", comment: "优惠金额" }
|
||||
- { name: "payment_price", type: "DECIMAL", comment: "实付金额" }
|
||||
|
||||
- name: "erp_finance_receipt"
|
||||
comment: "收款单表"
|
||||
entity: "ErpFinanceReceiptDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "no", type: "VARCHAR", comment: "收款单号(SKD+yyyyMMdd+6位序号)" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
- { name: "customer_id", type: "BIGINT", comment: "客户ID" }
|
||||
- { name: "account_id", type: "BIGINT", comment: "收款账户ID" }
|
||||
- { name: "receipt_time", type: "DATETIME", comment: "收款时间" }
|
||||
- { name: "total_price", type: "DECIMAL", comment: "合计金额" }
|
||||
- { name: "discount_price", type: "DECIMAL", comment: "优惠金额" }
|
||||
- { name: "receipt_price", type: "DECIMAL", comment: "实收金额" }
|
||||
|
||||
# 表关系(ER关系)
|
||||
relationships:
|
||||
# 产品相关
|
||||
- from: "erp_product"
|
||||
to: "erp_product_category"
|
||||
type: "N:1"
|
||||
foreign_key: "category_id"
|
||||
- from: "erp_product"
|
||||
to: "erp_product_unit"
|
||||
type: "N:1"
|
||||
foreign_key: "unit_id"
|
||||
|
||||
# 采购相关
|
||||
- from: "erp_purchase_order"
|
||||
to: "erp_supplier"
|
||||
type: "N:1"
|
||||
foreign_key: "supplier_id"
|
||||
- from: "erp_purchase_order_item"
|
||||
to: "erp_purchase_order"
|
||||
type: "N:1"
|
||||
foreign_key: "order_id"
|
||||
- from: "erp_purchase_in"
|
||||
to: "erp_purchase_order"
|
||||
type: "N:1"
|
||||
foreign_key: "order_id"
|
||||
- from: "erp_purchase_return"
|
||||
to: "erp_purchase_order"
|
||||
type: "N:1"
|
||||
foreign_key: "order_id"
|
||||
|
||||
# 销售相关
|
||||
- from: "erp_sale_order"
|
||||
to: "erp_customer"
|
||||
type: "N:1"
|
||||
foreign_key: "customer_id"
|
||||
- from: "erp_sale_order_item"
|
||||
to: "erp_sale_order"
|
||||
type: "N:1"
|
||||
foreign_key: "order_id"
|
||||
- from: "erp_sale_out"
|
||||
to: "erp_sale_order"
|
||||
type: "N:1"
|
||||
foreign_key: "order_id"
|
||||
- from: "erp_sale_return"
|
||||
to: "erp_sale_order"
|
||||
type: "N:1"
|
||||
foreign_key: "order_id"
|
||||
|
||||
# 库存相关
|
||||
- from: "erp_stock"
|
||||
to: "erp_product"
|
||||
type: "N:1"
|
||||
foreign_key: "product_id"
|
||||
- from: "erp_stock"
|
||||
to: "erp_warehouse"
|
||||
type: "N:1"
|
||||
foreign_key: "warehouse_id"
|
||||
- from: "erp_stock_record"
|
||||
to: "erp_product"
|
||||
type: "N:1"
|
||||
foreign_key: "product_id"
|
||||
- from: "erp_stock_record"
|
||||
to: "erp_warehouse"
|
||||
type: "N:1"
|
||||
foreign_key: "warehouse_id"
|
||||
|
||||
# ============================================
|
||||
# 第四阶段:代码使用设计
|
||||
# ============================================
|
||||
code_patterns:
|
||||
# Controller层规范
|
||||
controller:
|
||||
annotations:
|
||||
- "@Tag(name = '管理后台 - ERP XXX') - Swagger接口文档标签"
|
||||
- "@RestController - RESTful控制器"
|
||||
- "@RequestMapping('/erp/xxx') - 请求路径前缀"
|
||||
- "@PreAuthorize('@ss.hasPermission('erp:xxx:create')') - 权限控制"
|
||||
- "@Validated - 参数校验"
|
||||
example: |
|
||||
@Tag(name = "管理后台 - ERP 采购订单")
|
||||
@RestController
|
||||
@RequestMapping("/erp/purchase-order")
|
||||
@Validated
|
||||
public class ErpPurchaseOrderController {
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建采购订单")
|
||||
@PreAuthorize("@ss.hasPermission('erp:purchase-order:create')")
|
||||
public CommonResult<Long> createPurchaseOrder(@Valid @RequestBody ErpPurchaseOrderSaveReqVO createReqVO) {
|
||||
return success(purchaseOrderService.createPurchaseOrder(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update-status")
|
||||
@Operation(summary = "更新采购订单的状态")
|
||||
@PreAuthorize("@ss.hasPermission('erp:purchase-order:update-status')")
|
||||
public CommonResult<Boolean> updatePurchaseOrderStatus(@RequestParam("id") Long id,
|
||||
@RequestParam("status") Integer status) {
|
||||
purchaseOrderService.updatePurchaseOrderStatus(id, status);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得采购订单分页")
|
||||
@PreAuthorize("@ss.hasPermission('erp:purchase-order:query')")
|
||||
public CommonResult<PageResult<ErpPurchaseOrderRespVO>> getPurchaseOrderPage(@Valid ErpPurchaseOrderPageReqVO pageReqVO) {
|
||||
PageResult<ErpPurchaseOrderDO> pageResult = purchaseOrderService.getPurchaseOrderPage(pageReqVO);
|
||||
return success(buildPurchaseOrderVOPageResult(pageResult));
|
||||
}
|
||||
}
|
||||
|
||||
# Service层规范
|
||||
service:
|
||||
interface_pattern: |
|
||||
public interface ErpPurchaseOrderService {
|
||||
// 基础CRUD
|
||||
Long createPurchaseOrder(ErpPurchaseOrderSaveReqVO createReqVO);
|
||||
void updatePurchaseOrder(ErpPurchaseOrderSaveReqVO updateReqVO);
|
||||
void deletePurchaseOrder(List<Long> ids);
|
||||
ErpPurchaseOrderDO getPurchaseOrder(Long id);
|
||||
PageResult<ErpPurchaseOrderDO> getPurchaseOrderPage(ErpPurchaseOrderPageReqVO pageReqVO);
|
||||
|
||||
// 状态更新
|
||||
void updatePurchaseOrderStatus(Long id, Integer status);
|
||||
|
||||
// 关联更新
|
||||
void updatePurchaseOrderInCount(Long id, Map<Long, BigDecimal> inCountMap);
|
||||
void updatePurchaseOrderReturnCount(Long orderId, Map<Long, BigDecimal> returnCountMap);
|
||||
|
||||
// 校验方法
|
||||
ErpPurchaseOrderDO validatePurchaseOrder(Long id);
|
||||
|
||||
// 订单项查询
|
||||
List<ErpPurchaseOrderItemDO> getPurchaseOrderItemListByOrderId(Long orderId);
|
||||
}
|
||||
|
||||
impl_pattern: |
|
||||
@Service
|
||||
@Validated
|
||||
public class ErpPurchaseOrderServiceImpl implements ErpPurchaseOrderService {
|
||||
|
||||
@Resource
|
||||
private ErpPurchaseOrderMapper purchaseOrderMapper;
|
||||
@Resource
|
||||
private ErpPurchaseOrderItemMapper purchaseOrderItemMapper;
|
||||
@Resource
|
||||
private ErpNoRedisDAO noRedisDAO;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createPurchaseOrder(ErpPurchaseOrderSaveReqVO createReqVO) {
|
||||
// 1. 校验业务数据
|
||||
List<ErpPurchaseOrderItemDO> items = validatePurchaseOrderItems(createReqVO.getItems());
|
||||
supplierService.validateSupplier(createReqVO.getSupplierId());
|
||||
|
||||
// 2. 生成单据号
|
||||
String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX);
|
||||
|
||||
// 3. 计算价格
|
||||
ErpPurchaseOrderDO order = BeanUtils.toBean(createReqVO, ErpPurchaseOrderDO.class);
|
||||
order.setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus());
|
||||
calculateTotalPrice(order, items);
|
||||
|
||||
// 4. 保存主表和子表
|
||||
purchaseOrderMapper.insert(order);
|
||||
items.forEach(item -> item.setOrderId(order.getId()));
|
||||
purchaseOrderItemMapper.insertBatch(items);
|
||||
|
||||
return order.getId();
|
||||
}
|
||||
}
|
||||
|
||||
example: |
|
||||
// 采购入库审核流程
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updatePurchaseInStatus(Long id, Integer status) {
|
||||
boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status);
|
||||
// 1. 校验存在
|
||||
ErpPurchaseInDO purchaseIn = validatePurchaseInExists(id);
|
||||
// 2. 校验状态
|
||||
if (purchaseIn.getStatus().equals(status)) {
|
||||
throw exception(approve ? PURCHASE_IN_APPROVE_FAIL : PURCHASE_IN_PROCESS_FAIL);
|
||||
}
|
||||
|
||||
// 3. 更新库存
|
||||
if (approve) {
|
||||
List<ErpPurchaseInItemDO> items = purchaseInItemMapper.selectListByInId(id);
|
||||
items.forEach(item -> {
|
||||
// 创建库存记录
|
||||
ErpStockRecordCreateReqBO reqBO = new ErpStockRecordCreateReqBO();
|
||||
reqBO.setProductId(item.getProductId());
|
||||
reqBO.setWarehouseId(purchaseIn.getWarehouseId());
|
||||
reqBO.setCount(item.getCount());
|
||||
reqBO.setBizType(ErpStockRecordBizTypeEnum.PURCHASE_IN.getType());
|
||||
reqBO.setBizId(id);
|
||||
reqBO.setBizItemId(item.getId());
|
||||
reqBO.setBizNo(purchaseIn.getNo());
|
||||
// 更新库存
|
||||
stockRecordService.createStockRecord(reqBO);
|
||||
});
|
||||
} else {
|
||||
// 反审核,扣减库存
|
||||
stockRecordService.deleteStockRecord(id, ErpStockRecordBizTypeEnum.PURCHASE_IN_CANCEL.getType());
|
||||
}
|
||||
|
||||
// 4. 更新状态
|
||||
purchaseInMapper.updateById(new ErpPurchaseInDO().setId(id).setStatus(status));
|
||||
}
|
||||
|
||||
# 数据访问规范
|
||||
dal:
|
||||
mapper_pattern: |
|
||||
@Mapper
|
||||
public interface ErpPurchaseOrderMapper extends BaseMapperX<ErpPurchaseOrderDO> {
|
||||
|
||||
default ErpPurchaseOrderDO selectByNo(String no) {
|
||||
return selectOne(ErpPurchaseOrderDO::getNo, no);
|
||||
}
|
||||
|
||||
default PageResult<ErpPurchaseOrderDO> selectPage(ErpPurchaseOrderPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<ErpPurchaseOrderDO>()
|
||||
.likeIfPresent(ErpPurchaseOrderDO::getNo, reqVO.getNo())
|
||||
.eqIfPresent(ErpPurchaseOrderDO::getStatus, reqVO.getStatus())
|
||||
.eqIfPresent(ErpPurchaseOrderDO::getSupplierId, reqVO.getSupplierId())
|
||||
.betweenIfPresent(ErpPurchaseOrderDO::getOrderTime, reqVO.getOrderTime())
|
||||
.orderByDesc(ErpPurchaseOrderDO::getId));
|
||||
}
|
||||
|
||||
default int updateByIdAndStatus(Long id, Integer status, ErpPurchaseOrderDO update) {
|
||||
return update(update, new LambdaQueryWrapperX<ErpPurchaseOrderDO>()
|
||||
.eq(ErpPurchaseOrderDO::getId, id)
|
||||
.eq(ErpPurchaseOrderDO::getStatus, status));
|
||||
}
|
||||
}
|
||||
|
||||
example: |
|
||||
// 库存增量更新 - 使用乐观锁避免并发问题
|
||||
@Mapper
|
||||
public interface ErpStockMapper extends BaseMapperX<ErpStockDO> {
|
||||
|
||||
default int updateCountIncrement(Long id, BigDecimal count, Boolean negativeEnable) {
|
||||
// 如果不允许负库存,需要检查库存是否充足
|
||||
if (!negativeEnable) {
|
||||
return update(null, new LambdaUpdateWrapper<ErpStockDO>()
|
||||
.setSql("count = count + " + count)
|
||||
.eq(ErpStockDO::getId, id)
|
||||
.ge(count.compareTo(BigDecimal.ZERO) < 0, ErpStockDO::getCount, count.abs()));
|
||||
}
|
||||
return update(null, new LambdaUpdateWrapper<ErpStockDO>()
|
||||
.setSql("count = count + " + count)
|
||||
.eq(ErpStockDO::getId, id));
|
||||
}
|
||||
}
|
||||
|
||||
# 异常处理
|
||||
error_handling:
|
||||
code_prefix: "1-030-xxx-xxx"
|
||||
examples:
|
||||
- code: "1_030_101_000"
|
||||
message: "采购订单不存在"
|
||||
constant: "PURCHASE_ORDER_NOT_EXISTS"
|
||||
- code: "1_030_101_001"
|
||||
message: "采购订单({})已审核,无法删除"
|
||||
constant: "PURCHASE_ORDER_DELETE_FAIL_APPROVE"
|
||||
- code: "1_030_101_003"
|
||||
message: "审核失败,只有未审核的采购订单才能审核"
|
||||
constant: "PURCHASE_ORDER_APPROVE_FAIL"
|
||||
- code: "1_030_404_000"
|
||||
message: "操作失败,产品({})所在仓库({})的库存:{},小于变更数量:{}"
|
||||
constant: "STOCK_COUNT_NEGATIVE"
|
||||
|
||||
# 统一响应
|
||||
response:
|
||||
success_pattern: "CommonResult.success(data)"
|
||||
error_pattern: "throw exception(ERROR_CODE, args...)"
|
||||
|
||||
# ============================================
|
||||
# 第五阶段:扩展指南
|
||||
# ============================================
|
||||
extension_guide:
|
||||
# 新增功能步骤
|
||||
new_feature:
|
||||
title: "新增业务单据类型"
|
||||
steps:
|
||||
- step: "1. 创建DO实体类"
|
||||
detail: |
|
||||
在 dal/dataobject/{domain}/ 下创建主表DO和子表DO
|
||||
继承 BaseDO,使用 @TableName、@KeySequence 注解
|
||||
定义字段并使用 @TableId 标注主键
|
||||
|
||||
- step: "2. 创建Mapper接口"
|
||||
detail: |
|
||||
在 dal/mysql/{domain}/ 下创建Mapper接口
|
||||
继承 BaseMapperX<DO>,添加自定义查询方法
|
||||
实现分页查询、按ID查询等方法
|
||||
|
||||
- step: "3. 创建Service层"
|
||||
detail: |
|
||||
在 service/{domain}/ 下创建Service接口和实现类
|
||||
实现CRUD、状态更新、业务校验等方法
|
||||
使用 @Transactional 注解保证事务
|
||||
|
||||
- step: "4. 创建Controller层"
|
||||
detail: |
|
||||
在 controller/admin/{domain}/ 下创建Controller
|
||||
添加 @Tag、@RestController、@PreAuthorize 注解
|
||||
实现创建、更新、删除、查询、导出等接口
|
||||
|
||||
- step: "5. 创建VO类"
|
||||
detail: |
|
||||
在 controller/admin/{domain}/vo/ 下创建:
|
||||
- SaveReqVO: 创建/更新请求VO
|
||||
- PageReqVO: 分页查询请求VO
|
||||
- RespVO: 响应VO
|
||||
|
||||
- step: "6. 添加单据号前缀"
|
||||
detail: |
|
||||
在 ErpNoRedisDAO 中添加单据号前缀常量
|
||||
格式:X + X + XX(如:CGDD-采购订单)
|
||||
|
||||
- step: "7. 添加错误码"
|
||||
detail: |
|
||||
在 ErrorCodeConstants 中添加错误码
|
||||
格式:1_030_xxx_xxx
|
||||
|
||||
# 新增渠道/类型示例
|
||||
new_channel:
|
||||
title: "新增库存业务类型"
|
||||
steps:
|
||||
- step: "1. 添加业务类型枚举"
|
||||
detail: |
|
||||
在 ErpStockRecordBizTypeEnum 中添加新类型:
|
||||
NEW_TYPE(90, "新业务类型"),
|
||||
NEW_TYPE_CANCEL(91, "新业务类型(作废)")
|
||||
|
||||
- step: "2. 创建业务单据"
|
||||
detail: "参考采购入库/销售出库创建对应的业务单据"
|
||||
|
||||
- step: "3. 在业务审核时创建库存记录"
|
||||
detail: |
|
||||
审核通过时调用 stockRecordService.createStockRecord()
|
||||
反审核时调用 stockRecordService.deleteStockRecord()
|
||||
|
||||
# 最佳实践
|
||||
best_practices:
|
||||
- practice: "单据状态管理"
|
||||
description: |
|
||||
所有业务单据使用 ErpAuditStatus 枚举管理状态
|
||||
PROCESS(10) - 未审核/审核中
|
||||
APPROVE(20) - 已审核
|
||||
状态变更需要校验前置状态和关联数据
|
||||
|
||||
- practice: "主子表更新"
|
||||
description: |
|
||||
使用 diffList 方法对比新旧数据
|
||||
分别处理新增、修改、删除的子表记录
|
||||
批量操作使用 insertBatch、updateBatch、deleteByIds
|
||||
|
||||
- practice: "库存操作"
|
||||
description: |
|
||||
使用 updateCountIncrement 增量更新库存
|
||||
通过乐观锁避免并发问题
|
||||
每次库存变动都创建 stock_record 记录
|
||||
|
||||
- practice: "单据号生成"
|
||||
description: |
|
||||
使用 ErpNoRedisDAO.generate() 生成唯一单据号
|
||||
格式:前缀 + yyyyMMdd + 6位自增序号
|
||||
基于Redis保证分布式唯一性
|
||||
|
||||
- practice: "数据关联查询"
|
||||
description: |
|
||||
分页查询时批量查询关联数据
|
||||
使用 convertMap、convertMultiMap 构建映射
|
||||
避免N+1查询问题
|
||||
|
||||
# ============================================
|
||||
# 依赖关系
|
||||
# ============================================
|
||||
dependencies:
|
||||
# 内部依赖(其他模块)
|
||||
internal:
|
||||
- module: "system"
|
||||
api: "AdminUserApi"
|
||||
purpose: "获取用户信息,显示操作人姓名"
|
||||
|
||||
# 外部依赖(第三方库)
|
||||
external:
|
||||
- name: "MyBatis-Plus"
|
||||
version: "3.x"
|
||||
purpose: "ORM框架,简化数据库操作"
|
||||
- name: "Hutool"
|
||||
version: "5.x"
|
||||
purpose: "Java工具类库,日期、集合等工具"
|
||||
- name: "Spring Redis"
|
||||
version: "2.x"
|
||||
purpose: "Redis操作,单据号生成"
|
||||
|
||||
# ============================================
|
||||
# 关键文件清单
|
||||
# ============================================
|
||||
key_files:
|
||||
# 枚举和常量
|
||||
- path: "yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java"
|
||||
purpose: "审核状态枚举,定义单据状态流转"
|
||||
|
||||
- path: "yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java"
|
||||
purpose: "错误码常量,定义所有业务异常"
|
||||
|
||||
- path: "yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/stock/ErpStockRecordBizTypeEnum.java"
|
||||
purpose: "库存记录业务类型枚举"
|
||||
|
||||
# 核心服务
|
||||
- path: "yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java"
|
||||
purpose: "采购订单服务实现,展示标准业务流程"
|
||||
|
||||
- path: "yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java"
|
||||
purpose: "库存服务实现,展示库存增量更新"
|
||||
|
||||
- path: "yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordServiceImpl.java"
|
||||
purpose: "库存记录服务,管理库存变动明细"
|
||||
|
||||
# 数据访问
|
||||
- path: "yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java"
|
||||
purpose: "单据号生成器,分布式唯一ID"
|
||||
|
||||
- path: "yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java"
|
||||
purpose: "采购订单DO,展示主表设计"
|
||||
|
||||
- path: "yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockDO.java"
|
||||
purpose: "库存DO,展示库存表设计"
|
||||
|
||||
- path: "yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockRecordDO.java"
|
||||
purpose: "库存记录DO,展示库存明细设计"
|
||||
|
||||
# 业务对象
|
||||
- path: "yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/stock/bo/ErpStockRecordCreateReqBO.java"
|
||||
purpose: "库存记录创建请求BO,展示值对象设计"
|
||||
|
||||
# 控制器示例
|
||||
- path: "yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java"
|
||||
purpose: "采购订单控制器,展示标准Controller设计"
|
||||
@@ -1,748 +0,0 @@
|
||||
# Skill 文件 - yudao-module-im 模块
|
||||
# 即时通讯模块知识提取
|
||||
|
||||
skill:
|
||||
id: "skill-im"
|
||||
name: "Instant Messaging Skill"
|
||||
version: "1.0.0"
|
||||
module_path: "yudao-module-im"
|
||||
created_at: "2026-06-08"
|
||||
updated_at: "2026-06-08"
|
||||
|
||||
# ============================================
|
||||
# 第一阶段:设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
# 业务定位:模块解决什么业务问题?在整个系统中的定位?
|
||||
business_position: |
|
||||
im 模块是系统的"即时通讯层",提供全链路即时通讯能力:
|
||||
1. 好友关系管理 - 双向好友关系,支持拉黑、免打扰、置顶、备注名
|
||||
2. 群组管理 - 创建/解散群组,三级角色体系(群主/管理员/普通成员),审批入群
|
||||
3. 私聊消息 - 一对一聊天,支持多种消息类型,clientMessageId 幂等发送
|
||||
4. 群聊消息 - 群组聊天,支持 @提醒、已读回执统计、消息置顶
|
||||
5. 频道广播 - 单向广播频道,管理员发布素材推送给指定用户
|
||||
6. RTC 实时通话 - 音视频通话,集成 LiveKit,支持呼叫/接听/拒绝/取消/挂断
|
||||
7. 表情系统 - 系统表情包 + 用户私人表情
|
||||
8. 敏感词过滤 - 基于 Trie 树的敏感词检测和替换
|
||||
|
||||
定位:面向终端用户的即时通讯模块,支持单聊、群聊、频道广播及 RTC 音视频通话。
|
||||
|
||||
# 设计原则:遵循了哪些设计原则(SOLID、DDD)?有哪些架构决策?
|
||||
design_principles:
|
||||
- principle: "双层 Controller 模式"
|
||||
description: "用户端 Controller(无 RBAC,通过登录态鉴权)+ Manager 端 Controller(RBAC 权限控制),分离普通用户操作和管理后台操作"
|
||||
- principle: "双向好友模型"
|
||||
description: "每对好友在 im_friend 表中存储 2 行记录(A->B 和 B->A),各自拥有独立的 silent/pinned/blocked/displayName 属性"
|
||||
- principle: "幂等消息发送"
|
||||
description: "通过 clientMessageId 字段实现客户端消息去重,避免网络重试导致重复消息"
|
||||
- principle: "事务感知异步推送"
|
||||
description: "消息推送使用 afterCommit 回调,确保数据库事务提交后再推送,避免读到未提交数据"
|
||||
- principle: "Redis 已读游标"
|
||||
description: "使用 Redis 存储每个用户在每个会话中的最大已读消息 ID,避免频繁更新数据库"
|
||||
- principle: "LiveKit Webhook 安全"
|
||||
description: "LiveKit Webhook 接口使用 JWT + SHA256 签名验证,同时标记 @PermitAll + @TenantIgnore"
|
||||
- principle: "Trie 树敏感词过滤"
|
||||
description: "使用 sensitive-word 库实现基于 Trie 树的高效敏感词检测和替换"
|
||||
- principle: "OpenIM 兼容消息类型"
|
||||
description: "消息类型编号兼容 OpenIM 协议,便于与 OpenIM 生态对接"
|
||||
- principle: "配置驱动"
|
||||
description: "模块配置统一在 yudao.im.* 命名空间下,通过 ImProperties 类管理"
|
||||
|
||||
# 领域模型:核心领域对象有哪些?领域对象之间的关系?聚合根是什么?
|
||||
domain_model:
|
||||
aggregates:
|
||||
- name: "频道聚合"
|
||||
type: "聚合根"
|
||||
entities: ["ChannelDO", "ChannelMaterialDO", "ChannelMessageDO"]
|
||||
description: "ChannelDO 是频道聚合根,ChannelMaterialDO 是频道素材,ChannelMessageDO 是频道广播消息"
|
||||
- name: "表情聚合"
|
||||
type: "聚合根"
|
||||
entities: ["FacePackDO", "FacePackItemDO", "FaceUserItemDO"]
|
||||
description: "FacePackDO 是表情包聚合根,FacePackItemDO 是表情项,FaceUserItemDO 是用户私人表情"
|
||||
- name: "好友聚合"
|
||||
type: "聚合根"
|
||||
entities: ["FriendDO", "FriendRequestDO"]
|
||||
description: "FriendDO 是好友关系记录(双向 2 行),FriendRequestDO 是好友请求记录"
|
||||
- name: "群组聚合"
|
||||
type: "聚合根"
|
||||
entities: ["GroupDO", "GroupMemberDO", "GroupRequestDO"]
|
||||
description: "GroupDO 是群组聚合根,GroupMemberDO 是群成员,GroupRequestDO 是入群请求"
|
||||
- name: "消息聚合"
|
||||
type: "聚合根"
|
||||
entities: ["PrivateMessageDO", "GroupMessageDO"]
|
||||
description: "PrivateMessageDO 是私聊消息,GroupMessageDO 是群聊消息"
|
||||
- name: "RTC 通话聚合"
|
||||
type: "聚合根"
|
||||
entities: ["RtcCallDO", "RtcParticipantDO"]
|
||||
description: "RtcCallDO 是通话会话聚合根,RtcParticipantDO 是通话参与者"
|
||||
|
||||
value_objects:
|
||||
- name: "ImProperties"
|
||||
description: "IM 模块配置属性,yudao.im.* 命名空间下的配置项"
|
||||
- name: "GroupMemberRoleEnum"
|
||||
description: "群成员角色枚举:OWNER/ADMIN/NORMAL"
|
||||
- name: "MessageTypeEnum"
|
||||
description: "消息类型枚举,兼容 OpenIM 协议编号"
|
||||
|
||||
services:
|
||||
- name: "FriendService"
|
||||
description: "好友关系服务,管理好友的增删改查、拉黑/取消拉黑"
|
||||
- name: "FriendRequestService"
|
||||
description: "好友请求服务,管理申请、同意、拒绝流程"
|
||||
- name: "GroupService"
|
||||
description: "群组核心服务,管理群组生命周期"
|
||||
- name: "GroupMemberService"
|
||||
description: "群成员服务,管理成员的加入/退出/角色变更"
|
||||
- name: "GroupRequestService"
|
||||
description: "入群请求服务,管理申请和审批流程"
|
||||
- name: "PrivateMessageService"
|
||||
description: "私聊消息服务,管理消息的发送、拉取、已读、撤回"
|
||||
- name: "GroupMessageService"
|
||||
description: "群聊消息服务,管理群消息的发送、拉取、已读、撤回"
|
||||
- name: "ChannelService"
|
||||
description: "频道管理服务,管理频道的 CRUD 和状态"
|
||||
- name: "ChannelMaterialService"
|
||||
description: "素材管理服务,管理素材的创建、编辑、查询"
|
||||
- name: "ChannelMessageService"
|
||||
description: "频道消息服务,管理广播消息的发送和拉取"
|
||||
- name: "RtcService"
|
||||
description: "RTC 通话核心服务,管理通话生命周期"
|
||||
- name: "LiveKitService"
|
||||
description: "LiveKit 集成服务,管理房间和 Token 生成"
|
||||
- name: "FacePackService"
|
||||
description: "表情包服务,管理表情包的 CRUD 和状态"
|
||||
- name: "FaceUserItemService"
|
||||
description: "用户表情服务,管理用户的私人表情"
|
||||
- name: "SensitiveWordService"
|
||||
description: "敏感词服务,管理敏感词库的 CRUD"
|
||||
|
||||
# ============================================
|
||||
# 第二阶段:架构设计
|
||||
# ============================================
|
||||
architecture:
|
||||
# 分层架构
|
||||
layers:
|
||||
- name: "api"
|
||||
purpose: "模块间 API 接口,供其他模块 RPC 调用"
|
||||
components: []
|
||||
|
||||
- name: "controller"
|
||||
purpose: "HTTP 接口层,提供 RESTful API"
|
||||
components:
|
||||
- "admin/friend - 好友管理接口(用户端)"
|
||||
- "admin/friend-request - 好友请求接口(用户端)"
|
||||
- "admin/group - 群组管理接口(用户端)"
|
||||
- "admin/group-member - 群成员管理接口(用户端)"
|
||||
- "admin/group-request - 群组请求接口(用户端)"
|
||||
- "admin/message/private - 私聊消息接口(用户端)"
|
||||
- "admin/message/group - 群聊消息接口(用户端)"
|
||||
- "admin/message/channel - 频道消息接口(用户端)"
|
||||
- "admin/channel/material - 频道素材接口(用户端)"
|
||||
- "admin/face-pack - 表情包接口(用户端)"
|
||||
- "admin/face-user-item - 用户表情接口(用户端)"
|
||||
- "admin/rtc - RTC 通话接口(用户端)"
|
||||
- "admin/livekit - LiveKit Webhook 接口"
|
||||
- "admin/manager/* - 管理后台接口(RBAC 保护)"
|
||||
|
||||
- name: "service"
|
||||
purpose: "业务逻辑层"
|
||||
components:
|
||||
- "friend - 好友服务(FriendService, FriendRequestService)"
|
||||
- "group - 群组服务(GroupService, GroupMemberService, GroupRequestService)"
|
||||
- "message - 消息服务(PrivateMessageService, GroupMessageService, ChannelMessageService)"
|
||||
- "channel - 频道服务(ChannelService, ChannelMaterialService)"
|
||||
- "face - 表情服务(FacePackService, FacePackItemService, FaceUserItemService)"
|
||||
- "rtc - RTC 服务(RtcService, LiveKitService)"
|
||||
- "sensitive - 敏感词服务(SensitiveWordService)"
|
||||
|
||||
- name: "dal"
|
||||
purpose: "数据访问层"
|
||||
components:
|
||||
- "dataobject - DO 实体类定义"
|
||||
- "mysql - MyBatis Mapper 接口"
|
||||
|
||||
- name: "framework"
|
||||
purpose: "框架层,模块内部基础设施"
|
||||
components:
|
||||
- "config - 模块配置(ImProperties)"
|
||||
- "redis - Redis 已读游标操作"
|
||||
|
||||
# 设计模式应用
|
||||
design_patterns:
|
||||
- pattern: "双层 Controller 模式 (Two-Tier Controller)"
|
||||
location: "controller/admin/ + controller/admin/manager/"
|
||||
purpose: "用户端 Controller 通过登录态鉴权,管理端 Controller 通过 @PreAuthorize 进行 RBAC 权限校验"
|
||||
- pattern: "幂等模式 (Idempotency Pattern)"
|
||||
location: "service/message/"
|
||||
purpose: "通过 clientMessageId 字段和数据库唯一索引实现消息幂等发送"
|
||||
- pattern: "事务感知异步推送"
|
||||
location: "service/message/"
|
||||
purpose: "使用 TransactionSynchronizationManager.registerSynchronization(afterCommit) 确保事务提交后再推送"
|
||||
- pattern: "游标模式 (Cursor Pattern)"
|
||||
location: "service/message/ + framework/redis/"
|
||||
purpose: "Redis 存储每个用户在每个会话中的最大已读消息 ID,支持增量拉取"
|
||||
- pattern: "安全验证模式"
|
||||
location: "controller/admin/livekit/"
|
||||
purpose: "LiveKit Webhook 使用 JWT + SHA256 签名验证,@PermitAll + @TenantIgnore 组合"
|
||||
- pattern: "Trie 树过滤模式"
|
||||
location: "service/sensitive/"
|
||||
purpose: "基于 sensitive-word 库的 Trie 树实现高效敏感词检测"
|
||||
|
||||
# 模块间通信
|
||||
communication:
|
||||
apis:
|
||||
- name: "AdminUserApi"
|
||||
method: "getUser()"
|
||||
description: "查询用户基本信息,用于好友信息、群成员信息展示"
|
||||
- name: "AdminUserApi"
|
||||
method: "getUsers()"
|
||||
description: "批量查询用户信息,用于消息列表中展示发送者信息"
|
||||
- name: "FileApi"
|
||||
method: "createFile()"
|
||||
description: "上传文件(头像、图片消息、文件消息)"
|
||||
consumers:
|
||||
- module: "yudao-module-system"
|
||||
api: "AdminUserApi"
|
||||
purpose: "查询用户信息(好友、群成员、消息发送者)"
|
||||
- module: "yudao-module-infra"
|
||||
api: "FileApi"
|
||||
purpose: "上传头像、图片消息、文件消息"
|
||||
mq: []
|
||||
|
||||
# ============================================
|
||||
# 第三阶段:数据表设计
|
||||
# ============================================
|
||||
data_model:
|
||||
# 实体继承体系
|
||||
entity_hierarchy:
|
||||
base: "BaseDO"
|
||||
description: "所有 DO 继承 BaseDO,包含 creator, createTime, updater, updateTime, deleted 字段。"
|
||||
|
||||
# 核心数据表
|
||||
tables:
|
||||
# ========== 频道相关 ==========
|
||||
- name: "im_channel"
|
||||
comment: "频道表"
|
||||
entity: "ChannelDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "频道编号,主键" }
|
||||
- { name: "code", type: "VARCHAR", comment: "频道编码,唯一" }
|
||||
- { name: "name", type: "VARCHAR", comment: "频道名称" }
|
||||
- { name: "avatar", type: "VARCHAR", comment: "频道头像 URL" }
|
||||
- { name: "sort", type: "INT", comment: "排序值" }
|
||||
- { name: "status", type: "TINYINT", comment: "频道状态(启用/禁用)" }
|
||||
indexes:
|
||||
- { name: "uk_code", columns: ["code"], unique: true }
|
||||
|
||||
- name: "im_channel_material"
|
||||
comment: "频道素材表"
|
||||
entity: "ChannelMaterialDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "素材编号,主键" }
|
||||
- { name: "title", type: "VARCHAR", comment: "素材标题" }
|
||||
- { name: "cover_url", type: "VARCHAR", comment: "封面图片 URL" }
|
||||
- { name: "summary", type: "VARCHAR", comment: "内容摘要" }
|
||||
- { name: "content", type: "TEXT", comment: "内容 HTML" }
|
||||
- { name: "url", type: "VARCHAR", comment: "原文链接" }
|
||||
|
||||
- name: "im_channel_message"
|
||||
comment: "频道消息表"
|
||||
entity: "ChannelMessageDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "消息编号,主键" }
|
||||
- { name: "channel_id", type: "BIGINT", comment: "频道编号" }
|
||||
- { name: "material_id", type: "BIGINT", comment: "关联素材编号" }
|
||||
- { name: "receiver_user_ids", type: "VARCHAR", comment: "接收用户 ID 列表(JSON 数组)" }
|
||||
- { name: "send_time", type: "DATETIME", comment: "发送时间" }
|
||||
indexes:
|
||||
- { name: "idx_channel_id", columns: ["channel_id"] }
|
||||
|
||||
# ========== 表情相关 ==========
|
||||
- name: "im_face_pack"
|
||||
comment: "表情包表"
|
||||
entity: "FacePackDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "表情包编号,主键" }
|
||||
- { name: "name", type: "VARCHAR", comment: "表情包名称" }
|
||||
- { name: "icon", type: "VARCHAR", comment: "表情包图标 URL" }
|
||||
- { name: "sort", type: "INT", comment: "排序值" }
|
||||
- { name: "status", type: "TINYINT", comment: "启用状态" }
|
||||
|
||||
- name: "im_face_pack_item"
|
||||
comment: "表情项表"
|
||||
entity: "FacePackItemDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "表情项编号,主键" }
|
||||
- { name: "pack_id", type: "BIGINT", comment: "所属表情包编号" }
|
||||
- { name: "url", type: "VARCHAR", comment: "表情图片 URL" }
|
||||
- { name: "name", type: "VARCHAR", comment: "表情名称" }
|
||||
- { name: "width", type: "INT", comment: "图片宽度" }
|
||||
- { name: "height", type: "INT", comment: "图片高度" }
|
||||
indexes:
|
||||
- { name: "idx_pack_id", columns: ["pack_id"] }
|
||||
|
||||
- name: "im_face_user_item"
|
||||
comment: "用户表情表"
|
||||
entity: "FaceUserItemDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "表情编号,主键" }
|
||||
- { name: "user_id", type: "BIGINT", comment: "用户编号" }
|
||||
- { name: "url", type: "VARCHAR", comment: "表情图片 URL" }
|
||||
- { name: "name", type: "VARCHAR", comment: "表情名称" }
|
||||
- { name: "width", type: "INT", comment: "图片宽度" }
|
||||
- { name: "height", type: "INT", comment: "图片高度" }
|
||||
indexes:
|
||||
- { name: "idx_user_id", columns: ["user_id"] }
|
||||
|
||||
# ========== 好友相关 ==========
|
||||
- name: "im_friend"
|
||||
comment: "好友关系表"
|
||||
entity: "FriendDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "关系编号,主键" }
|
||||
- { name: "user_id", type: "BIGINT", comment: "用户编号" }
|
||||
- { name: "friend_id", type: "BIGINT", comment: "好友用户编号" }
|
||||
- { name: "silent", type: "BIT", comment: "是否免打扰" }
|
||||
- { name: "pinned", type: "BIT", comment: "是否置顶" }
|
||||
- { name: "blocked", type: "BIT", comment: "是否拉黑" }
|
||||
- { name: "display_name", type: "VARCHAR", comment: "好友备注名" }
|
||||
indexes:
|
||||
- { name: "uk_user_friend", columns: ["user_id", "friend_id"], unique: true }
|
||||
|
||||
- name: "im_friend_request"
|
||||
comment: "好友请求表"
|
||||
entity: "FriendRequestDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "请求编号,主键" }
|
||||
- { name: "user_id", type: "BIGINT", comment: "申请人用户编号" }
|
||||
- { name: "friend_id", type: "BIGINT", comment: "目标用户编号" }
|
||||
- { name: "apply_content", type: "VARCHAR", comment: "申请附言" }
|
||||
- { name: "display_name", type: "VARCHAR", comment: "对好友的备注名" }
|
||||
- { name: "add_source", type: "TINYINT", comment: "添加来源" }
|
||||
- { name: "handle_result", type: "TINYINT", comment: "处理结果" }
|
||||
- { name: "handle_time", type: "DATETIME", comment: "处理时间" }
|
||||
- { name: "handle_user_id", type: "BIGINT", comment: "处理人用户编号" }
|
||||
indexes:
|
||||
- { name: "idx_user_id", columns: ["user_id"] }
|
||||
- { name: "idx_friend_id", columns: ["friend_id"] }
|
||||
|
||||
# ========== 群组相关 ==========
|
||||
- name: "im_group"
|
||||
comment: "群组表"
|
||||
entity: "GroupDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "群组编号,主键" }
|
||||
- { name: "name", type: "VARCHAR", comment: "群组名称" }
|
||||
- { name: "owner_id", type: "BIGINT", comment: "群主用户编号" }
|
||||
- { name: "avatar", type: "VARCHAR", comment: "群组头像 URL" }
|
||||
- { name: "notice", type: "VARCHAR", comment: "群公告" }
|
||||
- { name: "join_approval", type: "BIT", comment: "是否开启入群审批" }
|
||||
- { name: "banned", type: "BIT", comment: "是否被封禁" }
|
||||
- { name: "muted_all", type: "BIT", comment: "是否全员禁言" }
|
||||
- { name: "pinned_message_ids", type: "VARCHAR", comment: "置顶消息 ID 列表(JSON 数组)" }
|
||||
indexes:
|
||||
- { name: "idx_owner_id", columns: ["owner_id"] }
|
||||
|
||||
- name: "im_group_member"
|
||||
comment: "群成员表"
|
||||
entity: "GroupMemberDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "成员编号,主键" }
|
||||
- { name: "group_id", type: "BIGINT", comment: "群组编号" }
|
||||
- { name: "user_id", type: "BIGINT", comment: "用户编号" }
|
||||
- { name: "role", type: "TINYINT", comment: "角色(OWNER/ADMIN/NORMAL)" }
|
||||
- { name: "nickname", type: "VARCHAR", comment: "群内昵称" }
|
||||
- { name: "mute_end_time", type: "DATETIME", comment: "禁言结束时间" }
|
||||
- { name: "add_source", type: "TINYINT", comment: "加入来源" }
|
||||
- { name: "inviter", type: "BIGINT", comment: "邀请人用户编号" }
|
||||
indexes:
|
||||
- { name: "uk_group_user", columns: ["group_id", "user_id"], unique: true }
|
||||
|
||||
- name: "im_group_request"
|
||||
comment: "群组请求表"
|
||||
entity: "GroupRequestDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "请求编号,主键" }
|
||||
- { name: "group_id", type: "BIGINT", comment: "群组编号" }
|
||||
- { name: "user_id", type: "BIGINT", comment: "申请人/被邀请人用户编号" }
|
||||
- { name: "type", type: "TINYINT", comment: "请求类型(用户申请/成员邀请)" }
|
||||
- { name: "apply_content", type: "VARCHAR", comment: "申请附言" }
|
||||
- { name: "handle_result", type: "TINYINT", comment: "处理结果" }
|
||||
- { name: "handle_time", type: "DATETIME", comment: "处理时间" }
|
||||
- { name: "handle_user_id", type: "BIGINT", comment: "处理人用户编号" }
|
||||
indexes:
|
||||
- { name: "idx_group_id", columns: ["group_id"] }
|
||||
- { name: "idx_user_id", columns: ["user_id"] }
|
||||
|
||||
# ========== 消息相关 ==========
|
||||
- name: "im_private_message"
|
||||
comment: "私聊消息表"
|
||||
entity: "PrivateMessageDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "消息编号,主键" }
|
||||
- { name: "client_message_id", type: "VARCHAR", comment: "客户端消息 ID(幂等键)" }
|
||||
- { name: "sender_id", type: "BIGINT", comment: "发送者用户编号" }
|
||||
- { name: "receiver_id", type: "BIGINT", comment: "接收者用户编号" }
|
||||
- { name: "type", type: "TINYINT", comment: "消息类型" }
|
||||
- { name: "content", type: "VARCHAR", comment: "消息内容(JSON 格式)" }
|
||||
- { name: "status", type: "TINYINT", comment: "消息状态(正常/已撤回)" }
|
||||
indexes:
|
||||
- { name: "uk_client_message_id", columns: ["client_message_id"], unique: true }
|
||||
- { name: "idx_sender_id", columns: ["sender_id"] }
|
||||
- { name: "idx_receiver_id", columns: ["receiver_id"] }
|
||||
|
||||
- name: "im_group_message"
|
||||
comment: "群聊消息表"
|
||||
entity: "GroupMessageDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "消息编号,主键" }
|
||||
- { name: "client_message_id", type: "VARCHAR", comment: "客户端消息 ID(幂等键)" }
|
||||
- { name: "sender_id", type: "BIGINT", comment: "发送者用户编号" }
|
||||
- { name: "group_id", type: "BIGINT", comment: "群组编号" }
|
||||
- { name: "type", type: "TINYINT", comment: "消息类型" }
|
||||
- { name: "content", type: "VARCHAR", comment: "消息内容(JSON 格式)" }
|
||||
- { name: "at_user_ids", type: "VARCHAR", comment: "@的用户 ID 列表(JSON 数组)" }
|
||||
- { name: "status", type: "TINYINT", comment: "消息状态(正常/已撤回)" }
|
||||
- { name: "receipt_status", type: "TINYINT", comment: "已读回执状态" }
|
||||
- { name: "read_count", type: "INT", comment: "已读人数" }
|
||||
indexes:
|
||||
- { name: "uk_client_message_id", columns: ["client_message_id"], unique: true }
|
||||
- { name: "idx_group_id", columns: ["group_id"] }
|
||||
- { name: "idx_sender_id", columns: ["sender_id"] }
|
||||
|
||||
# ========== RTC 相关 ==========
|
||||
- name: "im_rtc_call"
|
||||
comment: "RTC 通话表"
|
||||
entity: "RtcCallDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "通话编号,主键" }
|
||||
- { name: "room_id", type: "VARCHAR", comment: "LiveKit 房间 UUID" }
|
||||
- { name: "conversation_type", type: "TINYINT", comment: "会话类型(私聊/群聊)" }
|
||||
- { name: "media_type", type: "TINYINT", comment: "媒体类型(音频/视频)" }
|
||||
- { name: "inviter_uid", type: "BIGINT", comment: "发起者用户编号" }
|
||||
- { name: "status", type: "TINYINT", comment: "通话状态" }
|
||||
- { name: "start_time", type: "DATETIME", comment: "开始时间" }
|
||||
- { name: "accept_time", type: "DATETIME", comment: "接听时间" }
|
||||
- { name: "end_time", type: "DATETIME", comment: "结束时间" }
|
||||
indexes:
|
||||
- { name: "uk_room_id", columns: ["room_id"], unique: true }
|
||||
- { name: "idx_inviter_uid", columns: ["inviter_uid"] }
|
||||
|
||||
- name: "im_rtc_participant"
|
||||
comment: "RTC 参与者表"
|
||||
entity: "RtcParticipantDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "参与者编号,主键" }
|
||||
- { name: "call_id", type: "BIGINT", comment: "通话编号" }
|
||||
- { name: "user_id", type: "BIGINT", comment: "用户编号" }
|
||||
- { name: "role", type: "TINYINT", comment: "角色(发起者/参与者)" }
|
||||
- { name: "status", type: "TINYINT", comment: "状态(INVITING/JOINED/LEFT/REJECTED/NO_ANSWER)" }
|
||||
- { name: "join_time", type: "DATETIME", comment: "加入时间" }
|
||||
- { name: "leave_time", type: "DATETIME", comment: "离开时间" }
|
||||
indexes:
|
||||
- { name: "idx_call_id", columns: ["call_id"] }
|
||||
- { name: "idx_user_id", columns: ["user_id"] }
|
||||
|
||||
# ========== 敏感词相关 ==========
|
||||
- name: "im_sensitive_word"
|
||||
comment: "敏感词表"
|
||||
entity: "SensitiveWordDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "敏感词编号,主键" }
|
||||
- { name: "word", type: "VARCHAR", comment: "敏感词内容" }
|
||||
- { name: "status", type: "TINYINT", comment: "启用状态" }
|
||||
indexes:
|
||||
- { name: "uk_word", columns: ["word"], unique: true }
|
||||
|
||||
# 表关系(ER关系)
|
||||
relationships:
|
||||
- from: "im_channel_message"
|
||||
to: "im_channel"
|
||||
type: "N:1"
|
||||
foreign_key: "channel_id"
|
||||
- from: "im_channel_message"
|
||||
to: "im_channel_material"
|
||||
type: "N:1"
|
||||
foreign_key: "material_id"
|
||||
- from: "im_face_pack_item"
|
||||
to: "im_face_pack"
|
||||
type: "N:1"
|
||||
foreign_key: "pack_id"
|
||||
- from: "im_group_member"
|
||||
to: "im_group"
|
||||
type: "N:1"
|
||||
foreign_key: "group_id"
|
||||
- from: "im_group_request"
|
||||
to: "im_group"
|
||||
type: "N:1"
|
||||
foreign_key: "group_id"
|
||||
- from: "im_group_message"
|
||||
to: "im_group"
|
||||
type: "N:1"
|
||||
foreign_key: "group_id"
|
||||
- from: "im_rtc_participant"
|
||||
to: "im_rtc_call"
|
||||
type: "N:1"
|
||||
foreign_key: "call_id"
|
||||
|
||||
# ============================================
|
||||
# 第四阶段:代码使用设计
|
||||
# ============================================
|
||||
code_patterns:
|
||||
# Controller层规范
|
||||
controller:
|
||||
annotations:
|
||||
- "@Tag(name = 'xxx') - Swagger 文档标签"
|
||||
- "@RestController - REST 控制器"
|
||||
- "@RequestMapping('/im/xxx') - 请求路径前缀"
|
||||
- "@Validated - 参数校验"
|
||||
- "@PreAuthorize('@ss.hasPermission('im:xxx:action')') - 权限控制(仅管理端)"
|
||||
example: |
|
||||
@Tag(name = "用户端 - 好友管理")
|
||||
@RestController
|
||||
@RequestMapping("/im/friend")
|
||||
@Validated
|
||||
public class ImFriendController {
|
||||
@Resource
|
||||
private FriendService friendService;
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "获取好友列表")
|
||||
public CommonResult<List<FriendDO>> getFriendList() {
|
||||
return success(friendService.getFriendList(LoginContextHolder.getLoginUserId()));
|
||||
}
|
||||
}
|
||||
|
||||
# Service层规范
|
||||
service:
|
||||
interface_pattern: "XxxService 接口定义业务方法,XxxServiceImpl 实现类"
|
||||
impl_pattern: |
|
||||
@Service
|
||||
@Validated
|
||||
public class XxxServiceImpl implements XxxService {
|
||||
@Resource
|
||||
private XxxMapper xxxMapper;
|
||||
|
||||
// 使用 static final 定义常量
|
||||
// 使用 @Transactional 注解控制事务
|
||||
// 使用 validateXxxExists 方法校验存在性
|
||||
// 使用 exception(ErrorCode) 抛出业务异常
|
||||
}
|
||||
example: |
|
||||
@Service
|
||||
public class PrivateMessageServiceImpl implements PrivateMessageService {
|
||||
@Resource
|
||||
private ImPrivateMessageMapper privateMessageMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PrivateMessageDO sendPrivateMessage(PrivateMessageSendReqDTO reqDTO) {
|
||||
// 1. 校验接收者存在
|
||||
// 2. 检查 clientMessageId 幂等
|
||||
// 3. 敏感词过滤
|
||||
// 4. 保存消息
|
||||
PrivateMessageDO message = ...;
|
||||
privateMessageMapper.insert(message);
|
||||
// 5. 事务提交后异步推送
|
||||
TransactionSynchronizationManager.registerSynchronization(
|
||||
new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
// 推送消息给接收者
|
||||
}
|
||||
});
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
# 数据访问规范
|
||||
dal:
|
||||
mapper_pattern: |
|
||||
public interface XxxMapper extends BaseMapperX<XxxDO> {
|
||||
// 继承 BaseMapperX 获得通用 CRUD 方法
|
||||
// 自定义查询方法使用 @Select 注解或 XML
|
||||
// 分页查询返回 PageResult<XxxDO>
|
||||
}
|
||||
example: |
|
||||
public interface ImPrivateMessageMapper extends BaseMapperX<PrivateMessageDO> {
|
||||
default PageResult<PrivateMessageDO> selectPage(PrivateMessagePageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<PrivateMessageDO>()
|
||||
.eqIfPresent(PrivateMessageDO::getSenderId, reqVO.getSenderId())
|
||||
.eqIfPresent(PrivateMessageDO::getReceiverId, reqVO.getReceiverId())
|
||||
.betweenIfPresent(PrivateMessageDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(PrivateMessageDO::getId));
|
||||
}
|
||||
}
|
||||
|
||||
# 异常处理
|
||||
error_handling:
|
||||
code_prefix: "1-009-xxx-xxx"
|
||||
examples:
|
||||
- code: "好友已存在"
|
||||
- code: "不是好友关系"
|
||||
- code: "群组不存在"
|
||||
- code: "不是群成员"
|
||||
- code: "已被禁言"
|
||||
- code: "消息已撤回"
|
||||
- code: "撤回超时"
|
||||
- code: "通话不存在"
|
||||
|
||||
# 统一响应
|
||||
response:
|
||||
success_pattern: "CommonResult.success(data)"
|
||||
error_pattern: "throw exception(ErrorCode)"
|
||||
|
||||
# ============================================
|
||||
# 第五阶段:扩展指南
|
||||
# ============================================
|
||||
extension_guide:
|
||||
# 新增消息类型
|
||||
new_channel:
|
||||
title: "新增消息类型"
|
||||
steps:
|
||||
- step: "1. 定义消息类型枚举"
|
||||
description: "在 MessageTypeEnum 中添加新的消息类型编号,兼容 OpenIM 协议"
|
||||
- step: "2. 定义消息内容 DTO"
|
||||
description: "创建 XxxContentDTO 类定义该消息类型的 content JSON 结构"
|
||||
code: |
|
||||
@Data
|
||||
public class VideoContentDTO {
|
||||
private String url;
|
||||
private Integer duration;
|
||||
private Integer width;
|
||||
private Integer height;
|
||||
private String thumbUrl;
|
||||
}
|
||||
- step: "3. 实现消息渲染"
|
||||
description: "在客户端实现该消息类型的渲染逻辑"
|
||||
- step: "4. 敏感词处理"
|
||||
description: "如果消息类型包含文本内容,需要在发送时进行敏感词过滤"
|
||||
|
||||
# 新增管理接口
|
||||
new_feature:
|
||||
title: "新增管理后台接口"
|
||||
steps:
|
||||
- step: "1. 创建 Manager Controller"
|
||||
description: "在 controller/admin/manager/ 下创建新的 Controller 类"
|
||||
code: |
|
||||
@Tag(name = "管理后台 - XXX 管理")
|
||||
@RestController
|
||||
@RequestMapping("/im/manager/xxx")
|
||||
@Validated
|
||||
@PreAuthorize("@ss.hasPermission('im:xxx:query')")
|
||||
public class ImXxxManagerController {
|
||||
// 注入 Service
|
||||
// 定义 CRUD 接口
|
||||
}
|
||||
- step: "2. 定义权限标识"
|
||||
description: "在权限系统中注册 im:xxx:query/create/update/delete 权限"
|
||||
- step: "3. 实现 Service 方法"
|
||||
description: "在对应的 Service 中实现管理端的业务逻辑"
|
||||
- step: "4. 添加数据权限"
|
||||
description: "根据需要添加数据权限控制"
|
||||
|
||||
# 最佳实践
|
||||
best_practices:
|
||||
- practice: "clientMessageId 幂等发送"
|
||||
description: "客户端生成 UUID 作为 clientMessageId,服务端通过唯一索引保证幂等,避免网络重试导致重复消息"
|
||||
- practice: "事务感知异步推送"
|
||||
description: "使用 afterCommit 回调确保事务提交后再推送消息,避免接收方读到未提交数据"
|
||||
- practice: "Redis 已读游标"
|
||||
description: "使用 Redis 存储已读游标,避免频繁更新数据库,提高已读状态查询性能"
|
||||
- practice: "双向好友独立属性"
|
||||
description: "每对好友的 2 行记录拥有独立属性,更新时只修改当前用户方向的记录"
|
||||
- practice: "群组角色权限校验"
|
||||
description: "通过 GroupMemberDO.role 字段判断操作权限,注意权限边界(管理员不可踢管理员)"
|
||||
- practice: "LiveKit Webhook 安全"
|
||||
description: "使用 JWT + SHA256 签名验证 Webhook 请求,不要在未验证签名的情况下处理事件"
|
||||
- practice: "敏感词过滤时机"
|
||||
description: "敏感词过滤在消息发送时执行,过滤后直接存储,已发送消息不受后续词库更新影响"
|
||||
- practice: "消息增量拉取"
|
||||
description: "使用 maxMessageId 作为游标进行增量拉取,避免使用 offset 分页的性能问题"
|
||||
|
||||
# ============================================
|
||||
# 依赖关系
|
||||
# ============================================
|
||||
dependencies:
|
||||
# 内部依赖(其他模块)
|
||||
internal:
|
||||
- module: "yudao-module-system"
|
||||
purpose: "AdminUserApi - 查询用户信息、校验用户存在性"
|
||||
- module: "yudao-module-infra"
|
||||
purpose: "文件存储(头像、图片、文件消息等)"
|
||||
- module: "yudao-framework-common"
|
||||
purpose: "通用工具类、CommonResult、PageResult 等"
|
||||
- module: "yudao-framework-mybatis"
|
||||
purpose: "MyBatis-Plus 封装、BaseMapperX、BaseDO"
|
||||
- module: "yudao-framework-redis"
|
||||
purpose: "Redis 操作封装,用于已读游标存储"
|
||||
- module: "yudao-framework-tenant"
|
||||
purpose: "租户支持"
|
||||
|
||||
# 外部依赖(第三方库)
|
||||
external:
|
||||
- name: "MyBatis-Plus"
|
||||
purpose: "ORM 框架,提供 CRUD 封装"
|
||||
- name: "sensitive-word"
|
||||
purpose: "基于 Trie 树的敏感词过滤库"
|
||||
- name: "pinyin4j"
|
||||
purpose: "汉字转拼音,用于敏感词拼音匹配"
|
||||
- name: "LiveKit"
|
||||
purpose: "开源实时音视频通信框架,RTC 能力支撑"
|
||||
- name: "Swagger/OpenAPI"
|
||||
purpose: "API 文档注解"
|
||||
|
||||
# ============================================
|
||||
# 关键文件清单
|
||||
# ============================================
|
||||
key_files:
|
||||
- path: "yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendController.java"
|
||||
purpose: "好友管理用户端 Controller"
|
||||
- path: "yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java"
|
||||
purpose: "群组管理用户端 Controller"
|
||||
- path: "yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImPrivateMessageController.java"
|
||||
purpose: "私聊消息用户端 Controller"
|
||||
- path: "yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImGroupMessageController.java"
|
||||
purpose: "群聊消息用户端 Controller"
|
||||
- path: "yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/ImRtcController.java"
|
||||
purpose: "RTC 通话用户端 Controller"
|
||||
- path: "yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/livekit/ImLivekitController.java"
|
||||
purpose: "LiveKit Webhook Controller"
|
||||
- path: "yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/FriendService.java"
|
||||
purpose: "好友关系服务接口"
|
||||
- path: "yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/GroupService.java"
|
||||
purpose: "群组核心服务接口"
|
||||
- path: "yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/PrivateMessageService.java"
|
||||
purpose: "私聊消息服务接口"
|
||||
- path: "yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/GroupMessageService.java"
|
||||
purpose: "群聊消息服务接口"
|
||||
- path: "yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/RtcService.java"
|
||||
purpose: "RTC 通话服务接口"
|
||||
- path: "yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitive/SensitiveWordService.java"
|
||||
purpose: "敏感词服务接口"
|
||||
- path: "yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/GroupDO.java"
|
||||
purpose: "群组实体类"
|
||||
- path: "yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/PrivateMessageDO.java"
|
||||
purpose: "私聊消息实体类"
|
||||
- path: "yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/rtc/RtcCallDO.java"
|
||||
purpose: "RTC 通话实体类"
|
||||
- path: "yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java"
|
||||
purpose: "错误码常量定义"
|
||||
- path: "yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/config/ImProperties.java"
|
||||
purpose: "IM 模块配置属性类"
|
||||
@@ -1,662 +0,0 @@
|
||||
# Skill 文件 - yudao-module-infra 模块
|
||||
# 基础设施模块知识提取
|
||||
|
||||
skill:
|
||||
id: "skill-infra"
|
||||
name: "Infrastructure Skill"
|
||||
version: "1.0.0"
|
||||
module_path: "yudao-module-infra"
|
||||
created_at: "2026-03-18"
|
||||
updated_at: "2026-03-18"
|
||||
|
||||
# ============================================
|
||||
# 第一阶段:设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
# 业务定位:模块解决什么业务问题?在整个系统中的定位?
|
||||
business_position: |
|
||||
infra 模块是整个系统的"基础设施层",提供跨业务模块的通用能力支撑:
|
||||
1. 文件存储服务 - 统一的文件上传、下载、管理能力,支持多存储渠道(本地/S3/OSS/FTP等)
|
||||
2. 代码生成器 - 基于数据库表结构自动生成 CRUD 代码,提升开发效率
|
||||
3. 定时任务管理 - 基于 Quartz 的任务调度管理,支持动态配置和监控
|
||||
4. API 日志审计 - 记录 API 访问日志和异常日志,支持问题追踪和分析
|
||||
5. 系统配置管理 - 键值对形式的系统参数配置
|
||||
6. 数据源管理 - 多数据源配置和管理,支持代码生成器连接不同数据库
|
||||
|
||||
定位:作为底层基础设施模块,不直接面向终端用户业务,而是为其他业务模块提供技术能力支撑。
|
||||
|
||||
# 设计原则:遵循了哪些设计原则(SOLID、DDD)?有哪些架构决策?
|
||||
design_principles:
|
||||
- principle: "策略模式 + 工厂模式"
|
||||
description: "文件存储采用策略模式,通过 FileClientFactory 创建不同的 FileClient 实现,支持本地、S3、FTP、SFTP、数据库等多种存储方式"
|
||||
- principle: "模板方法模式"
|
||||
description: "AbstractFileClient 抽象类定义文件客户端的通用流程,子类实现具体的初始化和操作逻辑"
|
||||
- principle: "多态配置"
|
||||
description: "FileClientConfig 使用 Jackson @JsonTypeInfo 实现多态序列化,配置类与存储类型一一对应"
|
||||
- principle: "Builder 模式"
|
||||
description: "CodegenBuilder 负责构建代码生成的表和列定义对象,CodegenEngine 负责执行代码生成"
|
||||
- principle: "模板引擎"
|
||||
description: "代码生成使用 Velocity 模板引擎,支持多前端框架(Vue2/Vue3/Vben/UniApp)"
|
||||
- principle: "缓存优化"
|
||||
description: "FileClient 使用 Guava LoadingCache 实现10秒异步刷新,避免频繁查询数据库"
|
||||
- principle: "租户隔离忽略"
|
||||
description: "infra 模块大部分表使用 @TenantIgnore 注解,基础设施不参与租户隔离"
|
||||
|
||||
# 领域模型:核心领域对象有哪些?领域对象之间的关系?聚合根是什么?
|
||||
domain_model:
|
||||
aggregates:
|
||||
- name: "文件存储聚合"
|
||||
type: "聚合根"
|
||||
entities: ["FileDO", "FileConfigDO", "FileContentDO"]
|
||||
description: "FileConfigDO 是配置聚合根,FileDO 是文件记录,FileContentDO 是数据库存储的文件内容"
|
||||
- name: "代码生成聚合"
|
||||
type: "聚合根"
|
||||
entities: ["CodegenTableDO", "CodegenColumnDO"]
|
||||
description: "CodegenTableDO 是表定义聚合根,CodegenColumnDO 是列定义,两者是一对多关系"
|
||||
- name: "定时任务聚合"
|
||||
type: "聚合根"
|
||||
entities: ["JobDO", "JobLogDO"]
|
||||
description: "JobDO 是任务定义聚合根,JobLogDO 是执行日志,两者是一对多关系"
|
||||
- name: "API日志聚合"
|
||||
type: "聚合根"
|
||||
entities: ["ApiAccessLogDO", "ApiErrorLogDO"]
|
||||
description: "独立的日志实体,无关联关系"
|
||||
|
||||
value_objects:
|
||||
- name: "FileClientConfig"
|
||||
description: "文件客户端配置接口,有多种实现(LocalFileClientConfig, S3FileClientConfig 等)"
|
||||
- name: "FileStorageEnum"
|
||||
description: "文件存储类型枚举,定义存储类型与配置类、客户端类的映射关系"
|
||||
|
||||
services:
|
||||
- name: "FileService"
|
||||
description: "文件管理服务,提供文件上传、下载、删除等功能"
|
||||
- name: "FileConfigService"
|
||||
description: "文件配置服务,管理存储渠道配置"
|
||||
- name: "CodegenService"
|
||||
description: "代码生成服务,提供表导入、代码生成等功能"
|
||||
- name: "JobService"
|
||||
description: "定时任务服务,与 Quartz 集成管理任务调度"
|
||||
- name: "ConfigService"
|
||||
description: "系统配置服务,管理键值对配置"
|
||||
|
||||
# ============================================
|
||||
# 第二阶段:架构设计
|
||||
# ============================================
|
||||
architecture:
|
||||
# 分层架构
|
||||
layers:
|
||||
- name: "api"
|
||||
purpose: "模块间 API 接口,供其他模块 RPC 调用"
|
||||
components:
|
||||
- "FileApi - 文件上传接口"
|
||||
- "ConfigApi - 配置获取接口"
|
||||
- "WebSocketSenderApi - WebSocket 消息发送接口"
|
||||
|
||||
- name: "controller"
|
||||
purpose: "HTTP 接口层,提供 RESTful API"
|
||||
components:
|
||||
- "admin/codegen - 代码生成器接口"
|
||||
- "admin/file - 文件管理接口"
|
||||
- "admin/job - 定时任务管理接口"
|
||||
- "admin/logger - API 日志管理接口"
|
||||
- "admin/config - 系统配置接口"
|
||||
- "admin/db - 数据源管理接口"
|
||||
- "admin/redis - Redis 监控接口"
|
||||
- "app/file - App 端文件上传接口"
|
||||
- "admin/demo - 代码生成示例(Demo01/Demo02/Demo03)"
|
||||
|
||||
- name: "service"
|
||||
purpose: "业务逻辑层"
|
||||
components:
|
||||
- "codegen - 代码生成服务(CodegenService, CodegenBuilder, CodegenEngine)"
|
||||
- "file - 文件服务(FileService, FileConfigService)"
|
||||
- "job - 定时任务服务(JobService, JobLogService)"
|
||||
- "logger - 日志服务(ApiAccessLogService, ApiErrorLogService)"
|
||||
- "config - 配置服务(ConfigService)"
|
||||
- "db - 数据源服务(DataSourceConfigService, DatabaseTableService)"
|
||||
|
||||
- name: "dal"
|
||||
purpose: "数据访问层"
|
||||
components:
|
||||
- "dataobject - DO 实体类定义"
|
||||
- "mysql - MyBatis Mapper 接口"
|
||||
|
||||
- name: "framework"
|
||||
purpose: "框架层,模块内部基础设施"
|
||||
components:
|
||||
- "file/core/client - 文件客户端抽象和实现"
|
||||
- "file/core/enums - 文件存储枚举"
|
||||
- "codegen/config - 代码生成配置"
|
||||
|
||||
# 设计模式应用
|
||||
design_patterns:
|
||||
- pattern: "策略模式 (Strategy Pattern)"
|
||||
location: "framework/file/core/client/"
|
||||
purpose: "FileClient 接口定义统一操作,不同存储实现(LocalFileClient, S3FileClient, FtpFileClient 等)"
|
||||
- pattern: "工厂模式 (Factory Pattern)"
|
||||
location: "framework/file/core/client/FileClientFactoryImpl.java"
|
||||
purpose: "根据存储类型动态创建对应的 FileClient 实例"
|
||||
- pattern: "模板方法模式 (Template Method Pattern)"
|
||||
location: "framework/file/core/client/AbstractFileClient.java"
|
||||
purpose: "定义文件客户端的初始化和刷新模板流程,子类实现 doInit()"
|
||||
- pattern: "Builder 模式"
|
||||
location: "service/codegen/inner/CodegenBuilder.java"
|
||||
purpose: "构建 CodegenTableDO 和 CodegenColumnDO 对象"
|
||||
- pattern: "模板引擎模式"
|
||||
location: "service/codegen/inner/CodegenEngine.java"
|
||||
purpose: "使用 Velocity 模板生成代码,支持多前端框架"
|
||||
- pattern: "缓存模式"
|
||||
location: "service/file/FileConfigServiceImpl.java"
|
||||
purpose: "使用 Guava LoadingCache 缓存 FileClient,10秒异步刷新"
|
||||
|
||||
# 模块间通信
|
||||
communication:
|
||||
apis:
|
||||
- name: "FileApi"
|
||||
method: "createFile()"
|
||||
description: "其他模块调用上传文件,返回文件 URL"
|
||||
- name: "FileApi"
|
||||
method: "presignGetUrl()"
|
||||
description: "获取文件预签名访问地址"
|
||||
- name: "ConfigApi"
|
||||
method: "getConfigValueByKey()"
|
||||
description: "其他模块获取系统配置值"
|
||||
- name: "WebSocketSenderApi"
|
||||
method: "send()"
|
||||
description: "发送 WebSocket 消息"
|
||||
consumers:
|
||||
- module: "yudao-module-system"
|
||||
api: "FileApi"
|
||||
purpose: "用户头像上传"
|
||||
- module: "yudao-module-member"
|
||||
api: "FileApi"
|
||||
purpose: "会员头像上传"
|
||||
- module: "所有模块"
|
||||
api: "ConfigApi"
|
||||
purpose: "获取系统配置"
|
||||
mq: []
|
||||
|
||||
# ============================================
|
||||
# 第三阶段:数据表设计
|
||||
# ============================================
|
||||
data_model:
|
||||
# 实体继承体系
|
||||
entity_hierarchy:
|
||||
base: "BaseDO"
|
||||
description: "所有 DO 继承 BaseDO,包含 creator, createTime, updater, updateTime, deleted 字段。大部分表使用 @TenantIgnore 忽略租户隔离。"
|
||||
|
||||
# 核心数据表
|
||||
tables:
|
||||
# ========== 文件存储相关 ==========
|
||||
- name: "infra_file_config"
|
||||
comment: "文件配置表"
|
||||
entity: "FileConfigDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "配置编号,主键" }
|
||||
- { name: "name", type: "VARCHAR", comment: "配置名称" }
|
||||
- { name: "storage", type: "TINYINT", comment: "存储器类型,枚举 FileStorageEnum" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
- { name: "master", type: "BIT", comment: "是否主配置" }
|
||||
- { name: "config", type: "JSON", comment: "存储配置,JSON 格式" }
|
||||
indexes:
|
||||
- { name: "idx_master", columns: ["master"] }
|
||||
|
||||
- name: "infra_file"
|
||||
comment: "文件表"
|
||||
entity: "FileDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "文件编号,主键" }
|
||||
- { name: "config_id", type: "BIGINT", comment: "配置编号,关联 FileConfigDO" }
|
||||
- { name: "name", type: "VARCHAR", comment: "原文件名" }
|
||||
- { name: "path", type: "VARCHAR", comment: "文件路径" }
|
||||
- { name: "url", type: "VARCHAR", comment: "访问地址" }
|
||||
- { name: "type", type: "VARCHAR", comment: "MIME 类型" }
|
||||
- { name: "size", type: "BIGINT", comment: "文件大小(字节)" }
|
||||
indexes:
|
||||
- { name: "idx_config_id", columns: ["config_id"] }
|
||||
|
||||
- name: "infra_file_content"
|
||||
comment: "文件内容表(数据库存储方式)"
|
||||
entity: "FileContentDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号,主键" }
|
||||
- { name: "config_id", type: "BIGINT", comment: "配置编号" }
|
||||
- { name: "path", type: "VARCHAR", comment: "文件路径" }
|
||||
- { name: "content", type: "LONGBLOB", comment: "文件内容" }
|
||||
|
||||
# ========== 代码生成相关 ==========
|
||||
- name: "infra_codegen_table"
|
||||
comment: "代码生成表定义"
|
||||
entity: "CodegenTableDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号,主键" }
|
||||
- { name: "data_source_config_id", type: "BIGINT", comment: "数据源编号" }
|
||||
- { name: "scene", type: "TINYINT", comment: "生成场景" }
|
||||
- { name: "table_name", type: "VARCHAR", comment: "表名称" }
|
||||
- { name: "table_comment", type: "VARCHAR", comment: "表描述" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
- { name: "module_name", type: "VARCHAR", comment: "模块名" }
|
||||
- { name: "business_name", type: "VARCHAR", comment: "业务名" }
|
||||
- { name: "class_name", type: "VARCHAR", comment: "类名称" }
|
||||
- { name: "class_comment", type: "VARCHAR", comment: "类描述" }
|
||||
- { name: "author", type: "VARCHAR", comment: "作者" }
|
||||
- { name: "template_type", type: "TINYINT", comment: "模板类型" }
|
||||
- { name: "front_type", type: "TINYINT", comment: "前端类型" }
|
||||
- { name: "parent_menu_id", type: "BIGINT", comment: "父菜单编号" }
|
||||
- { name: "master_table_id", type: "BIGINT", comment: "主表编号(子表时使用)" }
|
||||
- { name: "sub_join_column_id", type: "BIGINT", comment: "子表关联字段编号" }
|
||||
- { name: "sub_join_many", type: "BIT", comment: "是否一对多" }
|
||||
- { name: "tree_parent_column_id", type: "BIGINT", comment: "树表父字段编号" }
|
||||
- { name: "tree_name_column_id", type: "BIGINT", comment: "树表名称字段编号" }
|
||||
|
||||
- name: "infra_codegen_column"
|
||||
comment: "代码生成列定义"
|
||||
entity: "CodegenColumnDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号,主键" }
|
||||
- { name: "table_id", type: "BIGINT", comment: "表编号,关联 CodegenTableDO" }
|
||||
- { name: "column_name", type: "VARCHAR", comment: "字段名" }
|
||||
- { name: "data_type", type: "VARCHAR", comment: "数据库类型" }
|
||||
- { name: "column_comment", type: "VARCHAR", comment: "字段描述" }
|
||||
- { name: "nullable", type: "BIT", comment: "是否允许空" }
|
||||
- { name: "primary_key", type: "BIT", comment: "是否主键" }
|
||||
- { name: "ordinal_position", type: "INT", comment: "排序" }
|
||||
- { name: "java_type", type: "VARCHAR", comment: "Java 类型" }
|
||||
- { name: "java_field", type: "VARCHAR", comment: "Java 属性名" }
|
||||
- { name: "dict_type", type: "VARCHAR", comment: "字典类型" }
|
||||
- { name: "example", type: "VARCHAR", comment: "数据示例" }
|
||||
- { name: "create_operation", type: "BIT", comment: "是否 Create 操作字段" }
|
||||
- { name: "update_operation", type: "BIT", comment: "是否 Update 操作字段" }
|
||||
- { name: "list_operation", type: "BIT", comment: "是否 List 查询字段" }
|
||||
- { name: "list_operation_condition", type: "VARCHAR", comment: "List 查询条件" }
|
||||
- { name: "list_operation_result", type: "BIT", comment: "是否 List 返回字段" }
|
||||
- { name: "html_type", type: "VARCHAR", comment: "显示类型" }
|
||||
|
||||
# ========== 定时任务相关 ==========
|
||||
- name: "infra_job"
|
||||
comment: "定时任务表"
|
||||
entity: "JobDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "任务编号,主键" }
|
||||
- { name: "name", type: "VARCHAR", comment: "任务名称" }
|
||||
- { name: "status", type: "TINYINT", comment: "任务状态" }
|
||||
- { name: "handler_name", type: "VARCHAR", comment: "处理器名称" }
|
||||
- { name: "handler_param", type: "VARCHAR", comment: "处理器参数" }
|
||||
- { name: "cron_expression", type: "VARCHAR", comment: "CRON 表达式" }
|
||||
- { name: "retry_count", type: "INT", comment: "重试次数" }
|
||||
- { name: "retry_interval", type: "INT", comment: "重试间隔(毫秒)" }
|
||||
- { name: "monitor_timeout", type: "INT", comment: "监控超时时间(毫秒)" }
|
||||
|
||||
- name: "infra_job_log"
|
||||
comment: "定时任务日志表"
|
||||
entity: "JobLogDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "日志编号,主键" }
|
||||
- { name: "job_id", type: "BIGINT", comment: "任务编号,关联 JobDO" }
|
||||
- { name: "handler_name", type: "VARCHAR", comment: "处理器名称" }
|
||||
- { name: "handler_param", type: "VARCHAR", comment: "处理器参数" }
|
||||
- { name: "execute_index", type: "INT", comment: "第几次执行" }
|
||||
- { name: "begin_time", type: "DATETIME", comment: "开始时间" }
|
||||
- { name: "end_time", type: "DATETIME", comment: "结束时间" }
|
||||
- { name: "duration", type: "INT", comment: "执行时长(毫秒)" }
|
||||
- { name: "status", type: "TINYINT", comment: "执行状态" }
|
||||
- { name: "result", type: "VARCHAR", comment: "执行结果" }
|
||||
|
||||
# ========== API 日志相关 ==========
|
||||
- name: "infra_api_access_log"
|
||||
comment: "API 访问日志表"
|
||||
entity: "ApiAccessLogDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号,主键" }
|
||||
- { name: "trace_id", type: "VARCHAR", comment: "链路追踪编号" }
|
||||
- { name: "user_id", type: "BIGINT", comment: "用户编号" }
|
||||
- { name: "user_type", type: "TINYINT", comment: "用户类型" }
|
||||
- { name: "application_name", type: "VARCHAR", comment: "应用名" }
|
||||
- { name: "request_method", type: "VARCHAR", comment: "请求方法" }
|
||||
- { name: "request_url", type: "VARCHAR", comment: "请求地址" }
|
||||
- { name: "request_params", type: "VARCHAR", comment: "请求参数" }
|
||||
- { name: "response_body", type: "VARCHAR", comment: "响应结果" }
|
||||
- { name: "user_ip", type: "VARCHAR", comment: "用户 IP" }
|
||||
- { name: "user_agent", type: "VARCHAR", comment: "浏览器 UA" }
|
||||
- { name: "operate_module", type: "VARCHAR", comment: "操作模块" }
|
||||
- { name: "operate_name", type: "VARCHAR", comment: "操作名" }
|
||||
- { name: "operate_type", type: "TINYINT", comment: "操作分类" }
|
||||
- { name: "begin_time", type: "DATETIME", comment: "开始时间" }
|
||||
- { name: "end_time", type: "DATETIME", comment: "结束时间" }
|
||||
- { name: "duration", type: "INT", comment: "执行时长(毫秒)" }
|
||||
- { name: "result_code", type: "INT", comment: "结果码" }
|
||||
- { name: "result_msg", type: "VARCHAR", comment: "结果提示" }
|
||||
|
||||
- name: "infra_api_error_log"
|
||||
comment: "API 异常日志表"
|
||||
entity: "ApiErrorLogDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号,主键" }
|
||||
- { name: "user_id", type: "BIGINT", comment: "用户编号" }
|
||||
- { name: "trace_id", type: "VARCHAR", comment: "链路追踪编号" }
|
||||
- { name: "user_type", type: "TINYINT", comment: "用户类型" }
|
||||
- { name: "application_name", type: "VARCHAR", comment: "应用名" }
|
||||
- { name: "request_method", type: "VARCHAR", comment: "请求方法" }
|
||||
- { name: "request_url", type: "VARCHAR", comment: "请求地址" }
|
||||
- { name: "request_params", type: "VARCHAR", comment: "请求参数" }
|
||||
- { name: "user_ip", type: "VARCHAR", comment: "用户 IP" }
|
||||
- { name: "user_agent", type: "VARCHAR", comment: "浏览器 UA" }
|
||||
- { name: "exception_time", type: "DATETIME", comment: "异常时间" }
|
||||
- { name: "exception_name", type: "VARCHAR", comment: "异常名" }
|
||||
- { name: "exception_message", type: "VARCHAR", comment: "异常消息" }
|
||||
- { name: "exception_root_cause_message", type: "VARCHAR", comment: "异常根消息" }
|
||||
- { name: "exception_stack_trace", type: "VARCHAR", comment: "异常栈轨迹" }
|
||||
- { name: "exception_class_name", type: "VARCHAR", comment: "异常类名" }
|
||||
- { name: "exception_file_name", type: "VARCHAR", comment: "异常文件名" }
|
||||
- { name: "exception_method_name", type: "VARCHAR", comment: "异常方法名" }
|
||||
- { name: "exception_line_number", type: "INT", comment: "异常行号" }
|
||||
- { name: "process_status", type: "TINYINT", comment: "处理状态" }
|
||||
- { name: "process_time", type: "DATETIME", comment: "处理时间" }
|
||||
- { name: "process_user_id", type: "BIGINT", comment: "处理用户编号" }
|
||||
|
||||
# ========== 系统配置相关 ==========
|
||||
- name: "infra_config"
|
||||
comment: "参数配置表"
|
||||
entity: "ConfigDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "参数主键" }
|
||||
- { name: "category", type: "VARCHAR", comment: "参数分类" }
|
||||
- { name: "name", type: "VARCHAR", comment: "参数名称" }
|
||||
- { name: "config_key", type: "VARCHAR", comment: "参数键名" }
|
||||
- { name: "value", type: "VARCHAR", comment: "参数键值" }
|
||||
- { name: "type", type: "TINYINT", comment: "参数类型" }
|
||||
- { name: "visible", type: "BIT", comment: "是否可见" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
|
||||
# ========== 数据源配置相关 ==========
|
||||
- name: "infra_data_source_config"
|
||||
comment: "数据源配置表"
|
||||
entity: "DataSourceConfigDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键编号,0 表示 Master 数据源" }
|
||||
- { name: "name", type: "VARCHAR", comment: "连接名" }
|
||||
- { name: "url", type: "VARCHAR", comment: "数据源连接" }
|
||||
- { name: "username", type: "VARCHAR", comment: "用户名" }
|
||||
- { name: "password", type: "VARCHAR", comment: "密码(加密存储)" }
|
||||
|
||||
# 表关系(ER关系)
|
||||
relationships:
|
||||
- from: "infra_file"
|
||||
to: "infra_file_config"
|
||||
type: "N:1"
|
||||
foreign_key: "config_id"
|
||||
- from: "infra_codegen_column"
|
||||
to: "infra_codegen_table"
|
||||
type: "N:1"
|
||||
foreign_key: "table_id"
|
||||
- from: "infra_job_log"
|
||||
to: "infra_job"
|
||||
type: "N:1"
|
||||
foreign_key: "job_id"
|
||||
- from: "infra_codegen_table"
|
||||
to: "infra_codegen_table"
|
||||
type: "N:1"
|
||||
foreign_key: "master_table_id"
|
||||
comment: "主子表关联"
|
||||
- from: "infra_codegen_table"
|
||||
to: "infra_data_source_config"
|
||||
type: "N:1"
|
||||
foreign_key: "data_source_config_id"
|
||||
|
||||
# ============================================
|
||||
# 第四阶段:代码使用设计
|
||||
# ============================================
|
||||
code_patterns:
|
||||
# Controller层规范
|
||||
controller:
|
||||
annotations:
|
||||
- "@Tag(name = '管理后台 - xxx') - Swagger 文档标签"
|
||||
- "@RestController - REST 控制器"
|
||||
- "@RequestMapping('/infra/xxx') - 请求路径前缀"
|
||||
- "@Validated - 参数校验"
|
||||
- "@PreAuthorize('@ss.hasPermission('infra:xxx:action')') - 权限控制"
|
||||
example: |
|
||||
@Tag(name = "管理后台 - 文件存储")
|
||||
@RestController
|
||||
@RequestMapping("/infra/file")
|
||||
@Validated
|
||||
public class FileController {
|
||||
@Resource
|
||||
private FileService fileService;
|
||||
|
||||
@PostMapping("/upload")
|
||||
@Operation(summary = "上传文件")
|
||||
public CommonResult<String> uploadFile(@Valid FileUploadReqVO uploadReqVO) throws Exception {
|
||||
MultipartFile file = uploadReqVO.getFile();
|
||||
byte[] content = IoUtil.readBytes(file.getInputStream());
|
||||
return success(fileService.createFile(content, file.getOriginalFilename(),
|
||||
uploadReqVO.getDirectory(), file.getContentType()));
|
||||
}
|
||||
}
|
||||
|
||||
# Service层规范
|
||||
service:
|
||||
interface_pattern: "XxxService 接口定义业务方法,XxxServiceImpl 实现类"
|
||||
impl_pattern: |
|
||||
@Service
|
||||
@Validated
|
||||
public class XxxServiceImpl implements XxxService {
|
||||
@Resource
|
||||
private XxxMapper xxxMapper;
|
||||
|
||||
// 使用 static final 定义常量
|
||||
// 使用 @Transactional 注解控制事务
|
||||
// 使用 validateXxxExists 方法校验存在性
|
||||
// 使用 exception(ErrorCode) 抛出业务异常
|
||||
}
|
||||
example: |
|
||||
@Service
|
||||
public class FileServiceImpl implements FileService {
|
||||
@Resource
|
||||
private FileConfigService fileConfigService;
|
||||
@Resource
|
||||
private FileMapper fileMapper;
|
||||
|
||||
@Override
|
||||
public String createFile(byte[] content, String name, String directory, String type) {
|
||||
// 1. 处理参数
|
||||
// 2. 生成上传路径
|
||||
String path = generateUploadPath(name, directory);
|
||||
// 3. 上传到文件存储器
|
||||
FileClient client = fileConfigService.getMasterFileClient();
|
||||
String url = client.upload(content, path, type);
|
||||
// 4. 保存到数据库
|
||||
fileMapper.insert(new FileDO()...);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
# 数据访问规范
|
||||
dal:
|
||||
mapper_pattern: |
|
||||
public interface XxxMapper extends BaseMapperX<XxxDO> {
|
||||
// 继承 BaseMapperX 获得通用 CRUD 方法
|
||||
// 自定义查询方法使用 @Select 注解或 XML
|
||||
// 分页查询返回 PageResult<XxxDO>
|
||||
}
|
||||
example: |
|
||||
public interface FileMapper extends BaseMapperX<FileDO> {
|
||||
default PageResult<FileDO> selectPage(FilePageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<FileDO>()
|
||||
.likeIfPresent(FileDO::getPath, reqVO.getPath())
|
||||
.likeIfPresent(FileDO::getName, reqVO.getName())
|
||||
.betweenIfPresent(FileDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(FileDO::getId));
|
||||
}
|
||||
}
|
||||
|
||||
# 异常处理
|
||||
error_handling:
|
||||
code_prefix: "1-001-xxx-xxx"
|
||||
examples:
|
||||
- code: "1_001_000_001"
|
||||
message: "参数配置不存在"
|
||||
- code: "1_001_001_000"
|
||||
message: "定时任务不存在"
|
||||
- code: "1_001_003_001"
|
||||
message: "文件不存在"
|
||||
- code: "1_001_004_002"
|
||||
message: "表定义已经存在"
|
||||
- code: "1_001_006_000"
|
||||
message: "文件配置不存在"
|
||||
|
||||
# 统一响应
|
||||
response:
|
||||
success_pattern: "CommonResult.success(data)"
|
||||
error_pattern: "throw exception(ErrorCode)"
|
||||
|
||||
# ============================================
|
||||
# 第五阶段:扩展指南
|
||||
# ============================================
|
||||
extension_guide:
|
||||
# 新增文件存储类型
|
||||
new_channel:
|
||||
title: "新增文件存储类型"
|
||||
steps:
|
||||
- step: "1. 创建配置类"
|
||||
description: "在 framework/file/core/client/ 下创建 XxxFileClientConfig 实现 FileClientConfig"
|
||||
code: |
|
||||
@Data
|
||||
public class XxxFileClientConfig implements FileClientConfig {
|
||||
private String endpoint;
|
||||
private String accessKey;
|
||||
private String secretKey;
|
||||
}
|
||||
- step: "2. 创建客户端类"
|
||||
description: "创建 XxxFileClient 继承 AbstractFileClient<XxxFileClientConfig>"
|
||||
code: |
|
||||
public class XxxFileClient extends AbstractFileClient<XxxFileClientConfig> {
|
||||
public XxxFileClient(Long id, XxxFileClientConfig config) {
|
||||
super(id, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
// 初始化客户端
|
||||
}
|
||||
|
||||
@Override
|
||||
public String upload(byte[] content, String path, String type) {
|
||||
// 实现上传逻辑
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String path) {
|
||||
// 实现删除逻辑
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContent(String path) {
|
||||
// 实现获取内容逻辑
|
||||
}
|
||||
}
|
||||
- step: "3. 注册枚举"
|
||||
description: "在 FileStorageEnum 中添加新存储类型"
|
||||
code: |
|
||||
XXX(30, XxxFileClientConfig.class, XxxFileClient.class);
|
||||
- step: "4. 测试验证"
|
||||
description: "编写单元测试验证功能正确性"
|
||||
|
||||
# 新增代码生成模板
|
||||
new_feature:
|
||||
title: "自定义代码生成模板"
|
||||
steps:
|
||||
- step: "1. 创建模板文件"
|
||||
description: "在 resources/codegen/ 下创建 .vm 模板文件"
|
||||
- step: "2. 注册模板路径"
|
||||
description: "在 CodegenEngine 的 SERVER_TEMPLATES 或 FRONT_TEMPLATES 中添加映射"
|
||||
code: |
|
||||
.put(javaTemplatePath("custom/template"), javaModuleImplMainFilePath("custom/${table.className}Custom"))
|
||||
- step: "3. 添加模板变量"
|
||||
description: "在 initBindingMap() 方法中添加模板需要的全局变量"
|
||||
- step: "4. 测试生成结果"
|
||||
description: "使用预览接口验证生成代码"
|
||||
|
||||
# 最佳实践
|
||||
best_practices:
|
||||
- practice: "文件上传路径规范"
|
||||
description: "使用 generateUploadPath() 方法生成唯一路径,包含日期前缀和时间戳后缀,避免文件名冲突"
|
||||
- practice: "文件配置缓存"
|
||||
description: "使用 Guava LoadingCache 缓存 FileClient,避免频繁查询数据库,支持异步刷新"
|
||||
- practice: "代码生成同步"
|
||||
description: "使用 syncCodegenFromDB() 同步数据库表结构变更,保持代码生成定义与数据库一致"
|
||||
- practice: "定时任务幂等"
|
||||
description: "JobHandler.execute() 应实现幂等性,避免重复执行产生副作用"
|
||||
- practice: "错误日志追踪"
|
||||
description: "通过 traceId 关联 API 访问日志和错误日志,便于问题排查"
|
||||
|
||||
# ============================================
|
||||
# 依赖关系
|
||||
# ============================================
|
||||
dependencies:
|
||||
# 内部依赖(其他模块)
|
||||
internal:
|
||||
- module: "yudao-framework-common"
|
||||
purpose: "通用工具类、CommonResult、PageResult 等"
|
||||
- module: "yudao-framework-mybatis"
|
||||
purpose: "MyBatis-Plus 封装、BaseMapperX、BaseDO"
|
||||
- module: "yudao-framework-quartz"
|
||||
purpose: "Quartz 封装、JobHandler、SchedulerManager"
|
||||
- module: "yudao-framework-tenant"
|
||||
purpose: "租户支持、@TenantIgnore"
|
||||
|
||||
# 外部依赖(第三方库)
|
||||
external:
|
||||
- name: "MyBatis-Plus"
|
||||
purpose: "ORM 框架,提供 CRUD 封装"
|
||||
- name: "Quartz"
|
||||
purpose: "定时任务调度框架"
|
||||
- name: "Velocity"
|
||||
purpose: "模板引擎,用于代码生成"
|
||||
- name: "Hutool"
|
||||
purpose: "Java 工具类库"
|
||||
- name: "Guava"
|
||||
purpose: "Google 工具库,使用 Cache、Table 等"
|
||||
- name: "MinIO/S3 SDK"
|
||||
purpose: "对象存储客户端"
|
||||
- name: "Swagger/OpenAPI"
|
||||
purpose: "API 文档注解"
|
||||
|
||||
# ============================================
|
||||
# 关键文件清单
|
||||
# ============================================
|
||||
key_files:
|
||||
- path: "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java"
|
||||
purpose: "文件服务实现,核心上传下载逻辑"
|
||||
- path: "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java"
|
||||
purpose: "文件配置服务,FileClient 缓存管理"
|
||||
- path: "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/FileClient.java"
|
||||
purpose: "文件客户端接口定义"
|
||||
- path: "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/AbstractFileClient.java"
|
||||
purpose: "文件客户端抽象类,模板方法模式"
|
||||
- path: "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/enums/FileStorageEnum.java"
|
||||
purpose: "文件存储类型枚举,配置类与客户端类映射"
|
||||
- path: "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImpl.java"
|
||||
purpose: "代码生成服务实现"
|
||||
- path: "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java"
|
||||
purpose: "代码生成引擎,模板渲染核心"
|
||||
- path: "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilder.java"
|
||||
purpose: "代码生成对象构建器"
|
||||
- path: "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/job/JobServiceImpl.java"
|
||||
purpose: "定时任务服务实现,Quartz 集成"
|
||||
- path: "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApi.java"
|
||||
purpose: "文件 API 接口,供其他模块调用"
|
||||
- path: "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java"
|
||||
purpose: "错误码常量定义"
|
||||
@@ -1,722 +0,0 @@
|
||||
# Skill 文档 - yudao-module-iot 模块
|
||||
# 物联网平台模块知识提取
|
||||
|
||||
skill:
|
||||
id: "skill-iot"
|
||||
name: "IoT Skill"
|
||||
version: "1.0.0"
|
||||
module_path: "yudao-module-iot"
|
||||
created_at: "2026-03-18"
|
||||
updated_at: "2026-03-18"
|
||||
|
||||
# ============================================
|
||||
# 第一阶段:设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
# 业务定位:物联网平台核心模块,提供设备接入、管理、数据采集、规则引擎、OTA升级等能力
|
||||
business_position: |
|
||||
IoT 物联网平台模块,作为整个系统的设备接入和管理中心:
|
||||
1. 设备管理:设备注册、认证、状态管理、分组管理
|
||||
2. 产品管理:产品定义、物模型配置、协议设置
|
||||
3. 协议适配:支持 MQTT、CoAP、HTTP、TCP、UDP、WebSocket、Modbus、EMQX 等多种协议
|
||||
4. 规则引擎:场景联动、数据流转、告警触发
|
||||
5. OTA升级:固件管理、升级任务、升级记录
|
||||
6. 数据处理:设备属性存储、消息记录、统计分析
|
||||
|
||||
# 设计原则:协议抽象、设备模型化、规则驱动
|
||||
design_principles:
|
||||
- "协议适配器模式:通过 IotProtocol 接口抽象不同传输协议,支持灵活扩展"
|
||||
- "产品-设备模型:产品定义设备模板,设备继承产品配置,支持一型一密动态注册"
|
||||
- "物模型驱动:通过 ThingModel 定义设备属性、事件、服务,实现标准化数据交互"
|
||||
- "规则引擎:基于触发器-条件-动作模型,实现场景联动自动化"
|
||||
- "多租户设计:设备、产品、规则等都支持租户隔离"
|
||||
- "分层架构:gateway 网关层处理协议,biz 业务层处理逻辑,core 核心层定义模型"
|
||||
|
||||
# 领域模型:核心领域对象及其关系
|
||||
domain_model:
|
||||
aggregates:
|
||||
- name: "设备聚合"
|
||||
type: "聚合根"
|
||||
entities: ["IotDeviceDO", "IotDeviceGroupDO", "IotDevicePropertyDO", "IotDeviceMessageDO"]
|
||||
description: "设备是 IoT 平台的核心实体,包含设备基本信息、状态、属性、消息等"
|
||||
|
||||
- name: "产品聚合"
|
||||
type: "聚合根"
|
||||
entities: ["IotProductDO", "IotProductCategoryDO", "IotThingModelDO"]
|
||||
description: "产品是设备的模板,定义物模型(属性、事件、服务)和协议配置"
|
||||
|
||||
- name: "规则引擎聚合"
|
||||
type: "聚合根"
|
||||
entities: ["IotSceneRuleDO", "IotDataRuleDO", "IotDataSinkDO", "IotAlertConfigDO", "IotAlertRecordDO"]
|
||||
description: "规则引擎处理设备数据流转、场景联动、告警触发"
|
||||
|
||||
- name: "OTA升级聚合"
|
||||
type: "聚合根"
|
||||
entities: ["IotOtaFirmwareDO", "IotOtaTaskDO", "IotOtaTaskRecordDO"]
|
||||
description: "OTA 升级管理固件版本、升级任务和升级记录"
|
||||
|
||||
value_objects:
|
||||
- name: "ThingModelProperty"
|
||||
description: "物模型属性定义,包含数据类型、单位、范围等"
|
||||
- name: "ThingModelEvent"
|
||||
description: "物模型事件定义,包含事件类型、参数等"
|
||||
- name: "ThingModelService"
|
||||
description: "物模型服务定义,包含输入输出参数"
|
||||
- name: "IotDeviceIdentity"
|
||||
description: "设备身份标识,包含 productKey、deviceName"
|
||||
|
||||
services:
|
||||
- name: "IotDeviceService"
|
||||
description: "设备管理服务,处理设备 CRUD、认证、状态更新"
|
||||
- name: "IotDeviceMessageService"
|
||||
description: "设备消息服务,处理消息收发、记录存储"
|
||||
- name: "IotSceneRuleService"
|
||||
description: "场景规则服务,执行场景联动逻辑"
|
||||
- name: "IotProtocol"
|
||||
description: "协议接口,定义协议生命周期(启动、停止、运行状态)"
|
||||
|
||||
# ============================================
|
||||
# 第二阶段:架构设计
|
||||
# ============================================
|
||||
architecture:
|
||||
# 分层架构
|
||||
layers:
|
||||
- name: "yudao-module-iot-gateway"
|
||||
purpose: "协议网关层,处理设备连接、协议解析"
|
||||
components:
|
||||
- "protocol/mqtt/IotMqttProtocol - MQTT 协议实现"
|
||||
- "protocol/coap/IotCoapProtocol - CoAP 协议实现"
|
||||
- "protocol/http/IotHttpProtocol - HTTP 协议实现"
|
||||
- "protocol/tcp/IotTcpProtocol - TCP 协议实现"
|
||||
- "protocol/udp/IotUdpProtocol - UDP 协议实现"
|
||||
- "protocol/websocket/IotWebSocketProtocol - WebSocket 协议实现"
|
||||
- "protocol/modbus/IotModbusTcpClientProtocol - Modbus TCP 客户端"
|
||||
- "protocol/modbus/IotModbusTcpServerProtocol - Modbus TCP 服务端"
|
||||
- "protocol/emqx/IotEmqxProtocol - EMQX 协议桥接"
|
||||
- "handler/upstream - 上行消息处理器"
|
||||
- "handler/downstream - 下行消息处理器"
|
||||
|
||||
- name: "yudao-module-iot-biz"
|
||||
purpose: "业务逻辑层,处理设备、产品、规则等核心业务"
|
||||
components:
|
||||
- "service/device - 设备管理服务"
|
||||
- "service/product - 产品管理服务"
|
||||
- "service/thingmodel - 物模型管理服务"
|
||||
- "service/rule - 规则引擎服务"
|
||||
- "service/ota - OTA 升级服务"
|
||||
- "service/alert - 告警服务"
|
||||
- "controller/admin - 管理后台接口"
|
||||
|
||||
- name: "yudao-module-iot-core"
|
||||
purpose: "核心层,定义公共模型、枚举、工具类"
|
||||
components:
|
||||
- "enums - 协议类型、设备状态、消息方法等枚举"
|
||||
- "mq/message - 设备消息模型"
|
||||
- "biz/dto - 业务 DTO"
|
||||
- "topic - Topic 定义和工具"
|
||||
|
||||
# 设计模式应用
|
||||
design_patterns:
|
||||
- pattern: "策略模式 / 适配器模式"
|
||||
location: "yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/IotProtocol.java"
|
||||
purpose: "定义协议接口,不同协议实现统一接口,支持灵活扩展新协议"
|
||||
|
||||
- pattern: "模板方法模式"
|
||||
location: "yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/AbstractIotProtocolDownstreamSubscriber.java"
|
||||
purpose: "抽象下行消息订阅者,子类实现具体的消息处理逻辑"
|
||||
|
||||
- pattern: "观察者模式"
|
||||
location: "yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/"
|
||||
purpose: "场景规则引擎,设备状态变化触发规则执行"
|
||||
|
||||
- pattern: "责任链模式"
|
||||
location: "yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/"
|
||||
purpose: "规则匹配器链,依次判断触发条件、执行条件是否满足"
|
||||
|
||||
- pattern: "工厂模式"
|
||||
location: "yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/serialize/IotMessageSerializerManager.java"
|
||||
purpose: "消息序列化器工厂,根据序列化类型获取对应的序列化器"
|
||||
|
||||
# 模块间通信
|
||||
communication:
|
||||
apis:
|
||||
- name: "IotDeviceCommonApi"
|
||||
path: "yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/IotDeviceCommonApi.java"
|
||||
description: "设备通用 API,提供设备认证、查询、注册等接口"
|
||||
methods:
|
||||
- "authDevice - 设备认证"
|
||||
- "getDevice - 获取设备信息"
|
||||
- "getModbusDeviceConfigList - 获取 Modbus 设备配置"
|
||||
- "registerDevice - 设备动态注册"
|
||||
- "registerSubDevices - 子设备动态注册"
|
||||
|
||||
consumers:
|
||||
- module: "yudao-module-system"
|
||||
api: "AdminUserApi"
|
||||
purpose: "获取告警接收用户信息"
|
||||
|
||||
mq:
|
||||
- type: "IotMessageBus"
|
||||
description: "内部消息总线,用于设备消息在 gateway 和 biz 之间传递"
|
||||
topics:
|
||||
- "device:message:upstream - 设备上行消息"
|
||||
- "device:message:downstream - 设备下行消息"
|
||||
|
||||
# ============================================
|
||||
# 第三阶段:数据表设计
|
||||
# ============================================
|
||||
data_model:
|
||||
# 实体继承体系
|
||||
entity_hierarchy:
|
||||
base: "TenantBaseDO | BaseDO"
|
||||
description: |
|
||||
- TenantBaseDO: 设备、产品、场景规则等需要租户隔离的实体
|
||||
- BaseDO: 设备分组、固件、告警记录等不需要租户隔离的实体
|
||||
|
||||
# 核心数据表
|
||||
tables:
|
||||
# 设备相关表
|
||||
- name: "iot_device"
|
||||
comment: "IoT 设备表"
|
||||
entity: "IotDeviceDO"
|
||||
extends: "TenantBaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "设备 ID,主键" }
|
||||
- { name: "device_name", type: "VARCHAR(64)", comment: "设备名称,产品内唯一" }
|
||||
- { name: "nickname", type: "VARCHAR(64)", comment: "设备备注名称" }
|
||||
- { name: "serial_number", type: "VARCHAR(64)", comment: "设备序列号" }
|
||||
- { name: "product_id", type: "BIGINT", comment: "产品编号" }
|
||||
- { name: "product_key", type: "VARCHAR(32)", comment: "产品标识(冗余)" }
|
||||
- { name: "device_type", type: "INT", comment: "设备类型(冗余)" }
|
||||
- { name: "gateway_id", type: "BIGINT", comment: "网关设备编号(子设备)" }
|
||||
- { name: "state", type: "INT", comment: "设备状态:0未激活、1在线、2离线" }
|
||||
- { name: "device_secret", type: "VARCHAR(64)", comment: "设备密钥" }
|
||||
- { name: "group_ids", type: "VARCHAR(255)", comment: "设备分组编号集合(JSON)" }
|
||||
- { name: "firmware_id", type: "BIGINT", comment: "固件编号" }
|
||||
- { name: "latitude", type: "DECIMAL(10,6)", comment: "纬度" }
|
||||
- { name: "longitude", type: "DECIMAL(10,6)", comment: "经度" }
|
||||
- { name: "online_time", type: "DATETIME", comment: "最后上线时间" }
|
||||
- { name: "offline_time", type: "DATETIME", comment: "最后离线时间" }
|
||||
- { name: "active_time", type: "DATETIME", comment: "激活时间" }
|
||||
indexes:
|
||||
- { name: "idx_product_id", columns: ["product_id"] }
|
||||
- { name: "idx_product_key_device_name", columns: ["product_key", "device_name"], unique: true }
|
||||
|
||||
- name: "iot_device_group"
|
||||
comment: "IoT 设备分组表"
|
||||
entity: "IotDeviceGroupDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "分组 ID" }
|
||||
- { name: "name", type: "VARCHAR(64)", comment: "分组名称" }
|
||||
- { name: "status", type: "INT", comment: "分组状态" }
|
||||
- { name: "description", type: "VARCHAR(255)", comment: "分组描述" }
|
||||
|
||||
- name: "iot_device_message"
|
||||
comment: "IoT 设备消息表"
|
||||
entity: "IotDeviceMessageDO"
|
||||
extends: "BaseDO"
|
||||
storage: "TDengine 时序数据库"
|
||||
columns:
|
||||
- { name: "device_id", type: "BIGINT", comment: "设备编号" }
|
||||
- { name: "product_key", type: "VARCHAR(32)", comment: "产品标识" }
|
||||
- { name: "device_name", type: "VARCHAR(64)", comment: "设备名称" }
|
||||
- { name: "method", type: "VARCHAR(64)", comment: "消息方法" }
|
||||
- { name: "request_id", type: "VARCHAR(64)", comment: "请求 ID" }
|
||||
- { name: "payload", type: "TEXT", comment: "消息内容" }
|
||||
|
||||
# 产品相关表
|
||||
- name: "iot_product"
|
||||
comment: "IoT 产品表"
|
||||
entity: "IotProductDO"
|
||||
extends: "TenantBaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "产品 ID" }
|
||||
- { name: "name", type: "VARCHAR(64)", comment: "产品名称" }
|
||||
- { name: "product_key", type: "VARCHAR(32)", comment: "产品标识" }
|
||||
- { name: "product_secret", type: "VARCHAR(64)", comment: "产品密钥" }
|
||||
- { name: "category_id", type: "BIGINT", comment: "产品分类编号" }
|
||||
- { name: "device_type", type: "INT", comment: "设备类型" }
|
||||
- { name: "net_type", type: "INT", comment: "联网方式" }
|
||||
- { name: "protocol_type", type: "VARCHAR(32)", comment: "协议类型" }
|
||||
- { name: "serialize_type", type: "VARCHAR(32)", comment: "序列化类型" }
|
||||
- { name: "status", type: "INT", comment: "产品状态" }
|
||||
- { name: "register_enabled", type: "BOOLEAN", comment: "是否开启动态注册" }
|
||||
|
||||
- name: "iot_product_category"
|
||||
comment: "IoT 产品分类表"
|
||||
entity: "IotProductCategoryDO"
|
||||
extends: "TenantBaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "分类 ID" }
|
||||
- { name: "name", type: "VARCHAR(64)", comment: "分类名称" }
|
||||
- { name: "parent_id", type: "BIGINT", comment: "父分类 ID" }
|
||||
- { name: "sort", type: "INT", comment: "排序" }
|
||||
|
||||
- name: "iot_thing_model"
|
||||
comment: "IoT 产品物模型功能表"
|
||||
entity: "IotThingModelDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "物模型功能 ID" }
|
||||
- { name: "product_id", type: "BIGINT", comment: "产品编号" }
|
||||
- { name: "product_key", type: "VARCHAR(32)", comment: "产品标识" }
|
||||
- { name: "identifier", type: "VARCHAR(64)", comment: "功能标识符" }
|
||||
- { name: "name", type: "VARCHAR(64)", comment: "功能名称" }
|
||||
- { name: "type", type: "INT", comment: "功能类型:1属性、2事件、3服务" }
|
||||
- { name: "property", type: "JSON", comment: "属性定义" }
|
||||
- { name: "event", type: "JSON", comment: "事件定义" }
|
||||
- { name: "service", type: "JSON", comment: "服务定义" }
|
||||
|
||||
# 规则引擎相关表
|
||||
- name: "iot_scene_rule"
|
||||
comment: "IoT 场景联动规则表"
|
||||
entity: "IotSceneRuleDO"
|
||||
extends: "TenantBaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "场景联动 ID" }
|
||||
- { name: "name", type: "VARCHAR(64)", comment: "场景名称" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
- { name: "triggers", type: "JSON", comment: "触发器配置" }
|
||||
- { name: "actions", type: "JSON", comment: "动作配置" }
|
||||
- { name: "last_trigger_time", type: "DATETIME", comment: "最后触发时间" }
|
||||
|
||||
- name: "iot_data_sink"
|
||||
comment: "IoT 数据流转目的地表"
|
||||
entity: "IotDataSinkDO"
|
||||
extends: "TenantBaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "数据目的地 ID" }
|
||||
- { name: "name", type: "VARCHAR(64)", comment: "名称" }
|
||||
- { name: "type", type: "VARCHAR(32)", comment: "类型:HTTP/MQTT/Kafka/RabbitMQ/Redis/RocketMQ/TCP/WebSocket" }
|
||||
- { name: "config", type: "JSON", comment: "配置信息" }
|
||||
|
||||
- name: "iot_alert_config"
|
||||
comment: "IoT 告警配置表"
|
||||
entity: "IotAlertConfigDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "告警配置 ID" }
|
||||
- { name: "name", type: "VARCHAR(64)", comment: "告警名称" }
|
||||
- { name: "level", type: "INT", comment: "告警级别" }
|
||||
- { name: "status", type: "INT", comment: "状态" }
|
||||
- { name: "scene_rule_ids", type: "VARCHAR(255)", comment: "关联的场景规则 ID 列表" }
|
||||
- { name: "receive_user_ids", type: "VARCHAR(255)", comment: "接收用户 ID 列表" }
|
||||
|
||||
# OTA 升级相关表
|
||||
- name: "iot_ota_firmware"
|
||||
comment: "IoT OTA 固件表"
|
||||
entity: "IotOtaFirmwareDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "固件 ID" }
|
||||
- { name: "name", type: "VARCHAR(64)", comment: "固件名称" }
|
||||
- { name: "version", type: "VARCHAR(32)", comment: "版本号" }
|
||||
- { name: "product_id", type: "BIGINT", comment: "产品编号" }
|
||||
- { name: "file_url", type: "VARCHAR(512)", comment: "固件文件 URL" }
|
||||
- { name: "file_size", type: "BIGINT", comment: "文件大小" }
|
||||
- { name: "file_digest_algorithm", type: "VARCHAR(32)", comment: "签名算法" }
|
||||
- { name: "file_digest_value", type: "VARCHAR(128)", comment: "签名值" }
|
||||
|
||||
- name: "iot_ota_task"
|
||||
comment: "IoT OTA 升级任务表"
|
||||
entity: "IotOtaTaskDO"
|
||||
extends: "TenantBaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "任务 ID" }
|
||||
- { name: "name", type: "VARCHAR(64)", comment: "任务名称" }
|
||||
- { name: "firmware_id", type: "BIGINT", comment: "固件编号" }
|
||||
- { name: "product_id", type: "BIGINT", comment: "产品编号" }
|
||||
- { name: "status", type: "INT", comment: "任务状态" }
|
||||
|
||||
- name: "iot_ota_task_record"
|
||||
comment: "IoT OTA 升级任务记录表"
|
||||
entity: "IotOtaTaskRecordDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "记录 ID" }
|
||||
- { name: "task_id", type: "BIGINT", comment: "任务 ID" }
|
||||
- { name: "device_id", type: "BIGINT", comment: "设备 ID" }
|
||||
- { name: "status", type: "INT", comment: "升级状态" }
|
||||
|
||||
# 表关系(ER关系)
|
||||
relationships:
|
||||
- from: "iot_device"
|
||||
to: "iot_product"
|
||||
type: "N:1"
|
||||
foreign_key: "product_id"
|
||||
description: "设备属于某个产品"
|
||||
|
||||
- from: "iot_device"
|
||||
to: "iot_device"
|
||||
type: "N:1"
|
||||
foreign_key: "gateway_id"
|
||||
description: "子设备关联网关设备"
|
||||
|
||||
- from: "iot_product"
|
||||
to: "iot_product_category"
|
||||
type: "N:1"
|
||||
foreign_key: "category_id"
|
||||
description: "产品属于某个分类"
|
||||
|
||||
- from: "iot_thing_model"
|
||||
to: "iot_product"
|
||||
type: "N:1"
|
||||
foreign_key: "product_id"
|
||||
description: "物模型属于某个产品"
|
||||
|
||||
- from: "iot_ota_firmware"
|
||||
to: "iot_product"
|
||||
type: "N:1"
|
||||
foreign_key: "product_id"
|
||||
description: "固件属于某个产品"
|
||||
|
||||
- from: "iot_ota_task"
|
||||
to: "iot_ota_firmware"
|
||||
type: "N:1"
|
||||
foreign_key: "firmware_id"
|
||||
description: "升级任务使用某个固件"
|
||||
|
||||
# ============================================
|
||||
# 第四阶段:代码使用设计
|
||||
# ============================================
|
||||
code_patterns:
|
||||
# Controller层规范
|
||||
controller:
|
||||
annotations:
|
||||
- "@Tag(name = \"管理后台 - IoT 设备\")"
|
||||
- "@RestController"
|
||||
- "@RequestMapping(\"/iot/device\")"
|
||||
- "@PreAuthorize(\"@ss.hasPermission('iot:device:create')\")"
|
||||
example: |
|
||||
@Tag(name = "管理后台 - IoT 设备")
|
||||
@RestController
|
||||
@RequestMapping("/iot/device")
|
||||
@Validated
|
||||
public class IotDeviceController {
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建设备")
|
||||
@PreAuthorize("@ss.hasPermission('iot:device:create')")
|
||||
public CommonResult<Long> createDevice(@Valid @RequestBody IotDeviceSaveReqVO createReqVO) {
|
||||
return success(deviceService.createDevice(createReqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得设备分页")
|
||||
@PreAuthorize("@ss.hasPermission('iot:device:query')")
|
||||
public CommonResult<PageResult<IotDeviceRespVO>> getDevicePage(@Valid IotDevicePageReqVO pageReqVO) {
|
||||
PageResult<IotDeviceDO> pageResult = deviceService.getDevicePage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, IotDeviceRespVO.class));
|
||||
}
|
||||
}
|
||||
|
||||
# Service层规范
|
||||
service:
|
||||
interface_pattern: |
|
||||
public interface IotDeviceService {
|
||||
// CRUD 操作
|
||||
Long createDevice(@Valid IotDeviceSaveReqVO createReqVO);
|
||||
void updateDevice(@Valid IotDeviceSaveReqVO updateReqVO);
|
||||
void deleteDevice(Long id);
|
||||
IotDeviceDO getDevice(Long id);
|
||||
PageResult<IotDeviceDO> getDevicePage(IotDevicePageReqVO pageReqVO);
|
||||
|
||||
// 业务操作
|
||||
boolean authDevice(@Valid IotDeviceAuthReqDTO authReqDTO);
|
||||
void updateDeviceState(Long id, Integer state);
|
||||
IotDeviceRegisterRespDTO registerDevice(@Valid IotDeviceRegisterReqDTO reqDTO);
|
||||
|
||||
// 缓存操作
|
||||
IotDeviceDO getDeviceFromCache(Long id);
|
||||
IotDeviceDO getDeviceFromCache(String productKey, String deviceName);
|
||||
}
|
||||
impl_pattern: |
|
||||
@Service
|
||||
@Validated
|
||||
public class IotDeviceServiceImpl implements IotDeviceService {
|
||||
@Resource
|
||||
private IotDeviceMapper deviceMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createDevice(IotDeviceSaveReqVO createReqVO) {
|
||||
// 1. 校验产品存在
|
||||
IotProductDO product = productService.validateProductExists(createReqVO.getProductId());
|
||||
// 2. 生成设备密钥
|
||||
String deviceSecret = generateDeviceSecret();
|
||||
// 3. 插入设备
|
||||
IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class)
|
||||
.setProductKey(product.getProductKey())
|
||||
.setDeviceType(product.getDeviceType())
|
||||
.setDeviceSecret(deviceSecret)
|
||||
.setState(IotDeviceStateEnum.INACTIVE.getState());
|
||||
deviceMapper.insert(device);
|
||||
return device.getId();
|
||||
}
|
||||
}
|
||||
example: |
|
||||
// 设备认证示例
|
||||
@Override
|
||||
public boolean authDevice(IotDeviceAuthReqDTO authReqDTO) {
|
||||
// 1. 获取设备信息
|
||||
IotDeviceDO device = getDeviceFromCache(authReqDTO.getProductKey(), authReqDTO.getDeviceName());
|
||||
if (device == null) {
|
||||
return false;
|
||||
}
|
||||
// 2. 校验设备密钥
|
||||
if (!StrUtil.equals(device.getDeviceSecret(), authReqDTO.getDeviceSecret())) {
|
||||
return false;
|
||||
}
|
||||
// 3. 更新设备状态为在线
|
||||
updateDeviceState(device, IotDeviceStateEnum.ONLINE.getState());
|
||||
return true;
|
||||
}
|
||||
|
||||
# 数据访问规范
|
||||
dal:
|
||||
mapper_pattern: |
|
||||
@Mapper
|
||||
public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> {
|
||||
default PageResult<IotDeviceDO> selectPage(IotDevicePageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<IotDeviceDO>()
|
||||
.likeIfPresent(IotDeviceDO::getDeviceName, reqVO.getDeviceName())
|
||||
.eqIfPresent(IotDeviceDO::getProductId, reqVO.getProductId())
|
||||
.eqIfPresent(IotDeviceDO::getState, reqVO.getState())
|
||||
.orderByDesc(IotDeviceDO::getId));
|
||||
}
|
||||
}
|
||||
example: |
|
||||
// 设备属性存储在 Redis 中
|
||||
@Resource
|
||||
private DevicePropertyRedisDAO devicePropertyRedisDAO;
|
||||
|
||||
public void updateDeviceProperty(String productKey, String deviceName, String identifier, Object value) {
|
||||
String key = formatDevicePropertyKey(productKey, deviceName, identifier);
|
||||
IotDevicePropertyDO property = IotDevicePropertyDO.builder()
|
||||
.value(value)
|
||||
.updateTime(LocalDateTime.now())
|
||||
.build();
|
||||
devicePropertyRedisDAO.set(key, property);
|
||||
}
|
||||
|
||||
# 协议扩展示例
|
||||
protocol:
|
||||
interface_definition: |
|
||||
public interface IotProtocol {
|
||||
String getId(); // 协议实例 ID
|
||||
String getServerId(); // 服务器 ID(消息追踪)
|
||||
IotProtocolTypeEnum getType(); // 协议类型
|
||||
void start(); // 启动协议服务
|
||||
void stop(); // 停止协议服务
|
||||
boolean isRunning(); // 检查运行状态
|
||||
}
|
||||
example: |
|
||||
// MQTT 协议实现示例
|
||||
public class IotMqttProtocol implements IotProtocol {
|
||||
private MqttServer mqttServer;
|
||||
private IotMqttConnectionManager connectionManager;
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
// 1. 创建 MQTT 服务器
|
||||
mqttServer = MqttServer.create(vertx, options);
|
||||
mqttServer.endpointHandler(this::handleEndpoint);
|
||||
|
||||
// 2. 启动服务器
|
||||
mqttServer.listen();
|
||||
running = true;
|
||||
|
||||
// 3. 启动下行消息订阅者
|
||||
downstreamSubscriber.start();
|
||||
}
|
||||
|
||||
private void handleEndpoint(MqttEndpoint endpoint) {
|
||||
// 1. 处理认证
|
||||
if (!authHandler.handleAuthenticationRequest(endpoint)) {
|
||||
endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD);
|
||||
return;
|
||||
}
|
||||
// 2. 设置消息处理器
|
||||
endpoint.publishHandler(message -> processMessage(endpoint, message));
|
||||
// 3. 接受连接
|
||||
endpoint.accept(false);
|
||||
}
|
||||
}
|
||||
|
||||
# 异常处理
|
||||
error_handling:
|
||||
code_prefix: "1-011"
|
||||
examples:
|
||||
- code: "1-011-000-000"
|
||||
message: "设备不存在"
|
||||
- code: "1-011-000-001"
|
||||
message: "设备认证失败"
|
||||
- code: "1-011-000-002"
|
||||
message: "设备已存在"
|
||||
- code: "1-011-001-000"
|
||||
message: "产品不存在"
|
||||
- code: "1-011-002-000"
|
||||
message: "物模型不存在"
|
||||
|
||||
# 统一响应
|
||||
response:
|
||||
success_pattern: "CommonResult.success(data)"
|
||||
error_pattern: "CommonResult.error(ErrorCodeConstants.DEVICE_NOT_EXISTS)"
|
||||
|
||||
# ============================================
|
||||
# 第五阶段:扩展指南
|
||||
# ============================================
|
||||
extension_guide:
|
||||
# 新增协议类型
|
||||
new_protocol:
|
||||
title: "新增协议类型"
|
||||
steps:
|
||||
- step: 1
|
||||
action: "添加协议类型枚举"
|
||||
file: "yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotProtocolTypeEnum.java"
|
||||
description: "在枚举中添加新的协议类型,如 GOPHER(\"gopher\")"
|
||||
|
||||
- step: 2
|
||||
action: "创建协议配置类"
|
||||
file: "yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/{protocol}/Iot{Protocol}Config.java"
|
||||
description: "定义协议特有的配置参数,如端口、超时时间、SSL 配置等"
|
||||
|
||||
- step: 3
|
||||
action: "实现协议接口"
|
||||
file: "yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/{protocol}/Iot{Protocol}Protocol.java"
|
||||
description: |
|
||||
实现 IotProtocol 接口:
|
||||
- start(): 启动协议服务,创建服务器/客户端
|
||||
- stop(): 停止协议服务,释放资源
|
||||
- handleUpstream(): 处理上行消息
|
||||
- handleDownstream(): 处理下行消息
|
||||
|
||||
- step: 4
|
||||
action: "实现上行消息处理器"
|
||||
file: "yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/{protocol}/handler/upstream/Iot{Protocol}UpstreamHandler.java"
|
||||
description: "处理设备上报的消息,解析后发送到消息总线"
|
||||
|
||||
- step: 5
|
||||
action: "实现下行消息订阅者"
|
||||
file: "yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/{protocol}/handler/downstream/Iot{Protocol}DownstreamSubscriber.java"
|
||||
description: "订阅下行消息,下发给设备"
|
||||
|
||||
- step: 6
|
||||
action: "注册协议到管理器"
|
||||
file: "yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/IotProtocolManager.java"
|
||||
description: "在配置中添加新协议的实例化逻辑"
|
||||
|
||||
# 新增设备类型
|
||||
new_device_type:
|
||||
title: "新增设备类型"
|
||||
steps:
|
||||
- step: 1
|
||||
action: "添加设备类型枚举"
|
||||
file: "yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java"
|
||||
description: "在枚举中添加新的设备类型"
|
||||
|
||||
- step: 2
|
||||
action: "定义物模型"
|
||||
file: "通过管理后台配置"
|
||||
description: "为新产品定义物模型(属性、事件、服务)"
|
||||
|
||||
- step: 3
|
||||
action: "配置协议"
|
||||
file: "产品管理页面"
|
||||
description: "设置产品使用的协议类型和序列化方式"
|
||||
|
||||
# 新增规则动作
|
||||
new_rule_action:
|
||||
title: "新增场景联动动作类型"
|
||||
steps:
|
||||
- step: 1
|
||||
action: "添加动作类型枚举"
|
||||
file: "yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleActionTypeEnum.java"
|
||||
description: "添加新的动作类型枚举值"
|
||||
|
||||
- step: 2
|
||||
action: "实现动作执行器"
|
||||
file: "yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/Iot{Action}SceneRuleAction.java"
|
||||
description: "实现 IotSceneRuleAction 接口,定义动作执行逻辑"
|
||||
|
||||
- step: 3
|
||||
action: "注册动作执行器"
|
||||
description: "在 Spring 容器中注册为 Bean,自动被规则引擎发现和调用"
|
||||
|
||||
# 最佳实践
|
||||
best_practices:
|
||||
- "设备密钥安全性:设备密钥应使用强随机数生成,避免硬编码或简单规则"
|
||||
- "协议扩展:新协议应继承 AbstractIotProtocolDownstreamSubscriber,复用下行消息处理逻辑"
|
||||
- "消息存储:设备消息使用 TDengine 时序数据库存储,支持高效查询和聚合"
|
||||
- "设备缓存:频繁访问的设备信息使用 Redis 缓存,减少数据库压力"
|
||||
- "规则引擎:场景规则应设置合理的触发条件,避免频繁触发导致性能问题"
|
||||
- "OTA升级:大规模升级应分批进行,避免网络拥塞和服务器压力"
|
||||
- "租户隔离:所有设备操作都应考虑租户隔离,防止越权访问"
|
||||
|
||||
# ============================================
|
||||
# 依赖关系
|
||||
# ============================================
|
||||
dependencies:
|
||||
# 内部依赖(其他模块)
|
||||
internal:
|
||||
- module: "yudao-module-system"
|
||||
api: "AdminUserApi"
|
||||
purpose: "获取告警接收用户信息"
|
||||
|
||||
# 外部依赖(第三方库)
|
||||
external:
|
||||
- name: "vertx-mqtt"
|
||||
version: "4.x"
|
||||
purpose: "MQTT 服务器实现"
|
||||
- name: "californium"
|
||||
version: "3.x"
|
||||
purpose: "CoAP 服务器实现"
|
||||
- name: "jmodbus"
|
||||
version: "1.x"
|
||||
purpose: "Modbus 协议实现"
|
||||
- name: "netty"
|
||||
version: "4.x"
|
||||
purpose: "TCP/UDP/WebSocket 网络框架"
|
||||
- name: "tdengine-jdbc"
|
||||
version: "3.x"
|
||||
purpose: "TDengine 时序数据库驱动"
|
||||
|
||||
# ============================================
|
||||
# 关键文件清单
|
||||
# ============================================
|
||||
key_files:
|
||||
- path: "yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotProtocolTypeEnum.java"
|
||||
purpose: "协议类型枚举定义"
|
||||
|
||||
- path: "yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/IotProtocol.java"
|
||||
purpose: "协议接口定义,所有协议必须实现此接口"
|
||||
|
||||
- path: "yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttProtocol.java"
|
||||
purpose: "MQTT 协议实现,最常用的设备接入协议"
|
||||
|
||||
- path: "yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java"
|
||||
purpose: "设备管理服务接口"
|
||||
|
||||
- path: "yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageService.java"
|
||||
purpose: "设备消息服务接口"
|
||||
|
||||
- path: "yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleService.java"
|
||||
purpose: "场景联动规则服务"
|
||||
|
||||
- path: "yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java"
|
||||
purpose: "设备实体类定义"
|
||||
|
||||
- path: "yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductDO.java"
|
||||
purpose: "产品实体类定义"
|
||||
|
||||
- path: "yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/IotThingModelDO.java"
|
||||
purpose: "物模型实体类定义"
|
||||
|
||||
- path: "yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java"
|
||||
purpose: "设备管理 Controller"
|
||||
|
||||
- path: "yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/IotDeviceCommonApi.java"
|
||||
purpose: "设备通用 API 接口"
|
||||
@@ -1,654 +0,0 @@
|
||||
# Skill 模板文件
|
||||
# 用于提取模块知识的标准格式
|
||||
|
||||
skill:
|
||||
id: "skill-mall"
|
||||
name: "Mall Skill"
|
||||
version: "1.0.0"
|
||||
module_path: "yudao-module-mall"
|
||||
created_at: "2026-03-18"
|
||||
updated_at: "2026-03-18"
|
||||
|
||||
# ============================================
|
||||
# 第一阶段:设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
# 业务定位:模块解决什么业务问题?在整个系统中的定位?
|
||||
business_position: |
|
||||
商城系统(Mall)是 ruoyi-vue-pro 项目的电商核心模块,提供完整的电商解决方案。
|
||||
包含商品管理、促销活动、交易订单、数据统计四大核心能力:
|
||||
- 商品管理:SPU/SKU 商品模型、商品分类、品牌、属性规格管理
|
||||
- 促销活动:优惠券、秒杀、拼团、砍价、满减送、积分商城等
|
||||
- 交易订单:购物车、订单创建/支付/发货/收货、售后退款
|
||||
- 数据统计:交易统计、商品统计、会员统计等
|
||||
|
||||
# 设计原则:遵循了哪些设计原则(SOLID、DDD)?有哪些架构决策?
|
||||
design_principles:
|
||||
- "SPU-SKU 模型:标准电商商品模型,SPU 为商品标准化单元,SKU 为库存单位"
|
||||
- "订单状态机:订单状态流转严格遵循状态机模式,确保状态变更可追溯"
|
||||
- "领域驱动设计:按业务领域划分子模块(product/promotion/trade/statistics)"
|
||||
- "分层架构:Controller -> Service -> DAL 三层架构,职责清晰"
|
||||
- "聚合根设计:订单(TradeOrder)和商品(ProductSpu)作为聚合根"
|
||||
- "API 模块分离:trade-api 独立模块,提供跨模块调用接口"
|
||||
|
||||
# 领域模型:核心领域对象有哪些?领域对象之间的关系?聚合根是什么?
|
||||
domain_model:
|
||||
aggregates:
|
||||
- name: "ProductSpu(商品SPU)"
|
||||
type: "聚合根"
|
||||
entities: ["ProductSku(商品SKU)", "ProductCategory(分类)", "ProductBrand(品牌)", "ProductProperty(属性)"]
|
||||
description: "商品SPU作为聚合根,管理SKU、分类、品牌、属性等关联实体"
|
||||
- name: "TradeOrder(交易订单)"
|
||||
type: "聚合根"
|
||||
entities: ["TradeOrderItem(订单项)", "AfterSale(售后单)", "Cart(购物车)"]
|
||||
description: "订单作为交易核心聚合根,管理订单项、售后、购物车等"
|
||||
- name: "CouponTemplate(优惠券模板)"
|
||||
type: "聚合根"
|
||||
entities: ["Coupon(优惠券)"]
|
||||
description: "优惠券模板管理优惠券的发放和使用"
|
||||
- name: "CombinationActivity(拼团活动)"
|
||||
type: "聚合根"
|
||||
entities: ["CombinationProduct(拼团商品)", "CombinationRecord(拼团记录)"]
|
||||
description: "拼团活动管理拼团商品和拼团参与记录"
|
||||
|
||||
value_objects:
|
||||
- "ProductSkuDO.Property(SKU属性值对象)"
|
||||
- "TradeOrderDO.Address(收货地址值对象)"
|
||||
- "DeliveryExpressTemplateCharge(运费模板计费规则)"
|
||||
|
||||
services:
|
||||
- "ProductSpuService:商品SPU业务逻辑"
|
||||
- "TradeOrderUpdateService:订单创建和状态更新"
|
||||
- "TradePriceService:价格计算服务"
|
||||
- "AfterSaleService:售后服务"
|
||||
|
||||
# ============================================
|
||||
# 第二阶段:架构设计
|
||||
# ============================================
|
||||
architecture:
|
||||
# 分层架构
|
||||
layers:
|
||||
- name: "api"
|
||||
purpose: "模块间API接口,提供RPC调用能力"
|
||||
components:
|
||||
- "ProductSpuApi/ProductSkuApi:商品信息查询、库存更新"
|
||||
- "TradeOrderApi:订单信息查询"
|
||||
- "CouponApi:优惠券使用"
|
||||
- "SeckillActivityApi/CombinationRecordApi:营销活动验证"
|
||||
|
||||
- name: "controller"
|
||||
purpose: "HTTP接口,分为管理后台(admin)和用户端(app)"
|
||||
components:
|
||||
- "admin/spu/ProductSpuController:商品SPU管理"
|
||||
- "admin/order/TradeOrderController:订单管理"
|
||||
- "app/spu/AppProductSpuController:商品展示"
|
||||
- "app/order/AppTradeOrderController:用户下单"
|
||||
|
||||
- name: "service"
|
||||
purpose: "业务逻辑层,核心业务处理"
|
||||
components:
|
||||
- "ProductSpuService/ProductSkuService:商品业务"
|
||||
- "TradeOrderUpdateService/TradeOrderQueryService:订单业务"
|
||||
- "AfterSaleService:售后业务"
|
||||
- "CartService:购物车业务"
|
||||
- "CouponService:优惠券业务"
|
||||
|
||||
- name: "dal"
|
||||
purpose: "数据访问层,MyBatis-Plus Mapper"
|
||||
components:
|
||||
- "ProductSpuMapper/ProductSkuMapper:商品数据访问"
|
||||
- "TradeOrderMapper/TradeOrderItemMapper:订单数据访问"
|
||||
- "CouponMapper/CouponTemplateMapper:优惠券数据访问"
|
||||
|
||||
# 设计模式应用
|
||||
design_patterns:
|
||||
- pattern: "状态模式"
|
||||
location: "TradeOrderStatusEnum, ProductSpuStatusEnum"
|
||||
purpose: "订单状态和商品状态的状态机管理,通过枚举定义状态流转"
|
||||
- pattern: "策略模式"
|
||||
location: "TradePriceService"
|
||||
purpose: "价格计算策略,支持不同促销活动的价格计算"
|
||||
- pattern: "模板方法模式"
|
||||
location: "BaseDO, TenantBaseDO"
|
||||
purpose: "数据实体基类,统一处理创建时间、更新时间、删除标记等"
|
||||
- pattern: "工厂模式"
|
||||
location: "ProductSpuConvert, TradeOrderConvert"
|
||||
purpose: "MapStruct转换器,VO与DO之间的对象转换"
|
||||
- pattern: "观察者模式"
|
||||
location: "TradeOrderLogService"
|
||||
purpose: "订单操作日志记录,订单状态变更时自动记录日志"
|
||||
|
||||
# 模块间通信
|
||||
communication:
|
||||
apis:
|
||||
- name: "ProductSpuApi"
|
||||
module: "yudao-module-product"
|
||||
purpose: "查询商品信息、更新库存"
|
||||
- name: "ProductSkuApi"
|
||||
module: "yudao-module-product"
|
||||
purpose: "查询SKU信息、更新库存"
|
||||
- name: "CouponApi"
|
||||
module: "yudao-module-promotion"
|
||||
purpose: "优惠券使用、回收"
|
||||
- name: "SeckillActivityApi"
|
||||
module: "yudao-module-promotion"
|
||||
purpose: "秒杀活动校验、库存扣减"
|
||||
|
||||
consumers:
|
||||
- module: "yudao-module-trade"
|
||||
api: "ProductSpuApi"
|
||||
purpose: "下单时查询商品信息、扣减库存"
|
||||
- module: "yudao-module-trade"
|
||||
api: "CouponApi"
|
||||
purpose: "下单时使用优惠券"
|
||||
- module: "yudao-module-statistics"
|
||||
api: "TradeOrderApi"
|
||||
purpose: "统计交易数据"
|
||||
|
||||
mq:
|
||||
- topic: "order.paid"
|
||||
purpose: "订单支付成功消息,触发积分发放、优惠券赠送等"
|
||||
- topic: "order.delivered"
|
||||
purpose: "订单发货消息,通知用户"
|
||||
|
||||
# ============================================
|
||||
# 第三阶段:数据表设计
|
||||
# ============================================
|
||||
data_model:
|
||||
# 实体继承体系
|
||||
entity_hierarchy:
|
||||
base: "BaseDO"
|
||||
description: "所有DO实体继承BaseDO,包含id、creator、createTime、updater、updateTime、deleted字段"
|
||||
|
||||
# 核心数据表
|
||||
tables:
|
||||
# ========== 商品模块 ==========
|
||||
- name: "product_spu"
|
||||
comment: "商品SPU表"
|
||||
entity: "ProductSpuDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "商品SPU编号" }
|
||||
- { name: "name", type: "String", comment: "商品名称" }
|
||||
- { name: "keyword", type: "String", comment: "关键字" }
|
||||
- { name: "introduction", type: "String", comment: "商品简介" }
|
||||
- { name: "description", type: "String", comment: "商品详情" }
|
||||
- { name: "category_id", type: "Long", comment: "分类编号" }
|
||||
- { name: "brand_id", type: "Long", comment: "品牌编号" }
|
||||
- { name: "pic_url", type: "String", comment: "商品封面图" }
|
||||
- { name: "slider_pic_urls", type: "List<String>", comment: "轮播图" }
|
||||
- { name: "status", type: "Integer", comment: "商品状态:-1回收站 0下架 1上架" }
|
||||
- { name: "spec_type", type: "Boolean", comment: "规格类型:false单规格 true多规格" }
|
||||
- { name: "price", type: "Integer", comment: "最低价格(分)" }
|
||||
- { name: "market_price", type: "Integer", comment: "市场价(分)" }
|
||||
- { name: "cost_price", type: "Integer", comment: "成本价(分)" }
|
||||
- { name: "stock", type: "Integer", comment: "库存总量" }
|
||||
- { name: "sales_count", type: "Integer", comment: "销量" }
|
||||
- { name: "delivery_types", type: "List<Integer>", comment: "配送方式" }
|
||||
- { name: "delivery_template_id", type: "Long", comment: "运费模板编号" }
|
||||
|
||||
- name: "product_sku"
|
||||
comment: "商品SKU表"
|
||||
entity: "ProductSkuDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "SKU编号" }
|
||||
- { name: "spu_id", type: "Long", comment: "SPU编号" }
|
||||
- { name: "properties", type: "List<Property>", comment: "属性数组JSON" }
|
||||
- { name: "price", type: "Integer", comment: "商品价格(分)" }
|
||||
- { name: "market_price", type: "Integer", comment: "市场价(分)" }
|
||||
- { name: "cost_price", type: "Integer", comment: "成本价(分)" }
|
||||
- { name: "bar_code", type: "String", comment: "商品条码" }
|
||||
- { name: "pic_url", type: "String", comment: "图片地址" }
|
||||
- { name: "stock", type: "Integer", comment: "库存" }
|
||||
- { name: "weight", type: "Double", comment: "重量(kg)" }
|
||||
- { name: "volume", type: "Double", comment: "体积(m^3)" }
|
||||
- { name: "first_brokerage_price", type: "Integer", comment: "一级分销佣金" }
|
||||
- { name: "second_brokerage_price", type: "Integer", comment: "二级分销佣金" }
|
||||
|
||||
- name: "product_category"
|
||||
comment: "商品分类表"
|
||||
entity: "ProductCategoryDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "分类编号" }
|
||||
- { name: "parent_id", type: "Long", comment: "父分类编号" }
|
||||
- { name: "name", type: "String", comment: "分类名称" }
|
||||
- { name: "pic_url", type: "String", comment: "分类图片" }
|
||||
- { name: "sort", type: "Integer", comment: "排序" }
|
||||
- { name: "status", type: "Integer", comment: "状态:0启用 1禁用" }
|
||||
|
||||
- name: "product_brand"
|
||||
comment: "商品品牌表"
|
||||
entity: "ProductBrandDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "品牌编号" }
|
||||
- { name: "name", type: "String", comment: "品牌名称" }
|
||||
- { name: "pic_url", type: "String", comment: "品牌图片" }
|
||||
- { name: "sort", type: "Integer", comment: "排序" }
|
||||
- { name: "status", type: "Integer", comment: "状态" }
|
||||
|
||||
# ========== 交易模块 ==========
|
||||
- name: "trade_order"
|
||||
comment: "交易订单表"
|
||||
entity: "TradeOrderDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "订单编号" }
|
||||
- { name: "no", type: "String", comment: "订单流水号" }
|
||||
- { name: "type", type: "Integer", comment: "订单类型" }
|
||||
- { name: "terminal", type: "Integer", comment: "订单来源终端" }
|
||||
- { name: "user_id", type: "Long", comment: "用户编号" }
|
||||
- { name: "status", type: "Integer", comment: "订单状态" }
|
||||
- { name: "product_count", type: "Integer", comment: "商品数量" }
|
||||
- { name: "total_price", type: "Integer", comment: "商品原价(分)" }
|
||||
- { name: "discount_price", type: "Integer", comment: "优惠金额(分)" }
|
||||
- { name: "delivery_price", type: "Integer", comment: "运费金额(分)" }
|
||||
- { name: "pay_price", type: "Integer", comment: "应付金额(分)" }
|
||||
- { name: "pay_order_id", type: "Long", comment: "支付订单编号" }
|
||||
- { name: "pay_status", type: "Boolean", comment: "是否已支付" }
|
||||
- { name: "pay_time", type: "LocalDateTime", comment: "付款时间" }
|
||||
- { name: "delivery_type", type: "Integer", comment: "配送方式" }
|
||||
- { name: "logistics_id", type: "Long", comment: "物流公司编号" }
|
||||
- { name: "logistics_no", type: "String", comment: "物流单号" }
|
||||
- { name: "receiver_name", type: "String", comment: "收件人名称" }
|
||||
- { name: "receiver_mobile", type: "String", comment: "收件人手机" }
|
||||
- { name: "receiver_detail_address", type: "String", comment: "收件人详细地址" }
|
||||
- { name: "refund_status", type: "Integer", comment: "售后状态" }
|
||||
- { name: "coupon_id", type: "Long", comment: "优惠券编号" }
|
||||
- { name: "coupon_price", type: "Integer", comment: "优惠券减免金额" }
|
||||
|
||||
- name: "trade_order_item"
|
||||
comment: "交易订单项表"
|
||||
entity: "TradeOrderItemDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "订单项编号" }
|
||||
- { name: "order_id", type: "Long", comment: "订单编号" }
|
||||
- { name: "user_id", type: "Long", comment: "用户编号" }
|
||||
- { name: "spu_id", type: "Long", comment: "SPU编号" }
|
||||
- { name: "sku_id", type: "Long", comment: "SKU编号" }
|
||||
- { name: "spu_name", type: "String", comment: "商品名称" }
|
||||
- { name: "sku_name", type: "String", comment: "SKU名称" }
|
||||
- { name: "price", type: "Integer", comment: "商品原价(分)" }
|
||||
- { name: "count", type: "Integer", comment: "购买数量" }
|
||||
- { name: "pay_price", type: "Integer", comment: "子订单实付金额" }
|
||||
- { name: "after_sale_status", type: "Integer", comment: "售后状态" }
|
||||
|
||||
- name: "trade_after_sale"
|
||||
comment: "售后订单表"
|
||||
entity: "AfterSaleDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "售后编号" }
|
||||
- { name: "no", type: "String", comment: "售后流水号" }
|
||||
- { name: "order_id", type: "Long", comment: "订单编号" }
|
||||
- { name: "order_item_id", type: "Long", comment: "订单项编号" }
|
||||
- { name: "user_id", type: "Long", comment: "用户编号" }
|
||||
- { name: "type", type: "Integer", comment: "售后类型:0退款1退货退款" }
|
||||
- { name: "way", type: "Integer", comment: "退款方式" }
|
||||
- { name: "status", type: "Integer", comment: "售后状态" }
|
||||
- { name: "refund_price", type: "Integer", comment: "退款金额" }
|
||||
- { name: "apply_reason", type: "String", comment: "申请原因" }
|
||||
|
||||
# ========== 促销模块 ==========
|
||||
- name: "promotion_coupon_template"
|
||||
comment: "优惠券模板表"
|
||||
entity: "CouponTemplateDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "模板编号" }
|
||||
- { name: "name", type: "String", comment: "优惠券名称" }
|
||||
- { name: "status", type: "Integer", comment: "状态" }
|
||||
- { name: "total_count", type: "Integer", comment: "发放数量" }
|
||||
- { name: "take_limit_count", type: "Integer", comment: "每人限领数量" }
|
||||
- { name: "take_type", type: "Integer", comment: "领取方式" }
|
||||
- { name: "product_scope", type: "Integer", comment: "商品范围" }
|
||||
- { name: "discount_type", type: "Integer", comment: "折扣类型" }
|
||||
|
||||
- name: "promotion_coupon"
|
||||
comment: "优惠券表"
|
||||
entity: "CouponDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "优惠券编号" }
|
||||
- { name: "template_id", type: "Long", comment: "模板编号" }
|
||||
- { name: "user_id", type: "Long", comment: "用户编号" }
|
||||
- { name: "status", type: "Integer", comment: "状态:1未使用2已使用3已过期" }
|
||||
|
||||
- name: "promotion_seckill_activity"
|
||||
comment: "秒杀活动表"
|
||||
entity: "SeckillActivityDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "活动编号" }
|
||||
- { name: "name", type: "String", comment: "活动名称" }
|
||||
- { name: "spu_id", type: "Long", comment: "SPU编号" }
|
||||
- { name: "status", type: "Integer", comment: "活动状态" }
|
||||
- { name: "start_time", type: "LocalDateTime", comment: "开始时间" }
|
||||
- { name: "end_time", type: "LocalDateTime", comment: "结束时间" }
|
||||
- { name: "total_limit_count", type: "Integer", comment: "总限购数量" }
|
||||
- { name: "single_limit_count", type: "Integer", comment: "单次限购数量" }
|
||||
|
||||
- name: "promotion_combination_activity"
|
||||
comment: "拼团活动表"
|
||||
entity: "CombinationActivityDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "活动编号" }
|
||||
- { name: "name", type: "String", comment: "活动名称" }
|
||||
- { name: "spu_id", type: "Long", comment: "SPU编号" }
|
||||
- { name: "status", type: "Integer", comment: "活动状态" }
|
||||
- { name: "user_size", type: "Integer", comment: "成团人数" }
|
||||
- { name: "limit_duration", type: "Integer", comment: "拼团时长(小时)" }
|
||||
|
||||
- name: "promotion_bargain_activity"
|
||||
comment: "砍价活动表"
|
||||
entity: "BargainActivityDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "活动编号" }
|
||||
- { name: "name", type: "String", comment: "活动名称" }
|
||||
- { name: "spu_id", type: "Long", comment: "SPU编号" }
|
||||
- { name: "status", type: "Integer", comment: "活动状态" }
|
||||
- { name: "bargain_first_price", type: "Integer", comment: "砍价起始价格" }
|
||||
- { name: "bargain_min_price", type: "Integer", comment: "砍价最低价格" }
|
||||
|
||||
# 表关系(ER关系)
|
||||
relationships:
|
||||
- from: "product_spu"
|
||||
to: "product_sku"
|
||||
type: "1:N"
|
||||
foreign_key: "spu_id"
|
||||
comment: "一个SPU对应多个SKU"
|
||||
- from: "product_spu"
|
||||
to: "product_category"
|
||||
type: "N:1"
|
||||
foreign_key: "category_id"
|
||||
comment: "多个SPU属于一个分类"
|
||||
- from: "product_spu"
|
||||
to: "product_brand"
|
||||
type: "N:1"
|
||||
foreign_key: "brand_id"
|
||||
comment: "多个SPU属于一个品牌"
|
||||
- from: "trade_order"
|
||||
to: "trade_order_item"
|
||||
type: "1:N"
|
||||
foreign_key: "order_id"
|
||||
comment: "一个订单包含多个订单项"
|
||||
- from: "trade_order_item"
|
||||
to: "product_sku"
|
||||
type: "N:1"
|
||||
foreign_key: "sku_id"
|
||||
comment: "订单项关联SKU"
|
||||
- from: "trade_order"
|
||||
to: "trade_after_sale"
|
||||
type: "1:N"
|
||||
foreign_key: "order_id"
|
||||
comment: "一个订单可能有多个售后单"
|
||||
- from: "promotion_coupon_template"
|
||||
to: "promotion_coupon"
|
||||
type: "1:N"
|
||||
foreign_key: "template_id"
|
||||
comment: "一个模板发放多张优惠券"
|
||||
|
||||
# ============================================
|
||||
# 第四阶段:代码使用设计
|
||||
# ============================================
|
||||
code_patterns:
|
||||
# Controller层规范
|
||||
controller:
|
||||
annotations:
|
||||
- "@RestController"
|
||||
- "@RequestMapping"
|
||||
- "@Tag(name = \"模块名称\")"
|
||||
- "@Operation(summary = \"接口描述\")"
|
||||
- "@PreAuthorize(\"@ss.hasPermission('权限标识')\")"
|
||||
example: |
|
||||
@Tag(name = "管理后台 - 商品 SPU")
|
||||
@RestController
|
||||
@RequestMapping("/product/spu")
|
||||
@Validated
|
||||
public class ProductSpuController {
|
||||
|
||||
@Resource
|
||||
private ProductSpuService productSpuService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建商品 SPU")
|
||||
@PreAuthorize("@ss.hasPermission('product:spu:create')")
|
||||
public CommonResult<Long> createProductSpu(@Valid @RequestBody ProductSpuSaveReqVO createReqVO) {
|
||||
return success(productSpuService.createSpu(createReqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得商品 SPU 分页")
|
||||
@PreAuthorize("@ss.hasPermission('product:spu:query')")
|
||||
public CommonResult<PageResult<ProductSpuRespVO>> getSpuPage(@Valid ProductSpuPageReqVO pageVO) {
|
||||
PageResult<ProductSpuDO> pageResult = productSpuService.getSpuPage(pageVO);
|
||||
return success(BeanUtils.toBean(pageResult, ProductSpuRespVO.class));
|
||||
}
|
||||
}
|
||||
|
||||
# Service层规范
|
||||
service:
|
||||
interface_pattern: |
|
||||
public interface ProductSpuService {
|
||||
Long createSpu(@Valid ProductSpuSaveReqVO createReqVO);
|
||||
void updateSpu(@Valid ProductSpuSaveReqVO updateReqVO);
|
||||
void deleteSpu(Long id);
|
||||
ProductSpuDO getSpu(Long id);
|
||||
PageResult<ProductSpuDO> getSpuPage(ProductSpuPageReqVO pageReqVO);
|
||||
}
|
||||
impl_pattern: |
|
||||
@Service
|
||||
@Validated
|
||||
public class ProductSpuServiceImpl implements ProductSpuService {
|
||||
|
||||
@Resource
|
||||
private ProductSpuMapper productSpuMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createSpu(ProductSpuSaveReqVO createReqVO) {
|
||||
// 1. 校验分类、品牌
|
||||
validateCategory(createReqVO.getCategoryId());
|
||||
brandService.validateProductBrand(createReqVO.getBrandId());
|
||||
// 2. 校验 SKU
|
||||
productSkuService.validateSkuList(skuSaveReqList, createReqVO.getSpecType());
|
||||
// 3. 插入 SPU
|
||||
ProductSpuDO spu = BeanUtils.toBean(createReqVO, ProductSpuDO.class);
|
||||
productSpuMapper.insert(spu);
|
||||
// 4. 插入 SKU
|
||||
productSkuService.createSkuList(spu.getId(), skuSaveReqList);
|
||||
return spu.getId();
|
||||
}
|
||||
}
|
||||
example: |
|
||||
// 商品创建流程
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createSpu(ProductSpuSaveReqVO createReqVO) {
|
||||
// 1. 校验分类、品牌
|
||||
validateCategory(createReqVO.getCategoryId());
|
||||
brandService.validateProductBrand(createReqVO.getBrandId());
|
||||
// 2. 校验 SKU
|
||||
List<ProductSkuSaveReqVO> skuSaveReqList = createReqVO.getSkus();
|
||||
productSkuService.validateSkuList(skuSaveReqList, createReqVO.getSpecType());
|
||||
// 3. 初始化 SPU 信息
|
||||
ProductSpuDO spu = BeanUtils.toBean(createReqVO, ProductSpuDO.class);
|
||||
initSpuFromSkus(spu, skuSaveReqList);
|
||||
// 4. 插入 SPU
|
||||
productSpuMapper.insert(spu);
|
||||
// 5. 插入 SKU
|
||||
productSkuService.createSkuList(spu.getId(), skuSaveReqList);
|
||||
return spu.getId();
|
||||
}
|
||||
|
||||
# 数据访问规范
|
||||
dal:
|
||||
mapper_pattern: |
|
||||
@Mapper
|
||||
public interface ProductSpuMapper extends BaseMapperX<ProductSpuDO> {
|
||||
|
||||
default PageResult<ProductSpuDO> selectPage(ProductSpuPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<ProductSpuDO>()
|
||||
.likeIfPresent(ProductSpuDO::getName, reqVO.getName())
|
||||
.eqIfPresent(ProductSpuDO::getCategoryId, reqVO.getCategoryId())
|
||||
.eqIfPresent(ProductSpuDO::getStatus, reqVO.getStatus())
|
||||
.orderByDesc(ProductSpuDO::getSort));
|
||||
}
|
||||
}
|
||||
example: |
|
||||
// 使用 MyBatis-Plus 查询
|
||||
List<ProductSpuDO> list = productSpuMapper.selectList(
|
||||
new LambdaQueryWrapper<ProductSpuDO>()
|
||||
.eq(ProductSpuDO::getStatus, ProductSpuStatusEnum.ENABLE.getStatus())
|
||||
.orderByDesc(ProductSpuDO::getSort)
|
||||
);
|
||||
|
||||
# 异常处理
|
||||
error_handling:
|
||||
code_prefix:
|
||||
product: "1-008-xxx-xxx"
|
||||
trade: "1-011-xxx-xxx"
|
||||
promotion: "1-013-xxx-xxx"
|
||||
examples:
|
||||
- code: "1_008_005_000"
|
||||
message: "商品 SPU 不存在"
|
||||
module: "product"
|
||||
- code: "1_008_006_004"
|
||||
message: "商品 SKU 库存不足"
|
||||
module: "product"
|
||||
- code: "1_011_000_011"
|
||||
message: "交易订单不存在"
|
||||
module: "trade"
|
||||
- code: "1_011_000_017"
|
||||
message: "交易订单发货失败,订单不是【待发货】状态"
|
||||
module: "trade"
|
||||
- code: "1_013_004_000"
|
||||
message: "优惠劵模板不存在"
|
||||
module: "promotion"
|
||||
- code: "1_013_008_006"
|
||||
message: "秒杀失败,原因:秒杀库存不足"
|
||||
module: "promotion"
|
||||
|
||||
# 统一响应
|
||||
response:
|
||||
success_pattern: "CommonResult.success(data)"
|
||||
error_pattern: "throw exception(ERROR_CODE, args)"
|
||||
|
||||
# ============================================
|
||||
# 第五阶段:扩展指南
|
||||
# ============================================
|
||||
extension_guide:
|
||||
# 新增功能步骤
|
||||
new_feature:
|
||||
title: "新增业务功能(以商品为例)"
|
||||
steps:
|
||||
- "1. 创建 DO 实体类:在 dal/dataobject 目录下创建实体类,继承 BaseDO"
|
||||
- "2. 创建 Mapper 接口:在 dal/mysql 目录下创建 Mapper,继承 BaseMapperX"
|
||||
- "3. 创建 Service 接口和实现:在 service 目录下创建接口和实现类"
|
||||
- "4. 创建 VO 类:在 controller/vo 目录下创建请求和响应 VO"
|
||||
- "5. 创建 Controller:在 controller 目录下创建控制器"
|
||||
- "6. 创建错误码:在 enums/ErrorCodeConstants.java 中定义错误码"
|
||||
- "7. 编写单元测试:验证业务逻辑正确性"
|
||||
|
||||
# 新增渠道/类型示例
|
||||
new_channel:
|
||||
title: "新增促销活动类型"
|
||||
steps:
|
||||
- "1. 创建活动 DO 实体:如 XxxActivityDO,关联商品和规则"
|
||||
- "2. 创建 API 接口:提供活动校验、库存扣减等跨模块调用能力"
|
||||
- "3. 创建 Service 层:处理活动创建、状态流转、库存管理"
|
||||
- "4. 集成到订单流程:在 TradePriceService 中增加价格计算逻辑"
|
||||
- "5. 集成到下单流程:在 TradeOrderUpdateService 中处理活动商品下单"
|
||||
- "6. 创建管理后台接口:活动创建、编辑、上下架等"
|
||||
- "7. 创建用户端接口:活动列表、详情、参与等"
|
||||
|
||||
# 最佳实践
|
||||
best_practices:
|
||||
- "商品状态管理:使用 ProductSpuStatusEnum 枚举,状态变更通过 updateSpuStatus 方法"
|
||||
- "订单状态流转:严格遵循状态机模式,状态变更记录日志"
|
||||
- "库存扣减:使用乐观锁或 Redis 原子操作,防止超卖"
|
||||
- "价格计算:统一使用分为单位,避免浮点精度问题"
|
||||
- "事务管理:涉及多表操作时使用 @Transactional 注解"
|
||||
- "异常处理:使用框架提供的 exception() 方法抛出业务异常"
|
||||
- "API 设计:跨模块调用通过 API 接口,避免直接依赖实现类"
|
||||
- "并发控制:使用分布式锁处理秒杀、拼团等高并发场景"
|
||||
|
||||
# ============================================
|
||||
# 依赖关系
|
||||
# ============================================
|
||||
dependencies:
|
||||
# 内部依赖(其他模块)
|
||||
internal:
|
||||
- module: "yudao-module-member"
|
||||
api: "MemberUserApi"
|
||||
purpose: "获取会员信息,用于订单关联用户"
|
||||
- module: "yudao-module-pay"
|
||||
api: "PayOrderApi"
|
||||
purpose: "创建支付订单,处理支付回调"
|
||||
- module: "yudao-module-system"
|
||||
api: "AreaApi"
|
||||
purpose: "获取地区信息,用于收货地址"
|
||||
|
||||
# 外部依赖(第三方库)
|
||||
external:
|
||||
- name: "MyBatis-Plus"
|
||||
version: "3.x"
|
||||
purpose: "ORM 框架,简化数据库操作"
|
||||
- name: "MapStruct"
|
||||
version: "1.x"
|
||||
purpose: "对象映射,VO 与 DO 转换"
|
||||
- name: "Spring Validation"
|
||||
version: "2.x"
|
||||
purpose: "参数校验"
|
||||
- name: "Swagger/OpenAPI"
|
||||
version: "3.x"
|
||||
purpose: "API 文档生成"
|
||||
- name: "Redis"
|
||||
version: "6.x"
|
||||
purpose: "缓存、分布式锁、库存扣减"
|
||||
|
||||
# ============================================
|
||||
# 关键文件清单
|
||||
# ============================================
|
||||
key_files:
|
||||
# 商品模块
|
||||
- path: "yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/spu/ProductSpuDO.java"
|
||||
purpose: "商品SPU实体类,电商核心领域模型"
|
||||
- path: "yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/sku/ProductSkuDO.java"
|
||||
purpose: "商品SKU实体类,库存管理单元"
|
||||
- path: "yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java"
|
||||
purpose: "商品SPU服务接口,定义商品CRUD操作"
|
||||
- path: "yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java"
|
||||
purpose: "商品SPU管理接口,后台商品管理入口"
|
||||
- path: "yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java"
|
||||
purpose: "商品模块错误码定义"
|
||||
|
||||
# 交易模块
|
||||
- path: "yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java"
|
||||
purpose: "交易订单实体类,订单聚合根"
|
||||
- path: "yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java"
|
||||
purpose: "订单更新服务,订单创建和状态流转"
|
||||
- path: "yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceService.java"
|
||||
purpose: "价格计算服务,订单金额计算核心"
|
||||
- path: "yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java"
|
||||
purpose: "售后服务,退款退货处理"
|
||||
- path: "yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java"
|
||||
purpose: "交易模块错误码定义"
|
||||
|
||||
# 促销模块
|
||||
- path: "yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponTemplateDO.java"
|
||||
purpose: "优惠券模板实体类"
|
||||
- path: "yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/seckill/SeckillActivityDO.java"
|
||||
purpose: "秒杀活动实体类"
|
||||
- path: "yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/combination/CombinationActivityDO.java"
|
||||
purpose: "拼团活动实体类"
|
||||
- path: "yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java"
|
||||
purpose: "促销模块错误码定义"
|
||||
|
||||
# 统计模块
|
||||
- path: "yudao-module-mall/yudao-module-statistics/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsService.java"
|
||||
purpose: "交易统计服务,订单数据汇总"
|
||||
@@ -1,609 +0,0 @@
|
||||
# Skill 文件 - yudao-module-member 会员模块
|
||||
# 用于提取模块知识的标准格式
|
||||
|
||||
skill:
|
||||
id: "skill-member"
|
||||
name: "Member Skill"
|
||||
version: "1.0.0"
|
||||
module_path: "yudao-module-member"
|
||||
created_at: "2026-03-18"
|
||||
updated_at: "2026-03-18"
|
||||
|
||||
# ============================================
|
||||
# 第一阶段:设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
# 业务定位:模块解决什么业务问题?在整个系统中的定位?
|
||||
business_position: |
|
||||
会员中心模块,负责管理C端用户的会员体系,包括:
|
||||
1. 会员用户管理:注册、登录、个人信息管理
|
||||
2. 会员等级体系:等级配置、经验累积、自动升降级
|
||||
3. 积分系统:积分获取、消费、记录追踪
|
||||
4. 签到功能:每日签到、连续签到奖励
|
||||
5. 会员标签与分组:用户分类管理
|
||||
6. 收货地址管理:多地址支持、默认地址
|
||||
|
||||
在整个系统中的定位:
|
||||
- 作为C端用户的核心数据层,与system模块的AdminUser区分
|
||||
- 为mall模块提供会员信息、积分抵扣、等级折扣等支持
|
||||
- 通过API接口对外暴露会员服务,支持跨模块调用
|
||||
|
||||
# 设计原则:遵循了哪些设计原则(SOLID、DDD)?有哪些架构决策?
|
||||
design_principles:
|
||||
- "领域驱动设计(DDD):会员(MemberUser)作为聚合根,关联等级、积分、标签等实体"
|
||||
- "分层架构:Controller -> Service -> DAL 清晰分层,API层提供跨模块调用"
|
||||
- "单一职责:每个Service专注一个业务领域,如MemberUserService、MemberLevelService"
|
||||
- "开闭原则:通过枚举定义业务类型(MemberPointBizTypeEnum),便于扩展新的积分/经验来源"
|
||||
- "事务一致性:积分变更、等级变更使用@Transactional保证数据一致性"
|
||||
- "TenantBaseDO继承:会员数据支持多租户隔离"
|
||||
|
||||
# 领域模型:核心领域对象有哪些?领域对象之间的关系?聚合根是什么?
|
||||
domain_model:
|
||||
aggregates:
|
||||
- name: "MemberUser"
|
||||
type: "聚合根"
|
||||
description: "会员用户,包含账号信息、基础信息、积分、等级、标签等"
|
||||
entities:
|
||||
- "MemberLevelDO (会员等级)"
|
||||
- "MemberGroupDO (会员分组)"
|
||||
- "MemberTagDO (会员标签)"
|
||||
- "MemberAddressDO (收货地址)"
|
||||
|
||||
value_objects:
|
||||
- name: "MemberPointRecord"
|
||||
type: "值对象"
|
||||
description: "积分变更记录,不可变的历史记录"
|
||||
- name: "MemberExperienceRecord"
|
||||
type: "值对象"
|
||||
description: "经验变更记录"
|
||||
- name: "MemberLevelRecord"
|
||||
type: "值对象"
|
||||
description: "等级变更记录"
|
||||
- name: "MemberSignInRecord"
|
||||
type: "值对象"
|
||||
description: "签到记录"
|
||||
|
||||
services:
|
||||
- name: "MemberUserService"
|
||||
description: "会员用户管理服务"
|
||||
- name: "MemberAuthService"
|
||||
description: "会员认证服务(登录、登出、token刷新)"
|
||||
- name: "MemberLevelService"
|
||||
description: "会员等级服务(等级配置、经验管理、自动升级)"
|
||||
- name: "MemberPointRecordService"
|
||||
description: "积分记录服务"
|
||||
- name: "MemberSignInRecordService"
|
||||
description: "签到服务"
|
||||
- name: "AddressService"
|
||||
description: "收货地址服务"
|
||||
|
||||
# ============================================
|
||||
# 第二阶段:架构设计
|
||||
# ============================================
|
||||
architecture:
|
||||
# 分层架构
|
||||
layers:
|
||||
- name: "api"
|
||||
purpose: "模块间API接口,供其他模块调用"
|
||||
components:
|
||||
- "MemberUserApi - 会员用户查询接口"
|
||||
- "MemberPointApi - 积分增减接口"
|
||||
- "MemberLevelApi - 等级查询、经验增减接口"
|
||||
- "MemberAddressApi - 收货地址查询接口"
|
||||
- "MemberConfigApi - 会员配置查询接口"
|
||||
|
||||
- name: "controller"
|
||||
purpose: "HTTP接口,分为admin(管理后台)和app(C端应用)"
|
||||
components:
|
||||
- "admin/user/MemberUserController - 会员用户管理"
|
||||
- "admin/level/MemberLevelController - 等级配置管理"
|
||||
- "admin/point/MemberPointRecordController - 积分记录查询"
|
||||
- "admin/signin/MemberSignInConfigController - 签到规则配置"
|
||||
- "admin/tag/MemberTagController - 标签管理"
|
||||
- "admin/group/MemberGroupController - 分组管理"
|
||||
- "app/auth/AppAuthController - C端认证(登录、注册、登出)"
|
||||
- "app/address/AppAddressController - C端收货地址"
|
||||
- "app/signin/AppMemberSignInRecordController - C端签到"
|
||||
|
||||
- name: "service"
|
||||
purpose: "业务逻辑层"
|
||||
components:
|
||||
- "user/MemberUserService - 会员用户核心服务"
|
||||
- "auth/MemberAuthService - 认证服务"
|
||||
- "level/MemberLevelService - 等级服务"
|
||||
- "level/MemberLevelRecordService - 等级变更记录"
|
||||
- "level/MemberExperienceRecordService - 经验记录"
|
||||
- "point/MemberPointRecordService - 积分记录"
|
||||
- "signin/MemberSignInRecordService - 签到服务"
|
||||
- "signin/MemberSignInConfigService - 签到配置"
|
||||
- "tag/MemberTagService - 标签服务"
|
||||
- "group/MemberGroupService - 分组服务"
|
||||
- "address/AddressService - 地址服务"
|
||||
- "config/MemberConfigService - 配置服务"
|
||||
|
||||
- name: "dal"
|
||||
purpose: "数据访问层"
|
||||
components:
|
||||
- "dataobject/user/MemberUserDO - 会员用户DO"
|
||||
- "dataobject/level/MemberLevelDO - 等级DO"
|
||||
- "dataobject/level/MemberLevelRecordDO - 等级记录DO"
|
||||
- "dataobject/level/MemberExperienceRecordDO - 经验记录DO"
|
||||
- "dataobject/point/MemberPointRecordDO - 积分记录DO"
|
||||
- "dataobject/signin/MemberSignInConfigDO - 签到配置DO"
|
||||
- "dataobject/signin/MemberSignInRecordDO - 签到记录DO"
|
||||
- "dataobject/tag/MemberTagDO - 标签DO"
|
||||
- "dataobject/group/MemberGroupDO - 分组DO"
|
||||
- "dataobject/address/MemberAddressDO - 地址DO"
|
||||
- "dataobject/config/MemberConfigDO - 配置DO"
|
||||
|
||||
# 设计模式应用
|
||||
design_patterns:
|
||||
- pattern: "DTO转换模式"
|
||||
location: "api/*/dto/*.java"
|
||||
purpose: "API层使用DTO隔离内部DO,保护领域模型"
|
||||
- pattern: "策略模式(枚举实现)"
|
||||
location: "enums/point/MemberPointBizTypeEnum.java"
|
||||
purpose: "定义积分业务类型,支持多种积分来源"
|
||||
- pattern: "模板方法"
|
||||
location: "service/level/MemberLevelServiceImpl.java"
|
||||
purpose: "等级变更流程:记录变更 -> 更新经验 -> 计算新等级 -> 通知用户"
|
||||
- pattern: "Facade模式"
|
||||
location: "api/*ApiImpl.java"
|
||||
purpose: "API实现类封装Service调用,简化跨模块交互"
|
||||
- pattern: "观察者模式(MQ)"
|
||||
location: "mq/producer/user/MemberUserProducer.java"
|
||||
purpose: "用户创建后发送消息,解耦后续处理逻辑"
|
||||
|
||||
# 模块间通信
|
||||
communication:
|
||||
apis:
|
||||
- name: "MemberUserApi"
|
||||
methods: ["getUser", "getUserList", "getUserByMobile", "validateUser"]
|
||||
consumers: ["pay", "mall-trade", "mall-promotion"]
|
||||
- name: "MemberPointApi"
|
||||
methods: ["addPoint", "reducePoint"]
|
||||
consumers: ["mall-trade", "mall-promotion"]
|
||||
- name: "MemberLevelApi"
|
||||
methods: ["getMemberLevel", "addExperience", "reduceExperience"]
|
||||
consumers: ["mall-trade"]
|
||||
- name: "MemberAddressApi"
|
||||
methods: ["getAddress", "getDefaultAddress"]
|
||||
consumers: ["mall-trade"]
|
||||
- name: "MemberConfigApi"
|
||||
methods: ["getConfig"]
|
||||
consumers: ["mall-trade"]
|
||||
|
||||
consumers:
|
||||
- module: "system"
|
||||
api: "SmsCodeApi"
|
||||
purpose: "短信验证码校验"
|
||||
- module: "system"
|
||||
api: "SocialClientApi"
|
||||
purpose: "社交登录、微信小程序登录"
|
||||
- module: "infra"
|
||||
api: "文件存储"
|
||||
purpose: "头像上传"
|
||||
|
||||
mq:
|
||||
- name: "MemberUserCreateMessage"
|
||||
direction: "producer"
|
||||
purpose: "用户创建后发送消息,通知其他模块"
|
||||
|
||||
# ============================================
|
||||
# 第三阶段:数据表设计
|
||||
# ============================================
|
||||
data_model:
|
||||
# 实体继承体系
|
||||
entity_hierarchy:
|
||||
base: "TenantBaseDO"
|
||||
description: "会员核心表(MemberUserDO)继承TenantBaseDO支持多租户,其他表继承BaseDO"
|
||||
|
||||
# 核心数据表
|
||||
tables:
|
||||
- name: "member_user"
|
||||
comment: "会员用户表"
|
||||
entity: "MemberUserDO"
|
||||
extends: "TenantBaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "用户ID" }
|
||||
- { name: "mobile", type: "String", comment: "手机号(唯一索引)" }
|
||||
- { name: "password", type: "String", comment: "加密密码" }
|
||||
- { name: "status", type: "Integer", comment: "状态(0启用1禁用)" }
|
||||
- { name: "register_ip", type: "String", comment: "注册IP" }
|
||||
- { name: "register_terminal", type: "Integer", comment: "注册终端" }
|
||||
- { name: "login_ip", type: "String", comment: "最后登录IP" }
|
||||
- { name: "login_date", type: "LocalDateTime", comment: "最后登录时间" }
|
||||
- { name: "nickname", type: "String", comment: "昵称" }
|
||||
- { name: "avatar", type: "String", comment: "头像" }
|
||||
- { name: "name", type: "String", comment: "真实姓名" }
|
||||
- { name: "sex", type: "Integer", comment: "性别" }
|
||||
- { name: "birthday", type: "LocalDateTime", comment: "生日" }
|
||||
- { name: "area_id", type: "Integer", comment: "地区ID" }
|
||||
- { name: "mark", type: "String", comment: "用户备注" }
|
||||
- { name: "point", type: "Integer", comment: "当前积分" }
|
||||
- { name: "tag_ids", type: "List<Long>", comment: "标签ID列表(JSON存储)" }
|
||||
- { name: "level_id", type: "Long", comment: "等级ID" }
|
||||
- { name: "experience", type: "Integer", comment: "当前经验" }
|
||||
- { name: "group_id", type: "Long", comment: "分组ID" }
|
||||
indexes:
|
||||
- { name: "uk_mobile", columns: ["mobile"] }
|
||||
|
||||
- name: "member_level"
|
||||
comment: "会员等级表"
|
||||
entity: "MemberLevelDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "等级ID" }
|
||||
- { name: "name", type: "String", comment: "等级名称" }
|
||||
- { name: "level", type: "Integer", comment: "等级值(用于排序)" }
|
||||
- { name: "experience", type: "Integer", comment: "升级所需经验" }
|
||||
- { name: "discount_percent", type: "Integer", comment: "享受折扣百分比" }
|
||||
- { name: "icon", type: "String", comment: "等级图标" }
|
||||
- { name: "background_url", type: "String", comment: "等级背景图" }
|
||||
- { name: "status", type: "Integer", comment: "状态(0启用1禁用)" }
|
||||
|
||||
- name: "member_point_record"
|
||||
comment: "积分记录表"
|
||||
entity: "MemberPointRecordDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "记录ID" }
|
||||
- { name: "user_id", type: "Long", comment: "用户ID" }
|
||||
- { name: "biz_id", type: "String", comment: "业务编码" }
|
||||
- { name: "biz_type", type: "Integer", comment: "业务类型(枚举)" }
|
||||
- { name: "title", type: "String", comment: "积分标题" }
|
||||
- { name: "description", type: "String", comment: "积分描述" }
|
||||
- { name: "point", type: "Integer", comment: "变动积分(正负)" }
|
||||
- { name: "total_point", type: "Integer", comment: "变动后积分" }
|
||||
|
||||
- name: "member_experience_record"
|
||||
comment: "经验记录表"
|
||||
entity: "MemberExperienceRecordDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "记录ID" }
|
||||
- { name: "user_id", type: "Long", comment: "用户ID" }
|
||||
- { name: "biz_type", type: "Integer", comment: "业务类型(枚举)" }
|
||||
- { name: "biz_id", type: "String", comment: "业务编号" }
|
||||
- { name: "title", type: "String", comment: "标题" }
|
||||
- { name: "description", type: "String", comment: "描述" }
|
||||
- { name: "experience", type: "Integer", comment: "变动经验(正负)" }
|
||||
- { name: "total_experience", type: "Integer", comment: "变动后经验" }
|
||||
|
||||
- name: "member_level_record"
|
||||
comment: "等级变更记录表"
|
||||
entity: "MemberLevelRecordDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "记录ID" }
|
||||
- { name: "user_id", type: "Long", comment: "用户ID" }
|
||||
- { name: "level_id", type: "Long", comment: "等级ID" }
|
||||
- { name: "level", type: "Integer", comment: "等级值(冗余)" }
|
||||
- { name: "discount_percent", type: "Integer", comment: "折扣(冗余)" }
|
||||
- { name: "experience", type: "Integer", comment: "升级经验(冗余)" }
|
||||
- { name: "user_experience", type: "Integer", comment: "用户变更后经验" }
|
||||
- { name: "remark", type: "String", comment: "备注" }
|
||||
- { name: "description", type: "String", comment: "描述" }
|
||||
|
||||
- name: "member_sign_in_config"
|
||||
comment: "签到配置表"
|
||||
entity: "MemberSignInConfigDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "配置ID" }
|
||||
- { name: "day", type: "Integer", comment: "签到第N天" }
|
||||
- { name: "point", type: "Integer", comment: "奖励积分" }
|
||||
- { name: "experience", type: "Integer", comment: "奖励经验" }
|
||||
- { name: "status", type: "Integer", comment: "状态" }
|
||||
|
||||
- name: "member_sign_in_record"
|
||||
comment: "签到记录表"
|
||||
entity: "MemberSignInRecordDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "记录ID" }
|
||||
- { name: "user_id", type: "Long", comment: "用户ID" }
|
||||
- { name: "day", type: "Integer", comment: "第几天签到" }
|
||||
- { name: "point", type: "Integer", comment: "签到获得积分" }
|
||||
- { name: "experience", type: "Integer", comment: "签到获得经验" }
|
||||
|
||||
- name: "member_tag"
|
||||
comment: "会员标签表"
|
||||
entity: "MemberTagDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "标签ID" }
|
||||
- { name: "name", type: "String", comment: "标签名称" }
|
||||
|
||||
- name: "member_group"
|
||||
comment: "会员分组表"
|
||||
entity: "MemberGroupDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "分组ID" }
|
||||
- { name: "name", type: "String", comment: "分组名称" }
|
||||
- { name: "remark", type: "String", comment: "备注" }
|
||||
- { name: "status", type: "Integer", comment: "状态" }
|
||||
|
||||
- name: "member_address"
|
||||
comment: "收货地址表"
|
||||
entity: "MemberAddressDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "地址ID" }
|
||||
- { name: "user_id", type: "Long", comment: "用户ID" }
|
||||
- { name: "name", type: "String", comment: "收件人名称" }
|
||||
- { name: "mobile", type: "String", comment: "手机号" }
|
||||
- { name: "area_id", type: "Long", comment: "地区ID" }
|
||||
- { name: "detail_address", type: "String", comment: "详细地址" }
|
||||
- { name: "default_status", type: "Boolean", comment: "是否默认" }
|
||||
|
||||
- name: "member_config"
|
||||
comment: "会员配置表"
|
||||
entity: "MemberConfigDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "配置ID" }
|
||||
- { name: "point_trade_deduct_enable", type: "Boolean", comment: "积分抵扣开关" }
|
||||
- { name: "point_trade_deduct_unit_price", type: "Integer", comment: "积分抵扣单位(1积分=多少分)" }
|
||||
- { name: "point_trade_deduct_max_price", type: "Integer", comment: "积分抵扣上限(分)" }
|
||||
- { name: "point_trade_give_point", type: "Integer", comment: "1元赠送积分" }
|
||||
|
||||
# 表关系(ER关系)
|
||||
relationships:
|
||||
- from: "member_user"
|
||||
to: "member_level"
|
||||
type: "N:1"
|
||||
foreign_key: "level_id"
|
||||
- from: "member_user"
|
||||
to: "member_group"
|
||||
type: "N:1"
|
||||
foreign_key: "group_id"
|
||||
- from: "member_user"
|
||||
to: "member_tag"
|
||||
type: "N:N"
|
||||
foreign_key: "tag_ids(JSON数组)"
|
||||
- from: "member_point_record"
|
||||
to: "member_user"
|
||||
type: "N:1"
|
||||
foreign_key: "user_id"
|
||||
- from: "member_experience_record"
|
||||
to: "member_user"
|
||||
type: "N:1"
|
||||
foreign_key: "user_id"
|
||||
- from: "member_level_record"
|
||||
to: "member_user"
|
||||
type: "N:1"
|
||||
foreign_key: "user_id"
|
||||
- from: "member_level_record"
|
||||
to: "member_level"
|
||||
type: "N:1"
|
||||
foreign_key: "level_id"
|
||||
- from: "member_sign_in_record"
|
||||
to: "member_user"
|
||||
type: "N:1"
|
||||
foreign_key: "user_id"
|
||||
- from: "member_address"
|
||||
to: "member_user"
|
||||
type: "N:1"
|
||||
foreign_key: "user_id"
|
||||
|
||||
# ============================================
|
||||
# 第四阶段:代码使用设计
|
||||
# ============================================
|
||||
code_patterns:
|
||||
# Controller层规范
|
||||
controller:
|
||||
annotations:
|
||||
- "@Tag(name = '管理后台 - XXX') - Swagger分组"
|
||||
- "@RestController @RequestMapping('/member/xxx')"
|
||||
- "@PreAuthorize('@ss.hasPermission(\\'member:xxx:action\\')') - 权限控制"
|
||||
- "@PermitAll - 无需登录(如登录接口)"
|
||||
example: |
|
||||
@Tag(name = "管理后台 - 会员用户")
|
||||
@RestController
|
||||
@RequestMapping("/member/user")
|
||||
@Validated
|
||||
public class MemberUserController {
|
||||
@Resource
|
||||
private MemberUserService memberUserService;
|
||||
|
||||
@PutMapping("/update-point")
|
||||
@Operation(summary = "更新会员用户积分")
|
||||
@PreAuthorize("@ss.hasPermission('member:user:update-point')")
|
||||
public CommonResult<Boolean> updateUserPoint(@Valid @RequestBody MemberUserUpdatePointReqVO updateReqVO) {
|
||||
memberPointRecordService.createPointRecord(updateReqVO.getId(), updateReqVO.getPoint(),
|
||||
MemberPointBizTypeEnum.ADMIN, String.valueOf(getLoginUserId()));
|
||||
return success(true);
|
||||
}
|
||||
}
|
||||
|
||||
# Service层规范
|
||||
service:
|
||||
interface_pattern: "定义在service包下,以Service结尾"
|
||||
impl_pattern: "实现类以ServiceImpl结尾,使用@Service注解"
|
||||
example: |
|
||||
// Service接口
|
||||
public interface MemberPointRecordService {
|
||||
void createPointRecord(Long userId, Integer point, MemberPointBizTypeEnum bizType, String bizId);
|
||||
}
|
||||
|
||||
// Service实现
|
||||
@Service
|
||||
@Validated
|
||||
public class MemberPointRecordServiceImpl implements MemberPointRecordService {
|
||||
@Resource
|
||||
private MemberPointRecordMapper memberPointRecordMapper;
|
||||
@Resource
|
||||
private MemberUserService memberUserService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void createPointRecord(Long userId, Integer point, MemberPointBizTypeEnum bizType, String bizId) {
|
||||
// 1. 校验用户积分余额
|
||||
MemberUserDO user = memberUserService.getUser(userId);
|
||||
int totalPoint = ObjectUtil.defaultIfNull(user.getPoint(), 0) + point;
|
||||
if (totalPoint < 0) {
|
||||
throw exception(USER_POINT_NOT_ENOUGH);
|
||||
}
|
||||
// 2. 更新用户积分
|
||||
memberUserService.updateUserPoint(userId, point);
|
||||
// 3. 增加积分记录
|
||||
MemberPointRecordDO record = new MemberPointRecordDO()
|
||||
.setUserId(userId).setBizId(bizId).setBizType(bizType.getType())
|
||||
.setPoint(point).setTotalPoint(totalPoint);
|
||||
memberPointRecordMapper.insert(record);
|
||||
}
|
||||
}
|
||||
|
||||
# 数据访问规范
|
||||
dal:
|
||||
mapper_pattern: "继承BaseMapperX,在mysql包下"
|
||||
example: |
|
||||
@Mapper
|
||||
public interface MemberUserMapper extends BaseMapperX<MemberUserDO> {
|
||||
default MemberUserDO selectByMobile(String mobile) {
|
||||
return selectOne(MemberUserDO::getMobile, mobile);
|
||||
}
|
||||
default int updatePointIncr(Long id, Integer point) {
|
||||
return update(new MemberUserDO().setPoint(point),
|
||||
new LambdaQueryWrapper<MemberUserDO>().eq(MemberUserDO::getId, id));
|
||||
}
|
||||
}
|
||||
|
||||
# 异常处理
|
||||
error_handling:
|
||||
code_prefix: "1_004_XXX_XXX"
|
||||
examples:
|
||||
- code: "1_004_001_000"
|
||||
message: "用户不存在"
|
||||
- code: "1_004_001_003"
|
||||
message: "用户积分余额不足"
|
||||
- code: "1_004_003_000"
|
||||
message: "登录失败,账号密码不正确"
|
||||
- code: "1_004_006_000"
|
||||
message: "用户标签不存在"
|
||||
- code: "1_004_008_000"
|
||||
message: "用户积分记录业务类型不支持"
|
||||
- code: "1_004_010_000"
|
||||
message: "今日已签到,请勿重复签到"
|
||||
- code: "1_004_011_000"
|
||||
message: "用户等级不存在"
|
||||
|
||||
# 统一响应
|
||||
response:
|
||||
success_pattern: "CommonResult.success(data)"
|
||||
error_pattern: "throw exception(ERROR_CODE, args...)"
|
||||
|
||||
# 枚举定义
|
||||
enums:
|
||||
- name: "MemberPointBizTypeEnum"
|
||||
description: "积分业务类型"
|
||||
values:
|
||||
- { type: 1, name: "SIGN", desc: "签到", add: true }
|
||||
- { type: 2, name: "ADMIN", desc: "管理员修改", add: true }
|
||||
- { type: 11, name: "ORDER_USE", desc: "订单积分抵扣", add: false }
|
||||
- { type: 21, name: "ORDER_GIVE", desc: "订单积分奖励", add: true }
|
||||
|
||||
- name: "MemberExperienceBizTypeEnum"
|
||||
description: "经验业务类型"
|
||||
values:
|
||||
- { type: 0, name: "ADMIN", desc: "管理员调整", add: true }
|
||||
- { type: 1, name: "INVITE_REGISTER", desc: "邀新奖励", add: true }
|
||||
- { type: 4, name: "SIGN_IN", desc: "签到奖励", add: true }
|
||||
- { type: 11, name: "ORDER_GIVE", desc: "下单奖励", add: true }
|
||||
|
||||
# ============================================
|
||||
# 第五阶段:扩展指南
|
||||
# ============================================
|
||||
extension_guide:
|
||||
# 新增功能步骤
|
||||
new_feature:
|
||||
title: "新增会员权益功能"
|
||||
steps:
|
||||
- "1. 在dal/dataobject下创建MemberBenefitDO实体类"
|
||||
- "2. 创建MemberBenefitMapper接口"
|
||||
- "3. 创建MemberBenefitService接口和实现类"
|
||||
- "4. 创建admin和app的Controller"
|
||||
- "5. 在ErrorCodeConstants中添加错误码"
|
||||
- "6. 如需对外暴露API,在api包下创建MemberBenefitApi"
|
||||
|
||||
# 新增会员等级步骤
|
||||
new_channel:
|
||||
title: "新增会员等级"
|
||||
steps:
|
||||
- "1. 通过管理后台或直接插入member_level表"
|
||||
- "2. 设置等级名称、等级值、升级所需经验"
|
||||
- "3. 配置享受折扣百分比"
|
||||
- "4. 上传等级图标和背景图"
|
||||
- "5. 确保升级经验值在相邻等级之间"
|
||||
|
||||
# 新增积分来源
|
||||
new_point_source:
|
||||
title: "新增积分来源类型"
|
||||
steps:
|
||||
- "1. 在MemberPointBizTypeEnum中添加新的枚举值"
|
||||
- "2. 设置type(唯一)、name、description、add(是否增加)"
|
||||
- "3. 在对应业务场景调用MemberPointApi.addPoint或reducePoint"
|
||||
- "4. 示例:MemberPointApi.addPoint(userId, 10, MemberPointBizTypeEnum.NEW_TYPE.getType(), bizId)"
|
||||
|
||||
# 最佳实践
|
||||
best_practices:
|
||||
- "积分变更使用MemberPointApi,保证记录完整性"
|
||||
- "经验变更使用MemberLevelApi,自动触发等级计算"
|
||||
- "用户创建后发送MQ消息,便于其他模块处理"
|
||||
- "等级经验配置需确保递增顺序,系统会自动校验"
|
||||
- "积分抵扣配置通过MemberConfigApi获取,支持动态调整"
|
||||
- "签到规则配置支持连续签到奖励递增"
|
||||
- "地址管理支持多地址,需处理默认地址切换逻辑"
|
||||
|
||||
# ============================================
|
||||
# 依赖关系
|
||||
# ============================================
|
||||
dependencies:
|
||||
# 内部依赖(其他模块)
|
||||
internal:
|
||||
- module: "system"
|
||||
api: "SmsCodeApi"
|
||||
purpose: "短信验证码校验(登录、修改手机、重置密码)"
|
||||
- module: "system"
|
||||
api: "SocialClientApi"
|
||||
purpose: "社交登录(微信、QQ等)、微信小程序登录"
|
||||
- module: "infra"
|
||||
api: "文件存储"
|
||||
purpose: "头像上传存储"
|
||||
|
||||
# 外部依赖(第三方库)
|
||||
external:
|
||||
- name: "spring-security-crypto"
|
||||
version: "Spring Boot自带"
|
||||
purpose: "密码加密(BCryptPasswordEncoder)"
|
||||
- name: "mybatis-plus"
|
||||
version: "3.x"
|
||||
purpose: "ORM框架,数据访问增强"
|
||||
- name: "hutool"
|
||||
version: "5.x"
|
||||
purpose: "工具类库"
|
||||
|
||||
# ============================================
|
||||
# 关键文件清单
|
||||
# ============================================
|
||||
key_files:
|
||||
- path: "yudao-module-member/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/user/MemberUserDO.java"
|
||||
purpose: "会员用户实体,聚合根"
|
||||
- path: "yudao-module-member/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java"
|
||||
purpose: "会员用户服务接口"
|
||||
- path: "yudao-module-member/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelService.java"
|
||||
purpose: "会员等级服务,含经验管理和自动升级"
|
||||
- path: "yudao-module-member/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApi.java"
|
||||
purpose: "积分API接口,供其他模块调用"
|
||||
- path: "yudao-module-member/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java"
|
||||
purpose: "积分业务类型枚举"
|
||||
- path: "yudao-module-member/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java"
|
||||
purpose: "错误码常量定义"
|
||||
- path: "yudao-module-member/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java"
|
||||
purpose: "C端认证控制器"
|
||||
- path: "yudao-module-member/src/main/java/cn/iocoder/yudao/module/member/service/signin/MemberSignInRecordService.java"
|
||||
purpose: "签到服务"
|
||||
- path: "yudao-module-member/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/config/MemberConfigDO.java"
|
||||
purpose: "会员配置(积分抵扣设置)"
|
||||
@@ -1,964 +0,0 @@
|
||||
# Skill 文件 - yudao-module-mes 模块
|
||||
# 制造执行系统模块知识提取
|
||||
|
||||
skill:
|
||||
id: "skill-mes"
|
||||
name: "Manufacturing Execution System Skill"
|
||||
version: "1.0.0"
|
||||
module_path: "yudao-module-mes"
|
||||
created_at: "2026-06-08"
|
||||
updated_at: "2026-06-08"
|
||||
|
||||
# ============================================
|
||||
# 第一阶段:设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
# 业务定位:模块解决什么业务问题?在整个系统中的定位?
|
||||
business_position: |
|
||||
mes 模块是制造执行系统(MES),连接 ERP 计划层与车间执行层,实现生产过程的数字化管理:
|
||||
1. 主数据管理 - 物料、BOM、工艺路线、SOP/SIP、客户/供应商、车间/工位、自动编码规则
|
||||
2. 生产管理 - 工单(草稿->确认->完工/取消)、工序任务(甘特图)、流转卡、报工反馈、安灯呼叫
|
||||
3. 质量管理 - 检验模板、检验指标、缺陷管理、IQC/IPQC/OQC/RQC 四大检验
|
||||
4. 设备管理 - 设备台账、点检计划/记录、保养记录、维修管理
|
||||
5. 工具管理 - 工具台账、工具类型
|
||||
6. 仓库管理 - 仓库/库区/库位、物料库存、批次、条码/SN、各类出入库、调拨、盘点
|
||||
7. 排班日历 - 工厂日历、节假日、排班计划、班组管理
|
||||
8. 首页看板 - 订单汇总、工单状态、生产趋势统计
|
||||
|
||||
定位:面向离散制造和流程制造,提供从生产计划下达到成品入库的全流程管理。
|
||||
|
||||
# 设计原则:遵循了哪些设计原则(SOLID、DDD)?有哪些架构决策?
|
||||
design_principles:
|
||||
- principle: "状态机模式"
|
||||
description: "工单/流转卡/报工/维修/调拨/排班计划等核心实体使用状态机管理状态流转,变更前校验前置状态,使用 CAS 防止并发覆盖"
|
||||
- principle: "策略模式"
|
||||
description: "自动编码规则支持日期、固定字符、输入字符、序列号等零件组合,不同零件类型有不同的生成策略"
|
||||
- principle: "VO Assembly 模式"
|
||||
description: "查询接口批量获取关联实体后组装 VO,避免 N+1 查询,提升查询性能"
|
||||
- principle: "标准 CRUD 模式"
|
||||
description: "全部 Controller/Service/Mapper 遵循统一的增删改查规范,配合 @PreAuthorize 权限控制"
|
||||
- principle: "主子表结构"
|
||||
description: "BOM、检验单、出入库单、维修单等使用主子表结构,子表通过外键关联主表"
|
||||
- principle: "Oracle/PG 兼容"
|
||||
description: "使用 @KeySequence 注解兼容 Oracle 和 PostgreSQL 的主键生成方式"
|
||||
- principle: "跨模块 API 集成"
|
||||
description: "通过 AdminUserApi、RoleApi 等接口与 system 模块集成,不直接依赖实现类"
|
||||
|
||||
# 领域模型:核心领域对象有哪些?领域对象之间的关系?聚合根是什么?
|
||||
domain_model:
|
||||
aggregates:
|
||||
- name: "物料聚合"
|
||||
type: "聚合根"
|
||||
entities: ["ItemDO", "ItemBatchConfigDO"]
|
||||
description: "ItemDO 是物料聚合根,BatchConfigDO 定义物料的批次生成规则"
|
||||
- name: "BOM 聚合"
|
||||
type: "聚合根"
|
||||
entities: ["ProductBomDO", "Bom子表"]
|
||||
description: "ProductBomDO 是 BOM 聚合根,定义产品由哪些子物料组成及用量"
|
||||
- name: "工艺路线聚合"
|
||||
type: "聚合根"
|
||||
entities: ["RouteDO", "RouteProcessDO", "RouteProductDO"]
|
||||
description: "RouteDO 是工艺路线聚合根,包含工序列表和适用产品"
|
||||
- name: "工单聚合"
|
||||
type: "聚合根"
|
||||
entities: ["WorkOrderDO", "WorkOrderBomDO"]
|
||||
description: "WorkOrderDO 是工单聚合根,关联 BOM 和工艺路线"
|
||||
- name: "任务聚合"
|
||||
type: "聚合根"
|
||||
entities: ["TaskDO", "TaskIssueDO"]
|
||||
description: "TaskDO 是生产任务聚合根,关联工序、工位、设备、操作员"
|
||||
- name: "流转卡聚合"
|
||||
type: "聚合根"
|
||||
entities: ["CardDO", "CardProcessDO"]
|
||||
description: "CardDO 是流转卡聚合根,跟踪各工序的执行情况"
|
||||
- name: "检验单聚合"
|
||||
type: "聚合根"
|
||||
entities: ["QcIqcDO/QcIpqcDO/QcOqcDO/QcRqcDO", "检验行"]
|
||||
description: "四大检验单各自为聚合根,主子表结构"
|
||||
- name: "设备聚合"
|
||||
type: "聚合根"
|
||||
entities: ["MachineryDO", "CheckPlanDO", "CheckRecordDO", "MaintenRecordDO", "RepairDO"]
|
||||
description: "MachineryDO 是设备聚合根,关联点检、保养、维修"
|
||||
- name: "仓库聚合"
|
||||
type: "聚合根"
|
||||
entities: ["WarehouseDO", "WarehouseAreaDO", "WarehouseLocationDO"]
|
||||
description: "WarehouseDO 是仓库聚合根,包含库区和库位的层级结构"
|
||||
- name: "库存聚合"
|
||||
type: "聚合根"
|
||||
entities: ["MaterialStockDO", "BatchDO", "BarcodeDO", "SnDO"]
|
||||
description: "MaterialStockDO 是库存聚合根,按物料/仓库/库位/批次维度管理"
|
||||
- name: "排班聚合"
|
||||
type: "聚合根"
|
||||
entities: ["CalPlanDO", "CalPlanShiftDO", "CalPlanTeamDO", "CalTeamDO"]
|
||||
description: "CalPlanDO 是排班计划聚合根,关联班次和班组"
|
||||
- name: "自动编码聚合"
|
||||
type: "聚合根"
|
||||
entities: ["AutoCodeRuleDO", "AutoCodePartDO", "AutoCodeRecordDO"]
|
||||
description: "AutoCodeRuleDO 是编码规则聚合根,由零件组合定义编码格式"
|
||||
|
||||
services:
|
||||
- name: "ItemService"
|
||||
description: "物料管理服务,提供物料的增删改查"
|
||||
- name: "ProductBomService"
|
||||
description: "BOM 管理服务,管理产品物料清单"
|
||||
- name: "RouteService"
|
||||
description: "工艺路线服务,管理生产工序流程"
|
||||
- name: "WorkOrderService"
|
||||
description: "工单管理服务,工单状态机流转"
|
||||
- name: "TaskService"
|
||||
description: "生产任务服务,支持甘特图排产"
|
||||
- name: "CardService"
|
||||
description: "流转卡服务,跟踪生产工序执行"
|
||||
- name: "FeedbackService"
|
||||
description: "报工反馈服务,提交/审批报工"
|
||||
- name: "QcIqcService/QcIpqcService/QcOqcService/QcRqcService"
|
||||
description: "四大检验服务,管理来料/过程/出货/退货检验"
|
||||
- name: "MachineryService"
|
||||
description: "设备管理服务,设备台账维护"
|
||||
- name: "RepairService"
|
||||
description: "维修管理服务,维修单状态流转"
|
||||
- name: "WarehouseService"
|
||||
description: "仓库管理服务,仓库/库区/库位层级管理"
|
||||
- name: "MaterialStockService"
|
||||
description: "库存管理服务,出入库、调拨、盘点"
|
||||
- name: "AutoCodeService"
|
||||
description: "自动编码服务,根据规则生成业务单号"
|
||||
- name: "CalPlanService"
|
||||
description: "排班计划服务,工厂日历和班组排班"
|
||||
|
||||
# ============================================
|
||||
# 第二阶段:架构设计
|
||||
# ============================================
|
||||
architecture:
|
||||
# 分层架构
|
||||
layers:
|
||||
- name: "controller"
|
||||
purpose: "HTTP 接口层,提供 RESTful API"
|
||||
components:
|
||||
- "admin/home - 首页看板接口"
|
||||
- "admin/cal - 排班日历接口(cal/holiday, cal/plan, cal/team 等)"
|
||||
- "admin/md - 主数据接口(item, bom, sop, sip, client, vendor, workshop, workstation, auto-code)"
|
||||
- "admin/pro - 生产管理接口(work-order, task, route, process, card, feedback, work-record, andon)"
|
||||
- "admin/qc - 质量管理接口(template, indicator, defect, iqc, ipqc, oqc, rqc, pending-inspect)"
|
||||
- "admin/dv - 设备管理接口(machinery, machinery-type, check-plan, check-record, mainten-record, repair, subject)"
|
||||
- "admin/tm - 工具管理接口(tool, tool-type)"
|
||||
- "admin/wm - 仓库管理接口(warehouse, area, location, material-stock, batch, barcode, sn, arrival-notice, item-receipt, product-receipt, product-issue, return-issue, misc-issue, misc-receipt, product-sales, return-sales, outsource-issue, outsource-receipt, return-vendor, transfer, package, stock-taking, sales-notice)"
|
||||
|
||||
- name: "service"
|
||||
purpose: "业务逻辑层"
|
||||
components:
|
||||
- "md - 主数据服务(ItemService, ProductBomService, RouteService, WorkshopService, WorkstationService, AutoCodeService)"
|
||||
- "pro - 生产服务(WorkOrderService, TaskService, CardService, FeedbackService, WorkRecordService, AndonService)"
|
||||
- "qc - 质量服务(QcTemplateService, QcIndicatorService, QcDefectService, QcIqcService, QcIpqcService, QcOqcService, QcRqcService)"
|
||||
- "dv - 设备服务(MachineryService, CheckPlanService, CheckRecordService, MaintenRecordService, RepairService)"
|
||||
- "tm - 工具服务(ToolService)"
|
||||
- "wm - 仓库服务(WarehouseService, MaterialStockService, BatchService, BarcodeService, 各出入库Service, TransferService, PackageService, StockTakingService)"
|
||||
- "cal - 日历服务(CalPlanService, CalHolidayService, CalTeamService)"
|
||||
- "home - 首页服务(HomeService)"
|
||||
|
||||
- name: "dal"
|
||||
purpose: "数据访问层"
|
||||
components:
|
||||
- "dataobject - DO 实体类定义,按域组织(md/pro/qc/dv/tm/wm/cal)"
|
||||
- "mysql - MyBatis Mapper 接口,继承 BaseMapperX"
|
||||
|
||||
- name: "enums"
|
||||
purpose: "枚举和错误码"
|
||||
components:
|
||||
- "MesBizTypeConstants - 集中管理业务类型常量"
|
||||
- "各状态枚举 - WorkOrderStatusEnum, CardStatusEnum, FeedbackStatusEnum, RepairStatusEnum 等"
|
||||
- "ErrorCodeConstants - 错误码常量定义"
|
||||
|
||||
# 设计模式应用
|
||||
design_patterns:
|
||||
- pattern: "状态机模式 (State Machine Pattern)"
|
||||
location: "service 层各核心实体的状态管理"
|
||||
purpose: "工单/流转卡/报工/维修/调拨/排班计划的状态流转管理,变更前校验前置状态"
|
||||
- pattern: "策略模式 (Strategy Pattern)"
|
||||
location: "service/md/AutoCodeService"
|
||||
purpose: "自动编码规则支持日期、固定字符、输入字符、序列号等零件组合,不同零件类型有不同的生成策略"
|
||||
- pattern: "VO Assembly 模式"
|
||||
location: "controller 层的 get/page 接口"
|
||||
purpose: "批量获取关联实体后组装 VO,避免 N+1 查询"
|
||||
- pattern: "标准 CRUD 模式"
|
||||
location: "全部 Controller/Service/Mapper"
|
||||
purpose: "统一的增删改查 + @PreAuthorize 权限控制"
|
||||
- pattern: "主子表模式"
|
||||
location: "BOM、检验单、出入库单、维修单等"
|
||||
purpose: "主表记录基本信息,子表记录明细,通过外键关联"
|
||||
- pattern: "CAS 更新模式"
|
||||
location: "状态变更、库存扣减"
|
||||
purpose: "使用 WHERE status = #{oldStatus} 防止并发覆盖"
|
||||
|
||||
# 模块间通信
|
||||
communication:
|
||||
apis:
|
||||
- name: "AdminUserApi"
|
||||
method: "getUser()"
|
||||
description: "获取用户信息,用于工位操作员、报工人等关联"
|
||||
- name: "RoleApi"
|
||||
method: "hasAnyRole()"
|
||||
description: "角色权限校验"
|
||||
consumers:
|
||||
- module: "yudao-module-system"
|
||||
api: "AdminUserApi"
|
||||
purpose: "获取用户信息(工位操作员名称、报工人名称等)"
|
||||
- module: "yudao-module-system"
|
||||
api: "RoleApi"
|
||||
purpose: "角色权限校验"
|
||||
mq: []
|
||||
|
||||
# ============================================
|
||||
# 第三阶段:数据表设计
|
||||
# ============================================
|
||||
data_model:
|
||||
# 实体继承体系
|
||||
entity_hierarchy:
|
||||
base: "BaseDO"
|
||||
description: "所有 DO 继承 BaseDO,包含 id, creator, createTime, updater, updateTime, deleted 字段。部分表使用 @KeySequence 注解兼容 Oracle/PostgreSQL。"
|
||||
|
||||
# 核心数据表(按域组织)
|
||||
tables:
|
||||
# ========== 主数据域 (MD) - 17 张表 ==========
|
||||
- name: "mes_md_item"
|
||||
comment: "物料表"
|
||||
entity: "MesItemDO"
|
||||
domain: "MD"
|
||||
- name: "mes_md_item_type"
|
||||
comment: "物料分类表"
|
||||
entity: "MesItemTypeDO"
|
||||
domain: "MD"
|
||||
- name: "mes_md_item_batch_config"
|
||||
comment: "物料批次配置表"
|
||||
entity: "MesItemBatchConfigDO"
|
||||
domain: "MD"
|
||||
- name: "mes_md_product_bom"
|
||||
comment: "产品 BOM 表"
|
||||
entity: "MesProductBomDO"
|
||||
domain: "MD"
|
||||
- name: "mes_md_product_sip"
|
||||
comment: "产品 SIP 表(标准检验程序)"
|
||||
entity: "MesProductSipDO"
|
||||
domain: "MD"
|
||||
- name: "mes_md_product_sop"
|
||||
comment: "产品 SOP 表(标准作业指导书)"
|
||||
entity: "MesProductSopDO"
|
||||
domain: "MD"
|
||||
- name: "mes_md_client"
|
||||
comment: "客户表"
|
||||
entity: "MesClientDO"
|
||||
domain: "MD"
|
||||
- name: "mes_md_vendor"
|
||||
comment: "供应商表"
|
||||
entity: "MesVendorDO"
|
||||
domain: "MD"
|
||||
- name: "mes_md_unit_measure"
|
||||
comment: "计量单位表"
|
||||
entity: "MesUnitMeasureDO"
|
||||
domain: "MD"
|
||||
- name: "mes_md_workshop"
|
||||
comment: "车间表"
|
||||
entity: "MesWorkshopDO"
|
||||
domain: "MD"
|
||||
- name: "mes_md_workstation"
|
||||
comment: "工位表"
|
||||
entity: "MesWorkstationDO"
|
||||
domain: "MD"
|
||||
- name: "mes_md_workstation_machine"
|
||||
comment: "工位机器关联表"
|
||||
entity: "MesWorkstationMachineDO"
|
||||
domain: "MD"
|
||||
- name: "mes_md_workstation_tool"
|
||||
comment: "工位工具关联表"
|
||||
entity: "MesWorkstationToolDO"
|
||||
domain: "MD"
|
||||
- name: "mes_md_workstation_worker"
|
||||
comment: "工位工人关联表"
|
||||
entity: "MesWorkstationWorkerDO"
|
||||
domain: "MD"
|
||||
- name: "mes_md_auto_code_rule"
|
||||
comment: "自动编码规则表"
|
||||
entity: "MesAutoCodeRuleDO"
|
||||
domain: "MD"
|
||||
- name: "mes_md_auto_code_part"
|
||||
comment: "编码规则零件表"
|
||||
entity: "MesAutoCodePartDO"
|
||||
domain: "MD"
|
||||
- name: "mes_md_auto_code_record"
|
||||
comment: "编码记录表"
|
||||
entity: "MesAutoCodeRecordDO"
|
||||
domain: "MD"
|
||||
|
||||
# ========== 生产域 (PRO) - 17 张表 ==========
|
||||
- name: "mes_pro_work_order"
|
||||
comment: "生产工单表"
|
||||
entity: "MesWorkOrderDO"
|
||||
domain: "PRO"
|
||||
- name: "mes_pro_work_order_bom"
|
||||
comment: "工单 BOM 表"
|
||||
entity: "MesWorkOrderBomDO"
|
||||
domain: "PRO"
|
||||
- name: "mes_pro_task"
|
||||
comment: "生产任务表"
|
||||
entity: "MesTaskDO"
|
||||
domain: "PRO"
|
||||
- name: "mes_pro_task_issue"
|
||||
comment: "任务领料表"
|
||||
entity: "MesTaskIssueDO"
|
||||
domain: "PRO"
|
||||
- name: "mes_pro_route"
|
||||
comment: "工艺路线表"
|
||||
entity: "MesRouteDO"
|
||||
domain: "PRO"
|
||||
- name: "mes_pro_route_process"
|
||||
comment: "工艺路线工序表"
|
||||
entity: "MesRouteProcessDO"
|
||||
domain: "PRO"
|
||||
- name: "mes_pro_route_product"
|
||||
comment: "工艺路线产品关联表"
|
||||
entity: "MesRouteProductDO"
|
||||
domain: "PRO"
|
||||
- name: "mes_pro_route_product_bom"
|
||||
comment: "工艺路线产品 BOM 关联表"
|
||||
entity: "MesRouteProductBomDO"
|
||||
domain: "PRO"
|
||||
- name: "mes_pro_process"
|
||||
comment: "工序表"
|
||||
entity: "MesProcessDO"
|
||||
domain: "PRO"
|
||||
- name: "mes_pro_process_content"
|
||||
comment: "工序内容表"
|
||||
entity: "MesProcessContentDO"
|
||||
domain: "PRO"
|
||||
- name: "mes_pro_card"
|
||||
comment: "流转卡表"
|
||||
entity: "MesCardDO"
|
||||
domain: "PRO"
|
||||
- name: "mes_pro_card_process"
|
||||
comment: "流转卡工序表"
|
||||
entity: "MesCardProcessDO"
|
||||
domain: "PRO"
|
||||
- name: "mes_pro_feedback"
|
||||
comment: "报工反馈表"
|
||||
entity: "MesFeedbackDO"
|
||||
domain: "PRO"
|
||||
- name: "mes_pro_work_record"
|
||||
comment: "报工记录表"
|
||||
entity: "MesWorkRecordDO"
|
||||
domain: "PRO"
|
||||
- name: "mes_pro_work_record_log"
|
||||
comment: "报工日志表"
|
||||
entity: "MesWorkRecordLogDO"
|
||||
domain: "PRO"
|
||||
- name: "mes_pro_andon_config"
|
||||
comment: "安灯配置表"
|
||||
entity: "MesAndonConfigDO"
|
||||
domain: "PRO"
|
||||
- name: "mes_pro_andon_record"
|
||||
comment: "安灯记录表"
|
||||
entity: "MesAndonRecordDO"
|
||||
domain: "PRO"
|
||||
|
||||
# ========== 质量域 (QC) - 17 张表 ==========
|
||||
- name: "mes_qc_template"
|
||||
comment: "检验模板表"
|
||||
entity: "MesQcTemplateDO"
|
||||
domain: "QC"
|
||||
- name: "mes_qc_template_item"
|
||||
comment: "检验模板检验项表"
|
||||
entity: "MesQcTemplateItemDO"
|
||||
domain: "QC"
|
||||
- name: "mes_qc_template_indicator"
|
||||
comment: "检验模板指标表"
|
||||
entity: "MesQcTemplateIndicatorDO"
|
||||
domain: "QC"
|
||||
- name: "mes_qc_indicator"
|
||||
comment: "检验指标表"
|
||||
entity: "MesQcIndicatorDO"
|
||||
domain: "QC"
|
||||
- name: "mes_qc_indicator_result"
|
||||
comment: "指标结果表"
|
||||
entity: "MesQcIndicatorResultDO"
|
||||
domain: "QC"
|
||||
- name: "mes_qc_indicator_result_detail"
|
||||
comment: "指标结果明细表"
|
||||
entity: "MesQcIndicatorResultDetailDO"
|
||||
domain: "QC"
|
||||
- name: "mes_qc_defect"
|
||||
comment: "缺陷类型表"
|
||||
entity: "MesQcDefectDO"
|
||||
domain: "QC"
|
||||
- name: "mes_qc_defect_record"
|
||||
comment: "缺陷记录表"
|
||||
entity: "MesQcDefectRecordDO"
|
||||
domain: "QC"
|
||||
- name: "mes_qc_iqc"
|
||||
comment: "来料检验单表"
|
||||
entity: "MesQcIqcDO"
|
||||
domain: "QC"
|
||||
- name: "mes_qc_iqc_line"
|
||||
comment: "来料检验行表"
|
||||
entity: "MesQcIqcLineDO"
|
||||
domain: "QC"
|
||||
- name: "mes_qc_ipqc"
|
||||
comment: "过程检验单表"
|
||||
entity: "MesQcIpqcDO"
|
||||
domain: "QC"
|
||||
- name: "mes_qc_ipqc_line"
|
||||
comment: "过程检验行表"
|
||||
entity: "MesQcIpqcLineDO"
|
||||
domain: "QC"
|
||||
- name: "mes_qc_oqc"
|
||||
comment: "出货检验单表"
|
||||
entity: "MesQcOqcDO"
|
||||
domain: "QC"
|
||||
- name: "mes_qc_oqc_line"
|
||||
comment: "出货检验行表"
|
||||
entity: "MesQcOqcLineDO"
|
||||
domain: "QC"
|
||||
- name: "mes_qc_rqc"
|
||||
comment: "退货检验单表"
|
||||
entity: "MesQcRqcDO"
|
||||
domain: "QC"
|
||||
- name: "mes_qc_rqc_line"
|
||||
comment: "退货检验行表"
|
||||
entity: "MesQcRqcLineDO"
|
||||
domain: "QC"
|
||||
|
||||
# ========== 设备域 (DV) - 12 张表 ==========
|
||||
- name: "mes_dv_machinery"
|
||||
comment: "设备台账表"
|
||||
entity: "MesMachineryDO"
|
||||
domain: "DV"
|
||||
- name: "mes_dv_machinery_type"
|
||||
comment: "设备类型表"
|
||||
entity: "MesMachineryTypeDO"
|
||||
domain: "DV"
|
||||
- name: "mes_dv_check_plan"
|
||||
comment: "点检计划表"
|
||||
entity: "MesCheckPlanDO"
|
||||
domain: "DV"
|
||||
- name: "mes_dv_check_plan_machinery"
|
||||
comment: "点检计划设备关联表"
|
||||
entity: "MesCheckPlanMachineryDO"
|
||||
domain: "DV"
|
||||
- name: "mes_dv_check_plan_subject"
|
||||
comment: "点检计划检查项关联表"
|
||||
entity: "MesCheckPlanSubjectDO"
|
||||
domain: "DV"
|
||||
- name: "mes_dv_check_record"
|
||||
comment: "点检记录表"
|
||||
entity: "MesCheckRecordDO"
|
||||
domain: "DV"
|
||||
- name: "mes_dv_check_record_line"
|
||||
comment: "点检记录行表"
|
||||
entity: "MesCheckRecordLineDO"
|
||||
domain: "DV"
|
||||
- name: "mes_dv_mainten_record"
|
||||
comment: "保养记录表"
|
||||
entity: "MesMaintenRecordDO"
|
||||
domain: "DV"
|
||||
- name: "mes_dv_mainten_record_line"
|
||||
comment: "保养记录行表"
|
||||
entity: "MesMaintenRecordLineDO"
|
||||
domain: "DV"
|
||||
- name: "mes_dv_repair"
|
||||
comment: "维修单表"
|
||||
entity: "MesRepairDO"
|
||||
domain: "DV"
|
||||
- name: "mes_dv_repair_line"
|
||||
comment: "维修行表"
|
||||
entity: "MesRepairLineDO"
|
||||
domain: "DV"
|
||||
- name: "mes_dv_subject"
|
||||
comment: "检查项目表"
|
||||
entity: "MesSubjectDO"
|
||||
domain: "DV"
|
||||
|
||||
# ========== 工具域 (TM) - 2 张表 ==========
|
||||
- name: "mes_tm_tool"
|
||||
comment: "工具台账表"
|
||||
entity: "MesToolDO"
|
||||
domain: "TM"
|
||||
- name: "mes_tm_tool_type"
|
||||
comment: "工具类型表"
|
||||
entity: "MesToolTypeDO"
|
||||
domain: "TM"
|
||||
|
||||
# ========== 仓库域 (WM) - 40+ 张表 ==========
|
||||
- name: "mes_wm_warehouse"
|
||||
comment: "仓库表"
|
||||
entity: "MesWarehouseDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_warehouse_area"
|
||||
comment: "库区表"
|
||||
entity: "MesWarehouseAreaDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_warehouse_location"
|
||||
comment: "库位表"
|
||||
entity: "MesWarehouseLocationDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_material_stock"
|
||||
comment: "物料库存表"
|
||||
entity: "MesMaterialStockDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_batch"
|
||||
comment: "批次表"
|
||||
entity: "MesBatchDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_barcode"
|
||||
comment: "条码表"
|
||||
entity: "MesBarcodeDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_barcode_config"
|
||||
comment: "条码配置表"
|
||||
entity: "MesBarcodeConfigDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_sn"
|
||||
comment: "SN 序列号表"
|
||||
entity: "MesSnDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_transaction"
|
||||
comment: "库存事务表"
|
||||
entity: "MesTransactionDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_arrival_notice"
|
||||
comment: "到货通知表"
|
||||
entity: "MesArrivalNoticeDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_arrival_notice_line"
|
||||
comment: "到货通知行表"
|
||||
entity: "MesArrivalNoticeLineDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_item_receipt"
|
||||
comment: "采购入库表"
|
||||
entity: "MesItemReceiptDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_item_receipt_line"
|
||||
comment: "采购入库行表"
|
||||
entity: "MesItemReceiptLineDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_item_receipt_detail"
|
||||
comment: "采购入库详情表"
|
||||
entity: "MesItemReceiptDetailDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_product_receipt"
|
||||
comment: "产品入库表"
|
||||
entity: "MesProductReceiptDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_product_issue"
|
||||
comment: "生产领料表"
|
||||
entity: "MesProductIssueDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_return_issue"
|
||||
comment: "生产退料表"
|
||||
entity: "MesReturnIssueDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_misc_issue"
|
||||
comment: "其他出库表"
|
||||
entity: "MesMiscIssueDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_misc_receipt"
|
||||
comment: "其他入库表"
|
||||
entity: "MesMiscReceiptDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_product_sales"
|
||||
comment: "销售出库表"
|
||||
entity: "MesProductSalesDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_return_sales"
|
||||
comment: "销售退货表"
|
||||
entity: "MesReturnSalesDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_outsource_issue"
|
||||
comment: "委外发料表"
|
||||
entity: "MesOutsourceIssueDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_outsource_receipt"
|
||||
comment: "委外收料表"
|
||||
entity: "MesOutsourceReceiptDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_return_vendor"
|
||||
comment: "供应商退货表"
|
||||
entity: "MesReturnVendorDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_transfer"
|
||||
comment: "库存调拨表"
|
||||
entity: "MesTransferDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_package"
|
||||
comment: "包装表"
|
||||
entity: "MesPackageDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_package_line"
|
||||
comment: "包装行表"
|
||||
entity: "MesPackageLineDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_stock_taking_plan"
|
||||
comment: "盘点计划表"
|
||||
entity: "MesStockTakingPlanDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_stock_taking_task"
|
||||
comment: "盘点任务表"
|
||||
entity: "MesStockTakingTaskDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_stock_taking_task_result"
|
||||
comment: "盘点结果表"
|
||||
entity: "MesStockTakingTaskResultDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_item_consume"
|
||||
comment: "物料消耗表"
|
||||
entity: "MesItemConsumeDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_product_produce"
|
||||
comment: "产品产出表"
|
||||
entity: "MesProductProduceDO"
|
||||
domain: "WM"
|
||||
- name: "mes_wm_sales_notice"
|
||||
comment: "销售通知表"
|
||||
entity: "MesSalesNoticeDO"
|
||||
domain: "WM"
|
||||
|
||||
# ========== 日历域 (CAL) - 7 张表 ==========
|
||||
- name: "mes_cal_holiday"
|
||||
comment: "节假日表"
|
||||
entity: "MesCalHolidayDO"
|
||||
domain: "CAL"
|
||||
- name: "mes_cal_plan"
|
||||
comment: "排班计划表"
|
||||
entity: "MesCalPlanDO"
|
||||
domain: "CAL"
|
||||
- name: "mes_cal_plan_shift"
|
||||
comment: "排班计划班次表"
|
||||
entity: "MesCalPlanShiftDO"
|
||||
domain: "CAL"
|
||||
- name: "mes_cal_plan_team"
|
||||
comment: "排班计划班组表"
|
||||
entity: "MesCalPlanTeamDO"
|
||||
domain: "CAL"
|
||||
- name: "mes_cal_team"
|
||||
comment: "班组表"
|
||||
entity: "MesCalTeamDO"
|
||||
domain: "CAL"
|
||||
- name: "mes_cal_team_member"
|
||||
comment: "班组成员表"
|
||||
entity: "MesCalTeamMemberDO"
|
||||
domain: "CAL"
|
||||
- name: "mes_cal_team_shift"
|
||||
comment: "班组班次表"
|
||||
entity: "MesCalTeamShiftDO"
|
||||
domain: "CAL"
|
||||
|
||||
# 表关系(ER关系)
|
||||
relationships:
|
||||
# 主数据域
|
||||
- { from: "mes_md_item", to: "mes_md_item_type", type: "N:1", foreign_key: "item_type_id" }
|
||||
- { from: "mes_md_workstation", to: "mes_md_workshop", type: "N:1", foreign_key: "workshop_id" }
|
||||
- { from: "mes_md_workstation_machine", to: "mes_md_workstation", type: "N:1", foreign_key: "workstation_id" }
|
||||
- { from: "mes_md_workstation_tool", to: "mes_md_workstation", type: "N:1", foreign_key: "workstation_id" }
|
||||
- { from: "mes_md_workstation_worker", to: "mes_md_workstation", type: "N:1", foreign_key: "workstation_id" }
|
||||
- { from: "mes_md_auto_code_part", to: "mes_md_auto_code_rule", type: "N:1", foreign_key: "rule_id" }
|
||||
- { from: "mes_md_auto_code_record", to: "mes_md_auto_code_rule", type: "N:1", foreign_key: "rule_id" }
|
||||
# 生产域
|
||||
- { from: "mes_pro_work_order_bom", to: "mes_pro_work_order", type: "N:1", foreign_key: "work_order_id" }
|
||||
- { from: "mes_pro_task", to: "mes_pro_work_order", type: "N:1", foreign_key: "work_order_id" }
|
||||
- { from: "mes_pro_task", to: "mes_pro_route_process", type: "N:1", foreign_key: "route_process_id" }
|
||||
- { from: "mes_pro_task_issue", to: "mes_pro_task", type: "N:1", foreign_key: "task_id" }
|
||||
- { from: "mes_pro_route_process", to: "mes_pro_route", type: "N:1", foreign_key: "route_id" }
|
||||
- { from: "mes_pro_route_process", to: "mes_pro_process", type: "N:1", foreign_key: "process_id" }
|
||||
- { from: "mes_pro_card_process", to: "mes_pro_card", type: "N:1", foreign_key: "card_id" }
|
||||
- { from: "mes_pro_feedback", to: "mes_pro_work_order", type: "N:1", foreign_key: "work_order_id" }
|
||||
- { from: "mes_pro_andon_record", to: "mes_pro_andon_config", type: "N:1", foreign_key: "config_id" }
|
||||
# 质量域
|
||||
- { from: "mes_qc_template_item", to: "mes_qc_template", type: "N:1", foreign_key: "template_id" }
|
||||
- { from: "mes_qc_template_indicator", to: "mes_qc_template_item", type: "N:1", foreign_key: "template_item_id" }
|
||||
- { from: "mes_qc_iqc_line", to: "mes_qc_iqc", type: "N:1", foreign_key: "iqc_id" }
|
||||
- { from: "mes_qc_ipqc_line", to: "mes_qc_ipqc", type: "N:1", foreign_key: "ipqc_id" }
|
||||
- { from: "mes_qc_oqc_line", to: "mes_qc_oqc", type: "N:1", foreign_key: "oqc_id" }
|
||||
- { from: "mes_qc_rqc_line", to: "mes_qc_rqc", type: "N:1", foreign_key: "rqc_id" }
|
||||
- { from: "mes_qc_indicator_result", to: "mes_qc_indicator", type: "N:1", foreign_key: "indicator_id" }
|
||||
- { from: "mes_qc_indicator_result_detail", to: "mes_qc_indicator_result", type: "N:1", foreign_key: "result_id" }
|
||||
- { from: "mes_qc_defect_record", to: "mes_qc_defect", type: "N:1", foreign_key: "defect_id" }
|
||||
# 设备域
|
||||
- { from: "mes_dv_machinery", to: "mes_dv_machinery_type", type: "N:1", foreign_key: "machinery_type_id" }
|
||||
- { from: "mes_dv_check_plan_machinery", to: "mes_dv_check_plan", type: "N:1", foreign_key: "plan_id" }
|
||||
- { from: "mes_dv_check_plan_subject", to: "mes_dv_check_plan", type: "N:1", foreign_key: "plan_id" }
|
||||
- { from: "mes_dv_check_record", to: "mes_dv_check_plan", type: "N:1", foreign_key: "plan_id" }
|
||||
- { from: "mes_dv_check_record_line", to: "mes_dv_check_record", type: "N:1", foreign_key: "record_id" }
|
||||
- { from: "mes_dv_mainten_record_line", to: "mes_dv_mainten_record", type: "N:1", foreign_key: "record_id" }
|
||||
- { from: "mes_dv_repair_line", to: "mes_dv_repair", type: "N:1", foreign_key: "repair_id" }
|
||||
# 仓库域
|
||||
- { from: "mes_wm_warehouse_area", to: "mes_wm_warehouse", type: "N:1", foreign_key: "warehouse_id" }
|
||||
- { from: "mes_wm_warehouse_location", to: "mes_wm_warehouse_area", type: "N:1", foreign_key: "area_id" }
|
||||
- { from: "mes_wm_material_stock", to: "mes_md_item", type: "N:1", foreign_key: "item_id" }
|
||||
- { from: "mes_wm_arrival_notice_line", to: "mes_wm_arrival_notice", type: "N:1", foreign_key: "notice_id" }
|
||||
- { from: "mes_wm_item_receipt_line", to: "mes_wm_item_receipt", type: "N:1", foreign_key: "receipt_id" }
|
||||
- { from: "mes_wm_item_receipt_detail", to: "mes_wm_item_receipt_line", type: "N:1", foreign_key: "line_id" }
|
||||
- { from: "mes_wm_package_line", to: "mes_wm_package", type: "N:1", foreign_key: "package_id" }
|
||||
- { from: "mes_wm_stock_taking_task", to: "mes_wm_stock_taking_plan", type: "N:1", foreign_key: "plan_id" }
|
||||
- { from: "mes_wm_stock_taking_task_result", to: "mes_wm_stock_taking_task", type: "N:1", foreign_key: "task_id" }
|
||||
# 日历域
|
||||
- { from: "mes_cal_plan_shift", to: "mes_cal_plan", type: "N:1", foreign_key: "plan_id" }
|
||||
- { from: "mes_cal_plan_team", to: "mes_cal_plan", type: "N:1", foreign_key: "plan_id" }
|
||||
- { from: "mes_cal_plan_team", to: "mes_cal_team", type: "N:1", foreign_key: "team_id" }
|
||||
- { from: "mes_cal_team_member", to: "mes_cal_team", type: "N:1", foreign_key: "team_id" }
|
||||
- { from: "mes_cal_team_shift", to: "mes_cal_team", type: "N:1", foreign_key: "team_id" }
|
||||
|
||||
# ============================================
|
||||
# 第四阶段:代码使用设计
|
||||
# ============================================
|
||||
code_patterns:
|
||||
# Controller层规范
|
||||
controller:
|
||||
annotations:
|
||||
- "@Tag(name = '管理后台 - xxx') - Swagger 文档标签"
|
||||
- "@RestController - REST 控制器"
|
||||
- "@RequestMapping('/admin-api/mes/xxx') - 请求路径前缀"
|
||||
- "@Validated - 参数校验"
|
||||
- "@PreAuthorize('@ss.hasPermission('mes:xxx:action')') - 权限控制"
|
||||
example: |
|
||||
@Tag(name = "管理后台 - 物料管理")
|
||||
@RestController
|
||||
@RequestMapping("/admin-api/mes/item")
|
||||
@Validated
|
||||
public class MesItemController {
|
||||
@Resource
|
||||
private MesItemService itemService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建物料")
|
||||
@PreAuthorize("@ss.hasPermission('mes:item:create')")
|
||||
public CommonResult<Long> create(@Valid @RequestBody MesItemSaveReqVO reqVO) {
|
||||
return success(itemService.create(reqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新物料")
|
||||
@PreAuthorize("@ss.hasPermission('mes:item:update')")
|
||||
public CommonResult<Boolean> update(@Valid @RequestBody MesItemSaveReqVO reqVO) {
|
||||
itemService.update(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除物料")
|
||||
@PreAuthorize("@ss.hasPermission('mes:item:delete')")
|
||||
public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
|
||||
itemService.delete(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获取物料详情")
|
||||
@PreAuthorize("@ss.hasPermission('mes:item:query')")
|
||||
public CommonResult<MesItemRespVO> get(@RequestParam("id") Long id) {
|
||||
return success(itemService.get(id));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "物料分页查询")
|
||||
@PreAuthorize("@ss.hasPermission('mes:item:query')")
|
||||
public CommonResult<PageResult<MesItemRespVO>> page(@Valid MesItemPageReqVO reqVO) {
|
||||
return success(itemService.page(reqVO));
|
||||
}
|
||||
}
|
||||
|
||||
# Service层规范
|
||||
service:
|
||||
interface_pattern: "XxxService 接口定义业务方法,XxxServiceImpl 实现类"
|
||||
impl_pattern: |
|
||||
@Service
|
||||
@Validated
|
||||
public class XxxServiceImpl implements XxxService {
|
||||
@Resource
|
||||
private XxxMapper xxxMapper;
|
||||
|
||||
// 使用 static final 定义常量
|
||||
// 使用 @Transactional 注解控制事务
|
||||
// 使用 validateXxxExists 方法校验存在性
|
||||
// 使用 exception(ErrorCode) 抛出业务异常
|
||||
}
|
||||
state_machine_example: |
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateStatus(Long id, Integer status) {
|
||||
MesWorkOrderDO order = validateWorkOrderExists(id);
|
||||
// 校验状态流转
|
||||
if (!MesWorkOrderStatusEnum.canTransit(order.getStatus(), status)) {
|
||||
throw exception(WORK_ORDER_STATUS_TRANSIT_FAIL);
|
||||
}
|
||||
// CAS 更新
|
||||
workOrderMapper.updateStatus(id, order.getStatus(), status);
|
||||
}
|
||||
|
||||
# 数据访问规范
|
||||
dal:
|
||||
mapper_pattern: |
|
||||
public interface XxxMapper extends BaseMapperX<XxxDO> {
|
||||
default PageResult<XxxDO> selectPage(XxxPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<XxxDO>()
|
||||
.likeIfPresent(XxxDO::getName, reqVO.getName())
|
||||
.eqIfPresent(XxxDO::getStatus, reqVO.getStatus())
|
||||
.orderByDesc(XxxDO::getId));
|
||||
}
|
||||
}
|
||||
example: |
|
||||
public interface MesItemMapper extends BaseMapperX<MesItemDO> {
|
||||
default PageResult<MesItemDO> selectPage(MesItemPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<MesItemDO>()
|
||||
.likeIfPresent(MesItemDO::getName, reqVO.getName())
|
||||
.likeIfPresent(MesItemDO::getCode, reqVO.getCode())
|
||||
.eqIfPresent(MesItemDO::getItemTypeId, reqVO.getItemTypeId())
|
||||
.eqIfPresent(MesItemDO::getStatus, reqVO.getStatus())
|
||||
.orderByDesc(MesItemDO::getId));
|
||||
}
|
||||
}
|
||||
|
||||
# VO Assembly 模式
|
||||
vo_assembly: |
|
||||
// 查询时批量获取关联数据,避免 N+1
|
||||
PageResult<MesWorkOrderDO> pageResult = workOrderMapper.selectPage(reqVO);
|
||||
Set<Long> itemIds = pageResult.getList().stream()
|
||||
.map(MesWorkOrderDO::getItemId).collect(Collectors.toSet());
|
||||
Map<Long, MesItemDO> itemMap = itemService.getItemMap(itemIds);
|
||||
|
||||
// 组装 VO
|
||||
return new PageResult<>(pageResult.getList().stream().map(order -> {
|
||||
MesWorkOrderRespVO vo = BeanUtils.toBean(order, MesWorkOrderRespVO.class);
|
||||
MesItemDO item = itemMap.get(order.getItemId());
|
||||
if (item != null) {
|
||||
vo.setItemName(item.getName());
|
||||
}
|
||||
return vo;
|
||||
}).collect(Collectors.toList()), pageResult.getTotal());
|
||||
|
||||
# 异常处理
|
||||
error_handling:
|
||||
code_prefix: "1-xxx-xxx-xxx"
|
||||
note: "具体错误码前缀需查阅 ErrorCodeConstants.java 确认"
|
||||
|
||||
# 统一响应
|
||||
response:
|
||||
success_pattern: "CommonResult.success(data)"
|
||||
error_pattern: "throw exception(ErrorCode)"
|
||||
|
||||
# ============================================
|
||||
# 第五阶段:扩展指南
|
||||
# ============================================
|
||||
extension_guide:
|
||||
# 新增物料属性
|
||||
new_feature:
|
||||
title: "新增物料属性"
|
||||
steps:
|
||||
- step: "1. 修改数据库表"
|
||||
description: "在 mes_md_item 表中添加新字段"
|
||||
- step: "2. 更新 DO 实体"
|
||||
description: "在 MesItemDO 中添加对应属性"
|
||||
- step: "3. 更新 VO"
|
||||
description: "在 MesItemSaveReqVO 和 MesItemRespVO 中添加字段"
|
||||
- step: "4. 更新 Controller"
|
||||
description: "确保新字段在创建/更新/查询接口中正确处理"
|
||||
|
||||
# 新增出入库类型
|
||||
new_channel:
|
||||
title: "新增出入库类型"
|
||||
steps:
|
||||
- step: "1. 创建出入库表"
|
||||
description: "参考 mes_wm_misc_issue 创建新的出入库主子表"
|
||||
- step: "2. 创建 DO 实体"
|
||||
description: "创建 XxxDO 继承 BaseDO"
|
||||
- step: "3. 创建 Mapper/Service/Controller"
|
||||
description: "遵循标准 CRUD 模式创建各层代码"
|
||||
- step: "4. 集成库存更新"
|
||||
description: "在 Service 中调用 MaterialStockService 更新库存"
|
||||
- step: "5. 添加权限配置"
|
||||
description: "在系统权限中添加 mes:xxx:create/update/delete/query 权限"
|
||||
|
||||
# 新增检验类型
|
||||
new_inspection:
|
||||
title: "新增检验类型"
|
||||
steps:
|
||||
- step: "1. 创建检验单表"
|
||||
description: "参考 mes_qc_iqc 创建新的检验主子表"
|
||||
- step: "2. 创建 DO/VO/Service/Controller"
|
||||
description: "遵循标准 CRUD + 主子表模式"
|
||||
- step: "3. 集成检验模板"
|
||||
description: "关联 QcTemplateDO 的检验项和指标"
|
||||
- step: "4. 集成缺陷管理"
|
||||
description: "检验不合格时关联 QcDefectRecordDO"
|
||||
- step: "5. 联动仓库操作"
|
||||
description: "检验结果触发对应的出入库操作"
|
||||
|
||||
# 最佳实践
|
||||
best_practices:
|
||||
- practice: "状态机校验"
|
||||
description: "所有状态变更前校验前置状态,使用 CAS 方式更新防止并发覆盖"
|
||||
- practice: "VO Assembly"
|
||||
description: "查询接口批量获取关联实体后组装 VO,避免 N+1 查询"
|
||||
- practice: "库存乐观锁"
|
||||
description: "出库操作使用 quantity >= #{outQuantity} 条件防止库存变为负数"
|
||||
- practice: "自动编码防重"
|
||||
description: "使用 SELECT FOR UPDATE 锁定编码记录,防止并发生成重复编码"
|
||||
- practice: "检验与入库联动"
|
||||
description: "IQC 合格后才能办理采购入库,不合格走供应商退货"
|
||||
- practice: "设备状态联动"
|
||||
description: "维修/保养完成后自动恢复设备状态为正常"
|
||||
- practice: "事务范围控制"
|
||||
description: "只包含写操作,查询移到事务外,避免长事务和锁竞争"
|
||||
|
||||
# ============================================
|
||||
# 依赖关系
|
||||
# ============================================
|
||||
dependencies:
|
||||
# 内部依赖(其他模块)
|
||||
internal:
|
||||
- module: "yudao-module-system"
|
||||
api: "AdminUserApi"
|
||||
purpose: "获取用户信息(工位操作员、报工人等)"
|
||||
- module: "yudao-module-system"
|
||||
api: "RoleApi"
|
||||
purpose: "角色权限校验"
|
||||
|
||||
# 外部依赖(第三方库)
|
||||
external:
|
||||
- name: "MyBatis-Plus"
|
||||
purpose: "ORM 框架,提供 CRUD 封装,BaseMapperX"
|
||||
- name: "Swagger/OpenAPI"
|
||||
purpose: "API 文档注解"
|
||||
- name: "MapStruct"
|
||||
purpose: "VO/DO 对象映射转换"
|
||||
- name: "Hutool"
|
||||
purpose: "Java 工具类库"
|
||||
|
||||
# ============================================
|
||||
# 关键文件清单
|
||||
# ============================================
|
||||
key_files:
|
||||
- path: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/ItemService.java"
|
||||
purpose: "物料管理服务接口"
|
||||
- path: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/WorkOrderService.java"
|
||||
purpose: "工单管理服务接口,核心状态机"
|
||||
- path: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/TaskService.java"
|
||||
purpose: "生产任务服务,甘特图支持"
|
||||
- path: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/CardService.java"
|
||||
purpose: "流转卡服务,工序执行跟踪"
|
||||
- path: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/FeedbackService.java"
|
||||
purpose: "报工反馈服务,审批流程"
|
||||
- path: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/QcIqcService.java"
|
||||
purpose: "来料检验服务"
|
||||
- path: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/MachineryService.java"
|
||||
purpose: "设备管理服务"
|
||||
- path: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/RepairService.java"
|
||||
purpose: "维修管理服务,状态机"
|
||||
- path: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/MaterialStockService.java"
|
||||
purpose: "库存管理服务,出入库核心"
|
||||
- path: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/TransferService.java"
|
||||
purpose: "调拨服务,双向库存更新"
|
||||
- path: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/AutoCodeService.java"
|
||||
purpose: "自动编码服务,策略模式"
|
||||
- path: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/CalPlanService.java"
|
||||
purpose: "排班计划服务"
|
||||
- path: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java"
|
||||
purpose: "业务类型常量定义"
|
||||
- path: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java"
|
||||
purpose: "错误码常量定义"
|
||||
@@ -1,638 +0,0 @@
|
||||
# Skill 文档 - 微信公众号模块 (yudao-module-mp)
|
||||
# 提取时间: 2026-03-18
|
||||
|
||||
skill:
|
||||
id: "skill-mp"
|
||||
name: "WeChat MP (Public Account) Skill"
|
||||
version: "1.0.0"
|
||||
module_path: "yudao-module-mp"
|
||||
created_at: "2026-03-18"
|
||||
updated_at: "2026-03-18"
|
||||
|
||||
# ============================================
|
||||
# 第一阶段:设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
# 业务定位:微信公众号全生命周期管理平台
|
||||
# 解决问题:统一管理公众号账号、粉丝、消息、素材、菜单、自动回复、模板消息等核心功能
|
||||
# 系统定位:作为企业与微信用户交互的关键渠道,支撑营销推送、客户服务等业务场景
|
||||
business_position: |
|
||||
微信公众号管理模块,提供完整的公众号运营能力:
|
||||
- 多公众号管理:支持接入多个公众号账号,实现统一管理
|
||||
- 粉丝管理:粉丝信息同步、标签分组、用户画像
|
||||
- 消息管理:消息接收、客服消息、模板消息推送
|
||||
- 素材管理:临时素材、永久素材、图文消息
|
||||
- 菜单管理:自定义菜单创建与响应
|
||||
- 自动回复:关注回复、关键词回复、消息回复
|
||||
- 数据统计:用户分析、消息分析、接口分析
|
||||
|
||||
# 设计原则
|
||||
design_principles:
|
||||
- name: "消息路由模式"
|
||||
description: "采用 WxMpMessageRouter 实现消息分发,通过责任链模式处理不同类型消息和事件"
|
||||
- name: "多账号隔离"
|
||||
description: "通过 MpServiceFactory 工厂模式管理多公众号 WxMpService 实例,实现账号级别的隔离"
|
||||
- name: "事件驱动架构"
|
||||
description: "微信回调事件通过 Handler 处理器链式处理,支持关注、取消关注、菜单点击、扫码等事件"
|
||||
- name: "素材统一管理"
|
||||
description: "临时素材和永久素材统一抽象,自动下载并存储到本地文件服务"
|
||||
- name: "多租户支持"
|
||||
description: "账号层支持租户隔离,通过 TenantBaseDO 实现数据隔离"
|
||||
|
||||
# 领域模型
|
||||
domain_model:
|
||||
aggregates:
|
||||
- name: "MpAccount"
|
||||
type: "聚合根"
|
||||
description: "公众号账号聚合根,管理公众号配置信息"
|
||||
entities: ["MpMenu", "MpTag", "MpStatistics"]
|
||||
- name: "MpUser"
|
||||
type: "聚合根"
|
||||
description: "公众号粉丝聚合根,关联粉丝标签和消息记录"
|
||||
entities: ["MpTag"]
|
||||
- name: "MpMessage"
|
||||
type: "聚合根"
|
||||
description: "消息聚合根,包含自动回复配置和消息模板"
|
||||
entities: ["MpAutoReply", "MpMessageTemplate"]
|
||||
- name: "MpMaterial"
|
||||
type: "聚合根"
|
||||
description: "素材聚合根,管理临时素材和永久素材"
|
||||
entities: []
|
||||
|
||||
value_objects:
|
||||
- name: "MpMessageDO.Article"
|
||||
description: "图文消息值对象,包含标题、描述、图片链接、跳转链接"
|
||||
- name: "MpAutoReplyTypeEnum"
|
||||
description: "自动回复类型枚举:关注回复、消息回复、关键词回复"
|
||||
- name: "MpAutoReplyMatchEnum"
|
||||
description: "关键词匹配模式枚举:完全匹配、半匹配"
|
||||
- name: "MpMessageSendFromEnum"
|
||||
description: "消息发送方向枚举:粉丝发给公众号、公众号发给粉丝"
|
||||
|
||||
services:
|
||||
- name: "MpAccountService"
|
||||
description: "公众号账号管理服务,提供账号CRUD和缓存能力"
|
||||
- name: "MpUserService"
|
||||
description: "粉丝管理服务,提供粉丝同步、更新、查询能力"
|
||||
- name: "MpMessageService"
|
||||
description: "消息服务,处理消息接收、发送、自动回复"
|
||||
- name: "MpMaterialService"
|
||||
description: "素材服务,处理素材上传、下载、管理"
|
||||
- name: "MpMenuService"
|
||||
description: "菜单服务,管理自定义菜单"
|
||||
- name: "MpAutoReplyService"
|
||||
description: "自动回复服务,处理关注回复、关键词回复、消息回复"
|
||||
- name: "MpMessageTemplateService"
|
||||
description: "模板消息服务,管理模板同步和发送"
|
||||
- name: "MpStatisticsService"
|
||||
description: "统计数据服务,获取用户分析、消息分析数据"
|
||||
|
||||
# ============================================
|
||||
# 第二阶段:架构设计
|
||||
# ============================================
|
||||
architecture:
|
||||
# 分层架构
|
||||
layers:
|
||||
- name: "controller"
|
||||
purpose: "HTTP接口层,提供管理后台和微信回调接口"
|
||||
components:
|
||||
- "MpAccountController: 公众号账号管理接口"
|
||||
- "MpUserController: 粉丝管理接口"
|
||||
- "MpMessageController: 消息管理接口"
|
||||
- "MpMessageTemplateController: 模板消息接口"
|
||||
- "MpAutoReplyController: 自动回复管理接口"
|
||||
- "MpMaterialController: 素材管理接口"
|
||||
- "MpMenuController: 菜单管理接口"
|
||||
- "MpTagController: 标签管理接口"
|
||||
- "MpOpenController: 微信回调入口(签名校验、消息处理)"
|
||||
- "MpStatisticsController: 数据统计接口"
|
||||
- "MpDraftController: 草稿管理接口"
|
||||
- "MpFreePublishController: 发布能力接口"
|
||||
|
||||
- name: "service"
|
||||
purpose: "业务逻辑层"
|
||||
components:
|
||||
- "MpAccountService: 账号管理核心服务"
|
||||
- "MpUserService: 粉丝管理服务"
|
||||
- "MpMessageService: 消息处理服务"
|
||||
- "MpMaterialService: 素材管理服务"
|
||||
- "MpMenuService: 菜单管理服务"
|
||||
- "MpAutoReplyService: 自动回复服务"
|
||||
- "MpMessageTemplateService: 模板消息服务"
|
||||
- "MpTagService: 标签管理服务"
|
||||
- "MpStatisticsService: 统计服务"
|
||||
|
||||
- name: "handler"
|
||||
purpose: "微信消息事件处理器"
|
||||
components:
|
||||
- "SubscribeHandler: 关注事件处理器"
|
||||
- "UnsubscribeHandler: 取消关注事件处理器"
|
||||
- "MenuHandler: 菜单点击事件处理器"
|
||||
- "MessageReceiveHandler: 消息接收处理器"
|
||||
- "MessageAutoReplyHandler: 自动回复处理器"
|
||||
- "LocationHandler: 地理位置处理器"
|
||||
- "ScanHandler: 扫码事件处理器"
|
||||
- "KfSessionHandler: 客服会话处理器"
|
||||
|
||||
- name: "dal"
|
||||
purpose: "数据访问层"
|
||||
components:
|
||||
- "MpAccountMapper: 账号数据访问"
|
||||
- "MpUserMapper: 粉丝数据访问"
|
||||
- "MpMessageMapper: 消息数据访问"
|
||||
- "MpMaterialMapper: 素材数据访问"
|
||||
- "MpMenuMapper: 菜单数据访问"
|
||||
- "MpAutoReplyMapper: 自动回复数据访问"
|
||||
- "MpMessageTemplateMapper: 模板消息数据访问"
|
||||
- "MpTagMapper: 标签数据访问"
|
||||
|
||||
- name: "framework/mp"
|
||||
purpose: "微信公众号框架核心"
|
||||
components:
|
||||
- "MpServiceFactory: WxMpService 工厂接口"
|
||||
- "DefaultMpServiceFactory: 默认工厂实现,管理多账号 WxMpService"
|
||||
- "MpContextHolder: 公众号上下文持有者"
|
||||
|
||||
# 设计模式应用
|
||||
design_patterns:
|
||||
- pattern: "Factory Pattern"
|
||||
location: "framework/mp/core/MpServiceFactory.java"
|
||||
purpose: "创建和管理 WxMpService 实例,支持多公众号场景"
|
||||
- pattern: "Chain of Responsibility"
|
||||
location: "DefaultMpServiceFactory.buildMpMessageRouter()"
|
||||
purpose: "WxMpMessageRouter 实现消息处理链,按规则路由到不同 Handler"
|
||||
- pattern: "Strategy Pattern"
|
||||
location: "service/handler/*"
|
||||
purpose: "不同类型消息/事件由不同 Handler 实现 WxMpMessageHandler 接口处理"
|
||||
- pattern: "Convert Pattern"
|
||||
location: "convert/*"
|
||||
purpose: "MapStruct 转换器,VO/DO/微信对象之间转换"
|
||||
- pattern: "Template Method"
|
||||
location: "MpOpenController.handleMessage()"
|
||||
purpose: "统一的微信回调处理流程:签名校验 -> 消息解析 -> 路由处理 -> 响应构建"
|
||||
|
||||
# 模块间通信
|
||||
communication:
|
||||
apis: [] # 无对外暴露的 API 模块
|
||||
|
||||
consumers:
|
||||
- module: "yudao-module-system"
|
||||
purpose: "依赖用户体系、权限体系"
|
||||
- module: "yudao-module-infra"
|
||||
purpose: "依赖文件服务存储素材"
|
||||
|
||||
mq: [] # 未使用消息队列
|
||||
|
||||
# ============================================
|
||||
# 第三阶段:数据表设计
|
||||
# ============================================
|
||||
data_model:
|
||||
# 实体继承体系
|
||||
entity_hierarchy:
|
||||
base: "TenantBaseDO | BaseDO"
|
||||
description: "MpAccountDO 使用 TenantBaseDO 支持多租户,其他实体使用 BaseDO"
|
||||
|
||||
# 核心数据表
|
||||
tables:
|
||||
- name: "mp_account"
|
||||
comment: "公众号账号表"
|
||||
entity: "MpAccountDO"
|
||||
extends: "TenantBaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "主键ID" }
|
||||
- { name: "name", type: "String", comment: "公众号名称" }
|
||||
- { name: "account", type: "String", comment: "公众号账号" }
|
||||
- { name: "app_id", type: "String", comment: "公众号AppId" }
|
||||
- { name: "app_secret", type: "String", comment: "公众号密钥" }
|
||||
- { name: "token", type: "String", comment: "公众号Token" }
|
||||
- { name: "aes_key", type: "String", comment: "消息加解密密钥" }
|
||||
- { name: "qr_code_url", type: "String", comment: "二维码图片URL" }
|
||||
- { name: "remark", type: "String", comment: "备注" }
|
||||
indexes:
|
||||
- { name: "uk_app_id", columns: ["app_id"] }
|
||||
|
||||
- name: "mp_user"
|
||||
comment: "公众号粉丝表"
|
||||
entity: "MpUserDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "主键ID" }
|
||||
- { name: "openid", type: "String", comment: "粉丝标识OpenId" }
|
||||
- { name: "union_id", type: "String", comment: "微信生态唯一标识" }
|
||||
- { name: "subscribe_status", type: "Integer", comment: "关注状态(1已关注/2取消关注)" }
|
||||
- { name: "subscribe_time", type: "LocalDateTime", comment: "关注时间" }
|
||||
- { name: "unsubscribe_time", type: "LocalDateTime", comment: "取消关注时间" }
|
||||
- { name: "nickname", type: "String", comment: "昵称" }
|
||||
- { name: "head_image_url", type: "String", comment: "头像地址" }
|
||||
- { name: "language", type: "String", comment: "语言" }
|
||||
- { name: "country", type: "String", comment: "国家" }
|
||||
- { name: "province", type: "String", comment: "省份" }
|
||||
- { name: "city", type: "String", comment: "城市" }
|
||||
- { name: "remark", type: "String", comment: "备注" }
|
||||
- { name: "tag_ids", type: "List<Long>", comment: "标签ID数组" }
|
||||
- { name: "account_id", type: "Long", comment: "公众号账号ID" }
|
||||
- { name: "app_id", type: "String", comment: "公众号AppId(冗余)" }
|
||||
indexes:
|
||||
- { name: "idx_account_id", columns: ["account_id"] }
|
||||
- { name: "uk_appid_openid", columns: ["app_id", "openid"] }
|
||||
|
||||
- name: "mp_message"
|
||||
comment: "公众号消息表"
|
||||
entity: "MpMessageDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "主键ID" }
|
||||
- { name: "msg_id", type: "Long", comment: "微信消息ID" }
|
||||
- { name: "account_id", type: "Long", comment: "公众号账号ID" }
|
||||
- { name: "app_id", type: "String", comment: "公众号AppId" }
|
||||
- { name: "user_id", type: "Long", comment: "粉丝编号" }
|
||||
- { name: "openid", type: "String", comment: "粉丝OpenId" }
|
||||
- { name: "type", type: "String", comment: "消息类型(text/image/voice/video等)" }
|
||||
- { name: "send_from", type: "Integer", comment: "消息来源(1粉丝发给公众号/2公众号发给粉丝)" }
|
||||
- { name: "content", type: "String", comment: "文本消息内容" }
|
||||
- { name: "media_id", type: "String", comment: "媒体文件ID" }
|
||||
- { name: "media_url", type: "String", comment: "媒体文件URL" }
|
||||
- { name: "recognition", type: "String", comment: "语音识别文本" }
|
||||
- { name: "format", type: "String", comment: "语音格式" }
|
||||
- { name: "title", type: "String", comment: "标题" }
|
||||
- { name: "description", type: "String", comment: "描述" }
|
||||
- { name: "thumb_media_id", type: "String", comment: "缩略图媒体ID" }
|
||||
- { name: "thumb_media_url", type: "String", comment: "缩略图URL" }
|
||||
- { name: "url", type: "String", comment: "链接地址" }
|
||||
- { name: "location_x", type: "Double", comment: "地理位置纬度" }
|
||||
- { name: "location_y", type: "Double", comment: "地理位置经度" }
|
||||
- { name: "scale", type: "Double", comment: "地图缩放级别" }
|
||||
- { name: "label", type: "String", comment: "地理位置信息" }
|
||||
- { name: "articles", type: "List<Article>", comment: "图文消息数组(JSON)" }
|
||||
- { name: "music_url", type: "String", comment: "音乐链接" }
|
||||
- { name: "hq_music_url", type: "String", comment: "高质量音乐链接" }
|
||||
- { name: "event", type: "String", comment: "事件类型" }
|
||||
- { name: "event_key", type: "String", comment: "事件Key值" }
|
||||
indexes:
|
||||
- { name: "idx_account_id", columns: ["account_id"] }
|
||||
- { name: "idx_user_id", columns: ["user_id"] }
|
||||
|
||||
- name: "mp_material"
|
||||
comment: "公众号素材表"
|
||||
entity: "MpMaterialDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "主键ID" }
|
||||
- { name: "account_id", type: "Long", comment: "公众号账号ID" }
|
||||
- { name: "app_id", type: "String", comment: "公众号AppId" }
|
||||
- { name: "media_id", type: "String", comment: "微信素材ID" }
|
||||
- { name: "type", type: "String", comment: "文件类型(image/voice/video/thumb)" }
|
||||
- { name: "permanent", type: "Boolean", comment: "是否永久素材" }
|
||||
- { name: "url", type: "String", comment: "文件服务器URL" }
|
||||
- { name: "name", type: "String", comment: "文件名称" }
|
||||
- { name: "mp_url", type: "String", comment: "公众号文件URL(永久素材)" }
|
||||
- { name: "title", type: "String", comment: "视频素材标题" }
|
||||
- { name: "introduction", type: "String", comment: "视频素材描述" }
|
||||
indexes:
|
||||
- { name: "idx_account_id", columns: ["account_id"] }
|
||||
- { name: "idx_media_id", columns: ["media_id"] }
|
||||
|
||||
- name: "mp_menu"
|
||||
comment: "公众号菜单表"
|
||||
entity: "MpMenuDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "主键ID" }
|
||||
- { name: "account_id", type: "Long", comment: "公众号账号ID" }
|
||||
- { name: "app_id", type: "String", comment: "公众号AppId" }
|
||||
- { name: "name", type: "String", comment: "菜单名称" }
|
||||
- { name: "menu_key", type: "String", comment: "菜单标识" }
|
||||
- { name: "parent_id", type: "Long", comment: "父菜单ID" }
|
||||
- { name: "type", type: "String", comment: "按钮类型(click/view/miniprogram等)" }
|
||||
- { name: "url", type: "String", comment: "网页链接" }
|
||||
- { name: "mini_program_app_id", type: "String", comment: "小程序AppId" }
|
||||
- { name: "mini_program_page_path", type: "String", comment: "小程序页面路径" }
|
||||
- { name: "article_id", type: "String", comment: "跳转图文媒体ID" }
|
||||
- { name: "reply_message_type", type: "String", comment: "回复消息类型" }
|
||||
- { name: "reply_content", type: "String", comment: "回复文本内容" }
|
||||
- { name: "reply_media_id", type: "String", comment: "回复媒体ID" }
|
||||
- { name: "reply_media_url", type: "String", comment: "回复媒体URL" }
|
||||
- { name: "reply_articles", type: "List<Article>", comment: "回复图文消息(JSON)" }
|
||||
indexes:
|
||||
- { name: "idx_account_id", columns: ["account_id"] }
|
||||
|
||||
- name: "mp_auto_reply"
|
||||
comment: "公众号自动回复表"
|
||||
entity: "MpAutoReplyDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "主键ID" }
|
||||
- { name: "account_id", type: "Long", comment: "公众号账号ID" }
|
||||
- { name: "app_id", type: "String", comment: "公众号AppId" }
|
||||
- { name: "type", type: "Integer", comment: "回复类型(1关注回复/2消息回复/3关键词回复)" }
|
||||
- { name: "request_keyword", type: "String", comment: "请求关键词" }
|
||||
- { name: "request_match", type: "Integer", comment: "关键词匹配模式(1完全匹配/2半匹配)" }
|
||||
- { name: "request_message_type", type: "String", comment: "请求消息类型" }
|
||||
- { name: "response_message_type", type: "String", comment: "响应消息类型" }
|
||||
- { name: "response_content", type: "String", comment: "响应文本内容" }
|
||||
- { name: "response_media_id", type: "String", comment: "响应媒体ID" }
|
||||
- { name: "response_media_url", type: "String", comment: "响应媒体URL" }
|
||||
- { name: "response_articles", type: "List<Article>", comment: "响应图文消息(JSON)" }
|
||||
indexes:
|
||||
- { name: "idx_account_id", columns: ["account_id"] }
|
||||
|
||||
- name: "mp_message_template"
|
||||
comment: "公众号模板消息表"
|
||||
entity: "MpMessageTemplateDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "主键ID" }
|
||||
- { name: "account_id", type: "Long", comment: "公众号账号ID" }
|
||||
- { name: "app_id", type: "String", comment: "公众号AppId" }
|
||||
- { name: "template_id", type: "String", comment: "模板ID" }
|
||||
- { name: "title", type: "String", comment: "模板标题" }
|
||||
- { name: "content", type: "String", comment: "模板内容" }
|
||||
- { name: "example", type: "String", comment: "模板示例" }
|
||||
- { name: "primary_industry", type: "String", comment: "一级行业" }
|
||||
- { name: "deputy_industry", type: "String", comment: "二级行业" }
|
||||
indexes:
|
||||
- { name: "idx_account_id", columns: ["account_id"] }
|
||||
- { name: "idx_template_id", columns: ["template_id"] }
|
||||
|
||||
- name: "mp_tag"
|
||||
comment: "公众号标签表"
|
||||
entity: "MpTagDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "主键ID" }
|
||||
- { name: "tag_id", type: "Long", comment: "微信标签ID" }
|
||||
- { name: "name", type: "String", comment: "标签名称" }
|
||||
- { name: "count", type: "Integer", comment: "标签下粉丝数" }
|
||||
- { name: "account_id", type: "Long", comment: "公众号账号ID" }
|
||||
- { name: "app_id", type: "String", comment: "公众号AppId" }
|
||||
indexes:
|
||||
- { name: "idx_account_id", columns: ["account_id"] }
|
||||
|
||||
# 表关系(ER关系)
|
||||
relationships:
|
||||
- from: "mp_user"
|
||||
to: "mp_account"
|
||||
type: "N:1"
|
||||
foreign_key: "account_id"
|
||||
- from: "mp_message"
|
||||
to: "mp_account"
|
||||
type: "N:1"
|
||||
foreign_key: "account_id"
|
||||
- from: "mp_message"
|
||||
to: "mp_user"
|
||||
type: "N:1"
|
||||
foreign_key: "user_id"
|
||||
- from: "mp_material"
|
||||
to: "mp_account"
|
||||
type: "N:1"
|
||||
foreign_key: "account_id"
|
||||
- from: "mp_menu"
|
||||
to: "mp_account"
|
||||
type: "N:1"
|
||||
foreign_key: "account_id"
|
||||
- from: "mp_auto_reply"
|
||||
to: "mp_account"
|
||||
type: "N:1"
|
||||
foreign_key: "account_id"
|
||||
- from: "mp_message_template"
|
||||
to: "mp_account"
|
||||
type: "N:1"
|
||||
foreign_key: "account_id"
|
||||
- from: "mp_tag"
|
||||
to: "mp_account"
|
||||
type: "N:1"
|
||||
foreign_key: "account_id"
|
||||
|
||||
# ============================================
|
||||
# 第四阶段:代码使用设计
|
||||
# ============================================
|
||||
code_patterns:
|
||||
# Controller层规范
|
||||
controller:
|
||||
annotations:
|
||||
- "@Tag(name = '管理后台 - XXX')"
|
||||
- "@RestController"
|
||||
- "@RequestMapping('/mp/xxx')"
|
||||
- "@Validated"
|
||||
- "@PreAuthorize('@ss.hasPermission(\"mp:xxx:action\")')"
|
||||
example: |
|
||||
@Tag(name = "管理后台 - 公众号模版消息")
|
||||
@RestController
|
||||
@RequestMapping("/mp/message-template")
|
||||
@Validated
|
||||
public class MpMessageTemplateController {
|
||||
@Resource
|
||||
private MpMessageTemplateService messageTemplateService;
|
||||
|
||||
@PostMapping("/send")
|
||||
@Operation(summary = "给粉丝发送模版消息")
|
||||
@PreAuthorize("@ss.hasPermission('mp:message-template:send')")
|
||||
public CommonResult<Boolean> sendMessageTemplate(@Valid @RequestBody MpMessageTemplateSendReqVO sendReqVO) {
|
||||
messageTemplateService.sendMessageTempalte(sendReqVO);
|
||||
return success(true);
|
||||
}
|
||||
}
|
||||
|
||||
# Service层规范
|
||||
service:
|
||||
interface_pattern: "接口定义业务方法,参数使用VO或BO对象"
|
||||
impl_pattern: "实现类注入MpServiceFactory获取WxMpService,处理微信API调用"
|
||||
example: |
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class MpMessageTemplateServiceImpl implements MpMessageTemplateService {
|
||||
|
||||
@Resource
|
||||
@Lazy // 延迟加载,解决循环依赖
|
||||
private MpServiceFactory mpServiceFactory;
|
||||
|
||||
@Override
|
||||
public void sendMessageTempalte(MpMessageTemplateSendReqVO sendReqVO) {
|
||||
// 1. 获得关联信息
|
||||
MpUserDO user = mpUserService.getRequiredUser(sendReqVO.getUserId());
|
||||
MpMessageTemplateDO template = validateMsgTemplateExists(sendReqVO.getId());
|
||||
|
||||
// 2. 构建模板消息并发送
|
||||
WxMpTemplateMessage templateMessage = buildTemplateMessage(template, user, sendReqVO);
|
||||
try {
|
||||
mpServiceFactory.getRequiredMpService(template.getAppId())
|
||||
.getTemplateMsgService().sendTemplateMsg(templateMessage);
|
||||
} catch (WxErrorException e) {
|
||||
throw exception(MESSAGE_TEMPLATE_SEND_FAIL, e.getError().getErrorMsg());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 消息处理器规范
|
||||
handler:
|
||||
pattern: "实现 WxMpMessageHandler 接口,通过 @Component 注册为 Spring Bean"
|
||||
example: |
|
||||
@Component
|
||||
@Slf4j
|
||||
public class SubscribeHandler implements WxMpMessageHandler {
|
||||
|
||||
@Resource
|
||||
private MpUserService mpUserService;
|
||||
@Resource
|
||||
private MpAutoReplyService mpAutoReplyService;
|
||||
|
||||
@Override
|
||||
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
|
||||
WxMpService weixinService, WxSessionManager sessionManager) {
|
||||
// 1. 获取粉丝信息
|
||||
WxMpUser wxMpUser = weixinService.getUserService().userInfo(wxMessage.getFromUser());
|
||||
|
||||
// 2. 保存粉丝信息
|
||||
mpUserService.saveUser(MpContextHolder.getAppId(), wxMpUser);
|
||||
|
||||
// 3. 回复关注欢迎语
|
||||
return mpAutoReplyService.replyForSubscribe(MpContextHolder.getAppId(), wxMessage);
|
||||
}
|
||||
}
|
||||
|
||||
# 数据访问规范
|
||||
dal:
|
||||
mapper_pattern: "继承 BaseMapperX,使用 LambdaQueryWrapperX 构建查询条件"
|
||||
example: |
|
||||
@Mapper
|
||||
public interface MpMessageMapper extends BaseMapperX<MpMessageDO> {
|
||||
|
||||
default PageResult<MpMessageDO> selectPage(MpMessagePageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<MpMessageDO>()
|
||||
.eqIfPresent(MpMessageDO::getAccountId, reqVO.getAccountId())
|
||||
.eqIfPresent(MpMessageDO::getUserId, reqVO.getUserId())
|
||||
.orderByDesc(MpMessageDO::getId));
|
||||
}
|
||||
}
|
||||
|
||||
# 异常处理
|
||||
error_handling:
|
||||
code_prefix: "1-006-XXX-XXX"
|
||||
examples:
|
||||
- code: "1_006_000_000"
|
||||
message: "公众号账号不存在"
|
||||
- code: "1_006_003_000"
|
||||
message: "粉丝不存在"
|
||||
- code: "1_006_005_000"
|
||||
message: "发送消息失败,原因:{}"
|
||||
- code: "1_006_009_000"
|
||||
message: "自动回复不存在"
|
||||
- code: "1_006_010_004"
|
||||
message: "发送模版消息失败,原因:{}"
|
||||
|
||||
# 统一响应
|
||||
response:
|
||||
success_pattern: "CommonResult.success(data)"
|
||||
error_pattern: "throw exception(ERROR_CODE, args...)"
|
||||
|
||||
# ============================================
|
||||
# 第五阶段:扩展指南
|
||||
# ============================================
|
||||
extension_guide:
|
||||
# 新增消息类型处理
|
||||
new_feature:
|
||||
title: "新增消息类型处理器"
|
||||
steps:
|
||||
- step: 1
|
||||
action: "创建Handler类"
|
||||
description: "在 service/handler/ 目录下创建新的处理器类,实现 WxMpMessageHandler 接口"
|
||||
- step: 2
|
||||
action: "注册到消息路由"
|
||||
description: "在 DefaultMpServiceFactory.buildMpMessageRouter() 中添加路由规则"
|
||||
code: |
|
||||
// 示例:添加新消息类型处理
|
||||
router.rule().async(false)
|
||||
.msgType(WxConsts.XmlMsgType.XXX)
|
||||
.handler(newHandler).end();
|
||||
- step: 3
|
||||
action: "实现业务逻辑"
|
||||
description: "在 handle() 方法中实现消息处理逻辑,返回 WxMpXmlOutMessage 响应"
|
||||
|
||||
# 新增自动回复类型
|
||||
new_channel:
|
||||
title: "新增自动回复类型"
|
||||
steps:
|
||||
- step: 1
|
||||
action: "扩展枚举"
|
||||
description: "在 MpAutoReplyTypeEnum 中添加新的类型"
|
||||
- step: 2
|
||||
action: "创建自动回复配置"
|
||||
description: "通过 MpAutoReplyController 创建自动回复规则"
|
||||
- step: 3
|
||||
action: "实现回复逻辑"
|
||||
description: "在 MpAutoReplyServiceImpl 中实现对应的回复逻辑"
|
||||
|
||||
# 最佳实践
|
||||
best_practices:
|
||||
- title: "多账号管理"
|
||||
description: "使用 MpServiceFactory 获取指定账号的 WxMpService,避免账号混淆"
|
||||
example: "WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);"
|
||||
- title: "消息上下文"
|
||||
description: "使用 MpContextHolder 在处理链中传递 appId 上下文"
|
||||
example: "String appId = MpContextHolder.getAppId();"
|
||||
- title: "异步处理"
|
||||
description: "消息接收使用异步处理,避免阻塞微信回调响应"
|
||||
example: "router.rule().handler(messageReceiveHandler).next(); // next() 表示继续执行后续规则"
|
||||
- title: "素材下载"
|
||||
description: "收到粉丝发送的媒体消息时,自动下载并存储到本地文件服务"
|
||||
example: "mpMaterialService.downloadMaterialUrl(accountId, mediaId, type);"
|
||||
- title: "错误处理"
|
||||
description: "捕获 WxErrorException 并转换为业务异常"
|
||||
example: |
|
||||
try {
|
||||
wxMpService.someOperation();
|
||||
} catch (WxErrorException e) {
|
||||
throw exception(ERROR_CODE, e.getError().getErrorMsg());
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# 依赖关系
|
||||
# ============================================
|
||||
dependencies:
|
||||
# 内部依赖(其他模块)
|
||||
internal:
|
||||
- module: "yudao-module-system"
|
||||
purpose: "依赖用户体系、权限验证"
|
||||
- module: "yudao-module-infra"
|
||||
purpose: "依赖文件服务存储素材"
|
||||
|
||||
# 外部依赖(第三方库)
|
||||
external:
|
||||
- name: "wx-java-mp-spring-boot-starter"
|
||||
version: "继承自父POM"
|
||||
purpose: "微信公众号Java SDK,提供WxMpService等核心API"
|
||||
- name: "yudao-spring-boot-starter-mybatis"
|
||||
version: "继承自父POM"
|
||||
purpose: "MyBatis-Plus封装,提供BaseMapperX等"
|
||||
- name: "yudao-spring-boot-starter-redis"
|
||||
version: "继承自父POM"
|
||||
purpose: "Redis封装,用于WxMpService配置存储"
|
||||
- name: "yudao-spring-boot-starter-biz-tenant"
|
||||
version: "继承自父POM"
|
||||
purpose: "多租户支持"
|
||||
|
||||
# ============================================
|
||||
# 关键文件清单
|
||||
# ============================================
|
||||
key_files:
|
||||
- path: "yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/MpServiceFactory.java"
|
||||
purpose: "WxMpService工厂接口,管理多公众号实例"
|
||||
- path: "yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java"
|
||||
purpose: "默认工厂实现,构建WxMpService和WxMpMessageRouter"
|
||||
- path: "yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/MpOpenController.java"
|
||||
purpose: "微信回调入口,处理签名校验和消息路由"
|
||||
- path: "yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/SubscribeHandler.java"
|
||||
purpose: "关注事件处理器,保存粉丝信息并回复欢迎语"
|
||||
- path: "yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageAutoReplyHandler.java"
|
||||
purpose: "自动回复处理器,处理关键词和消息类型回复"
|
||||
- path: "yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java"
|
||||
purpose: "消息服务实现,处理消息接收和发送"
|
||||
- path: "yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageTemplateServiceImpl.java"
|
||||
purpose: "模板消息服务实现,同步和发送模板消息"
|
||||
- path: "yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/account/MpAccountDO.java"
|
||||
purpose: "公众号账号实体,支持多租户"
|
||||
- path: "yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java"
|
||||
purpose: "消息实体,包含多种消息类型字段"
|
||||
- path: "yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java"
|
||||
purpose: "错误码定义"
|
||||
@@ -1,654 +0,0 @@
|
||||
# Skill 文档 - 支付模块 (yudao-module-pay)
|
||||
# 支付中台核心模块,支持多渠道支付、退款、转账等功能
|
||||
|
||||
skill:
|
||||
id: "skill-pay"
|
||||
name: "Pay Module Skill"
|
||||
version: "1.0.0"
|
||||
module_path: "yudao-module-pay"
|
||||
created_at: "2026-03-18"
|
||||
updated_at: "2026-03-18"
|
||||
|
||||
# ============================================
|
||||
# 第一阶段:设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
# 业务定位:模块解决什么业务问题?在整个系统中的定位?
|
||||
business_position: |
|
||||
支付中台模块,为整个系统提供统一的支付能力支持。
|
||||
核心功能包括:
|
||||
1. 多渠道支付:支持微信支付(JSAPI/小程序/App/Native/H5/付款码)、支付宝(PC/WAP/App/扫码/条码)、钱包支付、模拟支付
|
||||
2. 统一退款:支持全额退款和部分退款,自动处理退款状态同步
|
||||
3. 转账功能:支持企业付款到零钱、转账到银行卡
|
||||
4. 钱包系统:用户余额充值、消费、提现
|
||||
5. 异步通知:统一处理各渠道的支付/退款回调
|
||||
|
||||
# 设计原则:遵循了哪些设计原则(SOLID、DDD)?有哪些架构决策?
|
||||
design_principles:
|
||||
- "策略模式:各支付渠道实现 PayClient 接口,统一调用方式,隔离渠道差异"
|
||||
- "模板方法模式:AbstractPayClient 定义统一流程,子类实现具体细节"
|
||||
- "工厂模式:PayClientFactory 管理客户端创建和缓存,支持动态刷新配置"
|
||||
- "开闭原则:新增支付渠道只需创建新的 PayClient 实现类,无需修改现有代码"
|
||||
- "依赖倒置:Service 层依赖 PayClient 接口而非具体实现"
|
||||
- "单一职责:每个 PayClient 只负责对应渠道的支付逻辑"
|
||||
|
||||
# 领域模型:核心领域对象有哪些?领域对象之间的关系?聚合根是什么?
|
||||
domain_model:
|
||||
aggregates:
|
||||
- name: "PayOrder"
|
||||
type: "聚合根"
|
||||
description: "支付订单聚合根,管理订单生命周期"
|
||||
entities: ["PayOrderExtension"]
|
||||
invariants:
|
||||
- "订单状态:WAITING -> SUCCESS/REFUND/CLOSED"
|
||||
- "退款金额不能超过支付金额"
|
||||
|
||||
- name: "PayRefund"
|
||||
type: "聚合根"
|
||||
description: "退款单聚合根,管理退款流程"
|
||||
entities: []
|
||||
invariants:
|
||||
- "退款状态:WAITING -> SUCCESS/FAILURE"
|
||||
- "一个支付订单可以有多个退款单"
|
||||
|
||||
- name: "PayChannel"
|
||||
type: "实体"
|
||||
description: "支付渠道配置,关联支付客户端"
|
||||
entities: []
|
||||
|
||||
- name: "PayApp"
|
||||
type: "实体"
|
||||
description: "支付应用,业务系统的支付入口"
|
||||
entities: []
|
||||
|
||||
value_objects:
|
||||
- name: "PayClientConfig"
|
||||
description: "支付客户端配置,不同渠道有不同的配置类型"
|
||||
implementations: ["AlipayPayClientConfig", "WxPayClientConfig"]
|
||||
|
||||
services:
|
||||
- name: "PayOrderService"
|
||||
description: "支付订单领域服务,负责订单创建、提交、回调处理"
|
||||
- name: "PayRefundService"
|
||||
description: "退款领域服务,负责退款发起、回调处理"
|
||||
- name: "PayChannelService"
|
||||
description: "渠道管理服务,负责渠道配置和客户端获取"
|
||||
- name: "PayNotifyService"
|
||||
description: "通知服务,负责异步通知的处理和重试"
|
||||
|
||||
# ============================================
|
||||
# 第二阶段:架构设计
|
||||
# ============================================
|
||||
architecture:
|
||||
# 分层架构
|
||||
layers:
|
||||
- name: "api"
|
||||
purpose: "模块间API接口,供其他模块调用"
|
||||
components:
|
||||
- "PayOrderApi: 支付订单API"
|
||||
- "PayRefundApi: 退款API"
|
||||
- "PayTransferApi: 转账API"
|
||||
- "PayWalletApi: 钱包API"
|
||||
|
||||
- name: "controller"
|
||||
purpose: "HTTP接口,暴露给前端调用"
|
||||
components:
|
||||
- "PayOrderController: 支付订单管理"
|
||||
- "PayRefundController: 退款管理"
|
||||
- "PayChannelController: 渠道配置管理"
|
||||
- "PayAppController: 应用管理"
|
||||
- "PayNotifyController: 通知回调"
|
||||
|
||||
- name: "service"
|
||||
purpose: "业务逻辑层,核心领域服务"
|
||||
components:
|
||||
- "PayOrderServiceImpl: 订单创建、提交、回调"
|
||||
- "PayRefundServiceImpl: 退款创建、回调"
|
||||
- "PayChannelServiceImpl: 渠道管理"
|
||||
- "PayNotifyServiceImpl: 通知处理"
|
||||
|
||||
- name: "framework/pay/core/client"
|
||||
purpose: "支付客户端抽象层,对接各支付渠道SDK"
|
||||
components:
|
||||
- "PayClient: 客户端接口"
|
||||
- "AbstractPayClient: 抽象基类"
|
||||
- "PayClientFactory: 客户端工厂"
|
||||
|
||||
- name: "dal"
|
||||
purpose: "数据访问层"
|
||||
components:
|
||||
- "PayOrderMapper: 订单数据访问"
|
||||
- "PayRefundMapper: 退款数据访问"
|
||||
- "PayChannelMapper: 渠道数据访问"
|
||||
|
||||
# 设计模式应用
|
||||
design_patterns:
|
||||
- pattern: "Factory Pattern"
|
||||
location: "yudao-module-pay/framework/pay/core/client/impl/PayClientFactoryImpl.java"
|
||||
purpose: "根据渠道编码创建对应的支付客户端,缓存已创建的客户端,支持配置热刷新"
|
||||
implementation: |
|
||||
// 注册渠道与客户端类的映射
|
||||
clientClass.put(WX_PUB, WxPubPayClient.class);
|
||||
clientClass.put(ALIPAY_WAP, AlipayWapPayClient.class);
|
||||
|
||||
// 反射创建客户端实例
|
||||
ReflectUtil.newInstance(payClientClass, channelId, config);
|
||||
|
||||
- pattern: "Template Method Pattern"
|
||||
location: "yudao-module-pay/framework/pay/core/client/impl/AbstractPayClient.java"
|
||||
purpose: "定义支付/退款/转账的统一流程,包括参数校验、异常处理、日志记录"
|
||||
implementation: |
|
||||
// 模板方法:统一下单
|
||||
public final PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
ValidationUtils.validate(reqDTO); // 参数校验
|
||||
try {
|
||||
return doUnifiedOrder(reqDTO); // 子类实现
|
||||
} catch (Throwable ex) {
|
||||
throw buildPayException(ex); // 统一异常处理
|
||||
}
|
||||
}
|
||||
|
||||
- pattern: "Strategy Pattern"
|
||||
location: "yudao-module-pay/framework/pay/core/client/impl/*"
|
||||
purpose: "不同支付渠道实现不同的支付策略,但暴露统一接口"
|
||||
implementations:
|
||||
- "AbstractAlipayPayClient: 支付宝支付策略"
|
||||
- "AbstractWxPayClient: 微信支付策略"
|
||||
- "WalletPayClient: 钱包支付策略"
|
||||
- "MockPayClient: 模拟支付策略"
|
||||
|
||||
# 模块间通信
|
||||
communication:
|
||||
apis:
|
||||
- name: "PayOrderApi"
|
||||
methods: ["createOrder", "getOrder"]
|
||||
consumers: ["订单模块", "会员模块"]
|
||||
- name: "PayRefundApi"
|
||||
methods: ["createRefund", "getRefund"]
|
||||
consumers: ["订单模块"]
|
||||
- name: "PayWalletApi"
|
||||
methods: ["addBalance", "getWallet"]
|
||||
consumers: ["会员模块", "订单模块"]
|
||||
|
||||
consumers: []
|
||||
|
||||
mq:
|
||||
- type: "Job"
|
||||
name: "PayOrderSyncJob"
|
||||
purpose: "定时同步待支付订单状态"
|
||||
- type: "Job"
|
||||
name: "PayOrderExpireJob"
|
||||
purpose: "定时关闭过期订单"
|
||||
- type: "Job"
|
||||
name: "PayNotifyJob"
|
||||
purpose: "异步通知重试"
|
||||
|
||||
# ============================================
|
||||
# 第三阶段:数据表设计
|
||||
# ============================================
|
||||
data_model:
|
||||
# 实体继承体系
|
||||
entity_hierarchy:
|
||||
base: "BaseDO | TenantBaseDO"
|
||||
description: "支付渠道 PayChannelDO 继承 TenantBaseDO 支持多租户,其他实体继承 BaseDO"
|
||||
|
||||
# 核心数据表
|
||||
tables:
|
||||
- name: "pay_app"
|
||||
comment: "支付应用表"
|
||||
entity: "PayAppDO"
|
||||
extends: "TenantBaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "应用编号" }
|
||||
- { name: "name", type: "String", comment: "应用名称" }
|
||||
- { name: "status", type: "Integer", comment: "开启状态" }
|
||||
- { name: "order_notify_url", type: "String", comment: "订单回调地址" }
|
||||
- { name: "refund_notify_url", type: "String", comment: "退款回调地址" }
|
||||
|
||||
- name: "pay_channel"
|
||||
comment: "支付渠道表"
|
||||
entity: "PayChannelDO"
|
||||
extends: "TenantBaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "渠道编号" }
|
||||
- { name: "code", type: "String", comment: "渠道编码,如 wx_pub, alipay_wap" }
|
||||
- { name: "status", type: "Integer", comment: "开启状态" }
|
||||
- { name: "fee_rate", type: "Double", comment: "渠道费率" }
|
||||
- { name: "app_id", type: "Long", comment: "应用编号" }
|
||||
- { name: "config", type: "PayClientConfig", comment: "支付配置JSON" }
|
||||
|
||||
- name: "pay_order"
|
||||
comment: "支付订单表"
|
||||
entity: "PayOrderDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "订单编号" }
|
||||
- { name: "app_id", type: "Long", comment: "应用编号" }
|
||||
- { name: "channel_id", type: "Long", comment: "渠道编号" }
|
||||
- { name: "channel_code", type: "String", comment: "渠道编码" }
|
||||
- { name: "user_id", type: "Long", comment: "用户编号" }
|
||||
- { name: "merchant_order_id", type: "String", comment: "商户订单号" }
|
||||
- { name: "subject", type: "String", comment: "商品标题" }
|
||||
- { name: "body", type: "String", comment: "商品描述" }
|
||||
- { name: "price", type: "Integer", comment: "支付金额(分)" }
|
||||
- { name: "status", type: "Integer", comment: "支付状态: 0待支付/10成功/20退款/30关闭" }
|
||||
- { name: "refund_price", type: "Integer", comment: "已退款金额(分)" }
|
||||
- { name: "channel_order_no", type: "String", comment: "渠道订单号" }
|
||||
- { name: "expire_time", type: "LocalDateTime", comment: "过期时间" }
|
||||
- { name: "success_time", type: "LocalDateTime", comment: "支付成功时间" }
|
||||
indexes:
|
||||
- { name: "idx_app_merchant", columns: ["app_id", "merchant_order_id"] }
|
||||
|
||||
- name: "pay_order_extension"
|
||||
comment: "支付订单扩展表"
|
||||
entity: "PayOrderExtensionDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "扩展编号" }
|
||||
- { name: "order_id", type: "Long", comment: "订单编号" }
|
||||
- { name: "no", type: "String", comment: "外部订单号" }
|
||||
- { name: "channel_id", type: "Long", comment: "渠道编号" }
|
||||
- { name: "channel_code", type: "String", comment: "渠道编码" }
|
||||
- { name: "status", type: "Integer", comment: "支付状态" }
|
||||
- { name: "channel_notify_data", type: "String", comment: "渠道回调数据" }
|
||||
|
||||
- name: "pay_refund"
|
||||
comment: "退款订单表"
|
||||
entity: "PayRefundDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "退款编号" }
|
||||
- { name: "no", type: "String", comment: "外部退款号" }
|
||||
- { name: "app_id", type: "Long", comment: "应用编号" }
|
||||
- { name: "channel_id", type: "Long", comment: "渠道编号" }
|
||||
- { name: "order_id", type: "Long", comment: "支付订单编号" }
|
||||
- { name: "order_no", type: "String", comment: "支付订单号" }
|
||||
- { name: "merchant_order_id", type: "String", comment: "商户订单号" }
|
||||
- { name: "merchant_refund_id", type: "String", comment: "商户退款号" }
|
||||
- { name: "pay_price", type: "Integer", comment: "原支付金额(分)" }
|
||||
- { name: "refund_price", type: "Integer", comment: "退款金额(分)" }
|
||||
- { name: "status", type: "Integer", comment: "退款状态: 0待退款/1成功/2失败" }
|
||||
- { name: "reason", type: "String", comment: "退款原因" }
|
||||
- { name: "channel_refund_no", type: "String", comment: "渠道退款单号" }
|
||||
- { name: "success_time", type: "LocalDateTime", comment: "退款成功时间" }
|
||||
|
||||
- name: "pay_wallet"
|
||||
comment: "钱包表"
|
||||
entity: "PayWalletDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "钱包编号" }
|
||||
- { name: "user_id", type: "Long", comment: "用户编号" }
|
||||
- { name: "balance", type: "Integer", comment: "余额(分)" }
|
||||
- { name: "freeze_price", type: "Integer", comment: "冻结金额(分)" }
|
||||
|
||||
- name: "pay_wallet_transaction"
|
||||
comment: "钱包交易表"
|
||||
entity: "PayWalletTransactionDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "交易编号" }
|
||||
- { name: "wallet_id", type: "Long", comment: "钱包编号" }
|
||||
- { name: "biz_type", type: "Integer", comment: "业务类型" }
|
||||
- { name: "biz_id", type: "String", comment: "业务编号" }
|
||||
- { name: "amount", type: "Integer", comment: "交易金额(分)" }
|
||||
- { name: "balance", type: "Integer", comment: "交易后余额(分)" }
|
||||
|
||||
- name: "pay_transfer"
|
||||
comment: "转账表"
|
||||
entity: "PayTransferDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "转账编号" }
|
||||
- { name: "no", type: "String", comment: "外部转账号" }
|
||||
- { name: "app_id", type: "Long", comment: "应用编号" }
|
||||
- { name: "channel_id", type: "Long", comment: "渠道编号" }
|
||||
- { name: "type", type: "Integer", comment: "转账类型" }
|
||||
- { name: "price", type: "Integer", comment: "转账金额(分)" }
|
||||
- { name: "status", type: "Integer", comment: "转账状态" }
|
||||
- { name: "user_name", type: "String", comment: "收款人姓名" }
|
||||
- { name: "user_account", type: "String", comment: "收款人账号" }
|
||||
|
||||
- name: "pay_notify_task"
|
||||
comment: "通知任务表"
|
||||
entity: "PayNotifyTaskDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "Long", comment: "任务编号" }
|
||||
- { name: "app_id", type: "Long", comment: "应用编号" }
|
||||
- { name: "type", type: "Integer", comment: "通知类型: 1订单/2退款" }
|
||||
- { name: "data_id", type: "Long", comment: "数据编号" }
|
||||
- { name: "status", type: "Integer", comment: "通知状态" }
|
||||
- { name: "notify_times", type: "Integer", comment: "通知次数" }
|
||||
- { name: "max_notify_times", type: "Integer", comment: "最大通知次数" }
|
||||
|
||||
# 表关系(ER关系)
|
||||
relationships:
|
||||
- from: "pay_app"
|
||||
to: "pay_channel"
|
||||
type: "1:N"
|
||||
foreign_key: "app_id"
|
||||
description: "一个应用可以有多个支付渠道"
|
||||
|
||||
- from: "pay_app"
|
||||
to: "pay_order"
|
||||
type: "1:N"
|
||||
foreign_key: "app_id"
|
||||
description: "一个应用可以有多个支付订单"
|
||||
|
||||
- from: "pay_order"
|
||||
to: "pay_order_extension"
|
||||
type: "1:N"
|
||||
foreign_key: "order_id"
|
||||
description: "一个订单可以有多个扩展单(多次支付尝试)"
|
||||
|
||||
- from: "pay_order"
|
||||
to: "pay_refund"
|
||||
type: "1:N"
|
||||
foreign_key: "order_id"
|
||||
description: "一个订单可以有多个退款单"
|
||||
|
||||
- from: "pay_channel"
|
||||
to: "pay_order"
|
||||
type: "1:N"
|
||||
foreign_key: "channel_id"
|
||||
description: "一个渠道可以有多个订单"
|
||||
|
||||
# ============================================
|
||||
# 第四阶段:代码使用设计
|
||||
# ============================================
|
||||
code_patterns:
|
||||
# Controller层规范
|
||||
controller:
|
||||
annotations:
|
||||
- "@RestController"
|
||||
- "@RequestMapping"
|
||||
- "@PreAuthorize('@ss.hasPermission')")
|
||||
- "@Operation(summary = ...)"
|
||||
example: |
|
||||
@RestController
|
||||
@RequestMapping("/pay/order")
|
||||
@Tag(name = "管理后台 - 支付订单")
|
||||
public class PayOrderController {
|
||||
@PostMapping("/submit")
|
||||
@Operation(summary = "提交支付订单")
|
||||
@PermitAll // 支付提交允许匿名访问
|
||||
public CommonResult<PayOrderSubmitRespVO> submitOrder(
|
||||
@RequestBody @Valid PayOrderSubmitReqVO reqVO,
|
||||
HttpServletRequest request) {
|
||||
return success(orderService.submitOrder(reqVO, getClientIP(request)));
|
||||
}
|
||||
}
|
||||
|
||||
# Service层规范
|
||||
service:
|
||||
interface_pattern: "接口定义业务方法,使用 @Valid 注解校验参数"
|
||||
impl_pattern: "实现类使用 @Service 和 @Validated 注解,事务使用 @Transactional"
|
||||
example: |
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class PayOrderServiceImpl implements PayOrderService {
|
||||
|
||||
@Override
|
||||
public Long createOrder(PayOrderCreateReqDTO reqDTO) {
|
||||
// 1. 校验 App
|
||||
PayAppDO app = appService.validPayApp(reqDTO.getAppKey());
|
||||
|
||||
// 2. 查询是否已存在
|
||||
PayOrderDO order = orderMapper.selectByAppIdAndMerchantOrderId(
|
||||
app.getId(), reqDTO.getMerchantOrderId());
|
||||
if (order != null) {
|
||||
return order.getId();
|
||||
}
|
||||
|
||||
// 3. 创建订单
|
||||
order = PayOrderConvert.INSTANCE.convert(reqDTO)
|
||||
.setAppId(app.getId())
|
||||
.setStatus(PayOrderStatusEnum.WAITING.getStatus());
|
||||
orderMapper.insert(order);
|
||||
return order.getId();
|
||||
}
|
||||
}
|
||||
|
||||
# 数据访问规范
|
||||
dal:
|
||||
mapper_pattern: "继承 BaseMapperX,使用 MyBatis-Plus 的 LambdaQueryWrapper"
|
||||
example: |
|
||||
@Mapper
|
||||
public interface PayOrderMapper extends BaseMapperX<PayOrderDO> {
|
||||
|
||||
default PayOrderDO selectByAppIdAndMerchantOrderId(Long appId, String merchantOrderId) {
|
||||
return selectOne(PayOrderDO::getAppId, appId,
|
||||
PayOrderDO::getMerchantOrderId, merchantOrderId);
|
||||
}
|
||||
|
||||
default int updateByIdAndStatus(Long id, Integer status, PayOrderDO updateObj) {
|
||||
return update(updateObj, new LambdaUpdateWrapper<PayOrderDO>()
|
||||
.eq(PayOrderDO::getId, id)
|
||||
.eq(PayOrderDO::getStatus, status));
|
||||
}
|
||||
}
|
||||
|
||||
# 异常处理
|
||||
error_handling:
|
||||
code_prefix: "1-007-XXX-XXX"
|
||||
examples:
|
||||
- code: "1_007_000_000"
|
||||
message: "App 不存在"
|
||||
- code: "1_007_001_000"
|
||||
message: "支付渠道的配置不存在"
|
||||
- code: "1_007_002_000"
|
||||
message: "支付订单不存在"
|
||||
- code: "1_007_002_003"
|
||||
message: "支付订单已经过期"
|
||||
- code: "1_007_006_000"
|
||||
message: "退款金额超过订单可退款金额"
|
||||
|
||||
# 统一响应
|
||||
response:
|
||||
success_pattern: "CommonResult.success(data)"
|
||||
error_pattern: "throw exception(ERROR_CODE, args...)"
|
||||
|
||||
# 支付客户端使用示例
|
||||
pay_client_usage:
|
||||
create_order: |
|
||||
// 1. 获取支付客户端
|
||||
PayClient client = channelService.getPayClient(channelId);
|
||||
|
||||
// 2. 构建统一下单请求
|
||||
PayOrderUnifiedReqDTO reqDTO = new PayOrderUnifiedReqDTO()
|
||||
.setOutTradeNo(orderExtension.getNo())
|
||||
.setSubject(order.getSubject())
|
||||
.setPrice(order.getPrice())
|
||||
.setExpireTime(order.getExpireTime())
|
||||
.setNotifyUrl(notifyUrl);
|
||||
|
||||
// 3. 调用支付渠道
|
||||
PayOrderRespDTO respDTO = client.unifiedOrder(reqDTO);
|
||||
|
||||
create_refund: |
|
||||
// 1. 获取支付客户端
|
||||
PayClient client = channelService.getPayClient(channelId);
|
||||
|
||||
// 2. 构建退款请求
|
||||
PayRefundUnifiedReqDTO reqDTO = new PayRefundUnifiedReqDTO()
|
||||
.setOutTradeNo(order.getNo())
|
||||
.setOutRefundNo(refund.getNo())
|
||||
.setRefundPrice(refund.getRefundPrice())
|
||||
.setReason(refund.getReason());
|
||||
|
||||
// 3. 调用退款接口
|
||||
PayRefundRespDTO respDTO = client.unifiedRefund(reqDTO);
|
||||
|
||||
# ============================================
|
||||
# 第五阶段:扩展指南
|
||||
# ============================================
|
||||
extension_guide:
|
||||
# 新增支付渠道步骤
|
||||
new_channel:
|
||||
title: "新增支付渠道(如添加银联支付)"
|
||||
steps:
|
||||
- step: 1
|
||||
title: "创建支付渠道枚举"
|
||||
file: "PayChannelEnum.java"
|
||||
action: "添加新的渠道编码,如 UNION_PAY(\"union_pay\", \"银联支付\")"
|
||||
|
||||
- step: 2
|
||||
title: "创建配置类"
|
||||
file: "UnionPayClientConfig.java"
|
||||
action: |
|
||||
实现 PayClientConfig 接口,定义银联支付所需的配置字段:
|
||||
@Data
|
||||
public class UnionPayClientConfig implements PayClientConfig {
|
||||
private String merchantId; // 商户号
|
||||
private String apiKey; // API密钥
|
||||
private String certPath; // 证书路径
|
||||
// ...
|
||||
}
|
||||
|
||||
- step: 3
|
||||
title: "创建抽象客户端类(可选)"
|
||||
file: "AbstractUnionPayClient.java"
|
||||
action: |
|
||||
如果有多种支付方式(如扫码、APP),可以创建抽象类:
|
||||
public abstract class AbstractUnionPayClient
|
||||
extends AbstractPayClient<UnionPayClientConfig> {
|
||||
// 实现公共逻辑
|
||||
}
|
||||
|
||||
- step: 4
|
||||
title: "创建具体客户端实现类"
|
||||
file: "UnionPayQrPayClient.java"
|
||||
action: |
|
||||
继承 AbstractPayClient 或抽象类,实现具体支付逻辑:
|
||||
public class UnionPayQrPayClient extends AbstractPayClient<UnionPayClientConfig> {
|
||||
|
||||
public UnionPayQrPayClient(Long channelId, UnionPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.UNION_PAY.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
// 初始化银联SDK
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
// 调用银联下单接口
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayOrderRespDTO doParseOrderNotify(...) {
|
||||
// 解析银联回调
|
||||
}
|
||||
|
||||
// 实现其他抽象方法...
|
||||
}
|
||||
|
||||
- step: 5
|
||||
title: "注册到工厂"
|
||||
file: "PayClientFactoryImpl.java"
|
||||
action: |
|
||||
在构造函数中注册新的客户端:
|
||||
public PayClientFactoryImpl() {
|
||||
// ...
|
||||
clientClass.put(UNION_PAY, UnionPayQrPayClient.class);
|
||||
}
|
||||
|
||||
- step: 6
|
||||
title: "添加单元测试"
|
||||
file: "UnionPayQrPayClientTest.java"
|
||||
action: "编写测试用例验证支付流程"
|
||||
|
||||
# 新增功能步骤
|
||||
new_feature:
|
||||
title: "新增业务功能"
|
||||
steps:
|
||||
- step: 1
|
||||
title: "创建DO实体"
|
||||
action: "在 dal/dataobject 下创建实体类"
|
||||
- step: 2
|
||||
title: "创建Mapper"
|
||||
action: "在 dal/mysql 下创建Mapper接口"
|
||||
- step: 3
|
||||
title: "创建Service接口和实现"
|
||||
action: "在 service 下创建服务层"
|
||||
- step: 4
|
||||
title: "创建Controller"
|
||||
action: "在 controller 下创建HTTP接口"
|
||||
- step: 5
|
||||
title: "创建API(如需模块间调用)"
|
||||
action: "在 api 下创建对外API接口"
|
||||
|
||||
# 最佳实践
|
||||
best_practices:
|
||||
- "支付金额统一使用 Integer 类型,单位为分,避免浮点精度问题"
|
||||
- "订单状态变更使用乐观锁,通过 updateByIdAndStatus 方法保证并发安全"
|
||||
- "支付回调需要验签,使用渠道提供的验签方法"
|
||||
- "异步通知需要支持重试机制,使用 PayNotifyTask 记录通知状态"
|
||||
- "支付订单需要设置过期时间,定时任务关闭过期订单"
|
||||
- "退款金额累计不能超过支付金额,在 Service 层进行校验"
|
||||
- "客户端配置支持热刷新,通过 refresh 方法更新配置"
|
||||
|
||||
# ============================================
|
||||
# 依赖关系
|
||||
# ============================================
|
||||
dependencies:
|
||||
# 内部依赖(其他模块)
|
||||
internal:
|
||||
- module: "yudao-module-system"
|
||||
api: "TenantApi"
|
||||
purpose: "获取租户信息"
|
||||
- module: "yudao-module-member"
|
||||
api: "MemberUserApi"
|
||||
purpose: "获取用户信息(钱包支付)"
|
||||
|
||||
# 外部依赖(第三方库)
|
||||
external:
|
||||
- name: "alipay-sdk-java"
|
||||
version: "4.x"
|
||||
purpose: "支付宝支付SDK"
|
||||
- name: "weixin-java-pay"
|
||||
version: "4.x"
|
||||
purpose: "微信支付SDK(WxJava)"
|
||||
- name: "hutool-all"
|
||||
version: "5.x"
|
||||
purpose: "Java工具库"
|
||||
- name: "mybatis-plus"
|
||||
version: "3.x"
|
||||
purpose: "ORM框架"
|
||||
|
||||
# ============================================
|
||||
# 关键文件清单
|
||||
# ============================================
|
||||
key_files:
|
||||
- path: "yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClient.java"
|
||||
purpose: "支付客户端接口,定义统一的支付/退款/转账方法"
|
||||
|
||||
- path: "yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/AbstractPayClient.java"
|
||||
purpose: "支付客户端抽象基类,实现模板方法模式"
|
||||
|
||||
- path: "yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImpl.java"
|
||||
purpose: "支付客户端工厂,管理客户端创建和缓存"
|
||||
|
||||
- path: "yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java"
|
||||
purpose: "支付宝支付抽象类,实现支付宝统一的接口"
|
||||
|
||||
- path: "yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java"
|
||||
purpose: "微信支付抽象类,实现微信统一的接口"
|
||||
|
||||
- path: "yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java"
|
||||
purpose: "支付订单服务实现,核心业务逻辑"
|
||||
|
||||
- path: "yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java"
|
||||
purpose: "退款服务实现"
|
||||
|
||||
- path: "yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java"
|
||||
purpose: "支付订单实体"
|
||||
|
||||
- path: "yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java"
|
||||
purpose: "支付渠道枚举"
|
||||
|
||||
- path: "yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java"
|
||||
purpose: "错误码定义"
|
||||
@@ -1,345 +0,0 @@
|
||||
# Skill 文档 - yudao-module-report
|
||||
# 数据报表模块知识提取
|
||||
|
||||
skill:
|
||||
id: "skill-report"
|
||||
name: "Report Skill"
|
||||
version: "1.0.0"
|
||||
module_path: "yudao-module-report"
|
||||
created_at: "2026-03-18"
|
||||
updated_at: "2026-03-18"
|
||||
|
||||
# ============================================
|
||||
# 第一阶段:设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
# 业务定位:模块解决什么业务问题?在整个系统中的定位?
|
||||
business_position: |
|
||||
report 模块主要实现数据可视化报表功能,为系统提供报表设计、大屏设计、数据分析等能力。
|
||||
核心功能包括:
|
||||
1. 积木报表集成:基于 JimuReport 实现打印设计、报表设计、图形设计、大屏设计
|
||||
2. GoView 大屏设计器:提供可视化大屏报表设计能力,支持 SQL 和 HTTP 数据源
|
||||
3. 数据可视化:支持多种图表类型,满足企业数据展示需求
|
||||
模块定位:作为企业级应用的可视化报表解决方案,与业务模块解耦,提供独立的数据展示能力。
|
||||
|
||||
# 设计原则:遵循了哪些设计原则(SOLID、DDD)?有哪些架构决策?
|
||||
design_principles:
|
||||
- "单一职责原则:GoViewProject 负责 project 管理,GoViewData 负责数据查询"
|
||||
- "开放封闭原则:通过 JmReportTokenServiceI 接口扩展积木报表认证机制"
|
||||
- "依赖倒置原则:Service 层依赖接口而非实现,便于测试和扩展"
|
||||
- "策略模式:支持多种数据源查询策略(SQL、HTTP),可扩展新数据源"
|
||||
- "适配器模式:JmReportTokenServiceImpl 适配积木报表与 yudao 认证体系"
|
||||
- "数据源抽象:默认使用 JdbcTemplate,支持切换至 ClickHouse 等大数据引擎"
|
||||
|
||||
# 领域模型:核心领域对象有哪些?领域对象之间的关系?聚合根是什么?
|
||||
domain_model:
|
||||
aggregates:
|
||||
- name: "GoViewProject"
|
||||
type: "聚合根"
|
||||
description: "大屏报表项目,包含项目配置、发布状态、报表内容等"
|
||||
entities: ["GoViewProjectDO"]
|
||||
value_objects: ["GoViewDataRespVO"]
|
||||
|
||||
value_objects:
|
||||
- name: "GoViewDataRespVO"
|
||||
type: "值对象"
|
||||
description: "数据响应结构,包含维度(dimensions)和数据明细(source)"
|
||||
- name: "GoViewProjectCreateReqVO"
|
||||
type: "值对象"
|
||||
description: "项目创建请求参数"
|
||||
- name: "GoViewProjectUpdateReqVO"
|
||||
type: "值对象"
|
||||
description: "项目更新请求参数"
|
||||
|
||||
services:
|
||||
- name: "GoViewProjectService"
|
||||
type: "领域服务"
|
||||
description: "管理大屏项目的创建、更新、删除、查询"
|
||||
- name: "GoViewDataService"
|
||||
type: "领域服务"
|
||||
description: "提供数据查询能力,支持 SQL/HTTP 等数据源"
|
||||
|
||||
# ============================================
|
||||
# 第二阶段:架构设计
|
||||
# ============================================
|
||||
architecture:
|
||||
# 分层架构
|
||||
layers:
|
||||
- name: "controller"
|
||||
purpose: "HTTP 接口层,提供 REST API"
|
||||
components:
|
||||
- "GoViewProjectController: 大屏项目管理接口"
|
||||
- "GoViewDataController: 数据查询接口(SQL/HTTP)"
|
||||
|
||||
- name: "service"
|
||||
purpose: "业务逻辑层"
|
||||
components:
|
||||
- "GoViewProjectService/GoViewProjectServiceImpl: 项目管理业务逻辑"
|
||||
- "GoViewDataService/GoViewDataServiceImpl: 数据查询业务逻辑"
|
||||
|
||||
- name: "dal"
|
||||
purpose: "数据访问层"
|
||||
components:
|
||||
- "GoViewProjectMapper: 项目数据访问"
|
||||
- "GoViewProjectDO: 项目实体"
|
||||
|
||||
- name: "convert"
|
||||
purpose: "对象转换层"
|
||||
components:
|
||||
- "GoViewProjectConvert: DO/VO 对象转换(MapStruct)"
|
||||
|
||||
- name: "framework"
|
||||
purpose: "框架集成层"
|
||||
components:
|
||||
- "JmReportConfiguration: 积木报表配置"
|
||||
- "JmReportTokenServiceImpl: 积木报表 Token 认证适配"
|
||||
- "JmOnlDragExternalServiceImpl: 积木仪表盘扩展服务"
|
||||
- "SecurityConfiguration: 安全配置"
|
||||
|
||||
# 设计模式应用
|
||||
design_patterns:
|
||||
- pattern: "适配器模式"
|
||||
location: "framework/jmreport/core/service/JmReportTokenServiceImpl.java"
|
||||
purpose: "将 yudao 的 OAuth2 认证体系适配到积木报表的 JmReportTokenServiceI 接口"
|
||||
- pattern: "策略模式"
|
||||
location: "controller/admin/goview/GoViewDataController.java"
|
||||
purpose: "支持多种数据查询策略:getDataBySQL、getDataByHttp"
|
||||
- pattern: "转换器模式"
|
||||
location: "convert/goview/GoViewProjectConvert.java"
|
||||
purpose: "使用 MapStruct 实现 DO 与 VO 之间的转换"
|
||||
|
||||
# 模块间通信
|
||||
communication:
|
||||
apis: [] # 对外暴露的API(通过 HTTP 接口暴露)
|
||||
consumers:
|
||||
- module: "system"
|
||||
api: "OAuth2TokenCommonApi"
|
||||
purpose: "验证 Token 获取用户信息"
|
||||
- module: "system"
|
||||
api: "PermissionCommonApi"
|
||||
purpose: "权限校验,判断用户角色"
|
||||
mq: [] # 无消息队列使用
|
||||
|
||||
# ============================================
|
||||
# 第三阶段:数据表设计
|
||||
# ============================================
|
||||
data_model:
|
||||
# 实体继承体系
|
||||
entity_hierarchy:
|
||||
base: "BaseDO"
|
||||
description: "report 模块实体继承 BaseDO,包含 id、creator、create_time、updater、update_time、deleted 字段"
|
||||
|
||||
# 核心数据表
|
||||
tables:
|
||||
- name: "report_go_view_project"
|
||||
comment: "GoView 大屏项目表"
|
||||
entity: "GoViewProjectDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键ID,自增" }
|
||||
- { name: "name", type: "VARCHAR(255)", comment: "项目名称" }
|
||||
- { name: "pic_url", type: "VARCHAR(1024)", comment: "预览图片URL" }
|
||||
- { name: "content", type: "LONGTEXT", comment: "报表内容,JSON格式配置" }
|
||||
- { name: "status", type: "TINYINT", comment: "发布状态:0-已发布,1-未发布" }
|
||||
- { name: "remark", type: "VARCHAR(255)", comment: "项目备注" }
|
||||
- { name: "creator", type: "VARCHAR(64)", comment: "创建人" }
|
||||
- { name: "create_time", type: "DATETIME", comment: "创建时间" }
|
||||
- { name: "updater", type: "VARCHAR(64)", comment: "更新人" }
|
||||
- { name: "update_time", type: "DATETIME", comment: "更新时间" }
|
||||
- { name: "deleted", type: "BIT", comment: "是否删除" }
|
||||
indexes:
|
||||
- { name: "idx_creator", columns: ["creator"] }
|
||||
|
||||
# 表关系(ER关系)
|
||||
relationships:
|
||||
- from: "report_go_view_project"
|
||||
to: "system_users"
|
||||
type: "N:1"
|
||||
foreign_key: "creator"
|
||||
comment: "项目创建人关联用户表"
|
||||
|
||||
# ============================================
|
||||
# 第四阶段:代码使用设计
|
||||
# ============================================
|
||||
code_patterns:
|
||||
# Controller层规范
|
||||
controller:
|
||||
annotations:
|
||||
- "@Tag(name = '管理后台 - XXX')"
|
||||
- "@RestController"
|
||||
- "@RequestMapping('/report/xxx')"
|
||||
- "@Validated"
|
||||
- "@PreAuthorize('@ss.hasPermission(...)')"
|
||||
example: |
|
||||
@Tag(name = "管理后台 - GoView 项目")
|
||||
@RestController
|
||||
@RequestMapping("/report/go-view/project")
|
||||
@Validated
|
||||
public class GoViewProjectController {
|
||||
|
||||
@Resource
|
||||
private GoViewProjectService goViewProjectService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建项目")
|
||||
@PreAuthorize("@ss.hasPermission('report:go-view-project:create')")
|
||||
public CommonResult<Long> createProject(@Valid @RequestBody GoViewProjectCreateReqVO createReqVO) {
|
||||
return success(goViewProjectService.createProject(createReqVO));
|
||||
}
|
||||
}
|
||||
|
||||
# Service层规范
|
||||
service:
|
||||
interface_pattern: "定义接口 + 使用 @Service 注解的实现类"
|
||||
impl_pattern: "使用 @Validated 注解支持参数校验,依赖注入使用 @Resource"
|
||||
example: |
|
||||
public interface GoViewProjectService {
|
||||
Long createProject(@Valid GoViewProjectCreateReqVO createReqVO);
|
||||
void updateProject(@Valid GoViewProjectUpdateReqVO updateReqVO);
|
||||
void deleteProject(Long id);
|
||||
GoViewProjectDO getProject(Long id);
|
||||
PageResult<GoViewProjectDO> getMyProjectPage(PageParam pageReqVO, Long userId);
|
||||
}
|
||||
|
||||
@Service
|
||||
@Validated
|
||||
public class GoViewProjectServiceImpl implements GoViewProjectService {
|
||||
|
||||
@Resource
|
||||
private GoViewProjectMapper goViewProjectMapper;
|
||||
|
||||
@Override
|
||||
public Long createProject(GoViewProjectCreateReqVO createReqVO) {
|
||||
GoViewProjectDO goViewProject = GoViewProjectConvert.INSTANCE.convert(createReqVO)
|
||||
.setStatus(CommonStatusEnum.DISABLE.getStatus());
|
||||
goViewProjectMapper.insert(goViewProject);
|
||||
return goViewProject.getId();
|
||||
}
|
||||
}
|
||||
|
||||
# 数据访问规范
|
||||
dal:
|
||||
mapper_pattern: "继承 BaseMapperX<DO>,使用 default 方法实现自定义查询"
|
||||
example: |
|
||||
@Mapper
|
||||
public interface GoViewProjectMapper extends BaseMapperX<GoViewProjectDO> {
|
||||
|
||||
default PageResult<GoViewProjectDO> selectPage(PageParam reqVO, Long userId) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<GoViewProjectDO>()
|
||||
.eq(GoViewProjectDO::getCreator, userId)
|
||||
.orderByDesc(GoViewProjectDO::getId));
|
||||
}
|
||||
}
|
||||
|
||||
# 异常处理
|
||||
error_handling:
|
||||
code_prefix: "1_003_000_000"
|
||||
examples:
|
||||
- code: "1_003_000_000"
|
||||
message: "GoView 项目不存在"
|
||||
constant: "GO_VIEW_PROJECT_NOT_EXISTS"
|
||||
usage: |
|
||||
// 使用方式
|
||||
if (goViewProjectMapper.selectById(id) == null) {
|
||||
throw exception(GO_VIEW_PROJECT_NOT_EXISTS);
|
||||
}
|
||||
|
||||
# 统一响应
|
||||
response:
|
||||
success_pattern: "CommonResult.success(data)"
|
||||
error_pattern: "throw exception(ERROR_CODE)"
|
||||
|
||||
# ============================================
|
||||
# 第五阶段:扩展指南
|
||||
# ============================================
|
||||
extension_guide:
|
||||
# 新增功能步骤
|
||||
new_feature:
|
||||
title: "新增报表功能"
|
||||
steps:
|
||||
- "1. 在 dal/dataobject 下创建 DO 实体类,继承 BaseDO"
|
||||
- "2. 在 dal/mysql 下创建 Mapper 接口,继承 BaseMapperX"
|
||||
- "3. 在 controller/admin/vo 下创建请求/响应 VO"
|
||||
- "4. 在 convert 下创建 Convert 接口(使用 MapStruct)"
|
||||
- "5. 在 service 下创建 Service 接口和实现类"
|
||||
- "6. 在 controller/admin 下创建 Controller"
|
||||
- "7. 在 enums/ErrorCodeConstants 添加错误码"
|
||||
|
||||
# 新增数据源类型
|
||||
new_channel:
|
||||
title: "新增数据源类型"
|
||||
steps:
|
||||
- "1. 在 GoViewDataController 中新增查询方法,如 getDataByElasticsearch"
|
||||
- "2. 在 GoViewDataService 接口中定义新方法"
|
||||
- "3. 在 GoViewDataServiceImpl 中实现查询逻辑"
|
||||
- "4. 创建对应的请求 VO(如 GoViewDataGetByEsReqVO)"
|
||||
- "5. 添加权限注解 @PreAuthorize('@ss.hasPermission(...)')"
|
||||
example: |
|
||||
// 新增 Elasticsearch 数据源查询
|
||||
@RequestMapping("/get-by-es")
|
||||
@Operation(summary = "使用 Elasticsearch 查询数据")
|
||||
@PreAuthorize("@ss.hasPermission('report:go-view-data:get-by-es')")
|
||||
public CommonResult<GoViewDataRespVO> getDataByEs(@Valid @RequestBody GoViewDataGetByEsReqVO reqVO) {
|
||||
return success(goViewDataService.getDataByEs(reqVO));
|
||||
}
|
||||
|
||||
# 最佳实践
|
||||
best_practices:
|
||||
- "数据查询时考虑性能,大数据量场景建议使用 ClickHouse 等列式数据库"
|
||||
- "报表内容使用 JSON 格式存储,前端负责解析渲染"
|
||||
- "项目创建时默认状态为未发布(status=1),需手动发布"
|
||||
- "使用 creator 字段实现数据隔离,用户只能看到自己创建的项目"
|
||||
- "积木报表集成时需注意 Token 传递,通过 customApiHeader 方法处理"
|
||||
|
||||
# ============================================
|
||||
# 依赖关系
|
||||
# ============================================
|
||||
dependencies:
|
||||
# 内部依赖(其他模块)
|
||||
internal:
|
||||
- module: "system"
|
||||
api: "OAuth2TokenCommonApi"
|
||||
purpose: "积木报表 Token 验证"
|
||||
- module: "system"
|
||||
api: "PermissionCommonApi"
|
||||
purpose: "积木报表权限校验"
|
||||
|
||||
# 外部依赖(第三方库)
|
||||
external:
|
||||
- name: "jimureport-spring-boot-starter"
|
||||
version: "管理在父 POM"
|
||||
purpose: "积木报表核心依赖,提供报表设计器"
|
||||
- name: "jimubi-spring-boot-starter"
|
||||
version: "管理在父 POM"
|
||||
purpose: "积木仪表盘/大屏设计器"
|
||||
- name: "spring-boot-starter-jdbc"
|
||||
version: "Spring Boot 管理"
|
||||
purpose: "JdbcTemplate 数据源查询"
|
||||
- name: "mapstruct"
|
||||
version: "管理在父 POM"
|
||||
purpose: "对象转换"
|
||||
|
||||
# ============================================
|
||||
# 关键文件清单
|
||||
# ============================================
|
||||
key_files:
|
||||
- path: "yudao-module-report/src/main/java/cn/iocoder/yudao/module/report/dal/dataobject/goview/GoViewProjectDO.java"
|
||||
purpose: "GoView 项目实体类,对应 report_go_view_project 表"
|
||||
- path: "yudao-module-report/src/main/java/cn/iocoder/yudao/module/report/service/goview/GoViewProjectService.java"
|
||||
purpose: "项目管理服务接口"
|
||||
- path: "yudao-module-report/src/main/java/cn/iocoder/yudao/module/report/service/goview/GoViewDataServiceImpl.java"
|
||||
purpose: "数据查询服务实现,核心 SQL 查询逻辑"
|
||||
- path: "yudao-module-report/src/main/java/cn/iocoder/yudao/module/report/controller/admin/goview/GoViewProjectController.java"
|
||||
purpose: "项目管理 HTTP 接口"
|
||||
- path: "yudao-module-report/src/main/java/cn/iocoder/yudao/module/report/controller/admin/goview/GoViewDataController.java"
|
||||
purpose: "数据查询 HTTP 接口"
|
||||
- path: "yudao-module-report/src/main/java/cn/iocoder/yudao/module/report/framework/jmreport/config/JmReportConfiguration.java"
|
||||
purpose: "积木报表配置类"
|
||||
- path: "yudao-module-report/src/main/java/cn/iocoder/yudao/module/report/framework/jmreport/core/service/JmReportTokenServiceImpl.java"
|
||||
purpose: "积木报表 Token 认证适配器"
|
||||
- path: "yudao-module-report/src/main/java/cn/iocoder/yudao/module/report/enums/ErrorCodeConstants.java"
|
||||
purpose: "错误码定义"
|
||||
- path: "yudao-module-report/src/main/java/cn/iocoder/yudao/module/report/dal/mysql/goview/GoViewProjectMapper.java"
|
||||
purpose: "项目数据访问 Mapper"
|
||||
- path: "yudao-module-report/src/main/java/cn/iocoder/yudao/module/report/convert/goview/GoViewProjectConvert.java"
|
||||
purpose: "对象转换器(MapStruct)"
|
||||
@@ -1,840 +0,0 @@
|
||||
# Skill 文档 - yudao-module-system 模块
|
||||
# 系统管理模块完整知识提取
|
||||
|
||||
skill:
|
||||
id: "skill-system"
|
||||
name: "System Module Skill"
|
||||
version: "1.0.0"
|
||||
module_path: "yudao-module-system"
|
||||
created_at: "2026-03-18"
|
||||
updated_at: "2026-03-18"
|
||||
|
||||
# ============================================
|
||||
# 第一阶段:设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
# 业务定位:模块解决什么业务问题?在整个系统中的定位?
|
||||
business_position: |
|
||||
yudao-module-system 是整个平台的核心基础设施模块,负责系统级的管理功能:
|
||||
- 用户管理:管理后台用户的增删改查、密码管理、状态控制
|
||||
- 角色管理:基于RBAC模型的角色定义与权限分配
|
||||
- 权限管理:菜单权限、按钮权限、数据权限的统一控制
|
||||
- 部门管理:组织架构的树形结构管理
|
||||
- 岗位管理:岗位信息的维护
|
||||
- 租户管理:多租户架构的核心支撑,租户创建、套餐绑定、过期控制
|
||||
- 字典管理:系统字典类型与字典数据的维护
|
||||
- 通知公告:系统公告的发布与管理
|
||||
- 短信/邮件/站内信:消息通知的三驾马车
|
||||
- OAuth2:开放授权协议支持
|
||||
- 社交登录:第三方登录集成(微信、钉钉等)
|
||||
|
||||
该模块是其他所有业务模块的基础,提供统一的用户认证、权限校验、租户隔离能力。
|
||||
|
||||
# 设计原则:遵循了哪些设计原则(SOLID、DDD)?有哪些架构决策?
|
||||
design_principles:
|
||||
- principle: "RBAC权限模型"
|
||||
description: "采用基于角色的访问控制模型,用户-角色-菜单三层关联,支持细粒度的按钮级权限控制"
|
||||
- principle: "多租户架构"
|
||||
description: "通过TenantBaseDO基类实现租户数据隔离,所有业务表自动携带tenant_id字段"
|
||||
- principle: "数据权限"
|
||||
description: "支持5种数据权限范围:全部、自定义部门、本部门、本部门及子部门、仅本人"
|
||||
- principle: "分层架构"
|
||||
description: "严格遵循Controller-Service-DAL三层架构,API层提供跨模块调用能力"
|
||||
- principle: "接口隔离"
|
||||
description: "Service接口与实现分离,便于测试和扩展"
|
||||
- principle: "缓存优先"
|
||||
description: "权限数据使用Spring Cache + Redis缓存,减少数据库查询压力"
|
||||
- principle: "统一异常处理"
|
||||
description: "通过ErrorCodeConstants定义统一错误码,格式为1-002-XXX-XXX"
|
||||
|
||||
# 领域模型:核心领域对象有哪些?领域对象之间的关系?聚合根是什么?
|
||||
domain_model:
|
||||
aggregates:
|
||||
- name: "User(用户聚合)"
|
||||
type: "聚合根"
|
||||
entities: ["AdminUserDO", "UserRoleDO", "UserPostDO"]
|
||||
description: "用户是系统的核心实体,关联角色和岗位,属于租户"
|
||||
- name: "Role(角色聚合)"
|
||||
type: "聚合根"
|
||||
entities: ["RoleDO", "RoleMenuDO"]
|
||||
description: "角色是权限的载体,关联菜单实现功能权限控制"
|
||||
- name: "Menu(菜单聚合)"
|
||||
type: "聚合根"
|
||||
entities: ["MenuDO"]
|
||||
description: "菜单是权限的最小单元,支持目录、菜单、按钮三种类型"
|
||||
- name: "Dept(部门聚合)"
|
||||
type: "聚合根"
|
||||
entities: ["DeptDO"]
|
||||
description: "部门是组织架构的核心,树形结构,支持数据权限"
|
||||
- name: "Tenant(租户聚合)"
|
||||
type: "聚合根"
|
||||
entities: ["TenantDO", "TenantPackageDO"]
|
||||
description: "租户是SaaS架构的核心,绑定套餐控制功能范围"
|
||||
|
||||
value_objects:
|
||||
- name: "Post(岗位)"
|
||||
description: "岗位是用户的职位标识,不涉及复杂业务逻辑"
|
||||
- name: "DictType/DictData(字典)"
|
||||
description: "字典是系统配置的枚举值,全局共享不区分租户"
|
||||
- name: "Notice(通知公告)"
|
||||
description: "通知公告是系统级消息,简单CRUD"
|
||||
|
||||
services:
|
||||
- name: "PermissionService"
|
||||
description: "权限领域服务,处理用户-角色-菜单的关联关系"
|
||||
- name: "AdminAuthService"
|
||||
description: "认证领域服务,处理登录、登出、令牌刷新"
|
||||
- name: "TenantService"
|
||||
description: "租户领域服务,处理租户生命周期管理"
|
||||
|
||||
# ============================================
|
||||
# 第二阶段:架构设计
|
||||
# ============================================
|
||||
architecture:
|
||||
# 分层架构
|
||||
layers:
|
||||
- name: "api"
|
||||
purpose: "模块间API接口,供其他模块RPC调用"
|
||||
components:
|
||||
- "PermissionApi: 权限校验接口"
|
||||
- "AdminUserApi: 用户信息查询接口"
|
||||
- "DeptApi: 部门信息查询接口"
|
||||
- "PostApi: 岗位信息查询接口"
|
||||
- "DictDataApi: 字典数据查询接口"
|
||||
- "RoleApi: 角色信息查询接口"
|
||||
- "SmsSendApi: 短信发送接口"
|
||||
- "MailSendApi: 邮件发送接口"
|
||||
- "NotifyMessageSendApi: 站内信发送接口"
|
||||
- "SocialUserApi: 社交用户接口"
|
||||
- "LoginLogApi: 登录日志接口"
|
||||
- "OperateLogApi: 操作日志接口"
|
||||
|
||||
- name: "controller"
|
||||
purpose: "HTTP接口,供前端调用"
|
||||
components:
|
||||
- "AuthController: 认证相关(登录、登出、刷新令牌)"
|
||||
- "UserController: 用户管理"
|
||||
- "RoleController: 角色管理"
|
||||
- "MenuController: 菜单管理"
|
||||
- "DeptController: 部门管理"
|
||||
- "PostController: 岗位管理"
|
||||
- "DictTypeController/DictDataController: 字典管理"
|
||||
- "TenantController: 租户管理"
|
||||
- "TenantPackageController: 租户套餐管理"
|
||||
- "SmsChannelController/SmsTemplateController: 短信管理"
|
||||
- "MailAccountController/MailTemplateController: 邮件管理"
|
||||
- "NotifyTemplateController/NotifyMessageController: 站内信管理"
|
||||
- "OAuth2ClientController: OAuth2客户端管理"
|
||||
- "SocialClientController: 社交客户端管理"
|
||||
|
||||
- name: "service"
|
||||
purpose: "业务逻辑层"
|
||||
components:
|
||||
- "auth: AdminAuthService(认证服务)"
|
||||
- "permission: PermissionService, RoleService, MenuService(权限服务)"
|
||||
- "user: AdminUserService(用户服务)"
|
||||
- "dept: DeptService, PostService(组织服务)"
|
||||
- "dict: DictTypeService, DictDataService(字典服务)"
|
||||
- "tenant: TenantService, TenantPackageService(租户服务)"
|
||||
- "sms: SmsChannelService, SmsTemplateService, SmsSendService, SmsCodeService(短信服务)"
|
||||
- "mail: MailAccountService, MailTemplateService, MailSendService(邮件服务)"
|
||||
- "notify: NotifyTemplateService, NotifyMessageService, NotifySendService(站内信服务)"
|
||||
- "oauth2: OAuth2ClientService, OAuth2TokenService, OAuth2CodeService(OAuth2服务)"
|
||||
- "social: SocialClientService, SocialUserService(社交服务)"
|
||||
- "logger: LoginLogService, OperateLogService(日志服务)"
|
||||
|
||||
- name: "dal"
|
||||
purpose: "数据访问层"
|
||||
components:
|
||||
- "dataobject: 32个DO实体类"
|
||||
- "mysql: MyBatis Mapper接口"
|
||||
- "redis: Redis缓存操作"
|
||||
|
||||
# 设计模式应用
|
||||
design_patterns:
|
||||
- pattern: "策略模式"
|
||||
location: "SmsChannelService, MailSendService"
|
||||
purpose: "不同短信/邮件渠道的发送策略切换"
|
||||
- pattern: "模板方法模式"
|
||||
location: "NotifySendService, SmsSendService"
|
||||
purpose: "消息发送的统一流程,子类实现具体发送逻辑"
|
||||
- pattern: "工厂模式"
|
||||
location: "SmsClientFactory, MailClientFactory"
|
||||
purpose: "根据渠道类型创建对应的客户端实例"
|
||||
- pattern: "代理模式"
|
||||
location: "PermissionServiceImpl.getSelf()"
|
||||
purpose: "解决Spring AOP缓存注解生效问题"
|
||||
- pattern: "DTO模式"
|
||||
location: "api/dto/*"
|
||||
purpose: "跨模块数据传输对象,隔离内部实体"
|
||||
- pattern: "转换器模式"
|
||||
location: "convert/*"
|
||||
purpose: "DO与VO之间的对象转换,使用MapStruct"
|
||||
|
||||
# 模块间通信
|
||||
communication:
|
||||
apis: # 对外暴露的API
|
||||
- api: "PermissionApi"
|
||||
methods: ["hasAnyPermissions", "hasAnyRoles", "getUserRoleIdListByRoleIds"]
|
||||
consumers: ["yudao-module-infra", "yudao-module-member"]
|
||||
- api: "AdminUserApi"
|
||||
methods: ["getUser", "getUserList"]
|
||||
consumers: ["yudao-module-infra", "yudao-module-pay"]
|
||||
- api: "DeptApi"
|
||||
methods: ["getDept", "getDeptList"]
|
||||
consumers: ["yudao-module-infra"]
|
||||
- api: "DictDataApi"
|
||||
methods: ["parseDictData", "getDictDataList"]
|
||||
consumers: ["所有模块"]
|
||||
- api: "SmsSendApi"
|
||||
methods: ["sendSingleSms"]
|
||||
consumers: ["yudao-module-member"]
|
||||
- api: "MailSendApi"
|
||||
methods: ["sendSingleMail"]
|
||||
consumers: ["yudao-module-member"]
|
||||
|
||||
consumers: # 消费的其他模块API
|
||||
- api: "PermissionCommonApi (framework-common)"
|
||||
purpose: "基础权限校验能力"
|
||||
- api: "FileApi (yudao-module-infra)"
|
||||
purpose: "文件上传(用户头像等)"
|
||||
|
||||
mq: [] # 暂未使用消息队列
|
||||
|
||||
# ============================================
|
||||
# 第三阶段:数据表设计
|
||||
# ============================================
|
||||
data_model:
|
||||
# 实体继承体系
|
||||
entity_hierarchy:
|
||||
base: "BaseDO | TenantBaseDO"
|
||||
description: |
|
||||
- BaseDO: 包含id, creator, createTime, updater, updateTime, deleted字段
|
||||
- TenantBaseDO: 继承BaseDO,额外包含tenantId字段,实现多租户隔离
|
||||
- 使用@TenantIgnore注解标记不需要租户隔离的表(如system_menu, system_dict_data)
|
||||
|
||||
# 核心数据表
|
||||
tables:
|
||||
# ========== 用户相关 ==========
|
||||
- name: "system_users"
|
||||
comment: "用户表"
|
||||
entity: "AdminUserDO"
|
||||
extends: "TenantBaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "用户ID,主键" }
|
||||
- { name: "username", type: "VARCHAR(30)", comment: "用户账号" }
|
||||
- { name: "password", type: "VARCHAR(100)", comment: "加密密码" }
|
||||
- { name: "nickname", type: "VARCHAR(30)", comment: "用户昵称" }
|
||||
- { name: "dept_id", type: "BIGINT", comment: "部门ID" }
|
||||
- { name: "post_ids", type: "VARCHAR(255)", comment: "岗位编号数组,JSON格式" }
|
||||
- { name: "email", type: "VARCHAR(50)", comment: "用户邮箱" }
|
||||
- { name: "mobile", type: "VARCHAR(20)", comment: "手机号码" }
|
||||
- { name: "sex", type: "TINYINT", comment: "用户性别" }
|
||||
- { name: "avatar", type: "VARCHAR(100)", comment: "用户头像" }
|
||||
- { name: "status", type: "TINYINT", comment: "帐号状态(0正常 1停用)" }
|
||||
- { name: "login_ip", type: "VARCHAR(50)", comment: "最后登录IP" }
|
||||
- { name: "login_date", type: "DATETIME", comment: "最后登录时间" }
|
||||
indexes:
|
||||
- { name: "uk_username", columns: ["username", "tenant_id"] }
|
||||
- { name: "uk_mobile", columns: ["mobile", "tenant_id"] }
|
||||
|
||||
- name: "system_user_role"
|
||||
comment: "用户-角色关联表"
|
||||
entity: "UserRoleDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键" }
|
||||
- { name: "user_id", type: "BIGINT", comment: "用户ID" }
|
||||
- { name: "role_id", type: "BIGINT", comment: "角色ID" }
|
||||
indexes:
|
||||
- { name: "idx_user_id", columns: ["user_id"] }
|
||||
- { name: "idx_role_id", columns: ["role_id"] }
|
||||
|
||||
# ========== 角色相关 ==========
|
||||
- name: "system_role"
|
||||
comment: "角色表"
|
||||
entity: "RoleDO"
|
||||
extends: "TenantBaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "角色ID" }
|
||||
- { name: "name", type: "VARCHAR(30)", comment: "角色名称" }
|
||||
- { name: "code", type: "VARCHAR(100)", comment: "角色标识" }
|
||||
- { name: "sort", type: "INT", comment: "角色排序" }
|
||||
- { name: "status", type: "TINYINT", comment: "角色状态" }
|
||||
- { name: "type", type: "TINYINT", comment: "角色类型(1系统内置 2自定义)" }
|
||||
- { name: "data_scope", type: "TINYINT", comment: "数据范围" }
|
||||
- { name: "data_scope_dept_ids", type: "VARCHAR(500)", comment: "数据范围部门ID数组" }
|
||||
indexes:
|
||||
- { name: "uk_code", columns: ["code", "tenant_id"] }
|
||||
|
||||
- name: "system_role_menu"
|
||||
comment: "角色-菜单关联表"
|
||||
entity: "RoleMenuDO"
|
||||
extends: "TenantBaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键" }
|
||||
- { name: "role_id", type: "BIGINT", comment: "角色ID" }
|
||||
- { name: "menu_id", type: "BIGINT", comment: "菜单ID" }
|
||||
indexes:
|
||||
- { name: "idx_role_id", columns: ["role_id"] }
|
||||
- { name: "idx_menu_id", columns: ["menu_id"] }
|
||||
|
||||
# ========== 菜单相关 ==========
|
||||
- name: "system_menu"
|
||||
comment: "菜单表"
|
||||
entity: "MenuDO"
|
||||
extends: "BaseDO"
|
||||
annotation: "@TenantIgnore"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "菜单ID" }
|
||||
- { name: "name", type: "VARCHAR(50)", comment: "菜单名称" }
|
||||
- { name: "permission", type: "VARCHAR(100)", comment: "权限标识" }
|
||||
- { name: "type", type: "TINYINT", comment: "菜单类型(1目录 2菜单 3按钮)" }
|
||||
- { name: "sort", type: "INT", comment: "显示顺序" }
|
||||
- { name: "parent_id", type: "BIGINT", comment: "父菜单ID" }
|
||||
- { name: "path", type: "VARCHAR(200)", comment: "路由地址" }
|
||||
- { name: "icon", type: "VARCHAR(100)", comment: "菜单图标" }
|
||||
- { name: "component", type: "VARCHAR(255)", comment: "组件路径" }
|
||||
- { name: "component_name", type: "VARCHAR(100)", comment: "组件名" }
|
||||
- { name: "status", type: "TINYINT", comment: "菜单状态" }
|
||||
- { name: "visible", type: "BIT", comment: "是否可见" }
|
||||
- { name: "keep_alive", type: "BIT", comment: "是否缓存" }
|
||||
- { name: "always_show", type: "BIT", comment: "是否总是显示" }
|
||||
|
||||
# ========== 部门相关 ==========
|
||||
- name: "system_dept"
|
||||
comment: "部门表"
|
||||
entity: "DeptDO"
|
||||
extends: "TenantBaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "部门ID" }
|
||||
- { name: "name", type: "VARCHAR(30)", comment: "部门名称" }
|
||||
- { name: "parent_id", type: "BIGINT", comment: "父部门ID" }
|
||||
- { name: "sort", type: "INT", comment: "显示顺序" }
|
||||
- { name: "leader_user_id", type: "BIGINT", comment: "负责人" }
|
||||
- { name: "phone", type: "VARCHAR(20)", comment: "联系电话" }
|
||||
- { name: "email", type: "VARCHAR(50)", comment: "邮箱" }
|
||||
- { name: "status", type: "TINYINT", comment: "部门状态" }
|
||||
|
||||
- name: "system_post"
|
||||
comment: "岗位表"
|
||||
entity: "PostDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "岗位ID" }
|
||||
- { name: "name", type: "VARCHAR(50)", comment: "岗位名称" }
|
||||
- { name: "code", type: "VARCHAR(64)", comment: "岗位编码" }
|
||||
- { name: "sort", type: "INT", comment: "显示顺序" }
|
||||
- { name: "status", type: "TINYINT", comment: "状态" }
|
||||
|
||||
# ========== 租户相关 ==========
|
||||
- name: "system_tenant"
|
||||
comment: "租户表"
|
||||
entity: "TenantDO"
|
||||
extends: "BaseDO"
|
||||
annotation: "@TenantIgnore"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "租户编号" }
|
||||
- { name: "name", type: "VARCHAR(100)", comment: "租户名" }
|
||||
- { name: "contact_user_id", type: "BIGINT", comment: "联系人的用户编号" }
|
||||
- { name: "contact_name", type: "VARCHAR(100)", comment: "联系人" }
|
||||
- { name: "contact_mobile", type: "VARCHAR(20)", comment: "联系手机" }
|
||||
- { name: "status", type: "TINYINT", comment: "租户状态" }
|
||||
- { name: "websites", type: "VARCHAR(500)", comment: "绑定域名列表" }
|
||||
- { name: "package_id", type: "BIGINT", comment: "租户套餐编号" }
|
||||
- { name: "expire_time", type: "DATETIME", comment: "过期时间" }
|
||||
- { name: "account_count", type: "INT", comment: "账号数量" }
|
||||
|
||||
- name: "system_tenant_package"
|
||||
comment: "租户套餐表"
|
||||
entity: "TenantPackageDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "套餐编号" }
|
||||
- { name: "name", type: "VARCHAR(100)", comment: "套餐名" }
|
||||
- { name: "status", type: "TINYINT", comment: "套餐状态" }
|
||||
- { name: "menu_ids", type: "VARCHAR(2000)", comment: "关联菜单编号数组" }
|
||||
- { name: "remark", type: "VARCHAR(255)", comment: "备注" }
|
||||
|
||||
# ========== 字典相关 ==========
|
||||
- name: "system_dict_type"
|
||||
comment: "字典类型表"
|
||||
entity: "DictTypeDO"
|
||||
extends: "BaseDO"
|
||||
annotation: "@TenantIgnore"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "字典主键" }
|
||||
- { name: "name", type: "VARCHAR(100)", comment: "字典名称" }
|
||||
- { name: "type", type: "VARCHAR(100)", comment: "字典类型" }
|
||||
- { name: "status", type: "TINYINT", comment: "字典状态" }
|
||||
|
||||
- name: "system_dict_data"
|
||||
comment: "字典数据表"
|
||||
entity: "DictDataDO"
|
||||
extends: "BaseDO"
|
||||
annotation: "@TenantIgnore"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "字典主键" }
|
||||
- { name: "sort", type: "INT", comment: "字典排序" }
|
||||
- { name: "label", type: "VARCHAR(100)", comment: "字典标签" }
|
||||
- { name: "value", type: "VARCHAR(100)", comment: "字典值" }
|
||||
- { name: "dict_type", type: "VARCHAR(100)", comment: "字典类型" }
|
||||
- { name: "status", type: "TINYINT", comment: "状态" }
|
||||
- { name: "color_type", type: "VARCHAR(20)", comment: "颜色类型" }
|
||||
|
||||
# ========== 短信相关 ==========
|
||||
- name: "system_sms_channel"
|
||||
comment: "短信渠道表"
|
||||
entity: "SmsChannelDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "渠道编号" }
|
||||
- { name: "signature", type: "VARCHAR(12)", comment: "短信签名" }
|
||||
- { name: "code", type: "VARCHAR(63)", comment: "渠道编码" }
|
||||
- { name: "status", type: "TINYINT", comment: "开启状态" }
|
||||
- { name: "api_key", type: "VARCHAR(128)", comment: "短信API密钥" }
|
||||
- { name: "callback_url", type: "VARCHAR(255)", comment: "短信回调URL" }
|
||||
|
||||
- name: "system_sms_template"
|
||||
comment: "短信模板表"
|
||||
entity: "SmsTemplateDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "模板编号" }
|
||||
- { name: "name", type: "VARCHAR(63)", comment: "模板名称" }
|
||||
- { name: "code", type: "VARCHAR(63)", comment: "模板编码" }
|
||||
- { name: "content", type: "VARCHAR(255)", comment: "模板内容" }
|
||||
- { name: "channel_id", type: "BIGINT", comment: "短信渠道编号" }
|
||||
- { name: "channel_code", type: "VARCHAR(63)", comment: "短信渠道编码" }
|
||||
- { name: "status", type: "TINYINT", comment: "开启状态" }
|
||||
|
||||
- name: "system_sms_log"
|
||||
comment: "短信日志表"
|
||||
entity: "SmsLogDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "日志编号" }
|
||||
- { name: "channel_id", type: "BIGINT", comment: "短信渠道编号" }
|
||||
- { name: "template_id", type: "BIGINT", comment: "模板编号" }
|
||||
- { name: "template_code", type: "VARCHAR(63)", comment: "模板编码" }
|
||||
- { name: "template_content", type: "VARCHAR(255)", comment: "模板内容" }
|
||||
- { name: "template_params", type: "VARCHAR(255)", comment: "模板参数" }
|
||||
- { name: "mobile", type: "VARCHAR(11)", comment: "手机号" }
|
||||
- { name: "send_status", type: "TINYINT", comment: "发送状态" }
|
||||
- { name: "send_time", type: "DATETIME", comment: "发送时间" }
|
||||
- { name: "receive_status", type: "TINYINT", comment: "接收状态" }
|
||||
- { name: "receive_time", type: "DATETIME", comment: "接收时间" }
|
||||
|
||||
# ========== 邮件相关 ==========
|
||||
- name: "system_mail_account"
|
||||
comment: "邮箱账号表"
|
||||
entity: "MailAccountDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键" }
|
||||
- { name: "mail", type: "VARCHAR(100)", comment: "邮箱" }
|
||||
- { name: "username", type: "VARCHAR(100)", comment: "用户名" }
|
||||
- { name: "password", type: "VARCHAR(100)", comment: "密码" }
|
||||
- { name: "host", type: "VARCHAR(100)", comment: "SMTP服务器域名" }
|
||||
- { name: "port", type: "INT", comment: "SMTP服务器端口" }
|
||||
- { name: "ssl_enable", type: "BIT", comment: "是否开启SSL" }
|
||||
|
||||
- name: "system_mail_template"
|
||||
comment: "邮件模板表"
|
||||
entity: "MailTemplateDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "主键" }
|
||||
- { name: "name", type: "VARCHAR(63)", comment: "模板名称" }
|
||||
- { name: "code", type: "VARCHAR(63)", comment: "模板编码" }
|
||||
- { name: "account_id", type: "BIGINT", comment: "发送的邮箱账号编号" }
|
||||
- { name: "nickname", type: "VARCHAR(255)", comment: "发送人名称" }
|
||||
- { name: "title", type: "VARCHAR(255)", comment: "邮件标题" }
|
||||
- { name: "content", type: "VARCHAR(10240)", comment: "邮件内容" }
|
||||
- { name: "status", type: "TINYINT", comment: "开启状态" }
|
||||
|
||||
# ========== OAuth2相关 ==========
|
||||
- name: "system_oauth2_client"
|
||||
comment: "OAuth2客户端表"
|
||||
entity: "OAuth2ClientDO"
|
||||
extends: "TenantBaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "client_id", type: "VARCHAR(255)", comment: "客户端编号" }
|
||||
- { name: "secret", type: "VARCHAR(255)", comment: "客户端密钥" }
|
||||
- { name: "name", type: "VARCHAR(255)", comment: "应用名" }
|
||||
- { name: "logo", type: "VARCHAR(255)", comment: "应用图标" }
|
||||
- { name: "authorized_grant_types", type: "VARCHAR(255)", comment: "授权类型" }
|
||||
- { name: "scopes", type: "VARCHAR(255)", comment: "授权范围" }
|
||||
- { name: "redirect_uris", type: "VARCHAR(255)", comment: "回调地址" }
|
||||
- { name: "access_token_validity_seconds", type: "INT", comment: "访问令牌有效期" }
|
||||
- { name: "refresh_token_validity_seconds", type: "INT", comment: "刷新令牌有效期" }
|
||||
|
||||
- name: "system_oauth2_access_token"
|
||||
comment: "OAuth2访问令牌表"
|
||||
entity: "OAuth2AccessTokenDO"
|
||||
extends: "TenantBaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "编号" }
|
||||
- { name: "access_token", type: "VARCHAR(255)", comment: "访问令牌" }
|
||||
- { name: "user_id", type: "BIGINT", comment: "用户编号" }
|
||||
- { name: "user_type", type: "TINYINT", comment: "用户类型" }
|
||||
- { name: "client_id", type: "VARCHAR(255)", comment: "客户端编号" }
|
||||
- { name: "expires_time", type: "DATETIME", comment: "过期时间" }
|
||||
|
||||
# 表关系(ER关系)
|
||||
relationships:
|
||||
- from: "system_users"
|
||||
to: "system_dept"
|
||||
type: "N:1"
|
||||
foreign_key: "dept_id"
|
||||
description: "用户属于一个部门"
|
||||
- from: "system_users"
|
||||
to: "system_post"
|
||||
type: "N:N"
|
||||
foreign_key: "post_ids (JSON数组)"
|
||||
description: "用户可关联多个岗位"
|
||||
- from: "system_user_role"
|
||||
to: "system_users"
|
||||
type: "N:1"
|
||||
foreign_key: "user_id"
|
||||
- from: "system_user_role"
|
||||
to: "system_role"
|
||||
type: "N:1"
|
||||
foreign_key: "role_id"
|
||||
- from: "system_role_menu"
|
||||
to: "system_role"
|
||||
type: "N:1"
|
||||
foreign_key: "role_id"
|
||||
- from: "system_role_menu"
|
||||
to: "system_menu"
|
||||
type: "N:1"
|
||||
foreign_key: "menu_id"
|
||||
- from: "system_menu"
|
||||
to: "system_menu"
|
||||
type: "N:1"
|
||||
foreign_key: "parent_id"
|
||||
description: "菜单树形结构"
|
||||
- from: "system_dept"
|
||||
to: "system_dept"
|
||||
type: "N:1"
|
||||
foreign_key: "parent_id"
|
||||
description: "部门树形结构"
|
||||
- from: "system_tenant"
|
||||
to: "system_tenant_package"
|
||||
type: "N:1"
|
||||
foreign_key: "package_id"
|
||||
- from: "system_dict_data"
|
||||
to: "system_dict_type"
|
||||
type: "N:1"
|
||||
foreign_key: "dict_type"
|
||||
- from: "system_sms_template"
|
||||
to: "system_sms_channel"
|
||||
type: "N:1"
|
||||
foreign_key: "channel_id"
|
||||
- from: "system_mail_template"
|
||||
to: "system_mail_account"
|
||||
type: "N:1"
|
||||
foreign_key: "account_id"
|
||||
|
||||
# ============================================
|
||||
# 第四阶段:代码使用设计
|
||||
# ============================================
|
||||
code_patterns:
|
||||
# Controller层规范
|
||||
controller:
|
||||
annotations:
|
||||
- "@Tag(name = '管理后台 - XXX'): OpenAPI 3.0 文档标签"
|
||||
- "@RestController: RESTful控制器"
|
||||
- "@RequestMapping('/system/xxx'): 路由前缀"
|
||||
- "@Validated: 参数校验"
|
||||
- "@Operation(summary = 'XXX'): 接口文档说明"
|
||||
- "@PreAuthorize('@ss.hasPermission('system:xxx:add')'): 权限校验"
|
||||
- "@PermitAll: 允许匿名访问(如登录接口)"
|
||||
example: |
|
||||
@Tag(name = "管理后台 - 用户")
|
||||
@RestController
|
||||
@RequestMapping("/system/user")
|
||||
@Validated
|
||||
public class UserController {
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建用户")
|
||||
@PreAuthorize("@ss.hasPermission('system:user:create')")
|
||||
public CommonResult<Long> createUser(@Valid @RequestBody UserSaveReqVO createReqVO) {
|
||||
return success(userService.createUser(createReqVO));
|
||||
}
|
||||
}
|
||||
|
||||
# Service层规范
|
||||
service:
|
||||
interface_pattern: |
|
||||
- 接口定义在 service 包下
|
||||
- 方法参数使用 @Valid 注解进行校验
|
||||
- 返回值使用具体类型或 void
|
||||
impl_pattern: |
|
||||
- 实现类放在 service 包下,命名为 XxxServiceImpl
|
||||
- 使用 @Service 注解
|
||||
- 使用 @Resource 注入 Mapper 和其他 Service
|
||||
- 使用 @Transactional(rollbackFor = Exception.class) 标记事务方法
|
||||
- 使用 @Cacheable/@CacheEvict 进行缓存操作
|
||||
example: |
|
||||
@Service
|
||||
@Slf4j
|
||||
public class PermissionServiceImpl implements PermissionService {
|
||||
|
||||
@Resource
|
||||
private RoleMenuMapper roleMenuMapper;
|
||||
@Resource
|
||||
private UserRoleMapper userRoleMapper;
|
||||
|
||||
@Override
|
||||
@DSTransactional // 多数据源事务
|
||||
@Caching(evict = {
|
||||
@CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, allEntries = true),
|
||||
@CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, allEntries = true)
|
||||
})
|
||||
public void assignRoleMenu(Long roleId, Set<Long> menuIds) {
|
||||
// 业务逻辑
|
||||
}
|
||||
}
|
||||
|
||||
# 数据访问规范
|
||||
dal:
|
||||
mapper_pattern: |
|
||||
- Mapper 接口继承 BaseMapperX<DO类>
|
||||
- 使用 @Mapper 注解
|
||||
- 复杂查询使用 MPJLambdaWrapperWrapper 或 XML
|
||||
example: |
|
||||
@Mapper
|
||||
public interface UserMapper extends BaseMapperX<AdminUserDO> {
|
||||
|
||||
default AdminUserDO selectByUsername(String username) {
|
||||
return selectOne(AdminUserDO::getUsername, username);
|
||||
}
|
||||
|
||||
default List<AdminUserDO> selectListByDeptIds(Collection<Long> deptIds) {
|
||||
return selectList(new LambdaQueryWrapperX<AdminUserDO>()
|
||||
.inIfPresent(AdminUserDO::getDeptId, deptIds));
|
||||
}
|
||||
}
|
||||
|
||||
# 异常处理
|
||||
error_handling:
|
||||
code_prefix: "1-002-XXX-XXX"
|
||||
format: "模块号-子模块号-序号"
|
||||
examples:
|
||||
- code: "1_002_000_000"
|
||||
message: "登录失败,账号密码不正确"
|
||||
module: "AUTH"
|
||||
- code: "1_002_001_000"
|
||||
message: "已经存在该名字的菜单"
|
||||
module: "MENU"
|
||||
- code: "1_002_002_000"
|
||||
message: "角色不存在"
|
||||
module: "ROLE"
|
||||
- code: "1_002_003_000"
|
||||
message: "用户账号已经存在"
|
||||
module: "USER"
|
||||
- code: "1_002_004_000"
|
||||
message: "已经存在该名字的部门"
|
||||
module: "DEPT"
|
||||
- code: "1_002_015_000"
|
||||
message: "租户不存在"
|
||||
module: "TENANT"
|
||||
usage: |
|
||||
// 在Service中抛出业务异常
|
||||
if (user == null) {
|
||||
throw exception(USER_NOT_EXISTS);
|
||||
}
|
||||
// 带参数的异常
|
||||
throw exception(ROLE_NAME_DUPLICATE, name);
|
||||
|
||||
# 统一响应
|
||||
response:
|
||||
success_pattern: "CommonResult.success(data)"
|
||||
error_pattern: "CommonResult.error(errorCode)"
|
||||
usage: |
|
||||
// 成功响应
|
||||
return success(userService.getUser(id));
|
||||
// 失败响应(通常由全局异常处理器处理)
|
||||
throw exception(USER_NOT_EXISTS);
|
||||
|
||||
# ============================================
|
||||
# 第五阶段:扩展指南
|
||||
# ============================================
|
||||
extension_guide:
|
||||
# 新增功能步骤
|
||||
new_feature:
|
||||
title: "新增业务功能(以新增'操作日志'功能为例)"
|
||||
steps:
|
||||
- step: "1. 创建DO实体类"
|
||||
detail: |
|
||||
在 dal/dataobject/logger/ 下创建 OperateLogDO.java
|
||||
继承 BaseDO 或 TenantBaseDO
|
||||
使用 @TableName 指定表名
|
||||
- step: "2. 创建Mapper接口"
|
||||
detail: |
|
||||
在 dal/mysql/logger/ 下创建 OperateLogMapper.java
|
||||
继承 BaseMapperX<OperateLogDO>
|
||||
- step: "3. 创建Service接口和实现"
|
||||
detail: |
|
||||
在 service/logger/ 下创建 OperateLogService.java 接口
|
||||
在 service/logger/ 下创建 OperateLogServiceImpl.java 实现
|
||||
使用 @Service 和 @Resource 注解
|
||||
- step: "4. 创建Controller"
|
||||
detail: |
|
||||
在 controller/admin/logger/ 下创建 OperateLogController.java
|
||||
使用 @RestController, @RequestMapping, @Tag 等注解
|
||||
使用 @PreAuthorize 进行权限控制
|
||||
- step: "5. 创建VO类"
|
||||
detail: |
|
||||
在 controller/admin/logger/vo/ 下创建请求和响应VO
|
||||
使用 @Data, @Valid 等注解进行参数校验
|
||||
- step: "6. 添加错误码"
|
||||
detail: |
|
||||
在 enums/ErrorCodeConstants.java 中添加错误码
|
||||
格式:ErrorCode XXX_NOT_EXISTS = new ErrorCode(1_002_XXX_000, "XXX不存在");
|
||||
- step: "7. 添加菜单权限"
|
||||
detail: |
|
||||
在数据库 system_menu 表中添加菜单记录
|
||||
配置权限标识如 system:operate-log:query
|
||||
|
||||
# 新增渠道/类型示例
|
||||
new_channel:
|
||||
title: "新增短信渠道(以新增'华为云短信'为例)"
|
||||
steps:
|
||||
- step: "1. 添加渠道枚举"
|
||||
detail: |
|
||||
在 SmsChannelEnum 中添加新渠道类型
|
||||
HUAWEI("huawei", "华为云")
|
||||
- step: "2. 创建客户端实现"
|
||||
detail: |
|
||||
在 framework/sms/core/client/impl/ 下创建 HuaweiSmsClient.java
|
||||
继承 AbstractSmsClient 抽象类
|
||||
实现 doSendSms() 方法
|
||||
- step: "3. 注册到工厂"
|
||||
detail: |
|
||||
在 SmsClientFactory 中注册新渠道
|
||||
clients.put(SmsChannelEnum.HUAWEI, new HuaweiSmsClient());
|
||||
- step: "4. 添加配置类"
|
||||
detail: |
|
||||
创建 HuaweiSmsChannelProperties 配置类
|
||||
继承 SmsChannelProperties 基类
|
||||
|
||||
# 新增字典类型
|
||||
new_dict:
|
||||
title: "新增字典类型"
|
||||
steps:
|
||||
- step: "1. 通过管理后台添加"
|
||||
detail: |
|
||||
登录管理后台 -> 系统管理 -> 字典管理 -> 新增字典类型
|
||||
填写字典名称、字典类型(如 system_user_sex)
|
||||
- step: "2. 添加字典数据"
|
||||
detail: |
|
||||
在字典类型下添加字典数据项
|
||||
设置字典标签、字典值、排序、状态等
|
||||
- step: "3. 代码中使用"
|
||||
detail: |
|
||||
// 获取字典数据列表
|
||||
List<DictDataRespDTO> dictList = dictDataApi.getDictDataList("system_user_sex");
|
||||
// 解析字典值
|
||||
String label = dictDataApi.parseDictData("system_user_sex", "1");
|
||||
|
||||
# 最佳实践
|
||||
best_practices:
|
||||
- practice: "权限标识命名规范"
|
||||
detail: "使用 '模块:功能:操作' 格式,如 system:user:create、system:role:update"
|
||||
- practice: "数据权限使用"
|
||||
detail: "在Service方法上使用 @DataPermission 注解控制数据权限,使用 enable=false 关闭"
|
||||
- practice: "缓存使用"
|
||||
detail: "权限相关数据使用 @Cacheable 缓存,修改时使用 @CacheEvict 清除"
|
||||
- practice: "多租户注意"
|
||||
detail: "不需要租户隔离的表使用 @TenantIgnore 注解,如字典表、菜单表"
|
||||
- practice: "事务处理"
|
||||
detail: "跨数据源操作使用 @DSTransactional,单数据源使用 @Transactional"
|
||||
- practice: "日志记录"
|
||||
detail: "使用 @OperateLog 注解记录操作日志,使用 Lombok 的 @Slf4j 记录调试日志"
|
||||
- practice: "参数校验"
|
||||
detail: "VO类使用 @Valid、@NotNull、@NotBlank 等注解进行参数校验"
|
||||
- practice: "API设计"
|
||||
detail: "跨模块调用通过 API 层,不要直接调用 Service 层"
|
||||
|
||||
# ============================================
|
||||
# 依赖关系
|
||||
# ============================================
|
||||
dependencies:
|
||||
# 内部依赖(其他模块)
|
||||
internal:
|
||||
- module: "yudao-framework-common"
|
||||
api: "CommonResult, PageResult, ErrorCode"
|
||||
purpose: "通用响应、分页、错误码"
|
||||
- module: "yudao-framework-security"
|
||||
api: "SecurityFrameworkUtils, @PreAuthorize"
|
||||
purpose: "安全框架、权限注解"
|
||||
- module: "yudao-framework-tenant"
|
||||
api: "TenantBaseDO, @TenantIgnore"
|
||||
purpose: "多租户支持"
|
||||
- module: "yudao-framework-mybatis"
|
||||
api: "BaseMapperX, BaseDO"
|
||||
purpose: "MyBatis增强"
|
||||
- module: "yudao-framework-redis"
|
||||
api: "@Cacheable, RedisKeyConstants"
|
||||
purpose: "Redis缓存"
|
||||
- module: "yudao-module-infra"
|
||||
api: "FileApi"
|
||||
purpose: "文件上传"
|
||||
|
||||
# 外部依赖(第三方库)
|
||||
external:
|
||||
- name: "spring-boot-starter-web"
|
||||
version: "2.7.x"
|
||||
purpose: "Web框架"
|
||||
- name: "mybatis-plus-boot-starter"
|
||||
version: "3.5.x"
|
||||
purpose: "MyBatis增强"
|
||||
- name: "spring-boot-starter-security"
|
||||
version: "2.7.x"
|
||||
purpose: "安全框架"
|
||||
- name: "spring-boot-starter-validation"
|
||||
version: "2.7.x"
|
||||
purpose: "参数校验"
|
||||
- name: "spring-boot-starter-cache"
|
||||
version: "2.7.x"
|
||||
purpose: "缓存支持"
|
||||
- name: "mapstruct"
|
||||
version: "1.5.x"
|
||||
purpose: "对象转换"
|
||||
- name: "lombok"
|
||||
version: "1.18.x"
|
||||
purpose: "代码简化"
|
||||
- name: "hutool-all"
|
||||
version: "5.8.x"
|
||||
purpose: "工具类库"
|
||||
- name: "knife4j-spring-boot-starter"
|
||||
version: "3.0.x"
|
||||
purpose: "API文档"
|
||||
|
||||
# ============================================
|
||||
# 关键文件清单
|
||||
# ============================================
|
||||
key_files:
|
||||
- path: "yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java"
|
||||
purpose: "错误码常量定义,所有业务异常的统一入口"
|
||||
- path: "yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/user/AdminUserDO.java"
|
||||
purpose: "用户实体类,核心聚合根"
|
||||
- path: "yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/RoleDO.java"
|
||||
purpose: "角色实体类,权限载体"
|
||||
- path: "yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/MenuDO.java"
|
||||
purpose: "菜单实体类,权限最小单元"
|
||||
- path: "yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java"
|
||||
purpose: "权限服务实现,核心业务逻辑"
|
||||
- path: "yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java"
|
||||
purpose: "认证服务实现,登录登出逻辑"
|
||||
- path: "yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java"
|
||||
purpose: "认证控制器,登录接口入口"
|
||||
- path: "yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/permission/PermissionApi.java"
|
||||
purpose: "权限API接口,跨模块调用入口"
|
||||
- path: "yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/permission/DataScopeEnum.java"
|
||||
purpose: "数据权限枚举,5种数据范围定义"
|
||||
- path: "yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/permission/MenuTypeEnum.java"
|
||||
purpose: "菜单类型枚举,目录/菜单/按钮"
|
||||
@@ -1,832 +0,0 @@
|
||||
# Skill 文件 - yudao-module-wms 模块
|
||||
# 仓储管理系统知识提取
|
||||
|
||||
skill:
|
||||
id: "skill-wms"
|
||||
name: "Warehouse Management Skill"
|
||||
version: "1.0.0"
|
||||
module_path: "yudao-module-wms"
|
||||
created_at: "2026-06-08"
|
||||
updated_at: "2026-06-08"
|
||||
|
||||
# ============================================
|
||||
# 第一阶段:设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
# 业务定位:模块解决什么业务问题?在整个系统中的定位?
|
||||
business_position: |
|
||||
wms 模块是仓储管理系统,提供仓储业务全流程管理能力:
|
||||
1. 仓库管理 - 仓库基本信息维护,支持编码、名称、排序
|
||||
2. 物料管理 - 物料主数据管理,包含品牌、分类(树结构)、SKU(多规格)
|
||||
3. 商户管理 - 客户/供应商/既是客户也是供应商的业务伙伴管理
|
||||
4. 库存管理 - 基于 SKU + 仓库维度的库存余额管理,只读设计
|
||||
5. 入库管理 - 入库单创建、审核、完成、取消,完成入库增加库存
|
||||
6. 出库管理 - 出库单创建、审核、完成、取消,完成出库减少库存
|
||||
7. 移库管理 - 库间调拨单创建、审核、完成、取消,完成移库双向变更库存
|
||||
8. 盘点管理 - 库存盘点单创建、审核、完成、取消,完成盘点按实际数量调整库存
|
||||
9. 首页统计 - 订单汇总、订单趋势、库存汇总等仪表盘数据
|
||||
|
||||
定位:面向仓储管理场景,管理仓库、物料、库存及四大单据的完整业务闭环。SKU 作为库存单位,库存以 SKU + 仓库维度跟踪。
|
||||
|
||||
# 设计原则:遵循了哪些设计原则(SOLID、DDD)?有哪些架构决策?
|
||||
design_principles:
|
||||
- principle: "单据头/明细模式 (Header + Detail Pattern)"
|
||||
description: "所有四大单据(入库、出库、移库、盘点)均采用 Header + Detail 主子表结构,单据头存储汇总信息,明细行存储 SKU 级别数据"
|
||||
- principle: "只读库存控制器"
|
||||
description: "库存和库存历史 Controller 均为只读,所有库存变更必须通过单据完成(complete)操作触发,确保数据一致性"
|
||||
- principle: "并发安全库存更新"
|
||||
description: "使用 SELECT ... FOR UPDATE 行锁 + DuplicateKeyException 捕获实现库存更新的并发安全和懒初始化"
|
||||
- principle: "VO 组装模式"
|
||||
description: "通过批量查询关联实体到 Map 中,再通过 BeanUtils.toBean() 丰富 VO 对象,避免 N+1 查询"
|
||||
- principle: "单据状态机"
|
||||
description: "PREPARE(0) -> FINISHED(4) 或 CANCELED(5),单据只有在 PREPARE 状态下才能完成或取消"
|
||||
- principle: "SKU 作为库存单位"
|
||||
description: "库存以 SKU + 仓库维度跟踪,不跟踪物料级别库存,支持多规格管理"
|
||||
- principle: "订单类型常量分段"
|
||||
description: "WmsOrderTypeConstants 定义订单类型范围:入库 [100,200)、出库 [200,300),用于库存历史来源标识"
|
||||
- principle: "细粒度权限控制"
|
||||
description: "权限标识格式 wms:{entity}:{action},如 wms:receipt-order:create"
|
||||
- principle: "Excel 导出全覆盖"
|
||||
description: "所有主数据和单据均支持 Excel 导出功能"
|
||||
- principle: "字典集成"
|
||||
description: "单据状态、子类型等使用系统字典管理"
|
||||
|
||||
# 领域模型:核心领域对象有哪些?领域对象之间的关系?聚合根是什么?
|
||||
domain_model:
|
||||
aggregates:
|
||||
- name: "物料聚合"
|
||||
type: "聚合根"
|
||||
entities: ["ItemDO", "ItemSkuDO", "ItemBrandDO", "ItemCategoryDO"]
|
||||
description: "ItemDO 是物料聚合根,ItemSkuDO 是物料的多规格 SKU,ItemBrandDO 和 ItemCategoryDO 是物料的品牌和分类维度"
|
||||
- name: "商户聚合"
|
||||
type: "聚合根"
|
||||
entities: ["MerchantDO"]
|
||||
description: "MerchantDO 是商户聚合根,管理客户和供应商信息"
|
||||
- name: "仓库聚合"
|
||||
type: "聚合根"
|
||||
entities: ["WarehouseDO"]
|
||||
description: "WarehouseDO 是仓库聚合根,管理仓库基本信息"
|
||||
- name: "库存聚合"
|
||||
type: "聚合根"
|
||||
entities: ["InventoryDO", "InventoryHistoryDO"]
|
||||
description: "InventoryDO 以 SKU + 仓库为唯一维度管理库存余额,InventoryHistoryDO 记录每笔库存变动"
|
||||
- name: "入库单聚合"
|
||||
type: "聚合根"
|
||||
entities: ["ReceiptOrderDO", "ReceiptOrderDetailDO"]
|
||||
description: "ReceiptOrderDO 是入库单聚合根,ReceiptOrderDetailDO 是入库明细行,一对多关系"
|
||||
- name: "出库单聚合"
|
||||
type: "聚合根"
|
||||
entities: ["ShipmentOrderDO", "ShipmentOrderDetailDO"]
|
||||
description: "ShipmentOrderDO 是出库单聚合根,ShipmentOrderDetailDO 是出库明细行,一对多关系"
|
||||
- name: "移库单聚合"
|
||||
type: "聚合根"
|
||||
entities: ["MovementOrderDO", "MovementOrderDetailDO"]
|
||||
description: "MovementOrderDO 是移库单聚合根,MovementOrderDetailDO 是移库明细行,一对多关系"
|
||||
- name: "盘点单聚合"
|
||||
type: "聚合根"
|
||||
entities: ["CheckOrderDO", "CheckOrderDetailDO"]
|
||||
description: "CheckOrderDO 是盘点单聚合根,CheckOrderDetailDO 是盘点明细行,一对多关系"
|
||||
|
||||
value_objects:
|
||||
- name: "WmsOrderTypeConstants"
|
||||
description: "订单类型常量,定义各单据类型范围,用于库存历史来源标识"
|
||||
- name: "OrderStatusEnum"
|
||||
description: "单据状态枚举:PREPARE(0), FINISHED(4), CANCELED(5)"
|
||||
|
||||
services:
|
||||
- name: "ItemService"
|
||||
description: "物料管理服务,提供物料及 SKU 的 CRUD 操作"
|
||||
- name: "ItemBrandService"
|
||||
description: "品牌管理服务"
|
||||
- name: "ItemCategoryService"
|
||||
description: "分类管理服务,支持树形结构操作"
|
||||
- name: "MerchantService"
|
||||
description: "商户管理服务"
|
||||
- name: "WarehouseService"
|
||||
description: "仓库管理服务"
|
||||
- name: "InventoryService"
|
||||
description: "库存管理服务,提供库存查询和变更方法"
|
||||
- name: "InventoryHistoryService"
|
||||
description: "库存历史服务,记录每笔库存变动"
|
||||
- name: "ReceiptOrderService"
|
||||
description: "入库单管理服务"
|
||||
- name: "ShipmentOrderService"
|
||||
description: "出库单管理服务"
|
||||
- name: "MovementOrderService"
|
||||
description: "移库单管理服务"
|
||||
- name: "CheckOrderService"
|
||||
description: "盘点单管理服务"
|
||||
|
||||
# ============================================
|
||||
# 第二阶段:架构设计
|
||||
# ============================================
|
||||
architecture:
|
||||
# 分层架构
|
||||
layers:
|
||||
- name: "api"
|
||||
purpose: "模块间 API 接口,供其他模块 RPC 调用"
|
||||
components: []
|
||||
|
||||
- name: "controller"
|
||||
purpose: "HTTP 接口层,提供 RESTful API"
|
||||
components:
|
||||
- "admin/item - 物料管理接口"
|
||||
- "admin/item-sku - SKU 查询接口(只读)"
|
||||
- "admin/item-brand - 品牌管理接口"
|
||||
- "admin/item-category - 分类管理接口"
|
||||
- "admin/merchant - 商户管理接口"
|
||||
- "admin/warehouse - 仓库管理接口"
|
||||
- "admin/inventory - 库存查询接口(只读)"
|
||||
- "admin/inventory-history - 库存历史查询接口(只读)"
|
||||
- "admin/receipt-order - 入库单管理接口"
|
||||
- "admin/receipt-order-detail - 入库明细查询接口"
|
||||
- "admin/shipment-order - 出库单管理接口"
|
||||
- "admin/shipment-order-detail - 出库明细查询接口"
|
||||
- "admin/movement-order - 移库单管理接口"
|
||||
- "admin/movement-order-detail - 移库明细查询接口"
|
||||
- "admin/check-order - 盘点单管理接口"
|
||||
- "admin/check-order-detail - 盘点明细查询接口"
|
||||
- "admin/home-statistics - 首页统计接口"
|
||||
|
||||
- name: "service"
|
||||
purpose: "业务逻辑层"
|
||||
components:
|
||||
- "item - 物料服务(ItemService, ItemSkuService)"
|
||||
- "item-brand - 品牌服务(ItemBrandService)"
|
||||
- "item-category - 分类服务(ItemCategoryService)"
|
||||
- "merchant - 商户服务(MerchantService)"
|
||||
- "warehouse - 仓库服务(WarehouseService)"
|
||||
- "inventory - 库存服务(InventoryService, InventoryHistoryService)"
|
||||
- "receipt-order - 入库单服务(ReceiptOrderService, ReceiptOrderDetailService)"
|
||||
- "shipment-order - 出库单服务(ShipmentOrderService, ShipmentOrderDetailService)"
|
||||
- "movement-order - 移库单服务(MovementOrderService, MovementOrderDetailService)"
|
||||
- "check-order - 盘点单服务(CheckOrderService, CheckOrderDetailService)"
|
||||
|
||||
- name: "dal"
|
||||
purpose: "数据访问层"
|
||||
components:
|
||||
- "dataobject - DO 实体类定义"
|
||||
- "mysql - MyBatis Mapper 接口"
|
||||
|
||||
- name: "enums"
|
||||
purpose: "枚举和常量定义"
|
||||
components:
|
||||
- "WmsOrderTypeConstants - 订单类型常量(入库[100,200)、出库[200,300)等)"
|
||||
- "ErrorCodeConstants - 错误码常量"
|
||||
- "OrderStatusEnum - 单据状态枚举"
|
||||
|
||||
# 设计模式应用
|
||||
design_patterns:
|
||||
- pattern: "单据头/明细模式 (Header + Detail Pattern)"
|
||||
location: "service/*-order/"
|
||||
purpose: "所有四大单据采用主子表结构,Header 存储汇总信息,Detail 存储 SKU 级别数据"
|
||||
- pattern: "并发安全模式 (Pessimistic Locking)"
|
||||
location: "service/inventory/"
|
||||
purpose: "SELECT ... FOR UPDATE 行锁 + DuplicateKeyException 捕获实现库存更新并发安全"
|
||||
- pattern: "VO 组装模式 (VO Assembly Pattern)"
|
||||
location: "service/*/xxxServiceImpl.java"
|
||||
purpose: "批量查询关联实体到 Map,通过 BeanUtils.toBean() 丰富 VO,避免 N+1"
|
||||
- pattern: "状态机模式 (State Machine)"
|
||||
location: "service/*-order/"
|
||||
purpose: "单据状态:PREPARE(0) -> FINISHED(4) / CANCELED(5),操作前校验状态"
|
||||
- pattern: "只读控制器模式"
|
||||
location: "controller/admin/inventory/"
|
||||
purpose: "库存和库存历史 Controller 为只读,所有变更通过单据完成触发"
|
||||
|
||||
# 模块间通信
|
||||
communication:
|
||||
apis: []
|
||||
consumers:
|
||||
- module: "yudao-module-system"
|
||||
api: "AdminUserApi"
|
||||
purpose: "批量获取用户信息,用于 VO 组装展示用户名"
|
||||
mq: []
|
||||
|
||||
# ============================================
|
||||
# 第三阶段:数据表设计
|
||||
# ============================================
|
||||
data_model:
|
||||
# 实体继承体系
|
||||
entity_hierarchy:
|
||||
base: "BaseDO"
|
||||
description: "所有 DO 继承 BaseDO,包含 creator, createTime, updater, updateTime, deleted 字段。"
|
||||
|
||||
# 核心数据表
|
||||
tables:
|
||||
# ========== 物料相关 ==========
|
||||
- name: "wms_item_brand"
|
||||
comment: "品牌表"
|
||||
entity: "ItemBrandDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "品牌编号,主键" }
|
||||
- { name: "name", type: "VARCHAR", comment: "品牌名称" }
|
||||
- { name: "sort", type: "INT", comment: "排序" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
|
||||
- name: "wms_item_category"
|
||||
comment: "分类表"
|
||||
entity: "ItemCategoryDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "分类编号,主键" }
|
||||
- { name: "name", type: "VARCHAR", comment: "分类名称" }
|
||||
- { name: "parent_id", type: "BIGINT", comment: "父分类编号(0=顶级)" }
|
||||
- { name: "sort", type: "INT", comment: "排序" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
indexes:
|
||||
- { name: "idx_parent_id", columns: ["parent_id"] }
|
||||
|
||||
- name: "wms_item"
|
||||
comment: "物料表"
|
||||
entity: "ItemDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "物料编号,主键" }
|
||||
- { name: "name", type: "VARCHAR", comment: "物料名称" }
|
||||
- { name: "code", type: "VARCHAR", comment: "物料编码" }
|
||||
- { name: "unit", type: "VARCHAR", comment: "计量单位" }
|
||||
- { name: "category_id", type: "BIGINT", comment: "分类编号,关联 ItemCategoryDO" }
|
||||
- { name: "brand_id", type: "BIGINT", comment: "品牌编号,关联 ItemBrandDO" }
|
||||
- { name: "status", type: "TINYINT", comment: "物料状态" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
indexes:
|
||||
- { name: "uk_code", columns: ["code"], unique: true }
|
||||
- { name: "idx_category_id", columns: ["category_id"] }
|
||||
- { name: "idx_brand_id", columns: ["brand_id"] }
|
||||
|
||||
- name: "wms_item_sku"
|
||||
comment: "物料 SKU 表"
|
||||
entity: "ItemSkuDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "SKU 编号,主键" }
|
||||
- { name: "item_id", type: "BIGINT", comment: "物料编号,关联 ItemDO" }
|
||||
- { name: "sku_no", type: "VARCHAR", comment: "SKU 编号/条码" }
|
||||
- { name: "specifications", type: "VARCHAR", comment: "规格属性" }
|
||||
- { name: "bar_code", type: "VARCHAR", comment: "条形码" }
|
||||
- { name: "weight", type: "DECIMAL", comment: "重量" }
|
||||
- { name: "volume", type: "DECIMAL", comment: "体积" }
|
||||
- { name: "purchase_price", type: "DECIMAL", comment: "采购价格" }
|
||||
- { name: "selling_price", type: "DECIMAL", comment: "销售价格" }
|
||||
- { name: "status", type: "TINYINT", comment: "SKU 状态" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
indexes:
|
||||
- { name: "idx_item_id", columns: ["item_id"] }
|
||||
- { name: "idx_sku_no", columns: ["sku_no"] }
|
||||
|
||||
# ========== 商户与仓库 ==========
|
||||
- name: "wms_merchant"
|
||||
comment: "商户表"
|
||||
entity: "MerchantDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "商户编号,主键" }
|
||||
- { name: "name", type: "VARCHAR", comment: "商户名称" }
|
||||
- { name: "type", type: "TINYINT", comment: "商户类型(1=客户 2=供应商 3=两者)" }
|
||||
- { name: "contact", type: "VARCHAR", comment: "联系人" }
|
||||
- { name: "phone", type: "VARCHAR", comment: "联系电话" }
|
||||
- { name: "address", type: "VARCHAR", comment: "地址" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
|
||||
- name: "wms_warehouse"
|
||||
comment: "仓库表"
|
||||
entity: "WarehouseDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "仓库编号,主键" }
|
||||
- { name: "code", type: "VARCHAR", comment: "仓库编码" }
|
||||
- { name: "name", type: "VARCHAR", comment: "仓库名称" }
|
||||
- { name: "sort", type: "INT", comment: "排序" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
indexes:
|
||||
- { name: "uk_code", columns: ["code"], unique: true }
|
||||
|
||||
# ========== 库存相关 ==========
|
||||
- name: "wms_inventory"
|
||||
comment: "库存表"
|
||||
entity: "InventoryDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "库存编号,主键" }
|
||||
- { name: "sku_id", type: "BIGINT", comment: "SKU 编号,关联 ItemSkuDO" }
|
||||
- { name: "warehouse_id", type: "BIGINT", comment: "仓库编号,关联 WarehouseDO" }
|
||||
- { name: "quantity", type: "DECIMAL", comment: "库存数量" }
|
||||
- { name: "price", type: "DECIMAL", comment: "库存均价" }
|
||||
indexes:
|
||||
- { name: "uk_sku_warehouse", columns: ["sku_id", "warehouse_id"], unique: true }
|
||||
|
||||
- name: "wms_inventory_history"
|
||||
comment: "库存历史表"
|
||||
entity: "InventoryHistoryDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "历史编号,主键" }
|
||||
- { name: "sku_id", type: "BIGINT", comment: "SKU 编号,关联 ItemSkuDO" }
|
||||
- { name: "warehouse_id", type: "BIGINT", comment: "仓库编号,关联 WarehouseDO" }
|
||||
- { name: "before_quantity", type: "DECIMAL", comment: "变动前数量" }
|
||||
- { name: "after_quantity", type: "DECIMAL", comment: "变动后数量" }
|
||||
- { name: "quantity", type: "DECIMAL", comment: "变动数量(正=增加,负=减少)" }
|
||||
- { name: "price", type: "DECIMAL", comment: "变动价格" }
|
||||
- { name: "source_order_type", type: "INTEGER", comment: "来源单据类型" }
|
||||
- { name: "source_order_id", type: "BIGINT", comment: "来源单据编号" }
|
||||
- { name: "source_order_no", type: "VARCHAR", comment: "来源单号" }
|
||||
indexes:
|
||||
- { name: "idx_sku_warehouse", columns: ["sku_id", "warehouse_id"] }
|
||||
- { name: "idx_source_order", columns: ["source_order_type", "source_order_id"] }
|
||||
|
||||
# ========== 入库单相关 ==========
|
||||
- name: "wms_receipt_order"
|
||||
comment: "入库单表"
|
||||
entity: "ReceiptOrderDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "入库单编号,主键" }
|
||||
- { name: "no", type: "VARCHAR", comment: "入库单号" }
|
||||
- { name: "warehouse_id", type: "BIGINT", comment: "仓库编号,关联 WarehouseDO" }
|
||||
- { name: "merchant_id", type: "BIGINT", comment: "商户编号,关联 MerchantDO" }
|
||||
- { name: "status", type: "TINYINT", comment: "单据状态(0=PREPARE, 4=FINISHED, 5=CANCELED)" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
indexes:
|
||||
- { name: "uk_no", columns: ["no"], unique: true }
|
||||
- { name: "idx_warehouse_id", columns: ["warehouse_id"] }
|
||||
- { name: "idx_status", columns: ["status"] }
|
||||
|
||||
- name: "wms_receipt_order_detail"
|
||||
comment: "入库明细表"
|
||||
entity: "ReceiptOrderDetailDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "明细编号,主键" }
|
||||
- { name: "order_id", type: "BIGINT", comment: "入库单编号,关联 ReceiptOrderDO" }
|
||||
- { name: "sku_id", type: "BIGINT", comment: "SKU 编号,关联 ItemSkuDO" }
|
||||
- { name: "quantity", type: "DECIMAL", comment: "入库数量" }
|
||||
- { name: "price", type: "DECIMAL", comment: "入库单价" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
indexes:
|
||||
- { name: "idx_order_id", columns: ["order_id"] }
|
||||
|
||||
# ========== 出库单相关 ==========
|
||||
- name: "wms_shipment_order"
|
||||
comment: "出库单表"
|
||||
entity: "ShipmentOrderDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "出库单编号,主键" }
|
||||
- { name: "no", type: "VARCHAR", comment: "出库单号" }
|
||||
- { name: "warehouse_id", type: "BIGINT", comment: "仓库编号,关联 WarehouseDO" }
|
||||
- { name: "merchant_id", type: "BIGINT", comment: "商户编号,关联 MerchantDO" }
|
||||
- { name: "status", type: "TINYINT", comment: "单据状态(0=PREPARE, 4=FINISHED, 5=CANCELED)" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
indexes:
|
||||
- { name: "uk_no", columns: ["no"], unique: true }
|
||||
- { name: "idx_warehouse_id", columns: ["warehouse_id"] }
|
||||
- { name: "idx_status", columns: ["status"] }
|
||||
|
||||
- name: "wms_shipment_order_detail"
|
||||
comment: "出库明细表"
|
||||
entity: "ShipmentOrderDetailDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "明细编号,主键" }
|
||||
- { name: "order_id", type: "BIGINT", comment: "出库单编号,关联 ShipmentOrderDO" }
|
||||
- { name: "sku_id", type: "BIGINT", comment: "SKU 编号,关联 ItemSkuDO" }
|
||||
- { name: "quantity", type: "DECIMAL", comment: "出库数量" }
|
||||
- { name: "price", type: "DECIMAL", comment: "出库单价" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
indexes:
|
||||
- { name: "idx_order_id", columns: ["order_id"] }
|
||||
|
||||
# ========== 移库单相关 ==========
|
||||
- name: "wms_movement_order"
|
||||
comment: "移库单表"
|
||||
entity: "MovementOrderDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "移库单编号,主键" }
|
||||
- { name: "no", type: "VARCHAR", comment: "移库单号" }
|
||||
- { name: "from_warehouse_id", type: "BIGINT", comment: "源仓库编号,关联 WarehouseDO" }
|
||||
- { name: "to_warehouse_id", type: "BIGINT", comment: "目标仓库编号,关联 WarehouseDO" }
|
||||
- { name: "status", type: "TINYINT", comment: "单据状态(0=PREPARE, 4=FINISHED, 5=CANCELED)" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
indexes:
|
||||
- { name: "uk_no", columns: ["no"], unique: true }
|
||||
- { name: "idx_from_warehouse_id", columns: ["from_warehouse_id"] }
|
||||
- { name: "idx_to_warehouse_id", columns: ["to_warehouse_id"] }
|
||||
- { name: "idx_status", columns: ["status"] }
|
||||
|
||||
- name: "wms_movement_order_detail"
|
||||
comment: "移库明细表"
|
||||
entity: "MovementOrderDetailDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "明细编号,主键" }
|
||||
- { name: "order_id", type: "BIGINT", comment: "移库单编号,关联 MovementOrderDO" }
|
||||
- { name: "sku_id", type: "BIGINT", comment: "SKU 编号,关联 ItemSkuDO" }
|
||||
- { name: "quantity", type: "DECIMAL", comment: "移库数量" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
indexes:
|
||||
- { name: "idx_order_id", columns: ["order_id"] }
|
||||
|
||||
# ========== 盘点单相关 ==========
|
||||
- name: "wms_check_order"
|
||||
comment: "盘点单表"
|
||||
entity: "CheckOrderDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "盘点单编号,主键" }
|
||||
- { name: "no", type: "VARCHAR", comment: "盘点单号" }
|
||||
- { name: "warehouse_id", type: "BIGINT", comment: "仓库编号,关联 WarehouseDO" }
|
||||
- { name: "status", type: "TINYINT", comment: "单据状态(0=PREPARE, 4=FINISHED, 5=CANCELED)" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
indexes:
|
||||
- { name: "uk_no", columns: ["no"], unique: true }
|
||||
- { name: "idx_warehouse_id", columns: ["warehouse_id"] }
|
||||
- { name: "idx_status", columns: ["status"] }
|
||||
|
||||
- name: "wms_check_order_detail"
|
||||
comment: "盘点明细表"
|
||||
entity: "CheckOrderDetailDO"
|
||||
extends: "BaseDO"
|
||||
columns:
|
||||
- { name: "id", type: "BIGINT", comment: "明细编号,主键" }
|
||||
- { name: "order_id", type: "BIGINT", comment: "盘点单编号,关联 CheckOrderDO" }
|
||||
- { name: "sku_id", type: "BIGINT", comment: "SKU 编号,关联 ItemSkuDO" }
|
||||
- { name: "system_quantity", type: "DECIMAL", comment: "系统数量" }
|
||||
- { name: "actual_quantity", type: "DECIMAL", comment: "实际数量" }
|
||||
- { name: "remark", type: "VARCHAR", comment: "备注" }
|
||||
indexes:
|
||||
- { name: "idx_order_id", columns: ["order_id"] }
|
||||
|
||||
# 表关系(ER关系)
|
||||
relationships:
|
||||
- from: "wms_item"
|
||||
to: "wms_item_category"
|
||||
type: "N:1"
|
||||
foreign_key: "category_id"
|
||||
- from: "wms_item"
|
||||
to: "wms_item_brand"
|
||||
type: "N:1"
|
||||
foreign_key: "brand_id"
|
||||
- from: "wms_item_sku"
|
||||
to: "wms_item"
|
||||
type: "N:1"
|
||||
foreign_key: "item_id"
|
||||
- from: "wms_inventory"
|
||||
to: "wms_item_sku"
|
||||
type: "N:1"
|
||||
foreign_key: "sku_id"
|
||||
- from: "wms_inventory"
|
||||
to: "wms_warehouse"
|
||||
type: "N:1"
|
||||
foreign_key: "warehouse_id"
|
||||
- from: "wms_inventory_history"
|
||||
to: "wms_item_sku"
|
||||
type: "N:1"
|
||||
foreign_key: "sku_id"
|
||||
- from: "wms_inventory_history"
|
||||
to: "wms_warehouse"
|
||||
type: "N:1"
|
||||
foreign_key: "warehouse_id"
|
||||
- from: "wms_receipt_order"
|
||||
to: "wms_warehouse"
|
||||
type: "N:1"
|
||||
foreign_key: "warehouse_id"
|
||||
- from: "wms_receipt_order"
|
||||
to: "wms_merchant"
|
||||
type: "N:1"
|
||||
foreign_key: "merchant_id"
|
||||
- from: "wms_receipt_order_detail"
|
||||
to: "wms_receipt_order"
|
||||
type: "N:1"
|
||||
foreign_key: "order_id"
|
||||
- from: "wms_receipt_order_detail"
|
||||
to: "wms_item_sku"
|
||||
type: "N:1"
|
||||
foreign_key: "sku_id"
|
||||
- from: "wms_shipment_order"
|
||||
to: "wms_warehouse"
|
||||
type: "N:1"
|
||||
foreign_key: "warehouse_id"
|
||||
- from: "wms_shipment_order"
|
||||
to: "wms_merchant"
|
||||
type: "N:1"
|
||||
foreign_key: "merchant_id"
|
||||
- from: "wms_shipment_order_detail"
|
||||
to: "wms_shipment_order"
|
||||
type: "N:1"
|
||||
foreign_key: "order_id"
|
||||
- from: "wms_shipment_order_detail"
|
||||
to: "wms_item_sku"
|
||||
type: "N:1"
|
||||
foreign_key: "sku_id"
|
||||
- from: "wms_movement_order"
|
||||
to: "wms_warehouse"
|
||||
type: "N:1"
|
||||
foreign_key: "from_warehouse_id"
|
||||
comment: "源仓库"
|
||||
- from: "wms_movement_order"
|
||||
to: "wms_warehouse"
|
||||
type: "N:1"
|
||||
foreign_key: "to_warehouse_id"
|
||||
comment: "目标仓库"
|
||||
- from: "wms_movement_order_detail"
|
||||
to: "wms_movement_order"
|
||||
type: "N:1"
|
||||
foreign_key: "order_id"
|
||||
- from: "wms_movement_order_detail"
|
||||
to: "wms_item_sku"
|
||||
type: "N:1"
|
||||
foreign_key: "sku_id"
|
||||
- from: "wms_check_order"
|
||||
to: "wms_warehouse"
|
||||
type: "N:1"
|
||||
foreign_key: "warehouse_id"
|
||||
- from: "wms_check_order_detail"
|
||||
to: "wms_check_order"
|
||||
type: "N:1"
|
||||
foreign_key: "order_id"
|
||||
- from: "wms_check_order_detail"
|
||||
to: "wms_item_sku"
|
||||
type: "N:1"
|
||||
foreign_key: "sku_id"
|
||||
|
||||
# ============================================
|
||||
# 第四阶段:代码使用设计
|
||||
# ============================================
|
||||
code_patterns:
|
||||
# Controller层规范
|
||||
controller:
|
||||
annotations:
|
||||
- "@Tag(name = '管理后台 - xxx') - Swagger 文档标签"
|
||||
- "@RestController - REST 控制器"
|
||||
- "@RequestMapping('/wms/xxx') - 请求路径前缀"
|
||||
- "@Validated - 参数校验"
|
||||
- "@PreAuthorize('@ss.hasPermission('wms:xxx:action')') - 权限控制"
|
||||
example: |
|
||||
@Tag(name = "管理后台 - 入库单")
|
||||
@RestController
|
||||
@RequestMapping("/wms/receipt-order")
|
||||
@Validated
|
||||
public class ReceiptOrderController {
|
||||
@Resource
|
||||
private ReceiptOrderService receiptOrderService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建入库单")
|
||||
@PreAuthorize("@ss.hasPermission('wms:receipt-order:create')")
|
||||
public CommonResult<Long> createReceiptOrder(@Valid @RequestBody ReceiptOrderSaveReqVO reqVO) {
|
||||
return success(receiptOrderService.createReceiptOrder(reqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/complete")
|
||||
@Operation(summary = "完成入库")
|
||||
@PreAuthorize("@ss.hasPermission('wms:receipt-order:update')")
|
||||
public CommonResult<Boolean> completeReceiptOrder(@RequestParam("id") Long id) {
|
||||
receiptOrderService.completeReceiptOrder(id);
|
||||
return success(true);
|
||||
}
|
||||
}
|
||||
|
||||
# Service层规范
|
||||
service:
|
||||
interface_pattern: "XxxService 接口定义业务方法,XxxServiceImpl 实现类"
|
||||
impl_pattern: |
|
||||
@Service
|
||||
@Validated
|
||||
public class XxxServiceImpl implements XxxService {
|
||||
@Resource
|
||||
private XxxMapper xxxMapper;
|
||||
|
||||
// 使用 static final 定义常量
|
||||
// 使用 @Transactional 注解控制事务
|
||||
// 使用 validateXxxExists 方法校验存在性
|
||||
// 使用 exception(ErrorCode) 抛出业务异常
|
||||
}
|
||||
example: |
|
||||
@Service
|
||||
public class ReceiptOrderServiceImpl implements ReceiptOrderService {
|
||||
@Resource
|
||||
private ReceiptOrderMapper receiptOrderMapper;
|
||||
@Resource
|
||||
private ReceiptOrderDetailMapper receiptOrderDetailMapper;
|
||||
@Resource
|
||||
private InventoryService inventoryService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void completeReceiptOrder(Long id) {
|
||||
// 1. 校验单据存在且状态为 PREPARE
|
||||
ReceiptOrderDO order = receiptOrderMapper.selectById(id);
|
||||
validateReceiptOrderCanComplete(order);
|
||||
|
||||
// 2. 遍历明细行,增加库存
|
||||
List<ReceiptOrderDetailDO> details = receiptOrderDetailMapper.selectListByOrderId(id);
|
||||
for (ReceiptOrderDetailDO detail : details) {
|
||||
inventoryService.addStock(detail.getSkuId(), order.getWarehouseId(),
|
||||
detail.getQuantity(), detail.getPrice(),
|
||||
WmsOrderTypeConstants.RECEIPT_ORDER_TYPE, order.getId(), order.getNo());
|
||||
}
|
||||
|
||||
// 3. 更新单据状态为 FINISHED
|
||||
receiptOrderMapper.updateById(new ReceiptOrderDO().setId(id)
|
||||
.setStatus(OrderStatusEnum.FINISHED.getStatus()));
|
||||
}
|
||||
}
|
||||
|
||||
# 数据访问规范
|
||||
dal:
|
||||
mapper_pattern: |
|
||||
public interface XxxMapper extends BaseMapperX<XxxDO> {
|
||||
// 继承 BaseMapperX 获得通用 CRUD 方法
|
||||
// 自定义查询方法使用 @Select 注解或 XML
|
||||
// 分页查询返回 PageResult<XxxDO>
|
||||
}
|
||||
example: |
|
||||
public interface InventoryMapper extends BaseMapperX<InventoryDO> {
|
||||
default PageResult<InventoryDO> selectPage(InventoryPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<InventoryDO>()
|
||||
.eqIfPresent(InventoryDO::getSkuId, reqVO.getSkuId())
|
||||
.eqIfPresent(InventoryDO::getWarehouseId, reqVO.getWarehouseId())
|
||||
.orderByDesc(InventoryDO::getId));
|
||||
}
|
||||
}
|
||||
|
||||
# 库存并发安全模式
|
||||
inventory_concurrency:
|
||||
description: "库存更新使用 SELECT ... FOR UPDATE 行锁 + DuplicateKeyException 捕获实现并发安全"
|
||||
example: |
|
||||
@Service
|
||||
public class InventoryServiceImpl implements InventoryService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void addStock(Long skuId, Long warehouseId, BigDecimal quantity,
|
||||
BigDecimal price, Integer sourceOrderType,
|
||||
Long sourceOrderId, String sourceOrderNo) {
|
||||
// 1. 查询库存记录(带行锁)
|
||||
InventoryDO inventory = inventoryMapper.selectBySkuIdAndWarehouseIdForUpdate(skuId, warehouseId);
|
||||
|
||||
BigDecimal beforeQuantity;
|
||||
if (inventory == null) {
|
||||
// 2. 首次入库,尝试插入(懒初始化)
|
||||
try {
|
||||
inventory = new InventoryDO().setSkuId(skuId)
|
||||
.setWarehouseId(warehouseId).setQuantity(quantity).setPrice(price);
|
||||
inventoryMapper.insert(inventory);
|
||||
beforeQuantity = BigDecimal.ZERO;
|
||||
} catch (DuplicateKeyException e) {
|
||||
// 3. 并发插入冲突,重新查询
|
||||
inventory = inventoryMapper.selectBySkuIdAndWarehouseIdForUpdate(skuId, warehouseId);
|
||||
beforeQuantity = inventory.getQuantity();
|
||||
inventory.setQuantity(beforeQuantity.add(quantity));
|
||||
inventoryMapper.updateById(inventory);
|
||||
}
|
||||
} else {
|
||||
// 4. 已有记录,直接更新
|
||||
beforeQuantity = inventory.getQuantity();
|
||||
inventory.setQuantity(beforeQuantity.add(quantity));
|
||||
inventoryMapper.updateById(inventory);
|
||||
}
|
||||
|
||||
// 5. 记录库存变动历史
|
||||
inventoryHistoryService.createHistory(skuId, warehouseId,
|
||||
beforeQuantity, inventory.getQuantity(), quantity, price,
|
||||
sourceOrderType, sourceOrderId, sourceOrderNo);
|
||||
}
|
||||
}
|
||||
|
||||
# 异常处理
|
||||
error_handling:
|
||||
code_prefix: "wms 模块错误码需遵循项目统一的错误码分配规则"
|
||||
examples:
|
||||
- message: "物料不存在"
|
||||
- message: "仓库不存在"
|
||||
- message: "商户不存在"
|
||||
- message: "SKU 不存在"
|
||||
- message: "库存不足"
|
||||
- message: "单据状态不允许操作"
|
||||
- message: "品牌存在关联数据不允许删除"
|
||||
- message: "分类存在子分类不允许删除"
|
||||
|
||||
# 统一响应
|
||||
response:
|
||||
success_pattern: "CommonResult.success(data)"
|
||||
error_pattern: "throw exception(ErrorCode)"
|
||||
|
||||
# ============================================
|
||||
# 第五阶段:扩展指南
|
||||
# ============================================
|
||||
extension_guide:
|
||||
# 新增单据类型
|
||||
new_channel:
|
||||
title: "新增仓储单据类型"
|
||||
steps:
|
||||
- step: "1. 分配订单类型常量"
|
||||
description: "在 WmsOrderTypeConstants 中分配新的类型范围"
|
||||
code: |
|
||||
// 示例:新增调拨单,类型范围 [500, 600)
|
||||
public static final int TRANSFER_ORDER_TYPE_BEGIN = 500;
|
||||
public static final int TRANSFER_ORDER_TYPE_END = 600;
|
||||
- step: "2. 创建单据头和明细表"
|
||||
description: "创建主子表,单据头包含 no、warehouse_id、status 等字段"
|
||||
code: |
|
||||
CREATE TABLE wms_transfer_order (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
no VARCHAR(64) NOT NULL,
|
||||
warehouse_id BIGINT NOT NULL,
|
||||
status TINYINT NOT NULL DEFAULT 0,
|
||||
remark VARCHAR(512),
|
||||
-- BaseDO 字段
|
||||
creator VARCHAR(64), create_time DATETIME, updater VARCHAR(64),
|
||||
update_time DATETIME, deleted BIT DEFAULT 0,
|
||||
UNIQUE KEY uk_no (no)
|
||||
);
|
||||
- step: "3. 创建 DO、Mapper、Service、Controller"
|
||||
description: "按照既有模式创建完整的 CRUD 代码"
|
||||
- step: "4. 实现完成/取消逻辑"
|
||||
description: "在 Service 中实现单据完成时的库存变更逻辑"
|
||||
- step: "5. 添加权限配置"
|
||||
description: "在系统中配置权限标识 wms:transfer-order:{action}"
|
||||
|
||||
# 新增物料属性
|
||||
new_feature:
|
||||
title: "扩展物料属性"
|
||||
steps:
|
||||
- step: "1. 添加字段到 wms_item 或 wms_item_sku"
|
||||
description: "根据属性粒度决定添加到物料表还是 SKU 表"
|
||||
- step: "2. 更新 DO 类"
|
||||
description: "在对应的 DO 类中添加字段"
|
||||
- step: "3. 更新 VO 类"
|
||||
description: "在 SaveReqVO 和 RespVO 中添加字段"
|
||||
- step: "4. 更新 Controller 和 Service"
|
||||
description: "确保新字段在 CRUD 流程中正确处理"
|
||||
- step: "5. 更新 Excel 导出模板"
|
||||
description: "在导出 VO 中添加 @ExcelProperty 注解"
|
||||
|
||||
# 最佳实践
|
||||
best_practices:
|
||||
- practice: "库存更新必须使用行锁"
|
||||
description: "所有库存更新操作必须使用 SELECT ... FOR UPDATE 行锁,避免并发更新丢失"
|
||||
- practice: "单据完成操作使用事务"
|
||||
description: "单据完成涉及状态更新、库存变更、历史记录,必须在同一事务中完成"
|
||||
- practice: "VO 组装使用批量查询"
|
||||
description: "通过 AdminUserApi.getUserMap() 等批量查询避免 N+1 问题"
|
||||
- practice: "单据操作前校验状态"
|
||||
description: "完成、取消、更新、删除操作前必须校验单据状态为 PREPARE"
|
||||
- practice: "订单类型常量分段管理"
|
||||
description: "新单据类型必须在 WmsOrderTypeConstants 中分配范围,避免类型冲突"
|
||||
- practice: "SKU 作为库存单位"
|
||||
description: "库存以 SKU + 仓库维度管理,物料级别不跟踪库存"
|
||||
- practice: "分类树一次性加载"
|
||||
description: "分类查询建议一次性加载所有数据到内存后构建树,避免递归查询数据库"
|
||||
- practice: "Excel 导出控制数据量"
|
||||
description: "大数据量导出使用 EasyExcel 流式写入,避免内存溢出"
|
||||
|
||||
# ============================================
|
||||
# 依赖关系
|
||||
# ============================================
|
||||
dependencies:
|
||||
# 内部依赖(其他模块)
|
||||
internal:
|
||||
- module: "yudao-module-system"
|
||||
purpose: "AdminUserApi - 批量获取用户信息用于 VO 组装"
|
||||
- module: "yudao-framework-common"
|
||||
purpose: "通用工具类、CommonResult、PageResult 等"
|
||||
- module: "yudao-framework-mybatis"
|
||||
purpose: "MyBatis-Plus 封装、BaseMapperX、BaseDO"
|
||||
|
||||
# 外部依赖(第三方库)
|
||||
external:
|
||||
- name: "MyBatis-Plus"
|
||||
purpose: "ORM 框架,提供 CRUD 封装"
|
||||
- name: "Hutool"
|
||||
purpose: "Java 工具类库"
|
||||
- name: "Swagger/OpenAPI"
|
||||
purpose: "API 文档注解"
|
||||
- name: "Apache POI / EasyExcel"
|
||||
purpose: "Excel 导入导出"
|
||||
|
||||
# ============================================
|
||||
# 关键文件清单
|
||||
# ============================================
|
||||
key_files:
|
||||
- path: "yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/InventoryServiceImpl.java"
|
||||
purpose: "库存服务实现,核心库存变更逻辑(并发安全)"
|
||||
- path: "yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/InventoryHistoryServiceImpl.java"
|
||||
purpose: "库存历史服务,记录每笔库存变动"
|
||||
- path: "yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/receiptorder/ReceiptOrderServiceImpl.java"
|
||||
purpose: "入库单服务实现,完成入库触发库存增加"
|
||||
- path: "yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/shipmentorder/ShipmentOrderServiceImpl.java"
|
||||
purpose: "出库单服务实现,完成出库触发库存减少"
|
||||
- path: "yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/movementorder/MovementOrderServiceImpl.java"
|
||||
purpose: "移库单服务实现,完成移库触发双向库存变更"
|
||||
- path: "yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/checkorder/CheckOrderServiceImpl.java"
|
||||
purpose: "盘点单服务实现,完成盘点触发库存调整"
|
||||
- path: "yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/item/ItemServiceImpl.java"
|
||||
purpose: "物料服务实现,包含 SKU 嵌入管理"
|
||||
- path: "yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/InventoryMapper.java"
|
||||
purpose: "库存 Mapper,含 FOR UPDATE 查询方法"
|
||||
- path: "yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/WmsOrderTypeConstants.java"
|
||||
purpose: "订单类型常量,定义各单据类型范围"
|
||||
- path: "yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java"
|
||||
purpose: "错误码常量定义"
|
||||
@@ -1,91 +0,0 @@
|
||||
# 工厂模式知识库
|
||||
|
||||
pattern:
|
||||
id: "factory-pattern"
|
||||
name: "工厂模式"
|
||||
category: "创建型模式"
|
||||
description: "定义一个创建对象的接口,让子类决定实例化哪一个类,使一个类的实例化延迟到其子类"
|
||||
|
||||
# 模式结构
|
||||
structure:
|
||||
participants:
|
||||
- name: "Factory"
|
||||
role: "抽象工厂接口"
|
||||
description: "声明创建产品对象的抽象方法"
|
||||
- name: "ConcreteFactory"
|
||||
role: "具体工厂"
|
||||
description: "实现抽象工厂接口,创建具体产品"
|
||||
- name: "Product"
|
||||
role: "抽象产品"
|
||||
description: "定义产品的共同接口"
|
||||
- name: "ConcreteProduct"
|
||||
role: "具体产品"
|
||||
description: "实现抽象产品接口的具体类"
|
||||
|
||||
# 项目中的应用
|
||||
applications:
|
||||
- module: "pay"
|
||||
location: "PayClientFactory"
|
||||
purpose: "创建支付客户端实例"
|
||||
code_path: "yudao-module-pay/.../pay/core/client/PayClientFactory.java"
|
||||
implementation: |
|
||||
public interface PayClientFactory {
|
||||
PayClient getPayClient(Long channelId);
|
||||
<Config extends PayClientConfig> PayClient createOrUpdatePayClient(
|
||||
Long channelId, String channelCode, Config config);
|
||||
}
|
||||
|
||||
- module: "infra/sms"
|
||||
location: "SmsClientFactory"
|
||||
purpose: "创建短信客户端实例"
|
||||
code_path: "yudao-module-infra/.../sms/core/client/SmsClientFactory.java"
|
||||
implementation: |
|
||||
public class SmsClientFactoryImpl implements SmsClientFactory {
|
||||
private final ConcurrentMap<Long, AbstractSmsClient> channelIdClients = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public SmsClient createOrUpdateSmsClient(SmsChannelProperties properties) {
|
||||
AbstractSmsClient client = channelIdClients.get(properties.getId());
|
||||
if (client == null) {
|
||||
client = this.createSmsClient(properties);
|
||||
client.init();
|
||||
channelIdClients.put(client.getId(), client);
|
||||
} else {
|
||||
client.refresh(properties);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
# 使用场景
|
||||
scenarios:
|
||||
- "需要根据配置动态创建不同类型的客户端"
|
||||
- "需要管理多个同类对象的实例(如缓存)"
|
||||
- "创建逻辑复杂,需要封装"
|
||||
- "需要支持扩展新的产品类型"
|
||||
|
||||
# 优点
|
||||
advantages:
|
||||
- "解耦创建逻辑和使用逻辑"
|
||||
- "便于扩展新产品类型"
|
||||
- "可以缓存和复用对象实例"
|
||||
- "统一管理对象生命周期"
|
||||
|
||||
# 缺点
|
||||
disadvantages:
|
||||
- "增加了类的数量"
|
||||
- "需要维护工厂类的实现"
|
||||
|
||||
# 扩展指南
|
||||
extension_guide:
|
||||
title: "如何新增产品类型"
|
||||
steps:
|
||||
- step: 1
|
||||
action: "创建具体产品类"
|
||||
description: "实现Product接口"
|
||||
- step: 2
|
||||
action: "修改工厂创建逻辑"
|
||||
description: "在工厂中添加新类型的创建分支"
|
||||
- step: 3
|
||||
action: "配置支持"
|
||||
description: "添加配置枚举或配置项"
|
||||
@@ -1,58 +0,0 @@
|
||||
# 设计模式索引
|
||||
|
||||
patterns:
|
||||
# 创建型模式
|
||||
- id: "factory-pattern"
|
||||
name: "工厂模式"
|
||||
file: "factory-pattern.yaml"
|
||||
modules: ["pay", "infra"]
|
||||
description: "创建对象实例的工厂接口,支持多类型扩展"
|
||||
|
||||
# 行为型模式
|
||||
- id: "template-method-pattern"
|
||||
name: "模板方法模式"
|
||||
file: "template-method-pattern.yaml"
|
||||
modules: ["pay", "infra"]
|
||||
description: "定义算法骨架,子类实现具体步骤"
|
||||
|
||||
- id: "strategy-pattern"
|
||||
name: "策略模式"
|
||||
file: "strategy-pattern.yaml"
|
||||
modules: ["pay", "infra", "ai"]
|
||||
description: "封装可互换的算法族"
|
||||
|
||||
# 模式选择指南
|
||||
selection_guide:
|
||||
- scenario: "需要根据配置创建不同类型的客户端"
|
||||
patterns: ["factory-pattern", "strategy-pattern"]
|
||||
example: "支付渠道、短信渠道"
|
||||
|
||||
- scenario: "需要统一处理流程,但具体实现不同"
|
||||
patterns: ["template-method-pattern"]
|
||||
example: "支付下单流程、短信发送流程"
|
||||
|
||||
- scenario: "需要动态切换算法或行为"
|
||||
patterns: ["strategy-pattern"]
|
||||
example: "AI模型切换、支付方式切换"
|
||||
|
||||
# 模式组合
|
||||
combinations:
|
||||
- name: "工厂 + 策略"
|
||||
description: "工厂创建策略实例"
|
||||
usage: "PayClientFactory创建PayClient实例"
|
||||
|
||||
- name: "工厂 + 模板方法"
|
||||
description: "工厂创建模板方法的具体实现"
|
||||
usage: "SmsClientFactory创建AbstractSmsClient子类"
|
||||
|
||||
- name: "模板方法 + 策略"
|
||||
description: "模板方法定义骨架,策略实现变化部分"
|
||||
usage: "AbstractPayClient定义骨架,具体PayClient实现策略"
|
||||
|
||||
# 扩展指南
|
||||
extension:
|
||||
new_pattern:
|
||||
steps:
|
||||
- "创建模式知识库文件: skills/patterns/{pattern}-pattern.yaml"
|
||||
- "更新此索引文件"
|
||||
- "关联应用到相关模块"
|
||||
@@ -1,136 +0,0 @@
|
||||
# 策略模式知识库
|
||||
|
||||
pattern:
|
||||
id: "strategy-pattern"
|
||||
name: "策略模式"
|
||||
category: "行为型模式"
|
||||
description: "定义一系列算法,把它们封装起来,并使它们可互相替换,让算法独立于使用它的客户而变化"
|
||||
|
||||
# 模式结构
|
||||
structure:
|
||||
participants:
|
||||
- name: "Strategy"
|
||||
role: "策略接口"
|
||||
description: "定义所有支持的算法的公共接口"
|
||||
- name: "ConcreteStrategy"
|
||||
role: "具体策略"
|
||||
description: "实现策略接口的具体算法"
|
||||
- name: "Context"
|
||||
role: "上下文"
|
||||
description: "维护对策略对象的引用"
|
||||
|
||||
# 项目中的应用
|
||||
applications:
|
||||
- module: "pay"
|
||||
location: "PayClient接口及其实现"
|
||||
purpose: "支持多渠道支付"
|
||||
code_path: "yudao-module-pay/.../pay/core/client/PayClient.java"
|
||||
strategies:
|
||||
- name: "AlipayQrPayClient"
|
||||
description: "支付宝扫码支付"
|
||||
- name: "AlipayAppPayClient"
|
||||
description: "支付宝App支付"
|
||||
- name: "WxNativePayClient"
|
||||
description: "微信Native支付"
|
||||
- name: "WxAppPayClient"
|
||||
description: "微信App支付"
|
||||
implementation: |
|
||||
public interface PayClient<Config extends PayClientConfig> {
|
||||
Long getId();
|
||||
PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
|
||||
PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO);
|
||||
}
|
||||
|
||||
- module: "infra/sms"
|
||||
location: "SmsClient接口及其实现"
|
||||
purpose: "支持多渠道短信发送"
|
||||
strategies:
|
||||
- name: "AliyunSmsClient"
|
||||
description: "阿里云短信"
|
||||
- name: "TencentSmsClient"
|
||||
description: "腾讯云短信"
|
||||
- name: "HuaweiSmsClient"
|
||||
description: "华为云短信"
|
||||
- name: "QiniuSmsClient"
|
||||
description: "七牛云短信"
|
||||
|
||||
- module: "ai"
|
||||
location: "ChatModel接口及其实现"
|
||||
purpose: "支持多AI模型"
|
||||
strategies:
|
||||
- name: "OpenAIChatModel"
|
||||
description: "OpenAI聊天模型"
|
||||
- name: "BaiduChatModel"
|
||||
description: "百度文心一言"
|
||||
- name: "ZhipuChatModel"
|
||||
description: "智谱AI"
|
||||
- name: "TongyiChatModel"
|
||||
description: "阿里通义千问"
|
||||
|
||||
# 使用场景
|
||||
scenarios:
|
||||
- "需要在运行时选择算法"
|
||||
- "有多个类只在行为上有差异"
|
||||
- "需要动态切换算法"
|
||||
- "需要隐藏算法实现细节"
|
||||
|
||||
# 优点
|
||||
advantages:
|
||||
- "算法可自由切换"
|
||||
- "避免使用多重条件判断"
|
||||
- "易于扩展新策略"
|
||||
- "符合开闭原则"
|
||||
|
||||
# 缺点
|
||||
disadvantages:
|
||||
- "客户端需要了解所有策略"
|
||||
- "策略过多时类数量增加"
|
||||
|
||||
# 与工厂模式配合
|
||||
factory_integration:
|
||||
description: "策略模式通常与工厂模式配合使用,由工厂创建具体策略实例"
|
||||
example:
|
||||
factory: "PayClientFactory"
|
||||
purpose: "根据渠道配置创建对应的PayClient实例"
|
||||
code: |
|
||||
// 通过工厂获取策略
|
||||
PayClient client = payClientFactory.getPayClient(channelId);
|
||||
// 使用策略
|
||||
PayOrderRespDTO response = client.unifiedOrder(reqDTO);
|
||||
|
||||
# 扩展指南
|
||||
extension_guide:
|
||||
title: "如何新增策略"
|
||||
steps:
|
||||
- step: 1
|
||||
action: "创建策略实现类"
|
||||
description: "实现策略接口"
|
||||
- step: 2
|
||||
action: "定义配置类"
|
||||
description: "如果需要配置参数"
|
||||
- step: 3
|
||||
action: "修改工厂"
|
||||
description: "在工厂中添加创建分支"
|
||||
- step: 4
|
||||
action: "添加枚举"
|
||||
description: "如果需要类型枚举"
|
||||
|
||||
example:
|
||||
title: "新增短信渠道"
|
||||
code: |
|
||||
// 1. 创建实现类
|
||||
public class XxxSmsClient extends AbstractSmsClient {
|
||||
@Override
|
||||
protected void doInit() { ... }
|
||||
|
||||
@Override
|
||||
public SmsSendResultDTO sendSms(SmsSendMessageDTO message) { ... }
|
||||
}
|
||||
|
||||
// 2. 添加渠道枚举
|
||||
public enum SmsChannelEnum {
|
||||
XXX(10, "XXX短信");
|
||||
}
|
||||
|
||||
// 3. 修改工厂创建逻辑
|
||||
case XXX: return new XxxSmsClient(properties);
|
||||
@@ -1,119 +0,0 @@
|
||||
# 模板方法模式知识库
|
||||
|
||||
pattern:
|
||||
id: "template-method-pattern"
|
||||
name: "模板方法模式"
|
||||
category: "行为型模式"
|
||||
description: "定义一个操作中的算法骨架,将一些步骤延迟到子类中,使得子类可以不改变算法结构即可重定义算法的某些步骤"
|
||||
|
||||
# 模式结构
|
||||
structure:
|
||||
participants:
|
||||
- name: "AbstractClass"
|
||||
role: "抽象类"
|
||||
description: "定义抽象方法和模板方法,模板方法调用抽象方法"
|
||||
- name: "ConcreteClass"
|
||||
role: "具体类"
|
||||
description: "实现抽象方法,完成具体逻辑"
|
||||
|
||||
# 项目中的应用
|
||||
applications:
|
||||
- module: "pay"
|
||||
location: "AbstractPayClient"
|
||||
purpose: "统一支付流程骨架"
|
||||
code_path: "yudao-module-pay/.../pay/core/client/impl/AbstractPayClient.java"
|
||||
implementation: |
|
||||
public abstract class AbstractPayClient<Config extends PayClientConfig> implements PayClient<Config> {
|
||||
|
||||
// 模板方法:统一下单
|
||||
@Override
|
||||
public final PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
ValidationUtils.validate(reqDTO);
|
||||
try {
|
||||
return doUnifiedOrder(reqDTO); // 调用子类实现
|
||||
} catch (Throwable ex) {
|
||||
log.error("[unifiedOrder]异常", ex);
|
||||
throw buildPayException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
// 抽象方法:由子类实现具体下单逻辑
|
||||
protected abstract PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Throwable;
|
||||
|
||||
// 抽象方法:初始化
|
||||
protected abstract void doInit();
|
||||
}
|
||||
|
||||
- module: "infra/sms"
|
||||
location: "AbstractSmsClient"
|
||||
purpose: "统一短信发送流程"
|
||||
code_path: "yudao-module-infra/.../sms/core/client/AbstractSmsClient.java"
|
||||
implementation: |
|
||||
public abstract class AbstractSmsClient implements SmsClient {
|
||||
// 模板方法定义发送流程
|
||||
}
|
||||
|
||||
# 模板方法流程
|
||||
template_methods:
|
||||
- name: "unifiedOrder"
|
||||
module: "pay"
|
||||
steps:
|
||||
- "参数校验"
|
||||
- "调用子类实现下单"
|
||||
- "异常处理和转换"
|
||||
hook_methods:
|
||||
- "doUnifiedOrder()"
|
||||
- "doInit()"
|
||||
|
||||
# 使用场景
|
||||
scenarios:
|
||||
- "多个子类有公共行为,但具体实现不同"
|
||||
- "需要控制算法的整体流程"
|
||||
- "需要在不同点调用不同的实现"
|
||||
|
||||
# 优点
|
||||
advantages:
|
||||
- "复用公共代码"
|
||||
- "控制算法流程"
|
||||
- "符合开闭原则"
|
||||
- "便于维护和扩展"
|
||||
|
||||
# 缺点
|
||||
disadvantages:
|
||||
- "每个实现都需要定义子类"
|
||||
- "增加了类的数量"
|
||||
|
||||
# 扩展指南
|
||||
extension_guide:
|
||||
title: "如何新增具体实现"
|
||||
steps:
|
||||
- step: 1
|
||||
action: "创建具体类"
|
||||
description: "继承抽象类"
|
||||
- step: 2
|
||||
action: "实现抽象方法"
|
||||
description: "doUnifiedOrder(), doInit()等"
|
||||
- step: 3
|
||||
action: "注册到工厂"
|
||||
description: "在工厂中添加创建逻辑"
|
||||
|
||||
example:
|
||||
title: "新增支付渠道"
|
||||
code: |
|
||||
public class XxxPayClient extends AbstractPayClient<XxxPayClientConfig> {
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
// 初始化SDK
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Throwable {
|
||||
// 调用第三方支付API
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
||||
// 调用第三方退款API
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
# 模块 Skill 提取提示词模板
|
||||
|
||||
## 角色定义
|
||||
你是一个资深的企业级Java应用架构师,精通DDD领域驱动设计、微服务架构和Spring Boot生态。
|
||||
|
||||
## 任务目标
|
||||
为指定模块提取完整的 Skill 文档,形成可复用的知识资产。
|
||||
|
||||
## 分析框架(五阶段)
|
||||
|
||||
---
|
||||
|
||||
### 第一阶段:设计理念提取
|
||||
|
||||
从以下维度分析模块的设计理念:
|
||||
|
||||
1. **业务定位**
|
||||
- 模块解决什么业务问题?
|
||||
- 模块在整个系统中的定位?
|
||||
- 与其他模块的边界是什么?
|
||||
|
||||
2. **设计原则**
|
||||
- 遵循了哪些设计原则(SOLID、DDD等)?
|
||||
- 有哪些关键的架构决策?
|
||||
- 为什么这样设计?
|
||||
|
||||
3. **领域模型**
|
||||
- 核心领域对象有哪些?
|
||||
- 领域对象之间的关系?
|
||||
- 聚合根是什么?值对象有哪些?
|
||||
|
||||
---
|
||||
|
||||
### 第二阶段:架构设计提取
|
||||
|
||||
分析模块的分层架构:
|
||||
|
||||
1. **目录结构分析**
|
||||
```
|
||||
yudao-module-{xxx}/
|
||||
├── api/ # API接口层(模块间通信)
|
||||
├── controller/ # 控制器层(admin/app双端)
|
||||
├── convert/ # 转换层(对象映射)
|
||||
├── dal/ # 数据访问层(dataobject/mysql/redis)
|
||||
├── service/ # 业务逻辑层
|
||||
├── enums/ # 枚举定义
|
||||
├── framework/ # 模块框架配置
|
||||
├── job/ # 定时任务
|
||||
└── mq/ # 消息队列
|
||||
```
|
||||
|
||||
2. **设计模式应用**
|
||||
- 识别工厂模式、策略模式、模板方法等
|
||||
- 分析模式应用场景和位置
|
||||
- 说明模式选择的理由
|
||||
|
||||
3. **模块间通信**
|
||||
- API接口定义
|
||||
- 消息队列使用
|
||||
- 事件驱动设计
|
||||
|
||||
---
|
||||
|
||||
### 第三阶段:数据表设计提取
|
||||
|
||||
分析数据模型:
|
||||
|
||||
1. **实体继承体系**
|
||||
- BaseDO(基础字段:createTime, updateTime, creator, updater, deleted)
|
||||
- TenantBaseDO(多租户字段:tenantId)
|
||||
|
||||
2. **核心表结构**
|
||||
- 表名、说明、核心字段、关联关系
|
||||
- 主键策略、索引设计
|
||||
|
||||
3. **表关系**
|
||||
- 外键关系
|
||||
- 一对多/多对多关系
|
||||
|
||||
---
|
||||
|
||||
### 第四阶段:代码使用设计提取
|
||||
|
||||
提取代码规范和模式:
|
||||
|
||||
1. **Controller层规范**
|
||||
- @Tag、@Operation 注解
|
||||
- @PreAuthorize 权限控制
|
||||
- CommonResult 统一返回
|
||||
- PageResult 分页封装
|
||||
|
||||
2. **Service层规范**
|
||||
- 接口定义规范
|
||||
- 事务处理规范
|
||||
- 异常处理规范(ErrorCodeConstants)
|
||||
|
||||
3. **数据访问规范**
|
||||
- Mapper接口定义
|
||||
- 分页查询实现
|
||||
- 数据权限控制
|
||||
|
||||
---
|
||||
|
||||
### 第五阶段:扩展指南提取
|
||||
|
||||
总结如何基于此模块扩展:
|
||||
|
||||
1. **新增业务功能步骤**
|
||||
- 步骤化说明
|
||||
- 关键代码示例
|
||||
|
||||
2. **新增渠道/类型示例**
|
||||
- 具体实现步骤
|
||||
- 需要修改的文件
|
||||
|
||||
3. **最佳实践总结**
|
||||
- 开发注意事项
|
||||
- 常见问题解决
|
||||
|
||||
---
|
||||
|
||||
## 输出格式
|
||||
|
||||
生成两个文件:
|
||||
1. `skill-{module}.yaml` - 结构化Skill文档(使用模板格式)
|
||||
2. `skill-{module}.md` - 可读性文档(Markdown格式)
|
||||
|
||||
---
|
||||
|
||||
## 关键参考文件
|
||||
|
||||
| 文件类型 | 路径模式 |
|
||||
|---------|---------|
|
||||
| Controller | `yudao-module-{xxx}/.../controller/` |
|
||||
| Service | `yudao-module-{xxx}/.../service/` |
|
||||
| DO实体 | `yudao-module-{xxx}/.../dal/dataobject/` |
|
||||
| Mapper | `yudao-module-{xxx}/.../dal/mysql/` |
|
||||
| 枚举 | `yudao-module-{xxx}/.../enums/` |
|
||||
| 错误码 | `yudao-module-{xxx}/.../ErrorCodeConstants.java` |
|
||||
@@ -1,161 +0,0 @@
|
||||
# Skill 模板文件
|
||||
# 用于提取模块知识的标准格式
|
||||
|
||||
skill:
|
||||
id: "skill-{module}"
|
||||
name: "{模块名称} Skill"
|
||||
version: "1.0.0"
|
||||
module_path: "yudao-module-{module}"
|
||||
created_at: ""
|
||||
updated_at: ""
|
||||
|
||||
# ============================================
|
||||
# 第一阶段:设计理念
|
||||
# ============================================
|
||||
philosophy:
|
||||
# 业务定位:模块解决什么业务问题?在整个系统中的定位?
|
||||
business_position: ""
|
||||
|
||||
# 设计原则:遵循了哪些设计原则(SOLID、DDD)?有哪些架构决策?
|
||||
design_principles: []
|
||||
|
||||
# 领域模型:核心领域对象有哪些?领域对象之间的关系?聚合根是什么?
|
||||
domain_model:
|
||||
aggregates: []
|
||||
# - name: "聚合根名称"
|
||||
# type: "聚合根"
|
||||
# entities: ["关联实体"]
|
||||
|
||||
value_objects: []
|
||||
services: []
|
||||
|
||||
# ============================================
|
||||
# 第二阶段:架构设计
|
||||
# ============================================
|
||||
architecture:
|
||||
# 分层架构
|
||||
layers:
|
||||
- name: "api"
|
||||
purpose: "模块间API接口"
|
||||
components: []
|
||||
|
||||
- name: "controller"
|
||||
purpose: "HTTP接口"
|
||||
components: []
|
||||
|
||||
- name: "service"
|
||||
purpose: "业务逻辑"
|
||||
components: []
|
||||
|
||||
- name: "dal"
|
||||
purpose: "数据访问"
|
||||
components: []
|
||||
|
||||
# 设计模式应用
|
||||
design_patterns: []
|
||||
# - pattern: "Factory"
|
||||
# location: "具体文件路径"
|
||||
# purpose: "模式用途说明"
|
||||
|
||||
# 模块间通信
|
||||
communication:
|
||||
apis: [] # 对外暴露的API
|
||||
consumers: [] # 消费的其他模块API
|
||||
mq: [] # 消息队列使用
|
||||
|
||||
# ============================================
|
||||
# 第三阶段:数据表设计
|
||||
# ============================================
|
||||
data_model:
|
||||
# 实体继承体系
|
||||
entity_hierarchy:
|
||||
base: "BaseDO | TenantBaseDO"
|
||||
description: ""
|
||||
|
||||
# 核心数据表
|
||||
tables: []
|
||||
# - name: "表名"
|
||||
# comment: "表说明"
|
||||
# entity: "DO类名"
|
||||
# extends: "BaseDO/TenantBaseDO"
|
||||
# columns:
|
||||
# - { name: "字段名", type: "类型", comment: "说明" }
|
||||
# indexes:
|
||||
# - { name: "索引名", columns: ["字段"] }
|
||||
|
||||
# 表关系(ER关系)
|
||||
relationships: []
|
||||
# - from: "表A"
|
||||
# to: "表B"
|
||||
# type: "1:N | N:1 | N:N"
|
||||
# foreign_key: "外键字段"
|
||||
|
||||
# ============================================
|
||||
# 第四阶段:代码使用设计
|
||||
# ============================================
|
||||
code_patterns:
|
||||
# Controller层规范
|
||||
controller:
|
||||
annotations: [] # 常用注解
|
||||
example: ""
|
||||
|
||||
# Service层规范
|
||||
service:
|
||||
interface_pattern: ""
|
||||
impl_pattern: ""
|
||||
example: ""
|
||||
|
||||
# 数据访问规范
|
||||
dal:
|
||||
mapper_pattern: ""
|
||||
example: ""
|
||||
|
||||
# 异常处理
|
||||
error_handling:
|
||||
code_prefix: ""
|
||||
examples: []
|
||||
|
||||
# 统一响应
|
||||
response:
|
||||
success_pattern: ""
|
||||
error_pattern: ""
|
||||
|
||||
# ============================================
|
||||
# 第五阶段:扩展指南
|
||||
# ============================================
|
||||
extension_guide:
|
||||
# 新增功能步骤
|
||||
new_feature:
|
||||
title: "新增业务功能"
|
||||
steps: []
|
||||
|
||||
# 新增渠道/类型示例
|
||||
new_channel:
|
||||
title: "新增渠道/类型"
|
||||
steps: []
|
||||
|
||||
# 最佳实践
|
||||
best_practices: []
|
||||
|
||||
# ============================================
|
||||
# 依赖关系
|
||||
# ============================================
|
||||
dependencies:
|
||||
# 内部依赖(其他模块)
|
||||
internal: []
|
||||
# - module: "模块名"
|
||||
# api: "API接口名"
|
||||
# purpose: "依赖用途"
|
||||
|
||||
# 外部依赖(第三方库)
|
||||
external: []
|
||||
# - name: "库名"
|
||||
# version: "版本"
|
||||
# purpose: "用途"
|
||||
|
||||
# ============================================
|
||||
# 关键文件清单
|
||||
# ============================================
|
||||
key_files:
|
||||
- path: ""
|
||||
purpose: ""
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,363 +0,0 @@
|
||||
# 芋道源码 Skills 使用指南
|
||||
|
||||
> 快速查找适合你开发场景的提示词文档,让 AI 助手更精准地理解你的需求。
|
||||
|
||||
---
|
||||
|
||||
## 自动引用机制说明
|
||||
|
||||
**从 2026-03-31 起,使用样例支持自动引用设计规范**:
|
||||
|
||||
每个使用样例文档头部包含 YAML front matter 配置,声明该场景需要引用的规范文件:
|
||||
|
||||
```yaml
|
||||
---
|
||||
references:
|
||||
design: # 设计规范(全局约束)
|
||||
- skills/design/db-designer.yaml
|
||||
- skills/design/entity-designer.yaml
|
||||
module_guide: # 模块引用提示
|
||||
prompt: "请指定目标模块"
|
||||
mapping:
|
||||
mes: skills/modules/mes/skill-mes.yaml
|
||||
patterns: # 设计模式引用
|
||||
- skills/patterns/factory-pattern.yaml
|
||||
---
|
||||
```
|
||||
|
||||
**使用方式**:
|
||||
1. AI 读取使用样例时自动识别 `references` 配置
|
||||
2. AI 自动加载引用的设计规范文件内容
|
||||
3. 用户在提示词中指定模块名后,AI 加载对应模块 skill
|
||||
4. 生成的代码符合所有规范定义的标准
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [使用指南概述](#使用指南概述)
|
||||
2. [场景分类](#场景分类)
|
||||
3. [使用流程](#使用流程)
|
||||
4. [FAQ](#faq)
|
||||
|
||||
---
|
||||
|
||||
## 使用指南概述
|
||||
|
||||
本目录提供芋道源码项目的 AI 辅助开发使用场景指南。每个场景文档都包含:
|
||||
|
||||
- **适用情况**:什么场景下使用该提示词
|
||||
- **输入要求**:需要提供哪些信息
|
||||
- **预期输出**:AI 将生成什么样的代码或文档
|
||||
- **使用示例**:实际案例演示
|
||||
|
||||
### Skills 资源概览
|
||||
|
||||
| 资源类型 | 目录位置 | 说明 |
|
||||
|---------|---------|------|
|
||||
| 模块 Skills | `skills/modules/` | 各功能模块的完整技术规范 |
|
||||
| 设计模式 | `skills/patterns/` | 常用设计模式的实现模板 |
|
||||
| 文档模板 | `skills/templates/` | Skill 文档编写模板 |
|
||||
|
||||
---
|
||||
|
||||
## 场景分类
|
||||
|
||||
### 一、快速上手
|
||||
|
||||
适用于初次接触项目或快速了解某个模块的开发者。
|
||||
|
||||
| 场景 | 适用情况 | 文档链接 | 一句话描述 |
|
||||
|-----|---------|---------|-----------|
|
||||
| 了解项目结构 | 刚接触项目,需要快速了解整体架构 | `skills/index.yaml` | 查看 12 个核心模块的技术规范索引 |
|
||||
| 系统模块入门 | 需要了解用户、角色、权限等基础功能 | [skill-system.yaml](../modules/system/skill-system.yaml) | 系统管理模块完整开发指南 |
|
||||
| 基础设施入门 | 需要了解文件、配置、任务等基础服务 | [skill-infra.yaml](../modules/infra/skill-infra.yaml) | 基础设施模块完整开发指南 |
|
||||
| **实体类实现** | 需要从 SQL 到实体类创建完整 CRUD 功能 | [entity-implementation.md](entity-implementation.md) | 从建表到完整接口的端到端指南 |
|
||||
|
||||
**推荐使用流程**:
|
||||
|
||||
```
|
||||
阅读 index.yaml --> 选择目标模块 --> 阅读对应 skill-*.yaml --> 开始开发
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 二、扩展模块
|
||||
|
||||
适用于在现有模块基础上添加新功能的场景。
|
||||
|
||||
| 场景 | 适用情况 | 文档链接 | 一句话描述 |
|
||||
|-----|---------|---------|-----------|
|
||||
| 扩展系统模块 | 添加新的系统管理功能 | [skill-system.yaml](../modules/system/skill-system.yaml) | 遵循系统模块规范扩展功能 |
|
||||
| 扩展支付模块 | 添加新的支付渠道或支付方式 | [skill-pay.yaml](../modules/pay/skill-pay.yaml) | 遵循支付模块规范扩展功能 |
|
||||
| 扩展会员模块 | 添加会员等级、积分等功能 | [skill-member.yaml](../modules/member/skill-member.yaml) | 遵循会员模块规范扩展功能 |
|
||||
| 扩展商城模块 | 添加商品、订单相关功能 | [skill-mall.yaml](../modules/mall/skill-mall.yaml) | 遵循商城模块规范扩展功能 |
|
||||
| 扩展 CRM 模块 | 添加客户管理相关功能 | [skill-crm.yaml](../modules/crm/skill-crm.yaml) | 遵循 CRM 模块规范扩展功能 |
|
||||
| 扩展工作流模块 | 添加流程审批相关功能 | [skill-bpm.yaml](../modules/bpm/skill-bpm.yaml) | 遵循工作流模块规范扩展功能 |
|
||||
|
||||
**扩展开发要点**:
|
||||
|
||||
1. 阅读目标模块的 skill 文档
|
||||
2. 理解现有代码结构和命名规范
|
||||
3. 参考文档中的代码示例
|
||||
4. 遵循文档中的开发约束
|
||||
|
||||
---
|
||||
|
||||
### 三、改造模块
|
||||
|
||||
适用于修改现有功能或重构代码的场景。
|
||||
|
||||
| 场景 | 适用情况 | 文档链接 | 一句话描述 |
|
||||
|-----|---------|---------|-----------|
|
||||
| 改造系统功能 | 修改用户、角色、菜单等核心功能 | [skill-system.yaml](../modules/system/skill-system.yaml) | 系统模块改造技术规范 |
|
||||
| 改造支付流程 | 修改支付、退款等核心流程 | [skill-pay.yaml](../modules/pay/skill-pay.yaml) | 支付模块改造技术规范 |
|
||||
| 改造订单流程 | 修改订单创建、支付、发货流程 | [skill-mall.yaml](../modules/mall/skill-mall.yaml) | 商城模块改造技术规范 |
|
||||
| 改造审批流程 | 修改工作流定义和审批逻辑 | [skill-bpm.yaml](../modules/bpm/skill-bpm.yaml) | 工作流模块改造技术规范 |
|
||||
|
||||
**改造开发要点**:
|
||||
|
||||
1. 重点关注文档中的"核心流程"部分
|
||||
2. 了解模块间的依赖关系
|
||||
3. 注意数据库字段和索引的影响
|
||||
4. 遵循向后兼容原则
|
||||
|
||||
---
|
||||
|
||||
### 四、新增模块
|
||||
|
||||
适用于创建全新业务模块的场景。
|
||||
|
||||
| 场景 | 适用情况 | 文档链接 | 一句话描述 |
|
||||
|-----|---------|---------|-----------|
|
||||
| 新增业务模块 | 创建全新的业务功能模块 | [skill-template.yaml](../templates/skill-template.yaml) | Skill 文档编写模板 |
|
||||
| 应用设计模式 | 需要使用设计模式优化代码 | [patterns/](../patterns/) | 设计模式实现模板 |
|
||||
|
||||
**新增模块参考**:
|
||||
|
||||
参考现有模块的 skill 文档结构:
|
||||
|
||||
- 核心模块参考:`skill-system.yaml`、`skill-infra.yaml`
|
||||
- 业务模块参考:`skill-mall.yaml`、`skill-crm.yaml`
|
||||
- 技术模块参考:`skill-bpm.yaml`、`skill-ai.yaml`
|
||||
|
||||
**新增模块步骤**:
|
||||
|
||||
1. 使用 `skill-template.yaml` 创建新模块文档
|
||||
2. 参考 `extraction-prompt.md` 提取技术规范
|
||||
3. 按照模块规范编写代码
|
||||
4. 更新 `skills/index.yaml` 索引
|
||||
|
||||
---
|
||||
|
||||
### 五、设计模式应用
|
||||
|
||||
适用于需要使用设计模式优化代码结构的场景。
|
||||
|
||||
| 模式 | 适用情况 | 文档链接 | 一句话描述 |
|
||||
|-----|---------|---------|-----------|
|
||||
| 工厂模式 | 需要根据条件创建不同类型的对象 | [factory-pattern.yaml](../patterns/factory-pattern.yaml) | 对象创建的统一管理 |
|
||||
| 策略模式 | 需要在多种算法或策略间切换 | [strategy-pattern.yaml](../patterns/strategy-pattern.yaml) | 算法族的统一接口 |
|
||||
| 模板方法模式 | 需要定义算法骨架,子类实现细节 | [template-method-pattern.yaml](../patterns/template-method-pattern.yaml) | 算法骨架的固定结构 |
|
||||
|
||||
**设计模式选择指南**:
|
||||
|
||||
```
|
||||
是否需要创建对象? --> 多种类型 --> 工厂模式
|
||||
|
|
||||
v
|
||||
是否需要切换算法? --> 多种策略 --> 策略模式
|
||||
|
|
||||
v
|
||||
是否需要固定流程? --> 流程相同,细节不同 --> 模板方法模式
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 整体流程图
|
||||
|
||||
```
|
||||
+-------------------+
|
||||
| 明确开发需求 |
|
||||
+--------+----------+
|
||||
|
|
||||
+--------------+--------------+
|
||||
| |
|
||||
v v
|
||||
+-------+-------+ +--------+--------+
|
||||
| 新增功能? | | 改造现有功能? |
|
||||
+-------+-------+ +--------+--------+
|
||||
| |
|
||||
+-------+-------+ |
|
||||
| +-------+-------+
|
||||
v | v
|
||||
+-----+-----+ +-----+-----+ +-----+-----+
|
||||
| 新增模块 | | 扩展现有 | | 改造模块 |
|
||||
+-----+-----+ | 模块 | +-----+-----+
|
||||
| +-----+-----+ |
|
||||
v | v
|
||||
+-----+-----+ v +-----+-----+
|
||||
| 使用模板 | +-----+-----+ | 查看对应 |
|
||||
| 创建文档 | | 查看对应 | | 模块文档 |
|
||||
+-----+-----+ | 模块文档 | +-----+-----+
|
||||
| +-----+-----+ |
|
||||
v | v
|
||||
+-----+-----+ v +-----+-----+
|
||||
| 参考现有 | +-----+-----+ | 关注核心 |
|
||||
| 模块规范 | | 遵循规范 | | 流程部分 |
|
||||
+-----+-----+ | 开发 | +-----+-----+
|
||||
| +-----+-----+ |
|
||||
+-----------+-----------------+---------------+
|
||||
|
|
||||
v
|
||||
+-------+-------+
|
||||
| 开发完成 |
|
||||
+---------------+
|
||||
```
|
||||
|
||||
### 快速查找流程
|
||||
|
||||
1. **确定场景类型**:根据上方的场景分类确定你的开发场景
|
||||
2. **查找对应文档**:在场景表格中找到对应的文档链接
|
||||
3. **阅读技术规范**:打开对应的 skill 文档,了解技术要求
|
||||
4. **开始开发**:按照文档规范进行开发
|
||||
|
||||
### 模块优先级参考
|
||||
|
||||
根据 `skills/index.yaml` 中定义的优先级:
|
||||
|
||||
| 优先级 | 模块 | 说明 |
|
||||
|-------|------|------|
|
||||
| P1 (核心) | system, infra, pay | 系统核心功能,建议优先熟悉 |
|
||||
| P2 (业务) | member, mall, crm, bpm | 主要业务功能,使用频率高 |
|
||||
| P3 (扩展) | erp, ai, iot, mp, report | 扩展业务功能,按需使用 |
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
### Q1: 如何选择正确的 skill 文档?
|
||||
|
||||
**A:** 根据你的开发任务类型选择:
|
||||
|
||||
- 快速上手:从 `skill-system.yaml` 或 `skill-infra.yaml` 开始
|
||||
- 扩展功能:选择对应业务模块的 skill 文档
|
||||
- 改造功能:重点阅读对应模块的"核心流程"部分
|
||||
- 新增模块:使用 `skill-template.yaml` 作为模板
|
||||
|
||||
### Q2: skill 文档包含哪些内容?
|
||||
|
||||
**A:** 标准的 skill 文档包含以下部分:
|
||||
|
||||
1. **模块概述**:模块功能和技术栈说明
|
||||
2. **目录结构**:模块的代码组织结构
|
||||
3. **核心概念**:模块涉及的核心概念和术语
|
||||
4. **技术规范**:命名规范、代码风格、注解使用等
|
||||
5. **核心流程**:关键业务流程的实现方式
|
||||
6. **数据模型**:数据库表结构和实体关系
|
||||
7. **API 规范**:接口设计和参数说明
|
||||
8. **代码示例**:常见开发场景的代码参考
|
||||
9. **注意事项**:开发中需要注意的问题
|
||||
|
||||
### Q3: 如何使用设计模式文档?
|
||||
|
||||
**A:** 设计模式文档提供:
|
||||
|
||||
1. 模式的概念和适用场景
|
||||
2. 项目中的实际应用案例
|
||||
3. 代码实现模板
|
||||
4. 使用注意事项
|
||||
|
||||
当你需要优化代码结构或解决特定设计问题时,参考对应的设计模式文档。
|
||||
|
||||
### Q4: 如何创建新的 skill 文档?
|
||||
|
||||
**A:** 创建新模块的 skill 文档步骤:
|
||||
|
||||
1. 复制 `skills/templates/skill-template.yaml` 作为起点
|
||||
2. 参考 `skills/templates/extraction-prompt.md` 了解提取方法
|
||||
3. 参考现有模块的 skill 文档作为示例
|
||||
4. 按照模板结构填写模块信息
|
||||
5. 更新 `skills/index.yaml` 添加新模块索引
|
||||
|
||||
### Q5: 如何理解模块之间的依赖关系?
|
||||
|
||||
**A:** 模块依赖关系:
|
||||
|
||||
```
|
||||
system (系统管理) <-- 几乎所有模块都依赖
|
||||
|
|
||||
+-- member (会员) <-- mall, crm
|
||||
|
|
||||
+-- infra (基础设施) <-- 所有模块
|
||||
|
|
||||
+-- pay (支付) <-- mall, member
|
||||
```
|
||||
|
||||
开发时注意:
|
||||
|
||||
- system 模块提供用户、权限等基础能力
|
||||
- infra 模块提供文件、配置、任务等基础服务
|
||||
- 业务模块之间可能存在依赖关系
|
||||
|
||||
### Q6: 如何快速找到某个功能的实现位置?
|
||||
|
||||
**A:** 使用 skill 文档的"目录结构"和"核心流程"部分:
|
||||
|
||||
1. 在 skill 文档中查找对应功能的目录位置
|
||||
2. 阅读"核心流程"了解实现逻辑
|
||||
3. 参考"代码示例"找到具体实现
|
||||
|
||||
### Q7: 文档中的代码示例可以直接使用吗?
|
||||
|
||||
**A:** 可以,但需要注意:
|
||||
|
||||
1. 代码示例遵循项目规范,可作为模板
|
||||
2. 使用前需要根据实际业务需求调整
|
||||
3. 注意替换示例中的占位符和示例数据
|
||||
4. 建议先在测试环境验证
|
||||
|
||||
---
|
||||
|
||||
## 附录
|
||||
|
||||
### 模块文档索引
|
||||
|
||||
| 模块 | 文档路径 | 行数 | 状态 |
|
||||
|-----|---------|------|------|
|
||||
| 系统管理 | [skill-system.yaml](../modules/system/skill-system.yaml) | 839 | 已完成 |
|
||||
| 基础设施 | [skill-infra.yaml](../modules/infra/skill-infra.yaml) | 700+ | 已完成 |
|
||||
| 支付模块 | [skill-pay.yaml](../modules/pay/skill-pay.yaml) | 654 | 已完成 |
|
||||
| 会员模块 | [skill-member.yaml](../modules/member/skill-member.yaml) | 608 | 已完成 |
|
||||
| 商城模块 | [skill-mall.yaml](../modules/mall/skill-mall.yaml) | 650+ | 已完成 |
|
||||
| CRM 模块 | [skill-crm.yaml](../modules/crm/skill-crm.yaml) | 850+ | 已完成 |
|
||||
| ERP 模块 | [skill-erp.yaml](../modules/erp/skill-erp.yaml) | 900+ | 已完成 |
|
||||
| 工作流模块 | [skill-bpm.yaml](../modules/bpm/skill-bpm.yaml) | 650+ | 已完成 |
|
||||
| AI 模块 | [skill-ai.yaml](../modules/ai/skill-ai.yaml) | 670+ | 已完成 |
|
||||
| 物联网模块 | [skill-iot.yaml](../modules/iot/skill-iot.yaml) | 721 | 已完成 |
|
||||
| 公众号模块 | [skill-mp.yaml](../modules/mp/skill-mp.yaml) | 660+ | 已完成 |
|
||||
| 报表模块 | [skill-report.yaml](../modules/report/skill-report.yaml) | 320+ | 已完成 |
|
||||
|
||||
### 使用指南文档索引
|
||||
|
||||
| 文档 | 适用场景 | 一句话描述 |
|
||||
|-----|---------|-----------|
|
||||
| [quick-start.md](quick-start.md) | 快速上手 | Skill 文档快速使用指南和常用提示词模板 |
|
||||
| [entity-implementation.md](entity-implementation.md) | 实体类开发 | 从 SQL 建表到完整 CRUD 接口的端到端指南 |
|
||||
| [extend-module.md](extend-module.md) | 扩展模块 | 在现有模块基础上添加新功能 |
|
||||
| [new-module.md](new-module.md) | 新增模块 | 创建全新的业务模块 |
|
||||
| [refactor-module.md](refactor-module.md) | 改造模块 | 修改现有功能或重构代码 |
|
||||
| [pattern-usage.md](pattern-usage.md) | 设计模式 | 常用设计模式的使用场景和实现方法 |
|
||||
|
||||
### 更新记录
|
||||
|
||||
| 日期 | 版本 | 说明 |
|
||||
|-----|------|------|
|
||||
| 2026-03-25 | v1.1 | 新增实体类实现完整流程指南 |
|
||||
| 2026-03-18 | v1.0 | 初始版本,创建使用指南索引 |
|
||||
|
||||
---
|
||||
|
||||
> 提示:本文档会随着项目发展持续更新。如有问题或建议,请提交 Issue 或 Pull Request。
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,594 +0,0 @@
|
||||
# Skill 快速上手指南
|
||||
|
||||
> 本文档提供快速使用 Skill 文档的提示词模板,帮助开发者快速定位代码、理解业务、完成日常开发任务。
|
||||
|
||||
---
|
||||
|
||||
## 一、Skill 快速使用指南
|
||||
|
||||
### 什么是 Skill 文档
|
||||
|
||||
Skill 文档是从 ruoyi-vue-pro 项目代码中提取的结构化知识库,包含:
|
||||
- **设计理念**:业务定位、设计原则、领域模型
|
||||
- **架构设计**:分层架构、设计模式、模块通信
|
||||
- **数据表设计**:实体关系、表结构、字段说明
|
||||
- **代码规范**:Controller/Service/DAL 层代码模式
|
||||
- **扩展指南**:新增功能步骤、最佳实践
|
||||
|
||||
### 如何使用 Skill 文档
|
||||
|
||||
```
|
||||
使用 Skill 文档,帮我 [任务描述]
|
||||
|
||||
参考 Skill 文档:
|
||||
- skills/modules/[模块名]/skill-[模块名].yaml
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```
|
||||
使用 Skill 文档,帮我在 system 模块添加一个"操作日志"功能。
|
||||
参考 skills/modules/system/skill-system.yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、一分钟提示词模板
|
||||
|
||||
### 基础模板
|
||||
|
||||
```
|
||||
参考 Skill 文档 skills/modules/[模块]/skill-[模块].yaml,帮我完成以下任务:
|
||||
|
||||
[具体任务描述]
|
||||
|
||||
要求:
|
||||
1. 遵循项目现有的代码规范
|
||||
2. 使用统一响应格式 CommonResult
|
||||
3. 添加必要的权限控制
|
||||
```
|
||||
|
||||
### 快速填充指南
|
||||
|
||||
| 场景 | 模块路径 | 模块名 |
|
||||
|------|---------|--------|
|
||||
| 用户/角色/权限/菜单 | yudao-module-system | system |
|
||||
| 文件/配置/任务/日志 | yudao-module-infra | infra |
|
||||
| 支付/退款/钱包 | yudao-module-pay | pay |
|
||||
| 会员/积分/等级/签到 | yudao-module-member | member |
|
||||
| 商品/订单/促销 | yudao-module-mall | mall |
|
||||
| 客户/线索/合同 | yudao-module-crm | crm |
|
||||
| 采购/销售/库存 | yudao-module-erp | erp |
|
||||
| 工作流/审批 | yudao-module-bpm | bpm |
|
||||
| AI/大模型 | yudao-module-ai | ai |
|
||||
| 物联网设备 | yudao-module-iot | iot |
|
||||
| 微信公众号 | yudao-module-mp | mp |
|
||||
| 报表 | yudao-module-report | report |
|
||||
|
||||
---
|
||||
|
||||
## 三、常见场景快速提示词
|
||||
|
||||
### 3.1 查找代码位置
|
||||
|
||||
```
|
||||
参考 Skill 文档,帮我找到 [功能] 相关的代码位置:
|
||||
- Controller 入口
|
||||
- Service 实现
|
||||
- Mapper 接口
|
||||
- 数据表定义
|
||||
|
||||
模块:[模块名]
|
||||
功能关键词:[关键词]
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```
|
||||
参考 skills/modules/system/skill-system.yaml,帮我找到用户登录相关的代码位置:
|
||||
- Controller 入口
|
||||
- Service 实现
|
||||
- 认证逻辑
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.2 理解业务逻辑
|
||||
|
||||
```
|
||||
参考 Skill 文档 skills/modules/[模块]/skill-[模块].yaml,帮我理解 [业务场景] 的完整流程:
|
||||
|
||||
1. 业务入口在哪里?
|
||||
2. 核心业务逻辑在哪个 Service?
|
||||
3. 涉及哪些数据表?
|
||||
4. 状态流转是怎样的?
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```
|
||||
参考 skills/modules/mall/skill-mall.yaml,帮我理解订单支付的完整流程:
|
||||
1. 支付入口在哪里?
|
||||
2. 核心支付逻辑在哪个 Service?
|
||||
3. 支付回调如何处理?
|
||||
4. 订单状态如何流转?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.3 修复 Bug
|
||||
|
||||
```
|
||||
参考 Skill 文档 skills/modules/[模块]/skill-[模块].yaml,帮我分析和修复以下问题:
|
||||
|
||||
问题描述:[Bug 描述]
|
||||
错误信息:[错误日志或异常信息]
|
||||
复现步骤:[如何复现]
|
||||
|
||||
请帮我:
|
||||
1. 定位问题代码位置
|
||||
2. 分析问题原因
|
||||
3. 提供修复方案
|
||||
4. 说明需要修改的文件
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```
|
||||
参考 skills/modules/pay/skill-pay.yaml,帮我分析和修复以下问题:
|
||||
|
||||
问题描述:支付回调后订单状态未更新
|
||||
错误信息:无异常,但订单一直是待支付状态
|
||||
|
||||
请帮我:
|
||||
1. 定位回调处理代码
|
||||
2. 分析状态更新逻辑
|
||||
3. 检查事务是否正确
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.4 添加字段
|
||||
|
||||
```
|
||||
参考 Skill 文档 skills/modules/[模块]/skill-[模块].yaml,帮我在 [实体] 中添加新字段:
|
||||
|
||||
字段名:[字段名]
|
||||
字段类型:[类型]
|
||||
字段用途:[用途说明]
|
||||
|
||||
需要修改:
|
||||
1. DO 实体类
|
||||
2. 数据库表(提供 SQL)
|
||||
3. VO 类(请求/响应)
|
||||
4. Controller(如有需要)
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```
|
||||
参考 skills/modules/member/skill-member.yaml,帮我在会员用户中添加新字段:
|
||||
|
||||
字段名:vipExpireTime
|
||||
字段类型:LocalDateTime
|
||||
字段用途:VIP 会员过期时间
|
||||
|
||||
需要修改:
|
||||
1. MemberUserDO
|
||||
2. member_user 表
|
||||
3. 相关 VO 类
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.5 添加接口
|
||||
|
||||
```
|
||||
参考 Skill 文档 skills/modules/[模块]/skill-[模块].yaml,帮我添加一个新接口:
|
||||
|
||||
接口名称:[接口名]
|
||||
接口路径:[HTTP 方法和路径]
|
||||
功能描述:[功能说明]
|
||||
请求参数:[参数列表]
|
||||
响应数据:[响应结构]
|
||||
权限标识:[权限码]
|
||||
|
||||
请按照项目规范生成:
|
||||
1. Controller 方法
|
||||
2. Service 接口和实现
|
||||
3. VO 类
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```
|
||||
参考 skills/modules/system/skill-system.yaml,帮我添加一个新接口:
|
||||
|
||||
接口名称:获取当前用户信息
|
||||
接口路径:GET /system/user/current
|
||||
功能描述:获取当前登录用户的详细信息
|
||||
权限标识:system:user:query
|
||||
|
||||
请按照项目规范生成完整代码。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.6 添加权限
|
||||
|
||||
```
|
||||
参考 Skill 文档 skills/modules/system/skill-system.yaml,帮我添加一个新的权限:
|
||||
|
||||
权限名称:[权限名]
|
||||
权限标识:[权限码,格式:模块:功能:操作]
|
||||
父菜单ID:[父菜单 ID]
|
||||
权限类型:[菜单/按钮]
|
||||
|
||||
请提供:
|
||||
1. 菜单/按钮的 SQL 插入语句
|
||||
2. Controller 中的 @PreAuthorize 注解示例
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```
|
||||
参考 skills/modules/system/skill-system.yaml,帮我添加一个新的权限:
|
||||
|
||||
权限名称:导出用户
|
||||
权限标识:system:user:export
|
||||
父菜单ID:用户管理菜单 ID
|
||||
权限类型:按钮
|
||||
|
||||
请提供:
|
||||
1. 按钮的 SQL 插入语句
|
||||
2. Controller 中的权限注解示例
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、Skill 文档快速导航
|
||||
|
||||
### 4.1 模块索引
|
||||
|
||||
| 模块 | 文档路径 | 核心功能 | 关键实体 |
|
||||
|------|---------|---------|---------|
|
||||
| **system** | skills/modules/system/skill-system.yaml | 用户、角色、权限、菜单、租户、字典 | AdminUserDO, RoleDO, MenuDO |
|
||||
| **infra** | skills/modules/infra/skill-infra.yaml | 文件、配置、任务、日志、代码生成 | FileDO, JobDO, ConfigDO |
|
||||
| **pay** | skills/modules/pay/skill-pay.yaml | 支付、退款、钱包、转账 | PayOrderDO, PayRefundDO, PayWalletDO |
|
||||
| **member** | skills/modules/member/skill-member.yaml | 会员、积分、等级、签到、地址 | MemberUserDO, MemberLevelDO |
|
||||
| **mall** | skills/modules/mall/skill-mall.yaml | 商品、订单、促销、统计 | ProductSpuDO, TradeOrderDO, CouponDO |
|
||||
| **crm** | skills/modules/crm/skill-crm.yaml | 线索、客户、商机、合同、回款 | CrmCustomerDO, CrmContractDO |
|
||||
| **erp** | skills/modules/erp/skill-erp.yaml | 采购、销售、库存、财务 | ErpPurchaseDO, ErpSaleDO |
|
||||
| **bpm** | skills/modules/bpm/skill-bpm.yaml | 流程定义、流程实例、任务 | BpmProcessDefinitionDO |
|
||||
| **ai** | skills/modules/ai/skill-ai.yaml | AI 模型、对话、绘图 | AiChatMessageDO, AiImageDO |
|
||||
| **iot** | skills/modules/iot/skill-iot.yaml | 设备、产品、物模型 | IotDeviceDO, IotProductDO |
|
||||
| **mp** | skills/modules/mp/skill-mp.yaml | 公众号、菜单、消息 | MpAccountDO, MpMessageDO |
|
||||
| **report** | skills/modules/report/skill-report.yaml | 报表、数据源、图表 | ReportDataSourceDO |
|
||||
|
||||
### 4.2 错误码前缀速查
|
||||
|
||||
| 模块 | 错误码前缀 | 示例 |
|
||||
|------|-----------|------|
|
||||
| system | 1_002_XXX_XXX | 1_002_000_000 登录失败 |
|
||||
| infra | 1_001_XXX_XXX | 1_001_000_000 文件不存在 |
|
||||
| pay | 1_007_XXX_XXX | 1_007_002_000 支付订单不存在 |
|
||||
| member | 1_004_XXX_XXX | 1_004_001_000 用户不存在 |
|
||||
| product | 1_008_XXX_XXX | 1_008_005_000 商品不存在 |
|
||||
| trade | 1_011_XXX_XXX | 1_011_000_011 订单不存在 |
|
||||
| promotion | 1_013_XXX_XXX | 1_013_004_000 优惠券模板不存在 |
|
||||
|
||||
### 4.3 权限标识规范
|
||||
|
||||
```
|
||||
格式:模块:功能:操作
|
||||
|
||||
示例:
|
||||
- system:user:query # 查询用户
|
||||
- system:user:create # 创建用户
|
||||
- system:user:update # 更新用户
|
||||
- system:user:delete # 删除用户
|
||||
- system:user:export # 导出用户
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、常用代码片段
|
||||
|
||||
### 5.1 Controller 层
|
||||
|
||||
```java
|
||||
@Tag(name = "管理后台 - [功能名]")
|
||||
@RestController
|
||||
@RequestMapping("/[模块]/[功能]")
|
||||
@Validated
|
||||
public class XxxController {
|
||||
|
||||
@Resource
|
||||
private XxxService xxxService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建[功能]")
|
||||
@PreAuthorize("@ss.hasPermission('[模块]:[功能]:create')")
|
||||
public CommonResult<Long> createXxx(@Valid @RequestBody XxxSaveReqVO createReqVO) {
|
||||
return success(xxxService.createXxx(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新[功能]")
|
||||
@PreAuthorize("@ss.hasPermission('[模块]:[功能]:update')")
|
||||
public CommonResult<Boolean> updateXxx(@Valid @RequestBody XxxSaveReqVO updateReqVO) {
|
||||
xxxService.updateXxx(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除[功能]")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('[模块]:[功能]:delete')")
|
||||
public CommonResult<Boolean> deleteXxx(@RequestParam("id") Long id) {
|
||||
xxxService.deleteXxx(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得[功能]")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('[模块]:[功能]:query')")
|
||||
public CommonResult<XxxRespVO> getXxx(@RequestParam("id") Long id) {
|
||||
XxxDO xxx = xxxService.getXxx(id);
|
||||
return success(BeanUtils.toBean(xxx, XxxRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得[功能]分页")
|
||||
@PreAuthorize("@ss.hasPermission('[模块]:[功能]:query')")
|
||||
public CommonResult<PageResult<XxxRespVO>> getXxxPage(@Valid XxxPageReqVO pageReqVO) {
|
||||
PageResult<XxxDO> pageResult = xxxService.getXxxPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, XxxRespVO.class));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Service 层
|
||||
|
||||
```java
|
||||
// 接口
|
||||
public interface XxxService {
|
||||
Long createXxx(@Valid XxxSaveReqVO createReqVO);
|
||||
void updateXxx(@Valid XxxSaveReqVO updateReqVO);
|
||||
void deleteXxx(Long id);
|
||||
XxxDO getXxx(Long id);
|
||||
PageResult<XxxDO> getXxxPage(XxxPageReqVO pageReqVO);
|
||||
}
|
||||
|
||||
// 实现
|
||||
@Service
|
||||
@Validated
|
||||
public class XxxServiceImpl implements XxxService {
|
||||
|
||||
@Resource
|
||||
private XxxMapper xxxMapper;
|
||||
|
||||
@Override
|
||||
public Long createXxx(XxxSaveReqVO createReqVO) {
|
||||
XxxDO xxx = BeanUtils.toBean(createReqVO, XxxDO.class);
|
||||
xxxMapper.insert(xxx);
|
||||
return xxx.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateXxx(XxxSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateXxxExists(updateReqVO.getId());
|
||||
// 更新
|
||||
XxxDO updateObj = BeanUtils.toBean(updateReqVO, XxxDO.class);
|
||||
xxxMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteXxx(Long id) {
|
||||
// 校验存在
|
||||
validateXxxExists(id);
|
||||
// 删除
|
||||
xxxMapper.deleteById(id);
|
||||
}
|
||||
|
||||
private void validateXxxExists(Long id) {
|
||||
if (xxxMapper.selectById(id) == null) {
|
||||
throw exception(XXX_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public XxxDO getXxx(Long id) {
|
||||
return xxxMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<XxxDO> getXxxPage(XxxPageReqVO pageReqVO) {
|
||||
return xxxMapper.selectPage(pageReqVO);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 Mapper 层
|
||||
|
||||
```java
|
||||
@Mapper
|
||||
public interface XxxMapper extends BaseMapperX<XxxDO> {
|
||||
|
||||
default PageResult<XxxDO> selectPage(XxxPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<XxxDO>()
|
||||
.likeIfPresent(XxxDO::getName, reqVO.getName())
|
||||
.eqIfPresent(XxxDO::getStatus, reqVO.getStatus())
|
||||
.betweenIfPresent(XxxDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(XxxDO::getId));
|
||||
}
|
||||
|
||||
default List<XxxDO> selectListByStatus(Integer status) {
|
||||
return selectList(XxxDO::getStatus, status);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 DO 实体类
|
||||
|
||||
```java
|
||||
@TableName("[表名]")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class XxxDO extends TenantBaseDO { // 或 BaseDO
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
// ... 其他字段
|
||||
}
|
||||
```
|
||||
|
||||
### 5.5 VO 类
|
||||
|
||||
```java
|
||||
// 请求 VO(新增/修改共用)
|
||||
@Data
|
||||
public class XxxSaveReqVO {
|
||||
|
||||
@Schema(description = "编号", example = "1")
|
||||
private Long id; // 更新时必填
|
||||
|
||||
@Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试")
|
||||
@NotBlank(message = "名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
|
||||
@NotNull(message = "状态不能为空")
|
||||
private Integer status;
|
||||
}
|
||||
|
||||
// 分页请求 VO
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class XxxPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "名称", example = "测试")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "状态", example = "0")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
}
|
||||
|
||||
// 响应 VO
|
||||
@Data
|
||||
public class XxxRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
```
|
||||
|
||||
### 5.6 错误码定义
|
||||
|
||||
```java
|
||||
// 在 ErrorCodeConstants.java 中添加
|
||||
ErrorCode XXX_NOT_EXISTS = new ErrorCode(1_002_XXX_000, "[功能]不存在");
|
||||
ErrorCode XXX_NAME_DUPLICATE = new ErrorCode(1_002_XXX_001, "已存在该名字的[功能]");
|
||||
```
|
||||
|
||||
### 5.7 跨模块 API 调用
|
||||
|
||||
```java
|
||||
// API 接口定义
|
||||
public interface XxxApi {
|
||||
CommonResult<XxxRespDTO> getXxx(Long id);
|
||||
CommonResult<List<XxxRespDTO>> getXxxList(Collection<Long> ids);
|
||||
}
|
||||
|
||||
// API 实现
|
||||
@RestController
|
||||
@FeignClient(name = ApiConstants.NAME)
|
||||
public class XxxApiImpl implements XxxApi {
|
||||
|
||||
@Resource
|
||||
private XxxService xxxService;
|
||||
|
||||
@Override
|
||||
public CommonResult<XxxRespDTO> getXxx(Long id) {
|
||||
XxxDO xxx = xxxService.getXxx(id);
|
||||
return success(BeanUtils.toBean(xxx, XxxRespDTO.class));
|
||||
}
|
||||
}
|
||||
|
||||
// 调用方式
|
||||
@Resource
|
||||
private XxxApi xxxApi;
|
||||
|
||||
public void someMethod() {
|
||||
XxxRespDTO xxx = xxxApi.getXxx(id).getCheckedData();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、快速参考
|
||||
|
||||
### 6.1 分层架构速记
|
||||
|
||||
```
|
||||
Controller (HTTP入口)
|
||||
↓
|
||||
Service (业务逻辑)
|
||||
↓
|
||||
Mapper (数据访问)
|
||||
↓
|
||||
Database
|
||||
```
|
||||
|
||||
### 6.2 常用注解速查
|
||||
|
||||
| 层级 | 常用注解 |
|
||||
|------|---------|
|
||||
| Controller | @RestController, @RequestMapping, @Tag, @Operation, @PreAuthorize |
|
||||
| Service | @Service, @Validated, @Transactional |
|
||||
| Mapper | @Mapper |
|
||||
| DO | @TableName, @TableId, @Data |
|
||||
| VO | @Data, @Schema, @NotBlank, @NotNull |
|
||||
|
||||
### 6.3 命名规范
|
||||
|
||||
| 类型 | 命名规则 | 示例 |
|
||||
|------|---------|------|
|
||||
| Controller | XxxController | UserController |
|
||||
| Service接口 | XxxService | UserService |
|
||||
| Service实现 | XxxServiceImpl | UserServiceImpl |
|
||||
| Mapper | XxxMapper | UserMapper |
|
||||
| DO | XxxDO | UserDO |
|
||||
| 请求VO | XxxSaveReqVO / XxxPageReqVO | UserSaveReqVO |
|
||||
| 响应VO | XxxRespVO | UserRespVO |
|
||||
| API接口 | XxxApi | UserApi |
|
||||
| 错误码 | XXX_NOT_EXISTS | USER_NOT_EXISTS |
|
||||
|
||||
---
|
||||
|
||||
**提示**:使用 Skill 文档时,直接复制对应的提示词模板,替换占位符即可快速获得准确的代码生成和问题解答。
|
||||
File diff suppressed because it is too large
Load Diff
@@ -101,4 +101,3 @@ The expanded regression run then exposed a separate infrastructure limitation ra
|
||||
- `PracticeSessionServiceImplTest`: 24 passed before PostgreSQL-dialect persistence paths were included.
|
||||
- Full targeted suite starts after dependency/injection repair, then fails on confirmed H2/PostgreSQL dialect mismatch and downstream assertions.
|
||||
- No production behavior was changed by EDU-002.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# EDU-006 — Deliver Practice schema through module-owned Flyway
|
||||
|
||||
- **Status:** done — V4030 delivered and verified on an isolated disposable PostgreSQL test database
|
||||
- **Status:** done — V4030/V4040 delivered and verified; V4050 adds forward-only column documentation
|
||||
- **Type:** database implementation
|
||||
- **Phase:** 2 prerequisite
|
||||
- **Blockers:** EDU-005
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
The EDU-016 test seam now runs the real Flyway chain in a random disposable PostgreSQL schema. Its temporary `create_tables.sql` bridge was removed. The established 140 persistence tests and five migration-contract scenarios pass together.
|
||||
|
||||
Manual Education SQL mounts were removed from Docker Compose, and the active Education README and Pilot runbook now describe PostgreSQL/Flyway forward-only delivery. V4010/V4020 remained byte-for-byte unchanged. V4040 was not created because the capability menu still lacks a complete admin UI/product decision; no empty placeholder version was introduced.
|
||||
Manual Education SQL mounts were removed from Docker Compose, and the active Education README and Pilot runbook now describe PostgreSQL/Flyway forward-only delivery. V4010/V4020 remained byte-for-byte unchanged. V4040 adds the submit-claim token and lease timestamp required by EDU-009 crash recovery; it also normalizes adopted successful submit rows without changing V4030 release history.
|
||||
|
||||
No shared or production database was migrated. Successful migration evidence applies only to the isolated no-volume PostgreSQL container and random schemas used by the tests.
|
||||
|
||||
@@ -70,12 +70,12 @@ Also completed:
|
||||
|
||||
```text
|
||||
mvn -pl yudao-module-education -am -DskipTests clean package — BUILD SUCCESS
|
||||
V4010, V4020, V4030 present under target/classes/db/migration/education/
|
||||
V4010, V4020, V4030, V4040 present under target/classes/db/migration/education/ at the recorded verification point; V4050 must be included in the next verification run
|
||||
mvn -pl yudao-server -am -DskipTests clean compile — BUILD SUCCESS
|
||||
git diff --check — passed
|
||||
```
|
||||
|
||||
V4010 and V4020 retained their pre-ticket SHA-256 values. No shared or production PostgreSQL database was changed.
|
||||
V4010 and V4020 retained their pre-ticket SHA-256 values. V4050 was added later as a metadata-only forward migration to document protected snapshot and idempotency state columns. On 2026-07-30, the five-scenario Flyway integration suite migrated and validated V4010 through V4050 against an isolated no-volume PostgreSQL 16 container: 5 tests passed with no failures, errors, or skips. The corrected Education package and server compile also succeeded, and V4010 through V4050 were present under the packaged migration path. No shared or production PostgreSQL database was changed.
|
||||
|
||||
## Risk and rollback
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块 — 题库目录表回滚 DDL
|
||||
-- Ticket #20 rollback
|
||||
-- =============================================
|
||||
-- WARNING: This is an explicit data-destruction script.
|
||||
-- It is NOT part of application version rollback.
|
||||
-- Application version rollback preserves these tables and their data;
|
||||
-- this script is only used when permanently removing the catalog module.
|
||||
-- =============================================
|
||||
|
||||
DROP TABLE IF EXISTS `education_question_collection_question`;
|
||||
DROP TABLE IF EXISTS `education_practice_blueprint`;
|
||||
DROP TABLE IF EXISTS `education_question`;
|
||||
DROP TABLE IF EXISTS `education_question_collection`;
|
||||
DROP TABLE IF EXISTS `education_content_node`;
|
||||
DROP TABLE IF EXISTS `education_content_entry`;
|
||||
DROP TABLE IF EXISTS `education_category`;
|
||||
DROP TABLE IF EXISTS `education_subject`;
|
||||
DROP TABLE IF EXISTS `education_major`;
|
||||
DROP TABLE IF EXISTS `education_school`;
|
||||
DROP TABLE IF EXISTS `education_region`;
|
||||
@@ -1,349 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块 — 题库目录原生表 DDL
|
||||
-- Ticket #20: 原生实现题库目录浏览,替换 Scalar adapter
|
||||
-- Migration: 008
|
||||
-- Prerequisites: 000-education-schema.sql (database creation)
|
||||
-- 001-education-tenant-seed.sql (tenant seed data)
|
||||
-- =============================================
|
||||
|
||||
-- =============================================
|
||||
-- Preconditions
|
||||
-- =============================================
|
||||
-- Verify none of these tables already exist:
|
||||
-- SELECT TABLE_NAME FROM information_schema.tables
|
||||
-- WHERE table_schema = DATABASE()
|
||||
-- AND TABLE_NAME IN (
|
||||
-- 'education_region', 'education_school', 'education_major',
|
||||
-- 'education_subject', 'education_category',
|
||||
-- 'education_content_entry', 'education_content_node',
|
||||
-- 'education_question_collection', 'education_question',
|
||||
-- 'education_practice_blueprint', 'education_question_collection_question'
|
||||
-- );
|
||||
-- Result MUST be 0 before executing this migration.
|
||||
|
||||
-- =============================================
|
||||
-- 1. 地区表
|
||||
-- =============================================
|
||||
-- Indexes:
|
||||
-- idx_tenant — tenant-scoped queries (tenant-aware interceptor default)
|
||||
-- idx_tenant_active_order — active region listing ordered by display order
|
||||
CREATE TABLE `education_region` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`tenant_id` BIGINT NOT NULL COMMENT '租户编号',
|
||||
`legacy_id` VARCHAR(64) DEFAULT NULL COMMENT '旧系统地区 ID',
|
||||
`name` VARCHAR(100) NOT NULL COMMENT '地区名称',
|
||||
`code` VARCHAR(32) DEFAULT NULL COMMENT '地区编码',
|
||||
`short_name` VARCHAR(50) DEFAULT NULL COMMENT '地区简称',
|
||||
`full_name` VARCHAR(200) DEFAULT NULL COMMENT '地区全称',
|
||||
`icon` VARCHAR(500) DEFAULT NULL COMMENT '地区图标 URL',
|
||||
`pinyin` VARCHAR(200) DEFAULT NULL COMMENT '地区拼音',
|
||||
`is_hot` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否热门地区',
|
||||
`is_active` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否启用',
|
||||
`sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值',
|
||||
`creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant` (`tenant_id`),
|
||||
KEY `idx_tenant_active_order` (`tenant_id`, `is_active`, `sort_order`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-地区';
|
||||
|
||||
-- =============================================
|
||||
-- 2. 院校表
|
||||
-- =============================================
|
||||
CREATE TABLE `education_school` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`tenant_id` BIGINT NOT NULL COMMENT '租户编号',
|
||||
`legacy_id` VARCHAR(64) DEFAULT NULL COMMENT '旧系统院校 ID',
|
||||
`region_id` BIGINT DEFAULT NULL COMMENT '地区 ID',
|
||||
`module_id` BIGINT DEFAULT NULL COMMENT '地区模块 ID',
|
||||
`name` VARCHAR(200) NOT NULL COMMENT '院校名称',
|
||||
`professional_exam_date` VARCHAR(200) DEFAULT NULL COMMENT '专业考试日期说明',
|
||||
`metadata` JSON DEFAULT NULL COMMENT '院校扩展数据',
|
||||
`is_active` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否启用',
|
||||
`sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值',
|
||||
`creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant` (`tenant_id`),
|
||||
KEY `idx_tenant_region` (`tenant_id`, `region_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-院校';
|
||||
|
||||
-- =============================================
|
||||
-- 3. 专业表
|
||||
-- =============================================
|
||||
CREATE TABLE `education_major` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`tenant_id` BIGINT NOT NULL COMMENT '租户编号',
|
||||
`legacy_id` VARCHAR(64) DEFAULT NULL COMMENT '旧系统专业 ID',
|
||||
`region_id` BIGINT DEFAULT NULL COMMENT '地区 ID',
|
||||
`school_id` BIGINT DEFAULT NULL COMMENT '院校 ID',
|
||||
`name` VARCHAR(200) NOT NULL COMMENT '专业名称',
|
||||
`description` VARCHAR(500) DEFAULT NULL COMMENT '专业说明',
|
||||
`study_tips` VARCHAR(500) DEFAULT NULL COMMENT '学习建议',
|
||||
`is_active` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否启用',
|
||||
`sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值',
|
||||
`creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant` (`tenant_id`),
|
||||
KEY `idx_tenant_school` (`tenant_id`, `school_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-专业';
|
||||
|
||||
-- =============================================
|
||||
-- 4. 科目表
|
||||
-- =============================================
|
||||
CREATE TABLE `education_subject` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`tenant_id` BIGINT NOT NULL COMMENT '租户编号',
|
||||
`region_id` BIGINT DEFAULT NULL COMMENT '地区 ID',
|
||||
`school_id` BIGINT DEFAULT NULL COMMENT '院校 ID',
|
||||
`major_id` BIGINT DEFAULT NULL COMMENT '专业 ID',
|
||||
`module_id` BIGINT DEFAULT NULL COMMENT '地区模块 ID',
|
||||
`name` VARCHAR(200) NOT NULL COMMENT '科目名称',
|
||||
`type` VARCHAR(50) DEFAULT NULL COMMENT '科目类型',
|
||||
`is_active` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否启用',
|
||||
`sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值',
|
||||
`creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant` (`tenant_id`),
|
||||
KEY `idx_tenant_major` (`tenant_id`, `major_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-科目';
|
||||
|
||||
-- =============================================
|
||||
-- 5. 题目分类表
|
||||
-- =============================================
|
||||
CREATE TABLE `education_category` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`tenant_id` BIGINT NOT NULL COMMENT '租户编号',
|
||||
`subject_id` BIGINT DEFAULT NULL COMMENT '科目 ID',
|
||||
`legacy_node_id` VARCHAR(64) DEFAULT NULL COMMENT '旧导航节点 ID',
|
||||
`name` VARCHAR(200) NOT NULL COMMENT '分类名称',
|
||||
`is_active` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否启用',
|
||||
`sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值',
|
||||
`creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant` (`tenant_id`),
|
||||
KEY `idx_tenant_subject` (`tenant_id`, `subject_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-题目分类';
|
||||
|
||||
-- =============================================
|
||||
-- 6. 内容入口表
|
||||
-- =============================================
|
||||
-- Indexes:
|
||||
-- uk_tenant_entry_key — per-tenant uniqueness for entry key
|
||||
CREATE TABLE `education_content_entry` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`tenant_id` BIGINT NOT NULL COMMENT '租户编号',
|
||||
`legacy_id` VARCHAR(64) DEFAULT NULL COMMENT '旧系统内容入口 ID',
|
||||
`region_id` BIGINT DEFAULT NULL COMMENT '地区 ID',
|
||||
`entry_key` VARCHAR(100) NOT NULL COMMENT '内容入口唯一键',
|
||||
`name` VARCHAR(200) NOT NULL COMMENT '内容入口名称',
|
||||
`entry_type` VARCHAR(50) NOT NULL COMMENT '内容入口类型',
|
||||
`icon` VARCHAR(500) DEFAULT NULL COMMENT '图标',
|
||||
`route` VARCHAR(200) DEFAULT NULL COMMENT '前端路由',
|
||||
`description` VARCHAR(500) DEFAULT NULL COMMENT '入口说明',
|
||||
`visibility` VARCHAR(50) NOT NULL DEFAULT 'PUBLIC' COMMENT '可见性',
|
||||
`access_rules` JSON DEFAULT NULL COMMENT '访问规则 JSON',
|
||||
`layout_config` JSON DEFAULT NULL COMMENT '布局配置 JSON',
|
||||
`is_active` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否启用',
|
||||
`sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值',
|
||||
`creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant` (`tenant_id`),
|
||||
UNIQUE KEY `uk_tenant_entry_key` (`tenant_id`, `entry_key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-内容入口';
|
||||
|
||||
-- =============================================
|
||||
-- 7. 内容节点表(树形结构)
|
||||
-- =============================================
|
||||
-- Indexes:
|
||||
-- idx_entry_parent — supports tree navigation: find children of a parent within an entry
|
||||
-- idx_tenant_entry — tenant-scoped queries by entry
|
||||
CREATE TABLE `education_content_node` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`tenant_id` BIGINT NOT NULL COMMENT '租户编号',
|
||||
`entry_id` BIGINT NOT NULL COMMENT '内容入口 ID',
|
||||
`parent_id` BIGINT DEFAULT NULL COMMENT '父节点 ID(NULL=根节点)',
|
||||
`name` VARCHAR(200) NOT NULL COMMENT '节点名称',
|
||||
`title` VARCHAR(200) DEFAULT NULL COMMENT '节点标题',
|
||||
`node_type` VARCHAR(50) NOT NULL COMMENT '节点类型',
|
||||
`marker_type` VARCHAR(50) DEFAULT NULL COMMENT '节点标记类型',
|
||||
`depth` INT NOT NULL DEFAULT 0 COMMENT '树深度',
|
||||
`is_leaf` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否叶子节点',
|
||||
`is_selectable` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否可被选择',
|
||||
`is_active` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否启用',
|
||||
`sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值',
|
||||
`metadata` JSON DEFAULT NULL COMMENT '扩展元数据',
|
||||
`creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant` (`tenant_id`),
|
||||
KEY `idx_entry_parent` (`entry_id`, `parent_id`),
|
||||
KEY `idx_tenant_entry` (`tenant_id`, `entry_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-内容节点';
|
||||
|
||||
-- =============================================
|
||||
-- 8. 题集表
|
||||
-- =============================================
|
||||
CREATE TABLE `education_question_collection` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`tenant_id` BIGINT NOT NULL COMMENT '租户编号',
|
||||
`entry_id` BIGINT DEFAULT NULL COMMENT '内容入口 ID',
|
||||
`node_id` BIGINT DEFAULT NULL COMMENT '内容节点 ID',
|
||||
`name` VARCHAR(200) NOT NULL COMMENT '题集名称',
|
||||
`title` VARCHAR(200) DEFAULT NULL COMMENT '题集标题',
|
||||
`collection_type` VARCHAR(50) NOT NULL COMMENT '题集类型',
|
||||
`question_count` INT NOT NULL DEFAULT 0 COMMENT '题目数量',
|
||||
`duration_minutes` INT DEFAULT NULL COMMENT '建议作答时长(分钟)',
|
||||
`access_rules` JSON DEFAULT NULL COMMENT '访问规则 JSON',
|
||||
`is_active` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否启用',
|
||||
`sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值',
|
||||
`metadata` JSON DEFAULT NULL COMMENT '扩展元数据',
|
||||
`creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant` (`tenant_id`),
|
||||
KEY `idx_tenant_entry` (`tenant_id`, `entry_id`),
|
||||
KEY `idx_tenant_node` (`tenant_id`, `node_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-题集';
|
||||
|
||||
-- =============================================
|
||||
-- 9. 题目表(核心表)
|
||||
-- =============================================
|
||||
-- Indexes:
|
||||
-- idx_tenant_published — 查询已发布题目(主查询路径)
|
||||
-- idx_tenant_collection — 按题集筛选
|
||||
-- idx_tenant_subject — 按科目筛选
|
||||
-- idx_tenant_node — 按内容节点筛选
|
||||
-- idx_tenant_type_diff — 按题型和难度筛选
|
||||
-- Security note: correct_answer 和 explanation 列存在但需在 service 层过滤;
|
||||
-- 学生端 DTO 绝不得包含这两列。
|
||||
-- Options JSON: 存储 label, content, isCorrect, order — 完整选项数据。
|
||||
-- Service 层在学生端返回前剥离 isCorrect。
|
||||
CREATE TABLE `education_question` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`tenant_id` BIGINT NOT NULL COMMENT '租户编号',
|
||||
`content_version` INT NOT NULL DEFAULT 1 COMMENT '内容版本号(递增,保证历史报告稳定性)',
|
||||
`stem` TEXT NOT NULL COMMENT '题干',
|
||||
`type` VARCHAR(32) NOT NULL COMMENT '题型',
|
||||
`type_label` VARCHAR(50) DEFAULT NULL COMMENT '题型显示名称',
|
||||
`difficulty` VARCHAR(32) DEFAULT NULL COMMENT '难度',
|
||||
`question_content` JSON DEFAULT NULL COMMENT '题干结构化内容(兼容复杂题型)',
|
||||
`options` JSON NOT NULL COMMENT '选项 JSON [{label, content, isCorrect, order}]',
|
||||
`correct_answer` VARCHAR(500) DEFAULT NULL COMMENT '正确答案(敏感字段,学生端不可返回)',
|
||||
`explanation` TEXT DEFAULT NULL COMMENT '答案解析(敏感字段,学生端不可返回)',
|
||||
`analysis` TEXT DEFAULT NULL COMMENT '深度解析(敏感字段)',
|
||||
`status` VARCHAR(20) NOT NULL DEFAULT 'PUBLISHED' COMMENT '题目状态:PUBLISHED-已发布, HIDDEN-已隐藏, DRAFT-草稿, INACTIVE-停用',
|
||||
`is_published` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否已发布',
|
||||
`collection_id` BIGINT DEFAULT NULL COMMENT '所属题集 ID',
|
||||
`subject_id` BIGINT DEFAULT NULL COMMENT '所属科目 ID',
|
||||
`node_id` BIGINT DEFAULT NULL COMMENT '所属内容节点 ID',
|
||||
`tags` JSON DEFAULT NULL COMMENT '标签 JSON 数组',
|
||||
`sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值',
|
||||
`metadata` JSON DEFAULT NULL COMMENT '扩展元数据',
|
||||
`creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant` (`tenant_id`),
|
||||
KEY `idx_tenant_published` (`tenant_id`, `is_published`, `status`),
|
||||
KEY `idx_tenant_collection` (`tenant_id`, `collection_id`),
|
||||
KEY `idx_tenant_subject` (`tenant_id`, `subject_id`),
|
||||
KEY `idx_tenant_node` (`tenant_id`, `node_id`),
|
||||
KEY `idx_tenant_type_diff` (`tenant_id`, `type`, `difficulty`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-题目';
|
||||
|
||||
-- =============================================
|
||||
-- 10. 练习蓝图表
|
||||
-- =============================================
|
||||
CREATE TABLE `education_practice_blueprint` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`tenant_id` BIGINT NOT NULL COMMENT '租户编号',
|
||||
`mode` VARCHAR(50) NOT NULL COMMENT '练习模式',
|
||||
`entry_id` BIGINT DEFAULT NULL COMMENT '内容入口 ID',
|
||||
`node_id` BIGINT DEFAULT NULL COMMENT '内容节点 ID',
|
||||
`collection_id` BIGINT DEFAULT NULL COMMENT '题集 ID',
|
||||
`question_limit` INT DEFAULT NULL COMMENT '题目数量限制',
|
||||
`duration_minutes` INT DEFAULT NULL COMMENT '建议作答时长(分钟)',
|
||||
`eligible_count` INT NOT NULL DEFAULT 0 COMMENT '符合条件的题目数',
|
||||
`total_count` INT NOT NULL DEFAULT 0 COMMENT '题库总数',
|
||||
`available_types` JSON DEFAULT NULL COMMENT '可用题型列表 JSON',
|
||||
`available_difficulties` JSON DEFAULT NULL COMMENT '可用难度列表 JSON',
|
||||
`min_questions` INT NOT NULL DEFAULT 1 COMMENT '最少题目数',
|
||||
`max_questions` INT NOT NULL DEFAULT 200 COMMENT '最多题目数',
|
||||
`suggested_count` INT NOT NULL DEFAULT 20 COMMENT '建议题目数',
|
||||
`creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant` (`tenant_id`),
|
||||
KEY `idx_tenant_collection` (`tenant_id`, `collection_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-练习蓝图';
|
||||
|
||||
-- =============================================
|
||||
-- 11. 题集-题目关联表(多对多)
|
||||
-- =============================================
|
||||
CREATE TABLE `education_question_collection_question` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`collection_id` BIGINT NOT NULL COMMENT '题集 ID',
|
||||
`question_id` BIGINT NOT NULL COMMENT '题目 ID',
|
||||
`sort_order` INT NOT NULL DEFAULT 0 COMMENT '排序值',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_collection_question` (`collection_id`, `question_id`),
|
||||
KEY `idx_question` (`question_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-题集题目关联';
|
||||
|
||||
-- =============================================
|
||||
-- Post-migration verification queries
|
||||
-- =============================================
|
||||
-- Verify all tables exist:
|
||||
-- SELECT TABLE_NAME FROM information_schema.tables
|
||||
-- WHERE table_schema = DATABASE()
|
||||
-- AND TABLE_NAME LIKE 'education_%'
|
||||
-- ORDER BY TABLE_NAME;
|
||||
-- Verify indexes (sample):
|
||||
-- SHOW INDEX FROM education_question WHERE Key_name IN ('idx_tenant_published', 'idx_tenant_collection');
|
||||
-- SHOW INDEX FROM education_content_node WHERE Key_name = 'idx_entry_parent';
|
||||
-- Verify no orphan data (should be 0 after fresh migration for catalog tables):
|
||||
-- SELECT 'region', COUNT(*) FROM education_region
|
||||
-- UNION ALL SELECT 'school', COUNT(*) FROM education_school
|
||||
-- UNION ALL SELECT 'major', COUNT(*) FROM education_major
|
||||
-- UNION ALL SELECT 'subject', COUNT(*) FROM education_subject
|
||||
-- UNION ALL SELECT 'category', COUNT(*) FROM education_category
|
||||
-- UNION ALL SELECT 'content_entry', COUNT(*) FROM education_content_entry
|
||||
-- UNION ALL SELECT 'content_node', COUNT(*) FROM education_content_node
|
||||
-- UNION ALL SELECT 'question_collection', COUNT(*) FROM education_question_collection
|
||||
-- UNION ALL SELECT 'question', COUNT(*) FROM education_question
|
||||
-- UNION ALL SELECT 'practice_blueprint', COUNT(*) FROM education_practice_blueprint
|
||||
-- UNION ALL SELECT 'collection_question', COUNT(*) FROM education_question_collection_question;
|
||||
@@ -1,174 +0,0 @@
|
||||
-- =============================================
|
||||
-- Member 模块建表 — 从 PostgreSQL 测试 SQL 转换为 MySQL
|
||||
-- 用于本地开发环境补全 member 表
|
||||
-- =============================================
|
||||
|
||||
-- 1. 会员配置表
|
||||
CREATE TABLE IF NOT EXISTS member_config (
|
||||
id bigint NOT NULL AUTO_INCREMENT,
|
||||
group_id bigint NULL DEFAULT NULL COMMENT '用户分组编号',
|
||||
level_id bigint NULL DEFAULT NULL COMMENT '等级编号',
|
||||
tenant_id bigint NOT NULL DEFAULT 0,
|
||||
creator varchar(64) DEFAULT '' COMMENT '创建者',
|
||||
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updater varchar(64) DEFAULT '' COMMENT '更新者',
|
||||
update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
deleted bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员配置表';
|
||||
|
||||
-- 2. 会员标签表
|
||||
CREATE TABLE IF NOT EXISTS member_tag (
|
||||
id bigint NOT NULL AUTO_INCREMENT,
|
||||
name varchar(50) NOT NULL COMMENT '标签名称',
|
||||
creator varchar(64) DEFAULT '' COMMENT '创建者',
|
||||
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updater varchar(64) DEFAULT '' COMMENT '更新者',
|
||||
update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
deleted bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
tenant_id bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员标签';
|
||||
|
||||
-- 3. 用户分组表
|
||||
CREATE TABLE IF NOT EXISTS member_group (
|
||||
id bigint NOT NULL AUTO_INCREMENT,
|
||||
name varchar(50) NOT NULL COMMENT '分组名称',
|
||||
remark varchar(255) DEFAULT '' COMMENT '备注',
|
||||
status tinyint NOT NULL DEFAULT 0 COMMENT '状态',
|
||||
creator varchar(64) DEFAULT '' COMMENT '创建者',
|
||||
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updater varchar(64) DEFAULT '' COMMENT '更新者',
|
||||
update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
deleted bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
tenant_id bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员分组';
|
||||
|
||||
-- 4. 会员等级表(完整版,覆盖之前创建的简化版)
|
||||
CREATE TABLE IF NOT EXISTS member_level (
|
||||
id bigint NOT NULL AUTO_INCREMENT,
|
||||
name varchar(50) NOT NULL COMMENT '等级名称',
|
||||
experience int NOT NULL DEFAULT 0 COMMENT '所需经验',
|
||||
level int NOT NULL DEFAULT 1 COMMENT '等级',
|
||||
discount_percent int NOT NULL DEFAULT 100 COMMENT '折扣百分比',
|
||||
icon varchar(255) DEFAULT '' COMMENT '图标',
|
||||
background_url varchar(255) DEFAULT '' COMMENT '背景图',
|
||||
status tinyint NOT NULL DEFAULT 0 COMMENT '状态',
|
||||
creator varchar(64) DEFAULT '' COMMENT '创建者',
|
||||
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updater varchar(64) DEFAULT '' COMMENT '更新者',
|
||||
update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
deleted bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
tenant_id bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员等级';
|
||||
|
||||
-- 5. 会员等级记录表
|
||||
CREATE TABLE IF NOT EXISTS member_level_record (
|
||||
id bigint NOT NULL AUTO_INCREMENT,
|
||||
user_id bigint NOT NULL COMMENT '用户编号',
|
||||
level_id bigint NOT NULL COMMENT '等级编号',
|
||||
level_name varchar(50) DEFAULT '' COMMENT '等级名称',
|
||||
experience int NOT NULL DEFAULT 0 COMMENT '当前经验',
|
||||
remark varchar(255) DEFAULT '' COMMENT '备注',
|
||||
creator varchar(64) DEFAULT '' COMMENT '创建者',
|
||||
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updater varchar(64) DEFAULT '' COMMENT '更新者',
|
||||
update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
deleted bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
tenant_id bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员等级记录';
|
||||
|
||||
-- 6. 会员经验记录表
|
||||
CREATE TABLE IF NOT EXISTS member_experience_record (
|
||||
id bigint NOT NULL AUTO_INCREMENT,
|
||||
user_id bigint NOT NULL COMMENT '用户编号',
|
||||
biz_type varchar(50) NOT NULL COMMENT '业务类型',
|
||||
biz_id varchar(100) NOT NULL COMMENT '业务编号',
|
||||
title varchar(100) NOT NULL COMMENT '标题',
|
||||
experience int NOT NULL COMMENT '经验',
|
||||
total_experience int NOT NULL DEFAULT 0 COMMENT '累计经验',
|
||||
description varchar(255) DEFAULT '' COMMENT '描述',
|
||||
creator varchar(64) DEFAULT '' COMMENT '创建者',
|
||||
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updater varchar(64) DEFAULT '' COMMENT '更新者',
|
||||
update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
deleted bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
tenant_id bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员经验记录';
|
||||
|
||||
-- 7. 会员积分记录表
|
||||
CREATE TABLE IF NOT EXISTS member_point_record (
|
||||
id bigint NOT NULL AUTO_INCREMENT,
|
||||
user_id bigint NOT NULL COMMENT '用户编号',
|
||||
biz_type varchar(50) NOT NULL COMMENT '业务类型',
|
||||
biz_id varchar(100) NOT NULL COMMENT '业务编号',
|
||||
title varchar(100) NOT NULL COMMENT '标题',
|
||||
point int NOT NULL COMMENT '积分',
|
||||
total_point int NOT NULL DEFAULT 0 COMMENT '累计积分',
|
||||
description varchar(255) DEFAULT '' COMMENT '描述',
|
||||
creator varchar(64) DEFAULT '' COMMENT '创建者',
|
||||
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updater varchar(64) DEFAULT '' COMMENT '更新者',
|
||||
update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
deleted bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
tenant_id bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员积分记录';
|
||||
|
||||
-- 8. 会员签到配置表
|
||||
CREATE TABLE IF NOT EXISTS member_sign_in_config (
|
||||
id bigint NOT NULL AUTO_INCREMENT,
|
||||
day int NOT NULL COMMENT '签到天数',
|
||||
point int NOT NULL DEFAULT 0 COMMENT '奖励积分',
|
||||
experience int NOT NULL DEFAULT 0 COMMENT '奖励经验',
|
||||
status tinyint NOT NULL DEFAULT 0 COMMENT '状态',
|
||||
creator varchar(64) DEFAULT '' COMMENT '创建者',
|
||||
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updater varchar(64) DEFAULT '' COMMENT '更新者',
|
||||
update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
deleted bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
tenant_id bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员签到配置';
|
||||
|
||||
-- 9. 会员签到记录表
|
||||
CREATE TABLE IF NOT EXISTS member_sign_in_record (
|
||||
id bigint NOT NULL AUTO_INCREMENT,
|
||||
user_id bigint NOT NULL COMMENT '用户编号',
|
||||
day int NOT NULL COMMENT '签到天数',
|
||||
point int NOT NULL DEFAULT 0 COMMENT '获得积分',
|
||||
experience int NOT NULL DEFAULT 0 COMMENT '获得经验',
|
||||
creator varchar(64) DEFAULT '' COMMENT '创建者',
|
||||
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updater varchar(64) DEFAULT '' COMMENT '更新者',
|
||||
update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
deleted bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
tenant_id bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员签到记录';
|
||||
|
||||
-- 10. 会员地址表
|
||||
CREATE TABLE IF NOT EXISTS member_address (
|
||||
id bigint NOT NULL AUTO_INCREMENT,
|
||||
user_id bigint NOT NULL COMMENT '用户编号',
|
||||
name varchar(20) NOT NULL COMMENT '收件人姓名',
|
||||
mobile varchar(20) NOT NULL COMMENT '手机号',
|
||||
area_id bigint NOT NULL COMMENT '地区编号',
|
||||
detail_address varchar(250) NOT NULL COMMENT '详细地址',
|
||||
default_status bit(1) NOT NULL DEFAULT b'0' COMMENT '是否默认',
|
||||
creator varchar(64) DEFAULT '' COMMENT '创建者',
|
||||
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updater varchar(64) DEFAULT '' COMMENT '更新者',
|
||||
update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
deleted bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员地址';
|
||||
|
||||
-- =============================================
|
||||
-- 种子数据
|
||||
-- =============================================
|
||||
INSERT IGNORE INTO member_level (id, name, level, experience, status, tenant_id) VALUES (1, '普通会员', 1, 0, 0, 1);
|
||||
@@ -1,8 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块种子数据回滚 (PostgreSQL)
|
||||
-- =============================================
|
||||
|
||||
DELETE FROM system_menu
|
||||
WHERE id = 6801 AND permission = 'education:capability' AND parent_id = 6800;
|
||||
DELETE FROM system_menu
|
||||
WHERE id = 6800 AND path = '/education' AND name = '教育管理';
|
||||
@@ -1,7 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块 DDL (PostgreSQL)
|
||||
-- 当前为应用外壳阶段,无业务表;后续票据在此追加 CREATE TABLE 语句。
|
||||
-- =============================================
|
||||
-- 占位:education 模块当前无业务表
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS education;
|
||||
@@ -1,15 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块种子数据 (PostgreSQL)
|
||||
-- 菜单 ID 范围:6800-6899
|
||||
-- 权限标识前缀:education:
|
||||
-- 可重复执行;角色授权由管理员按租户完成
|
||||
-- =============================================
|
||||
|
||||
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
|
||||
SELECT 6800, '教育管理', '', 1, 50, 0, '/education', 'ep:school', NULL, NULL, 0, true, true, true, 'admin', NOW(), 'admin', NOW(), false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6800);
|
||||
|
||||
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
|
||||
SELECT 6801, '能力查询', 'education:capability', 3, 1, 6800, '', '', '', NULL, 0, true, true, true, 'admin', NOW(), 'admin', NOW(), false
|
||||
WHERE EXISTS (SELECT 1 FROM system_menu WHERE id = 6800 AND path = '/education' AND deleted = false)
|
||||
AND NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 6801);
|
||||
@@ -1 +0,0 @@
|
||||
-- Ticket #3 creates no database records, so rollback is intentionally a no-op.
|
||||
@@ -1,3 +0,0 @@
|
||||
-- Ticket #3 adds no administrator permission.
|
||||
-- Tenant resolution is @PermitAll and current education context only requires an authenticated Member session.
|
||||
-- Therefore no system_menu rows are required for this vertical slice.
|
||||
@@ -1,32 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块 — 练习会话与题目快照回滚
|
||||
-- Ticket #6 / Migration 002
|
||||
-- =============================================
|
||||
--
|
||||
-- WARNING: This file contains NO executable SQL.
|
||||
-- Destructive rollback (DROP TABLE) requires manual operator verification.
|
||||
--
|
||||
-- Manual rollback procedure (operator must execute):
|
||||
-- 1. Verify no other tables depend on these tables:
|
||||
-- SELECT tc.table_name, kcu.column_name, ccu.table_name AS referenced_table
|
||||
-- FROM information_schema.table_constraints tc
|
||||
-- JOIN information_schema.key_column_usage kcu
|
||||
-- ON tc.constraint_name = kcu.constraint_name
|
||||
-- JOIN information_schema.constraint_column_usage ccu
|
||||
-- ON tc.constraint_name = ccu.constraint_name
|
||||
-- WHERE tc.constraint_type = 'FOREIGN KEY'
|
||||
-- AND ccu.table_name IN ('education_practice_session', 'education_practice_question');
|
||||
-- Result MUST be empty before proceeding.
|
||||
--
|
||||
-- 2. Verify the tables contain only data from this migration:
|
||||
-- SELECT COUNT(*) AS session_count FROM education_practice_session;
|
||||
-- SELECT COUNT(*) AS question_count FROM education_practice_question;
|
||||
-- Operator must confirm these counts are acceptable to destroy.
|
||||
--
|
||||
-- 3. After verification, execute:
|
||||
-- DROP TABLE IF EXISTS education_practice_question;
|
||||
-- DROP TABLE IF EXISTS education_practice_session;
|
||||
--
|
||||
-- DO NOT uncomment or execute the lines below without operator verification.
|
||||
-- -- DROP TABLE IF EXISTS education_practice_question;
|
||||
-- -- DROP TABLE IF EXISTS education_practice_session;
|
||||
@@ -1,105 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块 — 练习会话与题目快照 DDL
|
||||
-- Ticket #6: 练习会话创建、题目快照、恢复与状态机
|
||||
-- Migration: 002
|
||||
-- Prerequisites: 000-education-schema.sql (database creation)
|
||||
-- 001-education-tenant-seed.sql (tenant seed data)
|
||||
-- =============================================
|
||||
|
||||
-- =============================================
|
||||
-- Preconditions
|
||||
-- =============================================
|
||||
-- This migration MUST fail if either table already exists (no IF NOT EXISTS).
|
||||
-- Operator is expected to verify:
|
||||
-- SELECT COUNT(*) FROM information_schema.tables
|
||||
-- WHERE table_schema = current_database()
|
||||
-- AND table_name IN ('education_practice_session', 'education_practice_question');
|
||||
-- Result MUST be 0 before executing this migration.
|
||||
|
||||
-- =============================================
|
||||
-- 练习会话表
|
||||
-- =============================================
|
||||
-- Indexes:
|
||||
-- uk_tenant_client_session — per-tenant uniqueness for clientSessionId idempotency.
|
||||
-- Used by: selectByTenantAndClientSessionId (idempotent create check),
|
||||
-- INSERT ... ON CONFLICT ... for concurrent-create race resolution.
|
||||
-- idx_tenant_user_status — covers getCurrentSession (latest ACTIVE by tenant+user)
|
||||
-- and ownership queries. Column order: (tenant_id, user_id, status) so the
|
||||
-- index supports both filtering by tenant+user and tenant+user+status.
|
||||
-- Lock impact: INSERT acquires a row-level lock on the unique index entry;
|
||||
-- concurrent inserts with same (tenant_id, client_session_id) serialize naturally.
|
||||
-- No additional table-level locks required.
|
||||
CREATE TABLE education_practice_session (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY /* 会话主键 */,
|
||||
tenant_id BIGINT NOT NULL /* 租户编号 */,
|
||||
user_id BIGINT NOT NULL /* Member 用户编号 */,
|
||||
client_session_id VARCHAR(36) NOT NULL /* 客户端生成的会话标识(UUID),用于幂等创建 */,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE'
|
||||
/* 会话状态:ACTIVE-进行中, SUBMITTED-已提交, EXPIRED-已过期, CANCELLED-已取消 */,
|
||||
question_count INT NOT NULL DEFAULT 0 /* 题目总数 */,
|
||||
collection_id VARCHAR(64) DEFAULT NULL /* 源题集 ID */,
|
||||
node_id VARCHAR(64) DEFAULT NULL /* 源目录节点 ID */,
|
||||
type VARCHAR(32) DEFAULT NULL /* 筛选题型 */,
|
||||
difficulty VARCHAR(32) DEFAULT NULL /* 筛选难度 */,
|
||||
version INT NOT NULL DEFAULT 0 /* 乐观锁版本号 */,
|
||||
creator VARCHAR(64) DEFAULT '' /* 创建者 */,
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP /* 创建时间 */,
|
||||
updater VARCHAR(64) DEFAULT '' /* 更新者 */,
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP /* 更新时间 */,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false /* 是否删除 */
|
||||
);
|
||||
COMMENT ON TABLE education_practice_session IS '教育-练习会话';
|
||||
|
||||
CREATE UNIQUE INDEX uk_tenant_client_session ON education_practice_session (tenant_id, client_session_id);
|
||||
CREATE INDEX idx_tenant_user_status ON education_practice_session (tenant_id, user_id, status);
|
||||
|
||||
-- =============================================
|
||||
-- 练习会话题目快照表
|
||||
-- =============================================
|
||||
-- Indexes:
|
||||
-- uk_session_sequence — per-session uniqueness for question sequence numbers.
|
||||
-- Used by: insertBatch to ensure no duplicate sequences within a session.
|
||||
-- idx_session_id — covers selectBySessionIdOrderBySequence (load all questions
|
||||
-- for a session, ordered by sequence). Also used by cascade delete lookups.
|
||||
-- Lock impact: INSERT acquires gap locks within session_id range on uk_session_sequence;
|
||||
-- concurrent inserts into different sessions are independent.
|
||||
-- Options column: JSONB data type stores only label, content, order — never isCorrect.
|
||||
-- Application layer (optionsToSafeJson) strips correctness before storage.
|
||||
CREATE TABLE education_practice_question (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY /* 主键 */,
|
||||
tenant_id BIGINT NOT NULL /* 租户编号 */,
|
||||
session_id BIGINT NOT NULL /* 会话 ID */,
|
||||
sequence INT NOT NULL /* 题目序号(1-based,服务端固定) */,
|
||||
question_id VARCHAR(64) NOT NULL /* 原始题目 ID */,
|
||||
content_version VARCHAR(64) NOT NULL DEFAULT '' /* 快照时的题目内容版本 */,
|
||||
stem TEXT NOT NULL /* 题干快照 */,
|
||||
type VARCHAR(32) NOT NULL /* 题型快照 */,
|
||||
difficulty VARCHAR(32) DEFAULT NULL /* 难度快照 */,
|
||||
options JSONB NOT NULL /* 选项快照 JSON(不含 isCorrect) */,
|
||||
correct_answer TEXT DEFAULT NULL /* 正确答案快照 */,
|
||||
explanation TEXT DEFAULT NULL /* 解析快照 */,
|
||||
selected_answer TEXT DEFAULT NULL /* 学生已选答案 */,
|
||||
is_answered BOOLEAN NOT NULL DEFAULT false /* 是否已作答 */,
|
||||
creator VARCHAR(64) DEFAULT '' /* 创建者 */,
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP /* 创建时间 */,
|
||||
updater VARCHAR(64) DEFAULT '' /* 更新者 */,
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP /* 更新时间 */,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false /* 是否删除 */
|
||||
);
|
||||
COMMENT ON TABLE education_practice_question IS '教育-练习会话题目快照';
|
||||
|
||||
CREATE UNIQUE INDEX uk_session_sequence ON education_practice_question (session_id, sequence);
|
||||
CREATE INDEX idx_session_id ON education_practice_question (session_id);
|
||||
|
||||
-- =============================================
|
||||
-- Post-migration verification queries
|
||||
-- =============================================
|
||||
-- Verify tables exist with correct structure:
|
||||
-- \d education_practice_session
|
||||
-- \d education_practice_question
|
||||
-- Verify unique keys are enforced:
|
||||
-- SELECT * FROM pg_indexes WHERE tablename = 'education_practice_session' AND indexname = 'uk_tenant_client_session';
|
||||
-- SELECT * FROM pg_indexes WHERE tablename = 'education_practice_question' AND indexname = 'uk_session_sequence';
|
||||
-- Verify no orphan data (should be 0 after fresh migration):
|
||||
-- SELECT COUNT(*) FROM education_practice_session;
|
||||
-- SELECT COUNT(*) FROM education_practice_question;
|
||||
@@ -1,35 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块 — 答案保存幂等性回滚 (PostgreSQL)
|
||||
-- Ticket #7 / Migration 003
|
||||
-- =============================================
|
||||
--
|
||||
-- WARNING: This file contains NO executable SQL.
|
||||
-- Destructive rollback requires manual operator verification.
|
||||
--
|
||||
-- Manual rollback procedure (operator must execute):
|
||||
-- 1. Verify no other tables depend on education_answer_idempotency:
|
||||
-- SELECT tc.table_name, kcu.column_name, ccu.table_name AS referenced_table
|
||||
-- FROM information_schema.table_constraints AS tc
|
||||
-- JOIN information_schema.key_column_usage AS kcu
|
||||
-- ON tc.constraint_name = kcu.constraint_name
|
||||
-- JOIN information_schema.constraint_column_usage AS ccu
|
||||
-- ON ccu.constraint_name = tc.constraint_name
|
||||
-- WHERE tc.constraint_type = 'FOREIGN KEY'
|
||||
-- AND ccu.table_name = 'education_answer_idempotency'
|
||||
-- AND tc.table_catalog = current_database();
|
||||
-- Result MUST be empty before proceeding.
|
||||
--
|
||||
-- 2. Verify the table contains only data from this migration:
|
||||
-- SELECT COUNT(*) AS idempotency_count FROM education_answer_idempotency;
|
||||
-- Operator must confirm this count is acceptable to destroy.
|
||||
--
|
||||
-- 3. Verify no application code depends on client_sequence column:
|
||||
-- Search codebase for 'clientSequence' / 'client_sequence' references.
|
||||
--
|
||||
-- 4. After verification, execute:
|
||||
-- DROP TABLE IF EXISTS education_answer_idempotency;
|
||||
-- ALTER TABLE education_practice_question DROP COLUMN client_sequence;
|
||||
--
|
||||
-- DO NOT uncomment or execute the lines below without operator verification.
|
||||
-- -- DROP TABLE IF EXISTS education_answer_idempotency;
|
||||
-- -- ALTER TABLE education_practice_question DROP COLUMN client_sequence;
|
||||
@@ -1,113 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块 — 答案保存幂等性 DDL (PostgreSQL)
|
||||
-- Ticket #7: 答案命令幂等、乐观锁并发控制、答案恢复
|
||||
-- Migration: 003
|
||||
-- Prerequisites: 002-education-practice-session.sql (session + question snapshots)
|
||||
-- =============================================
|
||||
|
||||
-- =============================================
|
||||
-- Preconditions
|
||||
-- =============================================
|
||||
-- Operator is expected to verify:
|
||||
-- SELECT COUNT(*) FROM information_schema.tables
|
||||
-- WHERE table_catalog = current_database()
|
||||
-- AND table_name = 'education_answer_idempotency';
|
||||
-- Result MUST be 0 before executing this migration.
|
||||
--
|
||||
-- Verify prerequisite tables exist:
|
||||
-- SELECT COUNT(*) FROM information_schema.tables
|
||||
-- WHERE table_catalog = current_database()
|
||||
-- AND table_name IN ('education_practice_session', 'education_practice_question');
|
||||
-- Result MUST be 2.
|
||||
|
||||
-- =============================================
|
||||
-- 答案命令幂等表
|
||||
-- =============================================
|
||||
-- Purpose: Provide durable idempotency for answer save commands.
|
||||
-- Same (tenant, user, operation, idempotency_key) + same request_hash → replay original response.
|
||||
-- Same key + different request_hash → conflict.
|
||||
-- Concurrent same-key inserts are resolved by unique constraint race handling.
|
||||
--
|
||||
-- Indexes:
|
||||
-- uk_answer_idempotency — per-tenant, per-actor, per-operation uniqueness for idempotency key.
|
||||
-- INSERT during answer save. DuplicateKeyException catch for concurrent-create race resolution.
|
||||
-- idx_tenant_session — covers lookup by session for audit/debug.
|
||||
--
|
||||
-- response_json: Stores the serialized answer response for replay after network timeout/retry.
|
||||
-- request_hash: SHA-256 of canonical payload (sorted JSON fields) for content-based dedup.
|
||||
CREATE TABLE education_answer_idempotency (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
operation VARCHAR(32) NOT NULL DEFAULT 'SUBMIT_ANSWER',
|
||||
idempotency_key VARCHAR(64) NOT NULL,
|
||||
request_hash VARCHAR(64) NOT NULL,
|
||||
session_id BIGINT NOT NULL,
|
||||
question_id VARCHAR(64) NOT NULL,
|
||||
selected_answer TEXT DEFAULT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'ACCEPTED',
|
||||
response_json TEXT NOT NULL,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
COMMENT ON TABLE education_answer_idempotency IS '教育-答案命令幂等记录';
|
||||
COMMENT ON COLUMN education_answer_idempotency.id IS '主键';
|
||||
COMMENT ON COLUMN education_answer_idempotency.tenant_id IS '租户编号';
|
||||
COMMENT ON COLUMN education_answer_idempotency.user_id IS '答题用户编号';
|
||||
COMMENT ON COLUMN education_answer_idempotency.operation IS '操作类型:SUBMIT_ANSWER';
|
||||
COMMENT ON COLUMN education_answer_idempotency.idempotency_key IS '客户端幂等键(UUID)';
|
||||
COMMENT ON COLUMN education_answer_idempotency.request_hash IS '请求载荷 SHA-256 哈希';
|
||||
COMMENT ON COLUMN education_answer_idempotency.session_id IS '会话 ID';
|
||||
COMMENT ON COLUMN education_answer_idempotency.question_id IS '题目 ID';
|
||||
COMMENT ON COLUMN education_answer_idempotency.selected_answer IS '学生已选答案';
|
||||
COMMENT ON COLUMN education_answer_idempotency.status IS '状态:ACCEPTED-已接受, CONFLICT-冲突';
|
||||
COMMENT ON COLUMN education_answer_idempotency.response_json IS '首次成功响应 JSON(用于重试重放)';
|
||||
COMMENT ON COLUMN education_answer_idempotency.creator IS '创建者';
|
||||
COMMENT ON COLUMN education_answer_idempotency.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN education_answer_idempotency.updater IS '更新者';
|
||||
COMMENT ON COLUMN education_answer_idempotency.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN education_answer_idempotency.deleted IS '是否删除';
|
||||
|
||||
CREATE UNIQUE INDEX uk_answer_idempotency ON education_answer_idempotency (tenant_id, user_id, operation, idempotency_key);
|
||||
CREATE INDEX idx_tenant_session ON education_answer_idempotency (tenant_id, session_id);
|
||||
|
||||
-- =============================================
|
||||
-- PracticeQuestionDO: add client_sequence column
|
||||
-- =============================================
|
||||
-- Purpose: Track the last accepted client command sequence per question.
|
||||
-- Rejects stale clientSequence: only sequences strictly greater than the
|
||||
-- stored value are accepted (monotonic forward progression).
|
||||
-- NULL means no answer has been accepted yet.
|
||||
ALTER TABLE education_practice_question
|
||||
ADD COLUMN client_sequence INT DEFAULT NULL;
|
||||
CREATE INDEX idx_client_sequence ON education_practice_question (client_sequence);
|
||||
|
||||
-- =============================================
|
||||
-- PracticeSessionDO: add last_client_sequence column
|
||||
-- =============================================
|
||||
-- Purpose: Session-wide monotonic counter for client commands.
|
||||
-- Rejects stale clientSequence across questions (not just per-question).
|
||||
-- CAS incrementVersion now updates this column alongside version.
|
||||
-- NULL means no answer has been accepted yet for this session.
|
||||
ALTER TABLE education_practice_session
|
||||
ADD COLUMN last_client_sequence INT DEFAULT NULL;
|
||||
|
||||
-- =============================================
|
||||
-- Post-migration verification queries
|
||||
-- =============================================
|
||||
-- Verify new table exists:
|
||||
-- \d education_answer_idempotency
|
||||
-- Verify unique key is enforced:
|
||||
-- SELECT * FROM pg_indexes WHERE tablename = 'education_answer_idempotency' AND indexname = 'uk_answer_idempotency';
|
||||
-- Verify column added to question table:
|
||||
-- SELECT column_name, data_type, column_default
|
||||
-- FROM information_schema.columns
|
||||
-- WHERE table_catalog = current_database()
|
||||
-- AND table_name = 'education_practice_question'
|
||||
-- AND column_name = 'client_sequence';
|
||||
-- Verify no orphan data (should be 0 after fresh migration):
|
||||
-- SELECT COUNT(*) FROM education_answer_idempotency;
|
||||
@@ -1,50 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块 — Ticket #8 迁移回滚 (PostgreSQL)
|
||||
-- 004-education-submit-report-rollback.sql
|
||||
-- =============================================
|
||||
--
|
||||
-- WARNING: This file contains NO executable SQL.
|
||||
-- Destructive rollback requires manual operator verification.
|
||||
--
|
||||
-- Manual rollback procedure (operator must execute):
|
||||
-- 1. Verify no other tables depend on these tables:
|
||||
-- SELECT
|
||||
-- tc.table_name,
|
||||
-- kcu.column_name,
|
||||
-- ccu.table_name AS referenced_table_name
|
||||
-- FROM information_schema.table_constraints AS tc
|
||||
-- JOIN information_schema.key_column_usage AS kcu
|
||||
-- ON tc.constraint_name = kcu.constraint_name
|
||||
-- JOIN information_schema.constraint_column_usage AS ccu
|
||||
-- ON ccu.constraint_name = tc.constraint_name
|
||||
-- WHERE tc.constraint_type = 'FOREIGN KEY'
|
||||
-- AND ccu.table_name IN ('education_submit_idempotency',
|
||||
-- 'education_practice_report', 'education_practice_report_detail')
|
||||
-- AND ccu.table_catalog = current_database();
|
||||
-- Result MUST be empty before proceeding.
|
||||
--
|
||||
-- 2. Verify columns are not referenced by application code:
|
||||
-- Search codebase for 'correct_answer' / 'explanation' references in
|
||||
-- education_practice_question to confirm no other consumers.
|
||||
--
|
||||
-- 3. Verify the tables contain only data from this migration:
|
||||
-- SELECT COUNT(*) AS idempotency_count FROM education_submit_idempotency;
|
||||
-- SELECT COUNT(*) AS report_count FROM education_practice_report;
|
||||
-- SELECT COUNT(*) AS detail_count FROM education_practice_report_detail;
|
||||
-- Operator must confirm these counts are acceptable to destroy.
|
||||
--
|
||||
-- 4. After verification, execute:
|
||||
-- DROP TABLE IF EXISTS education_practice_report_detail;
|
||||
-- DROP TABLE IF EXISTS education_practice_report;
|
||||
-- DROP TABLE IF EXISTS education_submit_idempotency;
|
||||
-- ALTER TABLE education_practice_question
|
||||
-- DROP COLUMN correct_answer,
|
||||
-- DROP COLUMN explanation;
|
||||
--
|
||||
-- DO NOT uncomment or execute the lines below without operator verification.
|
||||
-- -- DROP TABLE IF EXISTS education_practice_report_detail;
|
||||
-- -- DROP TABLE IF EXISTS education_practice_report;
|
||||
-- -- DROP TABLE IF EXISTS education_submit_idempotency;
|
||||
-- -- ALTER TABLE education_practice_question
|
||||
-- -- DROP COLUMN correct_answer,
|
||||
-- -- DROP COLUMN explanation;
|
||||
@@ -1,220 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块 — 交卷提交与成绩报告 DDL (PostgreSQL)
|
||||
-- Ticket #8: 交卷 CAS、保护性答案快照、评分与报告
|
||||
-- Migration: 004
|
||||
-- Prerequisites: 003-education-answer-idempotency.sql
|
||||
-- =============================================
|
||||
|
||||
-- =============================================
|
||||
-- Preconditions
|
||||
-- =============================================
|
||||
-- Operator is expected to verify:
|
||||
-- SELECT COUNT(*) FROM information_schema.tables
|
||||
-- WHERE table_catalog = current_database()
|
||||
-- AND table_name IN ('education_submit_idempotency',
|
||||
-- 'education_practice_report',
|
||||
-- 'education_practice_report_detail');
|
||||
-- Result MUST be 0 before executing this migration.
|
||||
--
|
||||
-- Verify prerequisite tables exist:
|
||||
-- SELECT COUNT(*) FROM information_schema.tables
|
||||
-- WHERE table_catalog = current_database()
|
||||
-- AND table_name IN ('education_practice_session',
|
||||
-- 'education_practice_question',
|
||||
-- 'education_answer_idempotency');
|
||||
-- Result MUST be 3.
|
||||
|
||||
-- =============================================
|
||||
-- PracticeQuestionDO: add protected answer snapshot columns
|
||||
-- =============================================
|
||||
-- Purpose: At session creation, snapshot correct_answer and explanation
|
||||
-- from the full CatalogQuestionDTO. These fields are NEVER exposed
|
||||
-- before submission (enforced by SafeQuestionRespVO allow-list and
|
||||
-- PracticeQuestionRespVO which does not include them).
|
||||
ALTER TABLE education_practice_question
|
||||
ADD COLUMN correct_answer TEXT DEFAULT NULL,
|
||||
ADD COLUMN explanation TEXT DEFAULT NULL;
|
||||
|
||||
COMMENT ON COLUMN education_practice_question.correct_answer IS '正确答案快照(不可在交卷前暴露)';
|
||||
COMMENT ON COLUMN education_practice_question.explanation IS '解析快照(不可在交卷前暴露)';
|
||||
|
||||
-- =============================================
|
||||
-- 交卷幂等表
|
||||
-- =============================================
|
||||
-- Purpose: Provide durable idempotency for submit-session commands.
|
||||
-- Same (tenant, user, operation, idempotency_key) + same request_hash → replay original report.
|
||||
-- Same key + different request_hash → conflict.
|
||||
-- Concurrent same-key inserts resolved by unique constraint race handling.
|
||||
--
|
||||
-- Indexes:
|
||||
-- uk_submit_idempotency — per-tenant, per-actor, per-operation uniqueness for idempotency key.
|
||||
-- INSERT during submit. DuplicateKeyException catch for concurrent-create race resolution.
|
||||
-- idx_submit_session — covers lookup by session for audit/debug.
|
||||
CREATE TABLE education_submit_idempotency (
|
||||
id BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
operation VARCHAR(32) NOT NULL DEFAULT 'SUBMIT_SESSION',
|
||||
idempotency_key VARCHAR(64) NOT NULL,
|
||||
request_hash VARCHAR(64) NOT NULL,
|
||||
session_id BIGINT NOT NULL,
|
||||
report_id BIGINT DEFAULT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'ACCEPTED',
|
||||
response_json TEXT NOT NULL,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
COMMENT ON TABLE education_submit_idempotency IS '教育-交卷幂等记录';
|
||||
COMMENT ON COLUMN education_submit_idempotency.id IS '主键';
|
||||
COMMENT ON COLUMN education_submit_idempotency.tenant_id IS '租户编号';
|
||||
COMMENT ON COLUMN education_submit_idempotency.user_id IS '交卷用户编号';
|
||||
COMMENT ON COLUMN education_submit_idempotency.operation IS '操作类型:SUBMIT_SESSION';
|
||||
COMMENT ON COLUMN education_submit_idempotency.idempotency_key IS '客户端幂等键(UUID)';
|
||||
COMMENT ON COLUMN education_submit_idempotency.request_hash IS '请求载荷 SHA-256 哈希';
|
||||
COMMENT ON COLUMN education_submit_idempotency.session_id IS '会话 ID';
|
||||
COMMENT ON COLUMN education_submit_idempotency.report_id IS '关联的报告 ID(成功时有值)';
|
||||
COMMENT ON COLUMN education_submit_idempotency.status IS '状态:ACCEPTED-已接受, CONFLICT-冲突';
|
||||
COMMENT ON COLUMN education_submit_idempotency.response_json IS '首次成功响应 JSON(用于重试重放)';
|
||||
COMMENT ON COLUMN education_submit_idempotency.creator IS '创建者';
|
||||
COMMENT ON COLUMN education_submit_idempotency.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN education_submit_idempotency.updater IS '更新者';
|
||||
COMMENT ON COLUMN education_submit_idempotency.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN education_submit_idempotency.deleted IS '是否删除';
|
||||
|
||||
CREATE UNIQUE INDEX uk_submit_idempotency ON education_submit_idempotency (tenant_id, user_id, operation, idempotency_key);
|
||||
CREATE INDEX idx_submit_session ON education_submit_idempotency (tenant_id, session_id);
|
||||
|
||||
-- =============================================
|
||||
-- 练习报告表(会话级)
|
||||
-- =============================================
|
||||
-- Purpose: Store the computed scoring result for a submitted session.
|
||||
-- One report per session. Immutable after creation.
|
||||
-- Question snapshots (stem, selectedAnswer, correctAnswer, explanation)
|
||||
-- are stored in report_details so source question edits don't affect history.
|
||||
--
|
||||
-- Indexes:
|
||||
-- uk_report_session — one report per session (unique).
|
||||
-- idx_report_tenant_user — covers paginated history queries for current tenant+user.
|
||||
-- idx_report_create_time — covers time-sorted listing.
|
||||
CREATE TABLE education_practice_report (
|
||||
id BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
session_id BIGINT NOT NULL,
|
||||
question_count INT NOT NULL,
|
||||
answered_count INT NOT NULL DEFAULT 0,
|
||||
unanswered_count INT NOT NULL DEFAULT 0,
|
||||
correct_count INT NOT NULL DEFAULT 0,
|
||||
incorrect_count INT NOT NULL DEFAULT 0,
|
||||
score INT NOT NULL DEFAULT 0,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'SUBMITTED',
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
COMMENT ON TABLE education_practice_report IS '教育-练习报告';
|
||||
COMMENT ON COLUMN education_practice_report.id IS '主键';
|
||||
COMMENT ON COLUMN education_practice_report.tenant_id IS '租户编号';
|
||||
COMMENT ON COLUMN education_practice_report.user_id IS '用户编号';
|
||||
COMMENT ON COLUMN education_practice_report.session_id IS '会话 ID';
|
||||
COMMENT ON COLUMN education_practice_report.question_count IS '题目总数';
|
||||
COMMENT ON COLUMN education_practice_report.answered_count IS '已答题数';
|
||||
COMMENT ON COLUMN education_practice_report.unanswered_count IS '未答题数';
|
||||
COMMENT ON COLUMN education_practice_report.correct_count IS '正确题数';
|
||||
COMMENT ON COLUMN education_practice_report.incorrect_count IS '错误题数';
|
||||
COMMENT ON COLUMN education_practice_report.score IS '得分(整数,满分 100 为基准)';
|
||||
COMMENT ON COLUMN education_practice_report.status IS '报告状态:SUBMITTED';
|
||||
COMMENT ON COLUMN education_practice_report.creator IS '创建者';
|
||||
COMMENT ON COLUMN education_practice_report.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN education_practice_report.updater IS '更新者';
|
||||
COMMENT ON COLUMN education_practice_report.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN education_practice_report.deleted IS '是否删除';
|
||||
|
||||
CREATE UNIQUE INDEX uk_report_session ON education_practice_report (session_id);
|
||||
CREATE INDEX idx_report_tenant_user ON education_practice_report (tenant_id, user_id);
|
||||
CREATE INDEX idx_report_create_time ON education_practice_report (create_time);
|
||||
|
||||
-- =============================================
|
||||
-- 练习报告明细表(逐题结果)
|
||||
-- =============================================
|
||||
-- Purpose: Store per-question scoring results at submission time.
|
||||
-- Includes snapshot of stem, selectedAnswer, correctAnswer, and explanation
|
||||
-- so that history is stable even if source questions are later edited.
|
||||
--
|
||||
-- Indexes:
|
||||
-- uk_report_sequence — per-report uniqueness for question sequence.
|
||||
-- idx_detail_session — covers lookup by session for report assembly.
|
||||
CREATE TABLE education_practice_report_detail (
|
||||
id BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
report_id BIGINT NOT NULL,
|
||||
session_id BIGINT NOT NULL,
|
||||
question_id VARCHAR(64) NOT NULL,
|
||||
sequence INT NOT NULL,
|
||||
stem TEXT NOT NULL,
|
||||
type VARCHAR(32) NOT NULL,
|
||||
difficulty VARCHAR(32) DEFAULT NULL,
|
||||
selected_answer TEXT DEFAULT NULL,
|
||||
correct_answer TEXT DEFAULT NULL,
|
||||
is_correct BOOLEAN NOT NULL DEFAULT false,
|
||||
explanation TEXT DEFAULT NULL,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
COMMENT ON TABLE education_practice_report_detail IS '教育-练习报告明细';
|
||||
COMMENT ON COLUMN education_practice_report_detail.id IS '主键';
|
||||
COMMENT ON COLUMN education_practice_report_detail.tenant_id IS '租户编号';
|
||||
COMMENT ON COLUMN education_practice_report_detail.user_id IS '用户编号';
|
||||
COMMENT ON COLUMN education_practice_report_detail.report_id IS '报告 ID';
|
||||
COMMENT ON COLUMN education_practice_report_detail.session_id IS '会话 ID';
|
||||
COMMENT ON COLUMN education_practice_report_detail.question_id IS '原始题目 ID';
|
||||
COMMENT ON COLUMN education_practice_report_detail.sequence IS '题目序号(1-based)';
|
||||
COMMENT ON COLUMN education_practice_report_detail.stem IS '题干快照';
|
||||
COMMENT ON COLUMN education_practice_report_detail.type IS '题型快照';
|
||||
COMMENT ON COLUMN education_practice_report_detail.difficulty IS '难度快照';
|
||||
COMMENT ON COLUMN education_practice_report_detail.selected_answer IS '学生已选答案';
|
||||
COMMENT ON COLUMN education_practice_report_detail.correct_answer IS '正确答案快照';
|
||||
COMMENT ON COLUMN education_practice_report_detail.is_correct IS '是否正确';
|
||||
COMMENT ON COLUMN education_practice_report_detail.explanation IS '解析快照';
|
||||
COMMENT ON COLUMN education_practice_report_detail.creator IS '创建者';
|
||||
COMMENT ON COLUMN education_practice_report_detail.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN education_practice_report_detail.updater IS '更新者';
|
||||
COMMENT ON COLUMN education_practice_report_detail.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN education_practice_report_detail.deleted IS '是否删除';
|
||||
|
||||
CREATE UNIQUE INDEX uk_report_sequence ON education_practice_report_detail (report_id, sequence);
|
||||
CREATE INDEX idx_detail_session ON education_practice_report_detail (session_id);
|
||||
|
||||
-- =============================================
|
||||
-- Post-migration verification queries
|
||||
-- =============================================
|
||||
-- Verify new tables exist:
|
||||
-- \d education_submit_idempotency
|
||||
-- \d education_practice_report
|
||||
-- \d education_practice_report_detail
|
||||
-- Verify columns added to question table:
|
||||
-- SELECT column_name, data_type, column_default
|
||||
-- FROM information_schema.columns
|
||||
-- WHERE table_catalog = current_database()
|
||||
-- AND table_name = 'education_practice_question'
|
||||
-- AND column_name IN ('correct_answer', 'explanation');
|
||||
-- Verify unique keys are enforced:
|
||||
-- SELECT * FROM pg_indexes WHERE tablename = 'education_submit_idempotency' AND indexname = 'uk_submit_idempotency';
|
||||
-- SELECT * FROM pg_indexes WHERE tablename = 'education_practice_report' AND indexname = 'uk_report_session';
|
||||
-- SELECT * FROM pg_indexes WHERE tablename = 'education_practice_report_detail' AND indexname = 'uk_report_sequence';
|
||||
-- Verify no orphan data (should be 0 after fresh migration):
|
||||
-- SELECT COUNT(*) FROM education_submit_idempotency;
|
||||
-- SELECT COUNT(*) FROM education_practice_report;
|
||||
-- SELECT COUNT(*) FROM education_practice_report_detail;
|
||||
@@ -1,25 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块 — 错题本 DDL Rollback
|
||||
-- Migration: 005
|
||||
-- =============================================
|
||||
-- IMPORTANT: This is a documentation-only rollback.
|
||||
-- No DROP/ALTER/DELETE statements are executed. The wrong_question
|
||||
-- table is provenance-safe: it only accumulates data and mastering
|
||||
-- is a status flag. Dropping these tables would lose student error
|
||||
-- history with no recovery path.
|
||||
--
|
||||
-- What this migration created:
|
||||
-- - education_wrong_question (new table)
|
||||
-- - education_wrong_question_idempotency (new table)
|
||||
-- - education_practice_report_detail.options (new column)
|
||||
-- - education_practice_session.review_fingerprint (new column)
|
||||
--
|
||||
-- Manual rollback requires:
|
||||
-- 1. Verified database backup before rollback
|
||||
-- 2. Operator approval (DBA sign-off)
|
||||
-- 3. Provenance of all wrong-question records preserved (exported)
|
||||
-- 4. Soft-delete via deleted = true before any hard drop
|
||||
--
|
||||
-- These tables are NOT deleted by this script. Wrong history is
|
||||
-- retained; if deletion is required by external policy, consult
|
||||
-- the DBA for a verified rollback procedure.
|
||||
@@ -1,156 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块 — 错题本 DDL
|
||||
-- Ticket #9: 错题自动收集、复习练习创建
|
||||
-- Migration: 005
|
||||
-- Prerequisites: 004-education-submit-report.sql (report + detail tables)
|
||||
-- =============================================
|
||||
|
||||
-- =============================================
|
||||
-- Preconditions
|
||||
-- =============================================
|
||||
-- Operator is expected to verify:
|
||||
-- SELECT COUNT(*) FROM information_schema.tables
|
||||
-- WHERE table_schema = current_database()
|
||||
-- AND table_name IN ('education_wrong_question',
|
||||
-- 'education_wrong_question_idempotency');
|
||||
-- Result MUST be 0 before executing this migration.
|
||||
--
|
||||
-- Verify prerequisite tables exist:
|
||||
-- SELECT COUNT(*) FROM information_schema.tables
|
||||
-- WHERE table_schema = current_database()
|
||||
-- AND table_name IN ('education_practice_report',
|
||||
-- 'education_practice_report_detail');
|
||||
-- Result MUST be 2.
|
||||
|
||||
-- =============================================
|
||||
-- PracticeReportDetailDO: add content_version + options snapshot columns
|
||||
-- =============================================
|
||||
-- Purpose: At submit time, snapshot question content version and options
|
||||
-- (without isCorrect) so wrong-question book and review sessions have
|
||||
-- stable display data. The options are already stripped of isCorrect
|
||||
-- by the submit flow.
|
||||
ALTER TABLE education_practice_report_detail
|
||||
ADD COLUMN content_version VARCHAR(64) NOT NULL DEFAULT '' /* 题目内容版本快照 */,
|
||||
ADD COLUMN options TEXT DEFAULT NULL /* 选项快照 JSON(不含 isCorrect) */;
|
||||
|
||||
-- =============================================
|
||||
-- PracticeSessionDO: add review fingerprint column
|
||||
-- =============================================
|
||||
-- Purpose: Persist the canonical fingerprint of wrong-question IDs used
|
||||
-- to create a review session. On idempotent replay, the fingerprint
|
||||
-- is compared: same tenant+clientSessionId+sameUser+sortedIDs match
|
||||
-- returns the existing session; different ID set returns
|
||||
-- SESSION_IDEMPOTENCY_MISMATCH.
|
||||
ALTER TABLE education_practice_session
|
||||
ADD COLUMN review_fingerprint VARCHAR(64) DEFAULT NULL /* 复习会话题目指纹(SHA-256 of sorted unique wrongQuestionIds) */;
|
||||
|
||||
-- 错题表
|
||||
-- =============================================
|
||||
-- Purpose: Persistent wrong-question book per student.
|
||||
-- Each (tenant, user, question) is a unique entry.
|
||||
-- Repeated wrong answers on the SAME question increment wrong_count
|
||||
-- and update last_wrong_time. The idempotency guard table ensures
|
||||
-- each (tenant, user, question, report) can upsert at most once.
|
||||
--
|
||||
-- master_status values: 'PENDING' (default) | 'MASTERED'
|
||||
-- Marking mastered retains the full history and count; it does NOT
|
||||
-- delete or archive the record. Students can optionally un-master.
|
||||
--
|
||||
-- Snapshot fields (stem, type, difficulty, options, content_version):
|
||||
-- populated from the latest report detail that touched this question.
|
||||
-- These are for listing/detail display without joining report details.
|
||||
--
|
||||
-- latest_correct_answer, latest_explanation:
|
||||
-- also from the latest report detail; available for detail display
|
||||
-- post-submit (not exposed in review session creation pre-submit).
|
||||
--
|
||||
-- Indexes:
|
||||
-- uk_tenant_user_question — per-tenant, per-user, per-question uniqueness.
|
||||
-- INSERT ... ON CONFLICT ... DO UPDATE is the primary write path.
|
||||
-- idx_tenant_user_status — covers filtered list queries (page with status filter).
|
||||
-- idx_tenant_user_last_wrong — covers time-sorted listing.
|
||||
CREATE TABLE education_wrong_question (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY /* 主键 */,
|
||||
tenant_id BIGINT NOT NULL /* 租户编号 */,
|
||||
user_id BIGINT NOT NULL /* 学生用户编号 */,
|
||||
question_id VARCHAR(64) NOT NULL /* 原始题目 ID */,
|
||||
-- snapshot fields for listing / detail (from latest report detail)
|
||||
stem TEXT NOT NULL /* 题干快照(最新) */,
|
||||
type VARCHAR(32) NOT NULL /* 题型快照 */,
|
||||
difficulty VARCHAR(32) DEFAULT NULL /* 难度快照 */,
|
||||
options JSONB NOT NULL /* 选项快照 JSON(不含 isCorrect) */,
|
||||
content_version VARCHAR(64) NOT NULL DEFAULT '' /* 题目内容版本 */,
|
||||
latest_correct_answer TEXT DEFAULT NULL /* 正确答案快照(最新,供详情展示) */,
|
||||
latest_explanation TEXT DEFAULT NULL /* 解析快照(最新,供详情展示) */,
|
||||
-- timing & count
|
||||
first_wrong_time TIMESTAMP NOT NULL /* 首次错误时间 */,
|
||||
last_wrong_time TIMESTAMP NOT NULL /* 最近错误时间 */,
|
||||
wrong_count INT NOT NULL DEFAULT 1 /* 累计错误次数 */,
|
||||
-- mastery
|
||||
master_status VARCHAR(20) NOT NULL DEFAULT 'PENDING'
|
||||
/* 掌握状态:PENDING-待掌握, MASTERED-已掌握 */,
|
||||
mastered_time TIMESTAMP DEFAULT NULL /* 标记掌握时间 */,
|
||||
-- provenance
|
||||
last_report_id BIGINT DEFAULT NULL /* 最近关联的报告 ID */,
|
||||
last_session_id BIGINT DEFAULT NULL /* 最近关联的会话 ID */,
|
||||
-- audit
|
||||
creator VARCHAR(64) DEFAULT '' /* 创建者 */,
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP /* 创建时间 */,
|
||||
updater VARCHAR(64) DEFAULT '' /* 更新者 */,
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP /* 更新时间 */,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false /* 是否删除 */
|
||||
);
|
||||
COMMENT ON TABLE education_wrong_question IS '教育-错题本';
|
||||
|
||||
CREATE UNIQUE INDEX uk_tenant_user_question ON education_wrong_question (tenant_id, user_id, question_id);
|
||||
CREATE INDEX idx_tenant_user_status ON education_wrong_question (tenant_id, user_id, master_status);
|
||||
CREATE INDEX idx_tenant_user_last_wrong ON education_wrong_question (tenant_id, user_id, last_wrong_time);
|
||||
|
||||
-- =============================================
|
||||
-- 错题流水幂等表
|
||||
-- =============================================
|
||||
-- Purpose: Ensure each (tenant, user, question, report) upserts the
|
||||
-- wrong-question book exactly once. The submitSession transaction
|
||||
-- INSERT ... ON CONFLICT DO NOTHING into this table BEFORE the
|
||||
-- wrong question upsert; a duplicate means this report already
|
||||
-- contributed to the count. This guards against:
|
||||
-- - Replayed submit (idempotent resubmit) double-counting
|
||||
-- - Concurrent submit races where both threads evaluate the
|
||||
-- same report details
|
||||
--
|
||||
-- Indexes:
|
||||
-- uk_tenant_user_question_report — per (tenant, user, question, report) uniqueness.
|
||||
-- INSERT ... ON CONFLICT DO NOTHING provides the idempotency guard
|
||||
-- BEFORE upserting. wrong_question_id is filled after upsert for
|
||||
-- audit purposes.
|
||||
-- idx_report — fast lookup by report for audit/debug.
|
||||
CREATE TABLE education_wrong_question_idempotency (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY /* 主键 */,
|
||||
tenant_id BIGINT NOT NULL /* 租户编号 */,
|
||||
user_id BIGINT NOT NULL /* 学生用户编号 */,
|
||||
wrong_question_id BIGINT DEFAULT NULL /* 错题记录 ID(upsert 后填充) */,
|
||||
report_id BIGINT NOT NULL /* 报告 ID */,
|
||||
question_id VARCHAR(64) NOT NULL /* 题目 ID */,
|
||||
creator VARCHAR(64) DEFAULT '' /* 创建者 */,
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP /* 创建时间 */,
|
||||
updater VARCHAR(64) DEFAULT '' /* 更新者 */,
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP /* 更新时间 */,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false /* 是否删除 */
|
||||
);
|
||||
COMMENT ON TABLE education_wrong_question_idempotency IS '教育-错题流水幂等';
|
||||
|
||||
CREATE UNIQUE INDEX uk_tenant_user_question_report ON education_wrong_question_idempotency (tenant_id, user_id, question_id, report_id);
|
||||
CREATE INDEX idx_report ON education_wrong_question_idempotency (report_id);
|
||||
|
||||
-- =============================================
|
||||
-- Post-migration verification queries
|
||||
-- =============================================
|
||||
-- Verify new tables exist:
|
||||
-- \d education_wrong_question
|
||||
-- \d education_wrong_question_idempotency
|
||||
-- Verify unique keys are enforced:
|
||||
-- SELECT * FROM pg_indexes WHERE tablename = 'education_wrong_question' AND indexname = 'uk_tenant_user_question';
|
||||
-- SELECT * FROM pg_indexes WHERE tablename = 'education_wrong_question_idempotency' AND indexname = 'uk_tenant_user_question_report';
|
||||
-- Verify no orphan data (should be 0 after fresh migration):
|
||||
-- SELECT COUNT(*) FROM education_wrong_question;
|
||||
-- SELECT COUNT(*) FROM education_wrong_question_idempotency;
|
||||
@@ -1,25 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块 — 收藏夹 DDL Rollback (PostgreSQL)
|
||||
-- Migration: 007
|
||||
-- =============================================
|
||||
-- IMPORTANT: This is a documentation-only rollback.
|
||||
-- No DROP/ALTER/DELETE statements are executed. The favorite
|
||||
-- table is provenance-safe: it only accumulates user preference
|
||||
-- data. Dropping this table would lose student favorites with
|
||||
-- no recovery path.
|
||||
--
|
||||
-- What this migration created:
|
||||
-- - education_favorite (new table)
|
||||
-- - idx_tenant_user (index)
|
||||
-- - idx_tenant_user_target_type (index)
|
||||
-- - uk_tenant_user_target (unique constraint)
|
||||
--
|
||||
-- Manual rollback requires:
|
||||
-- 1. Verified database backup before rollback
|
||||
-- 2. Operator approval (DBA sign-off)
|
||||
-- 3. Provenance of all favorite records preserved (exported)
|
||||
-- 4. Soft-delete via deleted = true before any hard drop
|
||||
--
|
||||
-- These tables are NOT deleted by this script. Favorite history is
|
||||
-- retained; if deletion is required by external policy, consult
|
||||
-- the DBA for a verified rollback procedure.
|
||||
@@ -1,95 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块 — 收藏夹 DDL (PostgreSQL)
|
||||
-- Ticket #10: 学生收藏题目
|
||||
-- Migration: 007
|
||||
-- Prerequisites: 000-education-schema.sql (base tables)
|
||||
-- =============================================
|
||||
|
||||
-- =============================================
|
||||
-- Preconditions
|
||||
-- =============================================
|
||||
-- Operator is expected to verify:
|
||||
-- SELECT COUNT(*) FROM information_schema.tables
|
||||
-- WHERE table_catalog = current_database()
|
||||
-- AND table_name = 'education_favorite';
|
||||
-- Result MUST be 0 before executing this migration.
|
||||
|
||||
-- =============================================
|
||||
-- 收藏表
|
||||
-- =============================================
|
||||
-- Purpose: Student favorites for questions with safe snapshots.
|
||||
-- Each (tenant, user, target_type, target_id) is a unique entry.
|
||||
-- Logical deletion: setting deleted=true marks as unfavorited.
|
||||
-- Re-adding after deletion reactivates the row via ON CONFLICT ... DO UPDATE.
|
||||
--
|
||||
-- target_type values: 'QUESTION' (extensible enum)
|
||||
--
|
||||
-- Snapshot fields (stem, type, difficulty, options, content_version):
|
||||
-- populated at creation time from the visible question's safe fields.
|
||||
-- These snapshots preserve the question state as it appeared when favorited,
|
||||
-- and remain stable even if the source question later changes or becomes unavailable.
|
||||
--
|
||||
-- available flag:
|
||||
-- FALSE when the source question becomes hidden/unpublished after being
|
||||
-- favorited. Existing favorites with available=FALSE remain listable but
|
||||
-- display an "unavailable" indicator. New favorites cannot be created for
|
||||
-- unavailable resources.
|
||||
--
|
||||
-- Indexes:
|
||||
-- uk_tenant_user_target — per (tenant, user, target_type, target_id) uniqueness.
|
||||
-- INSERT ... ON CONFLICT DO UPDATE is the primary reactivation path.
|
||||
-- idx_tenant_user — covers listing queries filtered by current tenant+user.
|
||||
-- idx_tenant_user_target_type — covers target-type-filtered listing.
|
||||
CREATE TABLE education_favorite (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
target_type VARCHAR(32) NOT NULL,
|
||||
target_id VARCHAR(64) NOT NULL,
|
||||
-- safe snapshot fields
|
||||
stem TEXT DEFAULT NULL,
|
||||
type VARCHAR(32) DEFAULT NULL,
|
||||
difficulty VARCHAR(32) DEFAULT NULL,
|
||||
options JSONB DEFAULT NULL,
|
||||
content_version VARCHAR(64) NOT NULL DEFAULT '',
|
||||
-- availability
|
||||
available BOOLEAN NOT NULL DEFAULT true,
|
||||
-- audit
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
CONSTRAINT uk_tenant_user_target UNIQUE (tenant_id, user_id, target_type, target_id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE education_favorite IS '教育-收藏夹';
|
||||
COMMENT ON COLUMN education_favorite.id IS '主键';
|
||||
COMMENT ON COLUMN education_favorite.tenant_id IS '租户编号';
|
||||
COMMENT ON COLUMN education_favorite.user_id IS '学生用户编号';
|
||||
COMMENT ON COLUMN education_favorite.target_type IS '目标类型:QUESTION';
|
||||
COMMENT ON COLUMN education_favorite.target_id IS '目标 ID(题目 ID)';
|
||||
COMMENT ON COLUMN education_favorite.stem IS '题干快照';
|
||||
COMMENT ON COLUMN education_favorite.type IS '题型快照';
|
||||
COMMENT ON COLUMN education_favorite.difficulty IS '难度快照';
|
||||
COMMENT ON COLUMN education_favorite.options IS '选项快照 JSON(不含 isCorrect)';
|
||||
COMMENT ON COLUMN education_favorite.content_version IS '题目内容版本';
|
||||
COMMENT ON COLUMN education_favorite.available IS '源资源是否可用';
|
||||
COMMENT ON COLUMN education_favorite.creator IS '创建者';
|
||||
COMMENT ON COLUMN education_favorite.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN education_favorite.updater IS '更新者';
|
||||
COMMENT ON COLUMN education_favorite.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN education_favorite.deleted IS '是否删除';
|
||||
|
||||
CREATE INDEX idx_tenant_user ON education_favorite (tenant_id, user_id);
|
||||
CREATE INDEX idx_tenant_user_target_type ON education_favorite (tenant_id, user_id, target_type);
|
||||
|
||||
-- =============================================
|
||||
-- Post-migration verification queries
|
||||
-- =============================================
|
||||
-- Verify new table exists:
|
||||
-- \d education_favorite
|
||||
-- Verify unique constraint is enforced:
|
||||
-- SELECT * FROM pg_indexes WHERE tablename = 'education_favorite' AND indexname = 'uk_tenant_user_target';
|
||||
-- Verify no orphan data (should be 0 after fresh migration):
|
||||
-- SELECT COUNT(*) FROM education_favorite;
|
||||
@@ -1,17 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块 — 题库目录表回滚 (PostgreSQL)
|
||||
-- Migration: 008 rollback
|
||||
-- Drops all 11 tables created by 008-education-catalog.sql
|
||||
-- =============================================
|
||||
|
||||
DROP TABLE IF EXISTS education_question_collection_question;
|
||||
DROP TABLE IF EXISTS education_practice_blueprint;
|
||||
DROP TABLE IF EXISTS education_question;
|
||||
DROP TABLE IF EXISTS education_question_collection;
|
||||
DROP TABLE IF EXISTS education_content_node;
|
||||
DROP TABLE IF EXISTS education_content_entry;
|
||||
DROP TABLE IF EXISTS education_category;
|
||||
DROP TABLE IF EXISTS education_subject;
|
||||
DROP TABLE IF EXISTS education_major;
|
||||
DROP TABLE IF EXISTS education_school;
|
||||
DROP TABLE IF EXISTS education_region;
|
||||
@@ -1,530 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块 — 题库目录原生表 DDL (PostgreSQL)
|
||||
-- Ticket #20: 原生实现题库目录浏览,替换 Scalar adapter
|
||||
-- Migration: 008
|
||||
-- Prerequisites: 000-education-schema.sql (database creation)
|
||||
-- 001-education-tenant-seed.sql (tenant seed data)
|
||||
-- =============================================
|
||||
|
||||
-- =============================================
|
||||
-- Preconditions
|
||||
-- =============================================
|
||||
-- Verify none of these tables already exist:
|
||||
-- SELECT table_name FROM information_schema.tables
|
||||
-- WHERE table_catalog = current_database()
|
||||
-- AND table_name IN (
|
||||
-- 'education_region', 'education_school', 'education_major',
|
||||
-- 'education_subject', 'education_category',
|
||||
-- 'education_content_entry', 'education_content_node',
|
||||
-- 'education_question_collection', 'education_question',
|
||||
-- 'education_practice_blueprint', 'education_question_collection_question'
|
||||
-- );
|
||||
-- Result MUST be 0 before executing this migration.
|
||||
|
||||
-- =============================================
|
||||
-- 1. 地区表
|
||||
-- =============================================
|
||||
-- Indexes:
|
||||
-- idx_tenant — tenant-scoped queries (tenant-aware interceptor default)
|
||||
-- idx_tenant_active_order — active region listing ordered by display order
|
||||
CREATE TABLE education_region (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
legacy_id VARCHAR(64) DEFAULT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
code VARCHAR(32) DEFAULT NULL,
|
||||
short_name VARCHAR(50) DEFAULT NULL,
|
||||
full_name VARCHAR(200) DEFAULT NULL,
|
||||
icon VARCHAR(500) DEFAULT NULL,
|
||||
pinyin VARCHAR(200) DEFAULT NULL,
|
||||
is_hot BOOLEAN NOT NULL DEFAULT false,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
COMMENT ON TABLE education_region IS '教育-地区';
|
||||
COMMENT ON COLUMN education_region.id IS '主键';
|
||||
COMMENT ON COLUMN education_region.tenant_id IS '租户编号';
|
||||
COMMENT ON COLUMN education_region.legacy_id IS '旧系统地区 ID';
|
||||
COMMENT ON COLUMN education_region.name IS '地区名称';
|
||||
COMMENT ON COLUMN education_region.code IS '地区编码';
|
||||
COMMENT ON COLUMN education_region.short_name IS '地区简称';
|
||||
COMMENT ON COLUMN education_region.full_name IS '地区全称';
|
||||
COMMENT ON COLUMN education_region.icon IS '地区图标 URL';
|
||||
COMMENT ON COLUMN education_region.pinyin IS '地区拼音';
|
||||
COMMENT ON COLUMN education_region.is_hot IS '是否热门地区';
|
||||
COMMENT ON COLUMN education_region.is_active IS '是否启用';
|
||||
COMMENT ON COLUMN education_region.sort_order IS '显示排序值';
|
||||
COMMENT ON COLUMN education_region.creator IS '创建者';
|
||||
COMMENT ON COLUMN education_region.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN education_region.updater IS '更新者';
|
||||
COMMENT ON COLUMN education_region.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN education_region.deleted IS '是否删除';
|
||||
CREATE INDEX idx_tenant ON education_region (tenant_id);
|
||||
CREATE INDEX idx_tenant_active_order ON education_region (tenant_id, is_active, sort_order);
|
||||
|
||||
-- =============================================
|
||||
-- 2. 院校表
|
||||
-- =============================================
|
||||
CREATE TABLE education_school (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
legacy_id VARCHAR(64) DEFAULT NULL,
|
||||
region_id BIGINT DEFAULT NULL,
|
||||
module_id BIGINT DEFAULT NULL,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
professional_exam_date VARCHAR(200) DEFAULT NULL,
|
||||
metadata JSONB DEFAULT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
COMMENT ON TABLE education_school IS '教育-院校';
|
||||
COMMENT ON COLUMN education_school.id IS '主键';
|
||||
COMMENT ON COLUMN education_school.tenant_id IS '租户编号';
|
||||
COMMENT ON COLUMN education_school.legacy_id IS '旧系统院校 ID';
|
||||
COMMENT ON COLUMN education_school.region_id IS '地区 ID';
|
||||
COMMENT ON COLUMN education_school.module_id IS '地区模块 ID';
|
||||
COMMENT ON COLUMN education_school.name IS '院校名称';
|
||||
COMMENT ON COLUMN education_school.professional_exam_date IS '专业考试日期说明';
|
||||
COMMENT ON COLUMN education_school.metadata IS '院校扩展数据';
|
||||
COMMENT ON COLUMN education_school.is_active IS '是否启用';
|
||||
COMMENT ON COLUMN education_school.sort_order IS '显示排序值';
|
||||
COMMENT ON COLUMN education_school.creator IS '创建者';
|
||||
COMMENT ON COLUMN education_school.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN education_school.updater IS '更新者';
|
||||
COMMENT ON COLUMN education_school.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN education_school.deleted IS '是否删除';
|
||||
CREATE INDEX idx_tenant ON education_school (tenant_id);
|
||||
CREATE INDEX idx_tenant_region ON education_school (tenant_id, region_id);
|
||||
|
||||
-- =============================================
|
||||
-- 3. 专业表
|
||||
-- =============================================
|
||||
CREATE TABLE education_major (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
legacy_id VARCHAR(64) DEFAULT NULL,
|
||||
region_id BIGINT DEFAULT NULL,
|
||||
school_id BIGINT DEFAULT NULL,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
description VARCHAR(500) DEFAULT NULL,
|
||||
study_tips VARCHAR(500) DEFAULT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
COMMENT ON TABLE education_major IS '教育-专业';
|
||||
COMMENT ON COLUMN education_major.id IS '主键';
|
||||
COMMENT ON COLUMN education_major.tenant_id IS '租户编号';
|
||||
COMMENT ON COLUMN education_major.legacy_id IS '旧系统专业 ID';
|
||||
COMMENT ON COLUMN education_major.region_id IS '地区 ID';
|
||||
COMMENT ON COLUMN education_major.school_id IS '院校 ID';
|
||||
COMMENT ON COLUMN education_major.name IS '专业名称';
|
||||
COMMENT ON COLUMN education_major.description IS '专业说明';
|
||||
COMMENT ON COLUMN education_major.study_tips IS '学习建议';
|
||||
COMMENT ON COLUMN education_major.is_active IS '是否启用';
|
||||
COMMENT ON COLUMN education_major.sort_order IS '显示排序值';
|
||||
COMMENT ON COLUMN education_major.creator IS '创建者';
|
||||
COMMENT ON COLUMN education_major.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN education_major.updater IS '更新者';
|
||||
COMMENT ON COLUMN education_major.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN education_major.deleted IS '是否删除';
|
||||
CREATE INDEX idx_tenant ON education_major (tenant_id);
|
||||
CREATE INDEX idx_tenant_school ON education_major (tenant_id, school_id);
|
||||
|
||||
-- =============================================
|
||||
-- 4. 科目表
|
||||
-- =============================================
|
||||
CREATE TABLE education_subject (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
region_id BIGINT DEFAULT NULL,
|
||||
school_id BIGINT DEFAULT NULL,
|
||||
major_id BIGINT DEFAULT NULL,
|
||||
module_id BIGINT DEFAULT NULL,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
type VARCHAR(50) DEFAULT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
COMMENT ON TABLE education_subject IS '教育-科目';
|
||||
COMMENT ON COLUMN education_subject.id IS '主键';
|
||||
COMMENT ON COLUMN education_subject.tenant_id IS '租户编号';
|
||||
COMMENT ON COLUMN education_subject.region_id IS '地区 ID';
|
||||
COMMENT ON COLUMN education_subject.school_id IS '院校 ID';
|
||||
COMMENT ON COLUMN education_subject.major_id IS '专业 ID';
|
||||
COMMENT ON COLUMN education_subject.module_id IS '地区模块 ID';
|
||||
COMMENT ON COLUMN education_subject.name IS '科目名称';
|
||||
COMMENT ON COLUMN education_subject.type IS '科目类型';
|
||||
COMMENT ON COLUMN education_subject.is_active IS '是否启用';
|
||||
COMMENT ON COLUMN education_subject.sort_order IS '显示排序值';
|
||||
COMMENT ON COLUMN education_subject.creator IS '创建者';
|
||||
COMMENT ON COLUMN education_subject.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN education_subject.updater IS '更新者';
|
||||
COMMENT ON COLUMN education_subject.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN education_subject.deleted IS '是否删除';
|
||||
CREATE INDEX idx_tenant ON education_subject (tenant_id);
|
||||
CREATE INDEX idx_tenant_major ON education_subject (tenant_id, major_id);
|
||||
|
||||
-- =============================================
|
||||
-- 5. 题目分类表
|
||||
-- =============================================
|
||||
CREATE TABLE education_category (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
subject_id BIGINT DEFAULT NULL,
|
||||
legacy_node_id VARCHAR(64) DEFAULT NULL,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
COMMENT ON TABLE education_category IS '教育-题目分类';
|
||||
COMMENT ON COLUMN education_category.id IS '主键';
|
||||
COMMENT ON COLUMN education_category.tenant_id IS '租户编号';
|
||||
COMMENT ON COLUMN education_category.subject_id IS '科目 ID';
|
||||
COMMENT ON COLUMN education_category.legacy_node_id IS '旧导航节点 ID';
|
||||
COMMENT ON COLUMN education_category.name IS '分类名称';
|
||||
COMMENT ON COLUMN education_category.is_active IS '是否启用';
|
||||
COMMENT ON COLUMN education_category.sort_order IS '显示排序值';
|
||||
COMMENT ON COLUMN education_category.creator IS '创建者';
|
||||
COMMENT ON COLUMN education_category.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN education_category.updater IS '更新者';
|
||||
COMMENT ON COLUMN education_category.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN education_category.deleted IS '是否删除';
|
||||
CREATE INDEX idx_tenant ON education_category (tenant_id);
|
||||
CREATE INDEX idx_tenant_subject ON education_category (tenant_id, subject_id);
|
||||
|
||||
-- =============================================
|
||||
-- 6. 内容入口表
|
||||
-- =============================================
|
||||
-- Indexes:
|
||||
-- uk_tenant_entry_key — per-tenant uniqueness for entry key
|
||||
CREATE TABLE education_content_entry (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
legacy_id VARCHAR(64) DEFAULT NULL,
|
||||
region_id BIGINT DEFAULT NULL,
|
||||
entry_key VARCHAR(100) NOT NULL,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
entry_type VARCHAR(50) NOT NULL,
|
||||
icon VARCHAR(500) DEFAULT NULL,
|
||||
route VARCHAR(200) DEFAULT NULL,
|
||||
description VARCHAR(500) DEFAULT NULL,
|
||||
visibility VARCHAR(50) NOT NULL DEFAULT 'PUBLIC',
|
||||
access_rules JSONB DEFAULT NULL,
|
||||
layout_config JSONB DEFAULT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
COMMENT ON TABLE education_content_entry IS '教育-内容入口';
|
||||
COMMENT ON COLUMN education_content_entry.id IS '主键';
|
||||
COMMENT ON COLUMN education_content_entry.tenant_id IS '租户编号';
|
||||
COMMENT ON COLUMN education_content_entry.legacy_id IS '旧系统内容入口 ID';
|
||||
COMMENT ON COLUMN education_content_entry.region_id IS '地区 ID';
|
||||
COMMENT ON COLUMN education_content_entry.entry_key IS '内容入口唯一键';
|
||||
COMMENT ON COLUMN education_content_entry.name IS '内容入口名称';
|
||||
COMMENT ON COLUMN education_content_entry.entry_type IS '内容入口类型';
|
||||
COMMENT ON COLUMN education_content_entry.icon IS '图标';
|
||||
COMMENT ON COLUMN education_content_entry.route IS '前端路由';
|
||||
COMMENT ON COLUMN education_content_entry.description IS '入口说明';
|
||||
COMMENT ON COLUMN education_content_entry.visibility IS '可见性';
|
||||
COMMENT ON COLUMN education_content_entry.access_rules IS '访问规则 JSON';
|
||||
COMMENT ON COLUMN education_content_entry.layout_config IS '布局配置 JSON';
|
||||
COMMENT ON COLUMN education_content_entry.is_active IS '是否启用';
|
||||
COMMENT ON COLUMN education_content_entry.sort_order IS '显示排序值';
|
||||
COMMENT ON COLUMN education_content_entry.creator IS '创建者';
|
||||
COMMENT ON COLUMN education_content_entry.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN education_content_entry.updater IS '更新者';
|
||||
COMMENT ON COLUMN education_content_entry.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN education_content_entry.deleted IS '是否删除';
|
||||
CREATE INDEX idx_tenant ON education_content_entry (tenant_id);
|
||||
CREATE UNIQUE INDEX uk_tenant_entry_key ON education_content_entry (tenant_id, entry_key);
|
||||
|
||||
-- =============================================
|
||||
-- 7. 内容节点表(树形结构)
|
||||
-- =============================================
|
||||
-- Indexes:
|
||||
-- idx_entry_parent — supports tree navigation: find children of a parent within an entry
|
||||
-- idx_tenant_entry — tenant-scoped queries by entry
|
||||
CREATE TABLE education_content_node (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
entry_id BIGINT NOT NULL,
|
||||
parent_id BIGINT DEFAULT NULL,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
title VARCHAR(200) DEFAULT NULL,
|
||||
node_type VARCHAR(50) NOT NULL,
|
||||
marker_type VARCHAR(50) DEFAULT NULL,
|
||||
depth INT NOT NULL DEFAULT 0,
|
||||
is_leaf BOOLEAN NOT NULL DEFAULT false,
|
||||
is_selectable BOOLEAN NOT NULL DEFAULT true,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
metadata JSONB DEFAULT NULL,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
COMMENT ON TABLE education_content_node IS '教育-内容节点';
|
||||
COMMENT ON COLUMN education_content_node.id IS '主键';
|
||||
COMMENT ON COLUMN education_content_node.tenant_id IS '租户编号';
|
||||
COMMENT ON COLUMN education_content_node.entry_id IS '内容入口 ID';
|
||||
COMMENT ON COLUMN education_content_node.parent_id IS '父节点 ID(NULL=根节点)';
|
||||
COMMENT ON COLUMN education_content_node.name IS '节点名称';
|
||||
COMMENT ON COLUMN education_content_node.title IS '节点标题';
|
||||
COMMENT ON COLUMN education_content_node.node_type IS '节点类型';
|
||||
COMMENT ON COLUMN education_content_node.marker_type IS '节点标记类型';
|
||||
COMMENT ON COLUMN education_content_node.depth IS '树深度';
|
||||
COMMENT ON COLUMN education_content_node.is_leaf IS '是否叶子节点';
|
||||
COMMENT ON COLUMN education_content_node.is_selectable IS '是否可被选择';
|
||||
COMMENT ON COLUMN education_content_node.is_active IS '是否启用';
|
||||
COMMENT ON COLUMN education_content_node.sort_order IS '显示排序值';
|
||||
COMMENT ON COLUMN education_content_node.metadata IS '扩展元数据';
|
||||
COMMENT ON COLUMN education_content_node.creator IS '创建者';
|
||||
COMMENT ON COLUMN education_content_node.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN education_content_node.updater IS '更新者';
|
||||
COMMENT ON COLUMN education_content_node.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN education_content_node.deleted IS '是否删除';
|
||||
CREATE INDEX idx_tenant ON education_content_node (tenant_id);
|
||||
CREATE INDEX idx_entry_parent ON education_content_node (entry_id, parent_id);
|
||||
CREATE INDEX idx_tenant_entry ON education_content_node (tenant_id, entry_id);
|
||||
|
||||
-- =============================================
|
||||
-- 8. 题集表
|
||||
-- =============================================
|
||||
CREATE TABLE education_question_collection (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
entry_id BIGINT DEFAULT NULL,
|
||||
node_id BIGINT DEFAULT NULL,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
title VARCHAR(200) DEFAULT NULL,
|
||||
collection_type VARCHAR(50) NOT NULL,
|
||||
question_count INT NOT NULL DEFAULT 0,
|
||||
duration_minutes INT DEFAULT NULL,
|
||||
access_rules JSONB DEFAULT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
metadata JSONB DEFAULT NULL,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
COMMENT ON TABLE education_question_collection IS '教育-题集';
|
||||
COMMENT ON COLUMN education_question_collection.id IS '主键';
|
||||
COMMENT ON COLUMN education_question_collection.tenant_id IS '租户编号';
|
||||
COMMENT ON COLUMN education_question_collection.entry_id IS '内容入口 ID';
|
||||
COMMENT ON COLUMN education_question_collection.node_id IS '内容节点 ID';
|
||||
COMMENT ON COLUMN education_question_collection.name IS '题集名称';
|
||||
COMMENT ON COLUMN education_question_collection.title IS '题集标题';
|
||||
COMMENT ON COLUMN education_question_collection.collection_type IS '题集类型';
|
||||
COMMENT ON COLUMN education_question_collection.question_count IS '题目数量';
|
||||
COMMENT ON COLUMN education_question_collection.duration_minutes IS '建议作答时长(分钟)';
|
||||
COMMENT ON COLUMN education_question_collection.access_rules IS '访问规则 JSON';
|
||||
COMMENT ON COLUMN education_question_collection.is_active IS '是否启用';
|
||||
COMMENT ON COLUMN education_question_collection.sort_order IS '显示排序值';
|
||||
COMMENT ON COLUMN education_question_collection.metadata IS '扩展元数据';
|
||||
COMMENT ON COLUMN education_question_collection.creator IS '创建者';
|
||||
COMMENT ON COLUMN education_question_collection.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN education_question_collection.updater IS '更新者';
|
||||
COMMENT ON COLUMN education_question_collection.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN education_question_collection.deleted IS '是否删除';
|
||||
CREATE INDEX idx_tenant ON education_question_collection (tenant_id);
|
||||
CREATE INDEX idx_tenant_entry ON education_question_collection (tenant_id, entry_id);
|
||||
CREATE INDEX idx_tenant_node ON education_question_collection (tenant_id, node_id);
|
||||
|
||||
-- =============================================
|
||||
-- 9. 题目表(核心表)
|
||||
-- =============================================
|
||||
-- Indexes:
|
||||
-- idx_tenant_published — 查询已发布题目(主查询路径)
|
||||
-- idx_tenant_collection — 按题集筛选
|
||||
-- idx_tenant_subject — 按科目筛选
|
||||
-- idx_tenant_node — 按内容节点筛选
|
||||
-- idx_tenant_type_diff — 按题型和难度筛选
|
||||
-- Security note: correct_answer 和 explanation 列存在但需在 service 层过滤;
|
||||
-- 学生端 DTO 绝不得包含这两列。
|
||||
-- Options JSONB: 存储 label, content, isCorrect, order — 完整选项数据。
|
||||
-- Service 层在学生端返回前剥离 isCorrect。
|
||||
CREATE TABLE education_question (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
content_version INT NOT NULL DEFAULT 1,
|
||||
stem TEXT NOT NULL,
|
||||
type VARCHAR(32) NOT NULL,
|
||||
type_label VARCHAR(50) DEFAULT NULL,
|
||||
difficulty VARCHAR(32) DEFAULT NULL,
|
||||
question_content JSONB DEFAULT NULL,
|
||||
options JSONB NOT NULL,
|
||||
correct_answer VARCHAR(500) DEFAULT NULL,
|
||||
explanation TEXT DEFAULT NULL,
|
||||
analysis TEXT DEFAULT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'PUBLISHED',
|
||||
is_published BOOLEAN NOT NULL DEFAULT true,
|
||||
collection_id BIGINT DEFAULT NULL,
|
||||
subject_id BIGINT DEFAULT NULL,
|
||||
node_id BIGINT DEFAULT NULL,
|
||||
tags JSONB DEFAULT NULL,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
metadata JSONB DEFAULT NULL,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
COMMENT ON TABLE education_question IS '教育-题目';
|
||||
COMMENT ON COLUMN education_question.id IS '主键';
|
||||
COMMENT ON COLUMN education_question.tenant_id IS '租户编号';
|
||||
COMMENT ON COLUMN education_question.content_version IS '内容版本号(递增,保证历史报告稳定性)';
|
||||
COMMENT ON COLUMN education_question.stem IS '题干';
|
||||
COMMENT ON COLUMN education_question.type IS '题型';
|
||||
COMMENT ON COLUMN education_question.type_label IS '题型显示名称';
|
||||
COMMENT ON COLUMN education_question.difficulty IS '难度';
|
||||
COMMENT ON COLUMN education_question.question_content IS '题干结构化内容(兼容复杂题型)';
|
||||
COMMENT ON COLUMN education_question.options IS '选项 JSONB [{label, content, isCorrect, order}]';
|
||||
COMMENT ON COLUMN education_question.correct_answer IS '正确答案(敏感字段,学生端不可返回)';
|
||||
COMMENT ON COLUMN education_question.explanation IS '答案解析(敏感字段,学生端不可返回)';
|
||||
COMMENT ON COLUMN education_question.analysis IS '深度解析(敏感字段)';
|
||||
COMMENT ON COLUMN education_question.status IS '题目状态:PUBLISHED-已发布, HIDDEN-已隐藏, DRAFT-草稿, INACTIVE-停用';
|
||||
COMMENT ON COLUMN education_question.is_published IS '是否已发布';
|
||||
COMMENT ON COLUMN education_question.collection_id IS '所属题集 ID';
|
||||
COMMENT ON COLUMN education_question.subject_id IS '所属科目 ID';
|
||||
COMMENT ON COLUMN education_question.node_id IS '所属内容节点 ID';
|
||||
COMMENT ON COLUMN education_question.tags IS '标签 JSONB 数组';
|
||||
COMMENT ON COLUMN education_question.sort_order IS '显示排序值';
|
||||
COMMENT ON COLUMN education_question.metadata IS '扩展元数据';
|
||||
COMMENT ON COLUMN education_question.creator IS '创建者';
|
||||
COMMENT ON COLUMN education_question.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN education_question.updater IS '更新者';
|
||||
COMMENT ON COLUMN education_question.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN education_question.deleted IS '是否删除';
|
||||
CREATE INDEX idx_tenant ON education_question (tenant_id);
|
||||
CREATE INDEX idx_tenant_published ON education_question (tenant_id, is_published, status);
|
||||
CREATE INDEX idx_tenant_collection ON education_question (tenant_id, collection_id);
|
||||
CREATE INDEX idx_tenant_subject ON education_question (tenant_id, subject_id);
|
||||
CREATE INDEX idx_tenant_node ON education_question (tenant_id, node_id);
|
||||
CREATE INDEX idx_tenant_type_diff ON education_question (tenant_id, type, difficulty);
|
||||
|
||||
-- =============================================
|
||||
-- 10. 练习蓝图表
|
||||
-- =============================================
|
||||
CREATE TABLE education_practice_blueprint (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
mode VARCHAR(50) NOT NULL,
|
||||
entry_id BIGINT DEFAULT NULL,
|
||||
node_id BIGINT DEFAULT NULL,
|
||||
collection_id BIGINT DEFAULT NULL,
|
||||
question_limit INT DEFAULT NULL,
|
||||
duration_minutes INT DEFAULT NULL,
|
||||
eligible_count INT NOT NULL DEFAULT 0,
|
||||
total_count INT NOT NULL DEFAULT 0,
|
||||
available_types JSONB DEFAULT NULL,
|
||||
available_difficulties JSONB DEFAULT NULL,
|
||||
min_questions INT NOT NULL DEFAULT 1,
|
||||
max_questions INT NOT NULL DEFAULT 200,
|
||||
suggested_count INT NOT NULL DEFAULT 20,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
COMMENT ON TABLE education_practice_blueprint IS '教育-练习蓝图';
|
||||
COMMENT ON COLUMN education_practice_blueprint.id IS '主键';
|
||||
COMMENT ON COLUMN education_practice_blueprint.tenant_id IS '租户编号';
|
||||
COMMENT ON COLUMN education_practice_blueprint.mode IS '练习模式';
|
||||
COMMENT ON COLUMN education_practice_blueprint.entry_id IS '内容入口 ID';
|
||||
COMMENT ON COLUMN education_practice_blueprint.node_id IS '内容节点 ID';
|
||||
COMMENT ON COLUMN education_practice_blueprint.collection_id IS '题集 ID';
|
||||
COMMENT ON COLUMN education_practice_blueprint.question_limit IS '题目数量限制';
|
||||
COMMENT ON COLUMN education_practice_blueprint.duration_minutes IS '建议作答时长(分钟)';
|
||||
COMMENT ON COLUMN education_practice_blueprint.eligible_count IS '符合条件的题目数';
|
||||
COMMENT ON COLUMN education_practice_blueprint.total_count IS '题库总数';
|
||||
COMMENT ON COLUMN education_practice_blueprint.available_types IS '可用题型列表 JSONB';
|
||||
COMMENT ON COLUMN education_practice_blueprint.available_difficulties IS '可用难度列表 JSONB';
|
||||
COMMENT ON COLUMN education_practice_blueprint.min_questions IS '最少题目数';
|
||||
COMMENT ON COLUMN education_practice_blueprint.max_questions IS '最多题目数';
|
||||
COMMENT ON COLUMN education_practice_blueprint.suggested_count IS '建议题目数';
|
||||
COMMENT ON COLUMN education_practice_blueprint.creator IS '创建者';
|
||||
COMMENT ON COLUMN education_practice_blueprint.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN education_practice_blueprint.updater IS '更新者';
|
||||
COMMENT ON COLUMN education_practice_blueprint.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN education_practice_blueprint.deleted IS '是否删除';
|
||||
CREATE INDEX idx_tenant ON education_practice_blueprint (tenant_id);
|
||||
CREATE INDEX idx_tenant_collection ON education_practice_blueprint (tenant_id, collection_id);
|
||||
|
||||
-- =============================================
|
||||
-- 11. 题集-题目关联表(多对多)
|
||||
-- =============================================
|
||||
CREATE TABLE education_question_collection_question (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
collection_id BIGINT NOT NULL,
|
||||
question_id BIGINT NOT NULL,
|
||||
sort_order INT NOT NULL DEFAULT 0
|
||||
);
|
||||
COMMENT ON TABLE education_question_collection_question IS '教育-题集题目关联';
|
||||
COMMENT ON COLUMN education_question_collection_question.id IS '主键';
|
||||
COMMENT ON COLUMN education_question_collection_question.collection_id IS '题集 ID';
|
||||
COMMENT ON COLUMN education_question_collection_question.question_id IS '题目 ID';
|
||||
COMMENT ON COLUMN education_question_collection_question.sort_order IS '排序值';
|
||||
CREATE UNIQUE INDEX uk_collection_question ON education_question_collection_question (collection_id, question_id);
|
||||
CREATE INDEX idx_question ON education_question_collection_question (question_id);
|
||||
|
||||
-- =============================================
|
||||
-- Post-migration verification queries
|
||||
-- =============================================
|
||||
-- Verify all tables exist:
|
||||
-- SELECT table_name FROM information_schema.tables
|
||||
-- WHERE table_catalog = current_database()
|
||||
-- AND table_name LIKE 'education_%'
|
||||
-- ORDER BY table_name;
|
||||
-- Verify indexes (sample):
|
||||
-- -- PostgreSQL equivalent of SHOW INDEX:
|
||||
-- SELECT * FROM pg_indexes WHERE tablename = 'education_question' AND indexname IN ('idx_tenant_published', 'idx_tenant_collection');
|
||||
-- SELECT * FROM pg_indexes WHERE tablename = 'education_content_node' AND indexname = 'idx_entry_parent';
|
||||
-- Verify no orphan data (should be 0 after fresh migration for catalog tables):
|
||||
-- SELECT 'region' AS tbl, COUNT(*) FROM education_region
|
||||
-- UNION ALL SELECT 'school', COUNT(*) FROM education_school
|
||||
-- UNION ALL SELECT 'major', COUNT(*) FROM education_major
|
||||
-- UNION ALL SELECT 'subject', COUNT(*) FROM education_subject
|
||||
-- UNION ALL SELECT 'category', COUNT(*) FROM education_category
|
||||
-- UNION ALL SELECT 'content_entry', COUNT(*) FROM education_content_entry
|
||||
-- UNION ALL SELECT 'content_node', COUNT(*) FROM education_content_node
|
||||
-- UNION ALL SELECT 'question_collection', COUNT(*) FROM education_question_collection
|
||||
-- UNION ALL SELECT 'question', COUNT(*) FROM education_question
|
||||
-- UNION ALL SELECT 'practice_blueprint', COUNT(*) FROM education_practice_blueprint
|
||||
-- UNION ALL SELECT 'collection_question', COUNT(*) FROM education_question_collection_question;
|
||||
@@ -1,13 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块 — 统一幂等表 回滚
|
||||
-- Migration: 009 回滚
|
||||
-- =============================================
|
||||
|
||||
DROP TABLE IF EXISTS education_idempotency;
|
||||
|
||||
-- Recreate the original split tables from 003 + 004 migrations if needed:
|
||||
-- (restore from backup or re-run 003 and 004 DDL scripts)
|
||||
|
||||
-- Remove review_fingerprint column
|
||||
ALTER TABLE education_practice_session
|
||||
DROP COLUMN IF EXISTS review_fingerprint;
|
||||
@@ -1,103 +0,0 @@
|
||||
-- =============================================
|
||||
-- Education 模块 — 统一幂等表 DDL (PostgreSQL)
|
||||
-- Ticket: 合并 Answer + Submit 幂等表为统一 education_idempotency
|
||||
-- Migration: 009
|
||||
-- Prerequisites: 002-education-practice-session.sql
|
||||
-- =============================================
|
||||
|
||||
-- =============================================
|
||||
-- Preconditions
|
||||
-- =============================================
|
||||
-- Verify table does not already exist:
|
||||
-- SELECT COUNT(*) FROM information_schema.tables
|
||||
-- WHERE table_catalog = current_database()
|
||||
-- AND table_name = 'education_idempotency';
|
||||
-- Result MUST be 0 before executing this migration.
|
||||
--
|
||||
-- NOTE: This table replaces education_answer_idempotency and education_submit_idempotency.
|
||||
-- Those tables should be dropped after this migration is verified:
|
||||
-- DROP TABLE IF EXISTS education_answer_idempotency;
|
||||
-- DROP TABLE IF EXISTS education_submit_idempotency;
|
||||
|
||||
-- =============================================
|
||||
-- 统一幂等表
|
||||
-- =============================================
|
||||
-- Purpose: Unified idempotency store for all education write operations.
|
||||
-- operation field distinguishes: SUBMIT_ANSWER vs SUBMIT_SESSION.
|
||||
-- Same (tenant, user, operation, idempotency_key) + same request_hash → replay original response.
|
||||
-- Same key + different request_hash → conflict.
|
||||
-- PostgreSQL ON CONFLICT DO NOTHING resolves concurrent same-key inserts.
|
||||
--
|
||||
-- Indexes:
|
||||
-- uk_idempotency — per-tenant, per-actor, per-operation uniqueness for idempotency key.
|
||||
-- Used by: IdempotencyStoreMapper.insertIgnore() ON CONFLICT resolution.
|
||||
-- idx_tenant_session — covers lookup by session for audit/debug.
|
||||
--
|
||||
-- Fields specific to SUBMIT_ANSWER: question_id, selected_answer (nullable for SUBMIT_SESSION).
|
||||
-- Fields specific to SUBMIT_SESSION: report_id (nullable for SUBMIT_ANSWER).
|
||||
-- response_json: stores serialized response for replay after timeout/retry.
|
||||
-- request_hash: SHA-256 of canonical payload for content-based dedup.
|
||||
CREATE TABLE education_idempotency (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
operation VARCHAR(32) NOT NULL,
|
||||
idempotency_key VARCHAR(64) NOT NULL,
|
||||
request_hash VARCHAR(64) NOT NULL,
|
||||
session_id BIGINT NOT NULL,
|
||||
question_id VARCHAR(64) DEFAULT NULL,
|
||||
selected_answer TEXT DEFAULT NULL,
|
||||
report_id BIGINT DEFAULT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'ACCEPTED',
|
||||
response_json TEXT DEFAULT NULL,
|
||||
business_payload JSONB DEFAULT NULL,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
COMMENT ON TABLE education_idempotency IS '教育-统一幂等记录(合并 answer + submit)';
|
||||
COMMENT ON COLUMN education_idempotency.id IS '主键';
|
||||
COMMENT ON COLUMN education_idempotency.tenant_id IS '租户编号';
|
||||
COMMENT ON COLUMN education_idempotency.user_id IS '用户编号';
|
||||
COMMENT ON COLUMN education_idempotency.operation IS '操作类型:SUBMIT_ANSWER-答题, SUBMIT_SESSION-交卷';
|
||||
COMMENT ON COLUMN education_idempotency.idempotency_key IS '客户端幂等键(UUID)';
|
||||
COMMENT ON COLUMN education_idempotency.request_hash IS '请求载荷 SHA-256 哈希';
|
||||
COMMENT ON COLUMN education_idempotency.session_id IS '会话 ID';
|
||||
COMMENT ON COLUMN education_idempotency.question_id IS '题目 ID(SUBMIT_ANSWER)';
|
||||
COMMENT ON COLUMN education_idempotency.selected_answer IS '学生已选答案(SUBMIT_ANSWER)';
|
||||
COMMENT ON COLUMN education_idempotency.report_id IS '关联报告 ID(SUBMIT_SESSION)';
|
||||
COMMENT ON COLUMN education_idempotency.status IS '状态:ACCEPTED-已接受, CONFLICT-冲突';
|
||||
COMMENT ON COLUMN education_idempotency.response_json IS '首次成功响应 JSON(用于重试重放)';
|
||||
COMMENT ON COLUMN education_idempotency.business_payload IS '业务扩展载荷(JSONB)';
|
||||
COMMENT ON COLUMN education_idempotency.creator IS '创建者';
|
||||
COMMENT ON COLUMN education_idempotency.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN education_idempotency.updater IS '更新者';
|
||||
COMMENT ON COLUMN education_idempotency.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN education_idempotency.deleted IS '是否删除';
|
||||
|
||||
CREATE UNIQUE INDEX uk_idempotency ON education_idempotency (tenant_id, user_id, operation, idempotency_key);
|
||||
CREATE INDEX idx_idempotency_tenant_session ON education_idempotency (tenant_id, session_id);
|
||||
|
||||
-- =============================================
|
||||
-- PracticeSessionDO: add review_fingerprint column
|
||||
-- =============================================
|
||||
-- Purpose: Store canonical fingerprint for wrong-question review session idempotency.
|
||||
-- SHA-256 of comma-joined sorted wrong_question IDs for deterministic comparison.
|
||||
ALTER TABLE education_practice_session
|
||||
ADD COLUMN review_fingerprint VARCHAR(64) DEFAULT NULL;
|
||||
|
||||
COMMENT ON COLUMN education_practice_session.review_fingerprint IS '错题复习指纹(SHA-256),用于幂等创建';
|
||||
|
||||
-- =============================================
|
||||
-- Post-migration verification queries
|
||||
-- =============================================
|
||||
-- Verify new table exists:
|
||||
-- \d education_idempotency
|
||||
-- Verify unique index:
|
||||
-- SELECT * FROM pg_indexes WHERE tablename = 'education_idempotency';
|
||||
-- Verify column added:
|
||||
-- SELECT column_name, data_type FROM information_schema.columns
|
||||
-- WHERE table_name = 'education_practice_session' AND column_name = 'review_fingerprint';
|
||||
@@ -0,0 +1,13 @@
|
||||
-- Document the state-machine and protected snapshot fields introduced by the
|
||||
-- already-published Practice migrations. This is metadata-only and forward-safe.
|
||||
|
||||
COMMENT ON COLUMN education_practice_question.correct_answer IS '练习题目快照标准答案;仅允许交卷后的受控报告链路读取';
|
||||
COMMENT ON COLUMN education_practice_question.explanation IS '练习题目快照解析;练习进行中不得返回客户端';
|
||||
COMMENT ON COLUMN education_practice_report_detail.correct_answer IS '不可变报告明细中的标准答案快照';
|
||||
COMMENT ON COLUMN education_practice_report_detail.explanation IS '不可变报告明细中的题目解析快照';
|
||||
COMMENT ON COLUMN education_wrong_question.latest_correct_answer IS '最近一次错题投影使用的标准答案快照';
|
||||
COMMENT ON COLUMN education_wrong_question.latest_explanation IS '最近一次错题投影使用的解析快照';
|
||||
COMMENT ON COLUMN education_idempotency.operation IS '幂等操作类型,例如 SUBMIT_ANSWER 或 SUBMIT_SESSION';
|
||||
COMMENT ON COLUMN education_idempotency.status IS '幂等处理状态;交卷使用 PROCESSING 与 COMPLETED 状态机';
|
||||
COMMENT ON COLUMN education_idempotency.response_json IS '完成后的不可变响应快照,用于同键重放';
|
||||
COMMENT ON COLUMN education_idempotency.business_payload IS '业务处理快照;用于恢复和一致性校验';
|
||||
@@ -94,7 +94,7 @@ class EducationFlywayMigrationIntegrationTest {
|
||||
|
||||
assertThat(queryStrings(schema,
|
||||
"SELECT COALESCE(version, 'BASELINE') FROM flyway_schema_history ORDER BY installed_rank"))
|
||||
.containsExactly("4009", "4010", "4020", "4030", "4040");
|
||||
.containsExactly("4009", "4010", "4020", "4030", "4040", "4050");
|
||||
assertThat(queryLong(schema,
|
||||
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = current_schema() " +
|
||||
"AND table_name = 'education_idempotency'"))
|
||||
@@ -156,7 +156,7 @@ class EducationFlywayMigrationIntegrationTest {
|
||||
|
||||
assertThat(queryStrings(schema,
|
||||
"SELECT version FROM flyway_schema_history WHERE success = TRUE ORDER BY installed_rank"))
|
||||
.containsExactly("4010", "4020", "4030", "4040");
|
||||
.containsExactly("4010", "4020", "4030", "4040", "4050");
|
||||
assertThat(queryStrings(schema,
|
||||
"SELECT table_name FROM information_schema.tables " +
|
||||
"WHERE table_schema = current_schema() AND table_name IN (" +
|
||||
|
||||
Reference in New Issue
Block a user