@specpow/framework 0.5.23 → 0.5.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,150 @@
1
+ # MES 项目公共工具方法参考
2
+
3
+ > 本文件为 AI 代码生成时的参考手册,列出项目中可直接复用的公共工具类和方法。
4
+ > 生成代码时应优先使用这些方法,避免重复实现。
5
+
6
+ ## 后端工具类
7
+
8
+ ### ResponseWrapper (com.twsz.mom.core.common.ResponseWrapper)
9
+ - `ofSuccess(T data)` → 返回 code=200 + data
10
+ - `ofFail(String message)` → 返回 code=600 + 错误消息
11
+ - `defaultSuccess()` → 返回 code=200 + data=null(无数据成功)
12
+ - `ofStatus(IHttpStatus status)` → 返回自定义状态码
13
+ - `ofException(BaseException e)` → 从异常构造响应
14
+ - `ofFlat(int code, String message, T data)` → 不截断消息的响应
15
+ - `ofMessage(String message)` → 返回 code=200 + 消息(无数据)
16
+ - `isSuccess()` → 判断是否成功(code==200 或 200<=code<300)
17
+
18
+ ### AIHttpStatus (com.twsz.mom.core.common.AIHttpStatus)
19
+ - 枚举类,实现 IHttpStatus 接口
20
+ - 新增 code 规则: 取枚举中已有 code 最大值 + 1(maxCode + 1),禁止硬编码数字
21
+ - 枚举命名: 中文提示的缩写,如 "类型不能为空" → `TYPE_NOT_EMPTY`
22
+ - 定义格式: `TYPE_NOT_EMPTY(10000000X, "类型不能为空")`
23
+ - code 格式: 100000001, 100000002, ... 递增
24
+ - message 支持 `[{0}]` 占位符,通过 data 数组传参
25
+ - 使用方式: `throw new BaseException(AIHttpStatus.XXX.getCode(), AIHttpStatus.XXX.getMessage())`
26
+ - 带参数: `throw new BaseException(AIHttpStatus.XXX.getCode(), AIHttpStatus.XXX.getMessage(), param1)`
27
+ - 响应: `ResponseWrapper.ofStatus(AIHttpStatus.XXX, Arrays.asList("param1"))`
28
+
29
+ ### PageForm<T> (com.twsz.mom.core.common.PageForm)
30
+ - `getSize()` → 每页条数
31
+ - `getCurrent()` → 当前页码(1-based)
32
+ - `getCondition()` → 查询条件对象(泛型 T)
33
+ - `getRecords()` → 结果记录列表
34
+
35
+ ### BaseException (com.twsz.mom.core.exception.BaseException)
36
+ - `new BaseException(HttpStatus status)` → 从状态码构造
37
+ - `new BaseException(String message)` → 从消息构造
38
+ - `new BaseException(HttpStatus status, Object... args)` → 带参数格式化
39
+
40
+ ### ExcelUtil (com.twsz.mom.core.utils.ExcelUtil)
41
+ - `importExcel(InputStream is)` → `List<Map<String, Object>>`(原始导入)
42
+ - `importExcel(InputStream is, Class<T> clazz)` → `List<T>`(类型化导入)
43
+ - `export(HttpServletResponse resp, Class<T> clazz, Boolean isCsv, Collection<?> data)` → 标准导出
44
+ - `export(HttpServletResponse resp, Class<T> clazz, Boolean isCsv, Supplier<Collection<?>> supplier)` → 懒加载导出
45
+ - `responseJson(HttpServletResponse resp, ResponseWrapper<T> wrapper)` → 写 JSON 错误响应
46
+ - `exportException(HttpServletResponse resp, Throwable e)` → 写异常为 JSON
47
+
48
+ ### SecurityUtils (com.twsz.mom.core.utils.SecurityUtils)
49
+ - `getCurrentUsername()` → 获取当前登录用户名
50
+ - `getCurrentOrgId()` → 获取当前组织 ID(通常不需要调用,多租户插件自动处理)
51
+ - `getCurrentUserId()` → 获取当前用户 ID
52
+
53
+ ### IdUtil (cn.hutool.core.util.IdUtil)
54
+ - `fastSimpleUUID()` → 生成无横线 UUID(用于导出文件名等)
55
+
56
+ ### EasyExcel (com.alibaba.excel.EasyExcel)
57
+ - `EasyExcel.write(OutputStream, Class<T>).sheet("sheet").doWrite(List)` → 简单导出
58
+ - `EasyExcel.read(InputStream, Class<T>, ReadListener).sheet().doRead()` → 监听式导入
59
+
60
+ ### BaseExcelExportTemplate (com.twsz.mom.core.web.excel.BaseExcelExportTemplate)
61
+ - 抽象类:`getTotal()` + `getData(Integer start, Integer size)` → 分批导出模板
62
+ - 默认 batch size: 1000, sheet max: 1,048,576
63
+
64
+ ### ViewModel 常量 (com.twsz.mom.ds.model.ViewModel)
65
+ - `ViewModel.OPT_ADD = "1"` — 新增标记
66
+ - `ViewModel.OPT_MODIFY = "2"` — 修改标记
67
+ - `ViewModel.OPT_DELETE = "3"` — 删除标记
68
+ - `ViewModel.OPT_NOT_MODIFY_DATE = "0"` — 不修改日期标记
69
+
70
+ ### MetaHandler (com.twsz.mom.ds.handler.MetaHandler)
71
+ - 自动填充: INSERT 时自动设置 createdBy/createdDate/lastUpdatedBy/lastUpdatedDate
72
+ - 自动填充: UPDATE 时自动设置 lastUpdatedBy/lastUpdatedDate
73
+ - 通过 @TableField(fill = FieldFill.INSERT/INSERT_UPDATE) 触发
74
+
75
+ ---
76
+
77
+ ## 前端工具方法
78
+
79
+ ### 请求封装 (@/libs/request)
80
+ - `axios.request({ url, method, data })` → 统一请求方法
81
+ - 响应拦截器: 自动提取 data.data,错误自动 toast 提示
82
+
83
+ ### 导出工具 (@/api/file)
84
+ - `exportExcel(url, data)` → 下载 Excel(Blob → 创建下载链接)
85
+ - `downloadFile(url, data)` → 通用文件下载
86
+ - `uploadFile(url, data)` → 文件上传
87
+
88
+ ### indexPage Mixin (_c/table-form/index-mixin)
89
+ - `this.add(formData)` → 打开新增表单
90
+ - `this.edit(row)` → 打开编辑表单(传入行数据)
91
+ - `this.view(row)` → 打开查看表单(只读模式)
92
+ - `this.refresh()` → 刷新列表(重新搜索第 2 页)
93
+ - `this.triggerBack(showIndexPage)` → 切换列表/表单视图
94
+ - `this.handleSuccess(item, back)` → 表单提交成功回调
95
+ - `this.showIndexPage` → 控制列表/表单显示状态
96
+ - `this.formData` → 传递给表单的数据
97
+ - `this.readonly` → 只读模式标记
98
+ - `this.dataSelections` → 当前选中行
99
+
100
+ ### BaseMixin (@/mixin)
101
+ - `this.startLoading()` → 开始 loading
102
+ - `this.finishLoading()` → 结束 loading
103
+ - `this.asyncLoading(Promise)` → 自动管理 loading 的异步调用
104
+ - `this.loading` → loading 状态
105
+
106
+ ### tw-table Option 常用配置
107
+ - `option.tableName` → 唯一标识(必须)
108
+ - `option.searchForm` → 搜索条件双向绑定
109
+ - `option.fixedTableHeight` → 自动填充剩余高度
110
+ - `option.add.enable` → 启用新增按钮
111
+ - `option.delete.enable` → 启用删除(支持函数动态判断)
112
+ - `option.delete.before(rows)` → 删除前校验(return false 阻止)
113
+ - `option.edit.enable` → 启用编辑(支持函数动态判断)
114
+ - `option.view.enable` → 启用查看
115
+ - `option.search.query` → 查询 API 方法
116
+ - `option.export.enable` → 启用导出
117
+ - `option.export.method` → 导出 API 方法
118
+
119
+ ### 国际化 ($t)
120
+ - `$t('key||中文默认值')` → 带默认值的国际化
121
+ - `$t('table.entityName.fieldName||字段中文名')` → 表格字段
122
+ - `$t('operate||操作')`, `$t('add||新增')`, `$t('edit||编辑')`
123
+ - `$t('submit||提交')`, `$t('submit.success||提交成功')`
124
+ - `$t('error.input.notEmpty||输入不能为空')`
125
+
126
+ ---
127
+
128
+ ## MyBatis-Plus 拦截器链
129
+
130
+ 按执行顺序:
131
+ 1. `TenantLineInnerInterceptor` — 多租户 org_id 自动注入
132
+ 2. `AdvanceQueryInterceptor` — 高级查询(Oracle)
133
+ 3. `PaginationInnerInterceptor` — 分页(overflow 已启用)
134
+ 4. `OptimisticLockerInnerInterceptor` — 乐观锁(@Version 字段)
135
+ 5. `BatchUpdateInterceptor` — 批量更新
136
+ 6. `AuditLogInterceptor` — 审计日志
137
+
138
+ ---
139
+
140
+ ## 全局注册组件
141
+
142
+ 通过 `Vue.component()` 全局注册,可直接使用:
143
+ - `tw-table` — 核心表格组件
144
+ - `master-sub` — 主子表布局(垂直分栏)
145
+ - `tw-card` — 卡片容器
146
+ - `RemoteSelect` — 远程下拉选择
147
+ - `RemoteCheckbox` — 远程复选框
148
+ - `RemoteRadio` — 远程单选框
149
+ - `TableSelect` — 表格弹窗选择(放大镜)
150
+ - `SelectModal` — 选择弹窗
@@ -0,0 +1,204 @@
1
+ # MES PRD to Code — 项目规范规则集
2
+
3
+ > 本文件由 specpow 框架的 `extractRulesFromSchemaFiles` 自动加载。
4
+ > 所有以 `-` 开头的行会被提取为约束规则,通过 `{{rules}}` 注入到 AI instruction 中。
5
+ > 来源: 项目实际代码(BaseModel/ViewModel/ServiceImpl/Mapper XML/Vue 组件)+ DATABASE.md + JAVA.md + TESTING.md
6
+
7
+ ## 数据库 DDL 规范
8
+
9
+ - 表名格式: `{模块简称}_{功能描述}`,小写下划线,模块前缀: BD/MM/WIP/FG/PM/QM/EM/MCC/AGV/RPT
10
+ - 子表命名: `{主表名}_DETAIL`,配置表: `{功能}_CONFIG`,历史表: `{功能}_H_T`
11
+ - 主键字段: `id NUMBER(19) NOT NULL`,使用雪花算法生成(IdType.ASSIGN_ID),禁止 Oracle SEQUENCE
12
+ - 多租户字段: `org_id NUMBER(19) NOT NULL`,仅在 DDL 中定义,不在 Entity 中声明(TenantLineInnerInterceptor 自动处理)
13
+ - 审计字段: `created_by NVARCHAR2(64)`, `created_date DATE DEFAULT SYSDATE`, `last_updated_by NVARCHAR2(64)`, `last_updated_date DATE DEFAULT SYSDATE`
14
+ - 字符串类型统一使用 NVARCHAR2,禁止使用 VARCHAR2 或 VARCHAR
15
+ - 数值类型: 主键用 NUMBER(19),状态/标志用 NUMBER(2) 或 NUMBER(1),数量/金额用 NUMBER(18,4)
16
+ - 日期类型: DATE,默认值 DEFAULT SYSDATE
17
+ - 扩展字段: ATTRIBUTE1~ATTRIBUTE5 NVARCHAR2(150)(预留扩展,按需添加)
18
+ - 表名字段名全部小写下划线命名(snake_case),Java 中自动映射为驼峰(camelCase)
19
+
20
+ ## 数据库索引规范
21
+
22
+ - 主键索引命名: `{tableName}_PK`
23
+ - 普通索引命名: `{tableName}_N1` ~ `{tableName}_N5`
24
+ - 唯一索引命名: `{tableName}_U1` ~ `{tableName}_U5`
25
+ - 唯一索引必须以 org_id 为首列: `(org_id, 业务字段1, 业务字段2...)`
26
+ - 数据表空间: TABLESPACE MES_DATA_{WORKS|WMS|COM}(生产/仓储/通用)
27
+ - 索引表空间: USING INDEX TABLESPACE MES_IDX_{WORKS|WMS|COM}
28
+ - 主键约束: `CONSTRAINT {表名}_PK PRIMARY KEY (id) USING INDEX TABLESPACE {索引表空间}`
29
+ - 每张表和每个字段必须有 COMMENT ON 注释
30
+ - 禁止使用 SELECT *,必须明确列出字段名
31
+
32
+ ## 后端 Entity 规范
33
+
34
+ - 必须继承 `com.twsz.mom.ds.model.BaseModel`(不是 com.twsz.mom.common.base.BaseModel)
35
+ - BaseModel 继承链: ViewModel → TimeModel → BaseModel,已提供 id/审计字段/查询字段
36
+ - BaseModel 已提供: id(Long, ASSIGN_ID, ToStringSerializer), createdBy, createdDate, lastUpdatedBy, lastUpdatedDate
37
+ - ViewModel 已提供: opt("1"=新增,"2"=修改,"3"=删除), columns, orderBy, fields, csv, rowNum — Entity 中禁止重复声明
38
+ - @EqualsAndHashCode(callSuper = true)(不是 false)
39
+ - @JsonInclude(JsonInclude.Include.NON_NULL)
40
+ - @TableName("表名")
41
+ - id 字段必须在 Entity 中显式声明: @TableId(type = IdType.ASSIGN_ID) + @JsonSerialize(using = ToStringSerializer.class)(覆盖 BaseModel 声明以确保序列化)
42
+ - org_id 禁止在 Entity 中声明(TenantLineInnerInterceptor 自动注入)
43
+ - 每个业务字段使用 @ExcelProperty("中文名") 注解(支持导入导出)
44
+ - 非数据库字段使用 @TableField(exist = false) + @ExcelIgnore
45
+ - Long 类型字段暴露给前端时必须加 @JsonSerialize(using = ToStringSerializer.class)(防止 JS 精度丢失)
46
+ - 日期输出格式化: @JsonFormat(pattern = "yyyy/MM/dd HH:mm:ss") 或 @JsonSerialize(using = LocalDateTimeSerializer.class)
47
+ - 使用 Lombok: @Data, @EqualsAndHashCode, @Builder, @NoArgsConstructor, @AllArgsConstructor
48
+ - 编号字段使用 @AutoGenCode 注解(自动生成编号)
49
+ - 主子表的子表集合字段: @TableField(exist = false) private List<DetailEntity> details
50
+
51
+ ## 后端 Controller 规范
52
+
53
+ - @RestController + @RequestMapping("/api/v1/entity-kebab-case")(含版本号 /api/v1/)
54
+ - 使用 @Autowired 或 @Resource 字段注入(项目惯例,非构造器注入)
55
+ - 统一返回 ResponseWrapper<T>(com.twsz.mom.core.common.ResponseWrapper)
56
+ - 标准方法: search(POST) / save(POST) / delete(DELETE) / list(POST) / export(POST)
57
+ - save 方法: id==null 时 insert,id!=null 时 update(Controller 层判断或 Service 层 saveModel)
58
+ - Controller 只做参数校验和结果封装,禁止写业务逻辑
59
+ - 禁止在 Controller 中 try-catch(全局异常处理器 @ControllerAdvice 统一处理)
60
+ - delete 接口: @DeleteMapping,接收 `Long[] ids`
61
+ - export 接口: 使用 ExcelUtil.export() 或 EasyExcel 直接写入 HttpServletResponse
62
+ - import 接口: 接收 MultipartFile,使用 ExcelUtil.importExcel() 或 EasyExcel.read()
63
+
64
+ ## 后端 Service 规范
65
+
66
+ - 接口继承 IService<T>,实现类继承 ServiceImpl<Mapper, Entity> 并实现接口
67
+ - @Slf4j + @Service 注解
68
+ - saveModel 方法: @Transactional(rollbackFor = Exception.class),id==null 调 insert,否则检查乐观锁后调 update
69
+ - 主子表保存模式: 先保存主表,再遍历明细列表按 opt 标记处理(1=新增, 2=修改, 3=删除)
70
+ - saveOrUpdateOrDelete 模式: 按 ViewModel.OPT_ADD/MODIFY/DELETE 分组,批量 saveBatch/updateBatchById/delete
71
+ - 乐观锁: 编辑时查库比对 lastUpdatedDate(withNano(0) 去掉纳秒),不匹配则 throw new BaseException
72
+ - 异常抛出: `throw new BaseException(AIHttpStatus.XXX.getCode(), AIHttpStatus.XXX.getMessage())`
73
+ - 带参数占位: `throw new BaseException(AIHttpStatus.XXX.getCode(), AIHttpStatus.XXX.getMessage(), param1, param2)`
74
+ - AIHttpStatus 新增 code 规则: 取 AIHttpStatus 枚举中已有 code 最大值 + 1(maxCode + 1),禁止硬编码数字
75
+ - AIHttpStatus 枚举命名: 中文提示的缩写,如 "类型不能为空" → TYPE_NOT_EMPTY
76
+ - 禁止在 Service 中手动设置 orgId(多租户插件自动注入)
77
+ - 禁止在循环中执行数据库查询(N+1 问题),必须批量查询后内存处理
78
+ - 新增/修改操作后必须清除相关 Redis 缓存
79
+ - 使用 LambdaQueryWrapper / LambdaUpdateWrapper,禁止硬编码字符串字段名
80
+ - @Transactional 写在 ServiceImpl 方法上,不写在接口上
81
+
82
+ ## 后端 Mapper XML 规范
83
+
84
+ - 使用 <sql id="EntityColumns"> 定义字段列表(t.前缀),支持 entity.columns 动态列选择,默认 t.*
85
+ - 使用 <sql id="EntityWhere"> 定义条件(动态 <if> 判断非空),字符串检查 `!= null and != ''`,数值/日期只检查 `!= null`
86
+ - 使用 <sql id="EntityJoins"> 定义关联(LEFT JOIN),默认为空
87
+ - 分页查询: IPage<Entity> pageSearch(Page<Entity> p, @Param("entity") Entity entity)
88
+ - list 查询: 使用 Oracle rownum 限制行数,默认 10000,通过 entity.rowNum 可配置
89
+ - Oracle 字符串拼接用 || 而非 CONCAT: `LIKE '%' || #{entity.field} || '%'`
90
+ - CDATA 用于特殊字符: `<![CDATA[<=]]>` for rownum <=
91
+ - 排序默认: ORDER BY t.id DESC,支持 entity.orderBy 动态排序
92
+ - XML 中不包含 org_id 列和 org_id 条件(多租户插件自动注入)
93
+ - 逻辑删除过滤: t.is_deleted = 0 或 t.is_delete = 0(如适用)
94
+ - 表别名统一使用 t
95
+ - XML 文件位置: src/main/resources/mapper/{submodule}/EntityMapper.xml
96
+
97
+ ## 后端公共类引用
98
+
99
+ - ResponseWrapper: ofSuccess(data) / ofFail(msg) / defaultSuccess() / ofStatus(AIHttpStatus) — com.twsz.mom.core.common.ResponseWrapper
100
+ - AIHttpStatus: 枚举类,code 从已有最大值+1 递增(maxCode+1),禁止硬编码 — com.twsz.mom.core.common.AIHttpStatus
101
+ - BaseException: `new BaseException(AIHttpStatus.XXX.getCode(), AIHttpStatus.XXX.getMessage())` — com.twsz.mom.core.exception.BaseException
102
+ - PageForm<T>: size(页大小), current(页码,1-based), condition(查询条件对象) — com.twsz.mom.core.common.PageForm
103
+ - SecurityUtils: 获取当前用户信息 — com.twsz.mom.core.utils.SecurityUtils
104
+ - ExcelUtil: importExcel/export — com.twsz.mom.core.utils.ExcelUtil
105
+ - EasyExcel: com.alibaba.excel.EasyExcel
106
+ - ViewModel.OPT_ADD = "1", OPT_MODIFY = "2", OPT_DELETE = "3", OPT_NOT_MODIFY_DATE = "0"
107
+ - IdUtil: cn.hutool.core.util.IdUtil(fastSimpleUUID 等)
108
+
109
+ ## 前端 API 规范
110
+
111
+ - 使用 `import axios from '@/libs/request'`
112
+ - 导出使用 `import { exportExcel } from '@/api/file'` 或 `import { exportExcel } from '../../file'`
113
+ - API 文件放在 `src/api/{domain}/{entity}.js`
114
+ - 标准方法: save, deleteById, list, search, exportData
115
+ - save: POST, data 为实体对象
116
+ - deleteById: DELETE, data 为数组 `Array.isArray(id) ? id : [id]`
117
+ - search: POST, data 为 PageForm 对象
118
+ - list: POST, data 为查询条件对象
119
+ - exportData: 调用 exportExcel(url, data)
120
+ - URL 前缀根据模块不同有多种模式: `/mm/{entity}`, `/api/{entity}`, `/mm/api/{sub}/{entity}`
121
+ - 导入接口: `{ headers: { 'Content-Type': 'multipart/form-data' } }`
122
+
123
+ ## 前端列表页规范(index.vue)
124
+
125
+ - 使用 indexPage mixin: `import { indexPage } from '_c/table-form/index-mixin'`
126
+ - 使用 tw-table 组件(非 search-table)
127
+ - option 配置: tableName(唯一标识), searchForm, fixedTableHeight, add/delete/edit/view/search/export
128
+ - option.add: `{ enable: true, method: this.add }`
129
+ - option.delete: `{ enable: true|fn(row), method: Api.deleteById, before: fn(rows) }`
130
+ - option.edit: `{ enable: true|fn(row), method: this.edit }`
131
+ - option.view: `{ enable: true, method: this.view }`
132
+ - option.search: `{ query: Api.search }`
133
+ - option.export: `{ enable: true, method: Api.exportData }`
134
+ - 表格列顺序: selection → 业务列 → 审计列(createdBy/createdDate/lastUpdatedBy/lastUpdatedDate) → operate
135
+ - 搜索条件: `<Form ref="conditionForm" class="search-condition-form" label-colon @keyup.enter.native="refresh" @submit.native.prevent>`
136
+ - 国际化: $t('key||中文'),如 $t('table.entityName.fieldName||字段中文名')
137
+ - 操作列: `<template slot-scope="{ row }" slot="operate">` 内含编辑/查看/删除按钮
138
+ - 权限控制: v-if="$auth('permission:key')"
139
+ - 列表/表单切换: showIndexPage 控制,triggerBack(false) 切换到表单,triggerBack(true) 回到列表
140
+
141
+ ## 前端表单页规范(form.vue)
142
+
143
+ - 使用 BaseMixin: `import BaseMixin from '@/mixin'`,mixins: [BaseMixin]
144
+ - props: readonly(Boolean), form(Object)
145
+ - data: `{ default: {}, mform: {} }`,created 中 `this.mform = Object.assign({}, this.default, this.form)`
146
+ - 使用 master-sub 组件(主子表布局):
147
+ - `<template #master-header-toolbar>` — 提交按钮
148
+ - `<template #master-form>` — 主表 Form
149
+ - `<template #sub-table>` — 子表组件
150
+ - 表单提交: doSubmit() → this.$refs.mform.validate() → Api.save(this.mform) → $emit('on-success', data)
151
+ - 按钮状态: `<Button type="primary" :loading="loading" @click="doSubmit">`
152
+ - 只读模式: `<Form :disabled="readonly">`, 按钮 `v-if="!readonly"`
153
+ - 主子表提交: `this.asyncLoading(this.$refs.SubTableForm.save())` 委托子表 save()
154
+
155
+ ## 前端子表规范(table-form.vue)
156
+
157
+ - 使用 tw-table: `<tw-table ref="searchTable" :option="option" :mainItem="mainItem" :columns="tableColumns" :tableRules="rules" :readonly="readonly">`
158
+ - option.save: 指向主表 Api.save(saveTable() 时使用)
159
+ - option.search.beforeQuery: `this.searchBefore` — 注入 mainId 过滤
160
+ - searchBefore(searchParam): `Object.assign(searchParam.condition, { mainId: this.mainItem.id }); return Boolean(this.mainItem.id)`
161
+ - 行内编辑 slot: `<template slot-scope="{ row, column, index, tableData, editIndex }" slot="input">`
162
+ - 编辑条件: `v-if="index === editIndex && !(column.disabled && Boolean(row.id))"`
163
+ - 必填列: `className: 'table-column-required'`
164
+ - 只读列: `readonly: true, disabled: 'true'`(审计字段等)
165
+ - tableRules: 定义每列校验规则 `{ fieldName: { required: true, type: 'string', message: '...' } }`
166
+ - save() 方法: 自定义校验 → `this.$refs.searchTable.saveTable()`
167
+ - opt 标记: 新增行 opt='1',修改行 opt='2',删除行 opt='3'
168
+
169
+ ## 测试规范
170
+
171
+ - 测试类名: 原类名 + MockTest(如 PcCadDataServiceImpl → PcCadDataServiceImplMockTest)
172
+ - 框架: JUnit 5 + Mockito(@ExtendWith(MockitoExtension.class),不是 @RunWith)
173
+ - @Mock 模拟依赖(Mapper、其他 Service)
174
+ - @InjectMocks 注入被测类
175
+ - setUp 中使用 ReflectionTestUtils.setField(entityService, "baseMapper", entityMapper) 注入 baseMapper
176
+ - 使用 @Nested 类按方法分组测试
177
+ - 使用 @DisplayName 描述测试场景(中文)
178
+ - 测试方法命名: testMethod_scenario() 或 should_预期行为_when_条件()
179
+ - 每个测试方法只测试一个行为
180
+ - Mock 外部依赖(Feign 调用、Redis、数据库),不依赖外部环境
181
+ - Arrange-Act-Assert 模式: when() → 调用方法 → assertEquals/verify
182
+ - 执行命令: mvn test -pl <module> -Dtest=ClassNameMockTest
183
+
184
+ ## Java 通用编码规范
185
+
186
+ - 必须使用 Java 8 语法,禁止 Java 9+ 特性(var、模块化、HttpClient)
187
+ - 禁止 System.out.println,必须使用 @Slf4j + log.info/warn/error
188
+ - 禁止硬编码密码、密钥、连接串(使用 Jasypt 加密或 Nacos 配置中心)
189
+ - 禁止硬编码 IP 地址和端口
190
+ - 方法体不超过 80 行,超过必须拆分子方法
191
+ - 所有公共方法必须添加 Javadoc 注释
192
+ - 使用 4 空格缩进,禁止 Tab
193
+ - 禁止在 for 循环中发起数据库查询或远程调用
194
+
195
+ ## Vue 通用编码规范
196
+
197
+ - 必须使用 Vue 2 Options API(data/methods/computed/watch)
198
+ - 禁止使用 Vue 3 Composition API(setup/ref/reactive)
199
+ - 组件 name 属性必须与文件名一致(PascalCase)
200
+ - props 必须定义 type 和 default
201
+ - 禁止直接修改 props,必须通过 $emit 通知父组件
202
+ - 统一使用 View Design 4.x 组件,禁止引入其他 UI 库
203
+ - 全局状态使用 Vuex,禁止在组件中直接修改 state
204
+ - 组件库: tw-table, master-sub, tw-card, RemoteSelect, TableSelect, RemoteCheckbox 等全局注册组件
@@ -1,6 +1,6 @@
1
1
  # MES PRD to Code Schema
2
2
  # 功能文档 → 技术文档 → 完整代码
3
- # 工件流: parse → confirm-scope → tech-design → tasks → code
3
+ # 工件流: parse → confirm-scope → tech-design → tasks → test-gen → code
4
4
 
5
5
  name: mes-prd-to-code
6
6
  description: |
@@ -147,6 +147,28 @@ artifacts:
147
147
  - 如果有外部接口 → 确认接口鉴权方式
148
148
  - 如果涉及已有模块修改 → 确认修改范围和回归测试
149
149
 
150
+ ### Q7: 表名前缀
151
+ - 提问:"表名的模块前缀是什么?例如 pcba_(PCBA)、mm_(仓储)、wip_(生产)、bd_(基础数据)"
152
+ - 根据模块推断建议值,等待用户确认
153
+ - 所有新建表将使用此前缀,如 `{prefix}_cad_data`
154
+
155
+ ### Q8: 表空间选择
156
+ - 提问:"数据表归属哪个业务模块?(决定表空间)"
157
+ - 表空间映射规则:
158
+ - 生产/车间数据 → MES_DATA_WORKS(数据)/ MES_IDX_WORKS(索引)
159
+ - 仓储/物流数据 → MES_DATA_WMS(数据)/ MES_IDX_WMS(索引)
160
+ - 基础/通用数据 → MES_DATA_COM(数据)/ MES_IDX_COM(索引)
161
+ - 默认根据模块推断,等待用户确认
162
+
163
+ ### Q9: 索引策略
164
+ - 根据解析结果中的业务规则,分析需要建索引的字段,提出建议:
165
+ - 唯一性校验字段 → 唯一索引(U1, U2...)
166
+ - 高频搜索字段 → 普通索引(N1, N2...)
167
+ - 外键关联字段 → 普通索引
168
+ - **唯一索引规范**:必须以 `org_id` 作为首列,组成联合唯一索引,格式:`(org_id, 业务字段1, 业务字段2...)`
169
+ - 向用户展示建议的索引清单,等待确认或调整
170
+ - 这是持续交互过程,用户可以增减索引字段
171
+
150
172
  ## 确认结果记录
151
173
  将所有确认结果写入 confirm-scope 记录中,作为技术文档的输入。
152
174
  格式:
@@ -157,9 +179,12 @@ artifacts:
157
179
  - 后端包路径: com.twsz.mom.xxx
158
180
  - 前端 API 目录: src/api/xxx/
159
181
  - 前端页面目录: src/views/xxx/
182
+ - 表名前缀: {prefix}_
183
+ - 表空间: MES_DATA_xxx(数据)/ MES_IDX_xxx(索引)
160
184
  - 编号生成: @AutoGenCode / 无
161
185
  - 状态流转: 有(审核/弃审) / 无
162
186
  - 已有代码影响: [列表]
187
+ - 索引策略: [索引清单]
163
188
  ```
164
189
 
165
190
  - id: tech-design
@@ -173,15 +198,29 @@ artifacts:
173
198
  ## 文档结构
174
199
 
175
200
  ### 1. 数据库设计
201
+ - 读取 confirm-scope 中的表名前缀、表空间、索引策略
176
202
  - 将功能文档的 DDL 适配为 Oracle 语法
177
203
  - 适配规则:
178
204
  - INT/BIGINT AUTO_INCREMENT → NUMBER(19) 雪花算法
179
- - VARCHAR(n) → VARCHAR2(n)
205
+ - VARCHAR(n) → NVARCHAR2(n)
180
206
  - DATETIME → DATE
181
207
  - CURRENT_TIMESTAMP → SYSDATE
182
208
  - 审计字段统一为: created_by, created_date, last_updated_by, last_updated_date
183
209
  - 去除 is_deleted 逻辑删除字段(MES 使用物理删除)
184
- - 包含完整 DDL + 索引 + 注释
210
+ - 每张表必须包含 org_id NUMBER(19) NOT NULL 字段(多租户字段)
211
+ **重要**: org_id 仅在 DDL 中定义(用于唯一索引组合列),不在 Entity 中声明。
212
+ MyBatis-Plus TenantLineInnerInterceptor 已配置多租户,INSERT/SELECT 自动注入 org_id,业务代码无需显式处理。
213
+ {{rules}}
214
+ - 包含完整 DDL + 索引 + 注释 + 表空间
215
+ - **表名**: 使用 confirm-scope 确认的模块前缀,如 `{prefix}_cad_data`
216
+ - **索引命名规范**(严格遵守):
217
+ - 主键索引: `{tableName}_PK`
218
+ - 普通索引: `{tableName}_N1`, `{tableName}_N2`, ... `{tableName}_N5`
219
+ - 唯一索引: `{tableName}_U1`, `{tableName}_U2`, ... `{tableName}_U5`
220
+ - 唯一索引必须以 `org_id` 为首列: `UNIQUE INDEX xxx_U1(tableName, org_id, 业务字段1, ...)`
221
+ - **表空间**(使用 confirm-scope 确认的值):
222
+ - 数据段: TABLESPACE {data_tablespace}
223
+ - 索引段: USING INDEX TABLESPACE {index_tablespace}
185
224
  - 标注建表方式: dbx MCP(优先)/ SQL 脚本
186
225
 
187
226
  ### 2. API 接口设计
@@ -204,12 +243,20 @@ artifacts:
204
243
  对每个 Entity 给出:
205
244
 
206
245
  #### Entity(完整代码模板)
207
- - 继承 BaseModel
208
- - @TableName, @Data, @EqualsAndHashCode(callSuper=false)
209
- - @TableId(type=IdType.ASSIGN_ID) + @JsonSerialize(ToStringSerializer)
246
+ - 继承 BaseModel(com.twsz.mom.ds.model.BaseModel,不是 com.twsz.mom.common.base.BaseModel)
247
+ - @TableName, @Data, @EqualsAndHashCode(callSuper=true)
248
+ - @JsonInclude(JsonInclude.Include.NON_NULL)
249
+ - @TableId(type=IdType.ASSIGN_ID) + @JsonSerialize(ToStringSerializer) — id 字段必须在 Entity 中显式声明
250
+ - BaseModel 已提供审计字段(createdBy/Date, lastUpdatedBy/Date),Entity 中禁止重复声明
251
+ - ViewModel 已提供 opt 字段,Entity 中禁止重复声明
252
+ - org_id 禁止在 Entity 中声明(多租户 TenantLineInnerInterceptor 自动处理)
253
+ - @ExcelProperty("中文名")(每个业务字段,支持导入导出)
254
+ - 非数据库字段: @TableField(exist = false) + @ExcelIgnore
255
+ - Long 类型字段: @JsonSerialize(using = ToStringSerializer.class)(防止 JS 精度丢失)
210
256
  - @AutoGenCode(如有编号字段)
211
- - @ExcelProperty(每个业务字段)
257
+ - 主子表: @TableField(exist = false) private List<DetailEntity> details
212
258
  - 给出完整 Java 代码
259
+ {{rules}}
213
260
 
214
261
  #### Mapper(完整代码模板)
215
262
  - 接口继承 BaseMapper<T>
@@ -227,9 +274,13 @@ artifacts:
227
274
  - 不要求完整实现代码
228
275
 
229
276
  #### Controller(完整代码模板)
230
- - @RestController + @RequestMapping
231
- - 每个方法的签名和参数
277
+ - @RestController + @RequestMapping("/api/v1/entity-kebab-case")(含版本号 /api/v1/)
278
+ - @Autowired 或 @Resource 字段注入(项目惯例,非构造器注入)
279
+ - 统一返回 ResponseWrapper<T>(com.twsz.mom.core.common.ResponseWrapper)
280
+ - 标准方法: search(POST) / save(POST) / delete(DELETE) / list(POST) / export(POST)
281
+ - Controller 只做参数校验和结果封装,禁止写业务逻辑
232
282
  - 给出完整代码
283
+ {{rules}}
233
284
 
234
285
  ### 4. 前端代码结构
235
286
  对每个页面给出:
@@ -303,9 +354,13 @@ artifacts:
303
354
  8. API 文件(完整代码)
304
355
  9. 列表页 index.vue(完整代码)
305
356
  10. 表单页 form.vue(完整代码)
357
+ 11. 子表页 table-form.vue(如有主子表,完整代码)
358
+
359
+ ### 测试任务(每个 ServiceImpl 一组)
360
+ 12. ServiceImpl 单元测试(XxxServiceImplMockTest.java)
306
361
 
307
362
  ### 配置任务
308
- 11. 菜单注册 SQL
363
+ 13. 菜单注册 SQL
309
364
 
310
365
  ### 如有外部接口
311
366
  12. 外部接口 Controller(独立路径)
@@ -329,14 +384,52 @@ artifacts:
329
384
 
330
385
  output: tasks.md
331
386
 
387
+ - id: test-gen
388
+ name: 测试代码生成
389
+ description: 基于 ServiceImpl 生成单元测试代码
390
+ requires: [tasks]
391
+ template: test-gen.md
392
+ instruction: |
393
+ 基于 tasks.md 中的 ServiceImpl 代码,生成对应的单元测试。
394
+
395
+ 生成前请先读取本 schema 目录下的 references.md,了解可用的公共工具类和方法,测试中 Mock 的依赖方法签名需与 references.md 一致。
396
+
397
+ ## 测试规范
398
+ - 测试类名 = 原类名 + MockTest(如 PcCadDataServiceImpl → PcCadDataServiceImplMockTest)
399
+ - 框架: JUnit 5 + Mockito
400
+ - @ExtendWith(MockitoExtension.class)
401
+ - @Mock 模拟依赖(Mapper、其他Service)
402
+ - @InjectMocks 注入被测类
403
+ - setUp 中使用 ReflectionTestUtils.setField 注入 baseMapper
404
+ - 使用 @Nested 类按方法分组,@DisplayName 描述场景
405
+ - 测试方法命名: should_预期行为_when_条件()
406
+
407
+ ## 必须覆盖的场景
408
+ - saveModel: 新增成功、编辑成功、乐观锁冲突
409
+ - deleteByIds: 批量删除+级联
410
+ - search: 分页查询
411
+ - list: 列表查询
412
+ - importExcel/uploadExcel: 逐行校验、跳过统计(如有)
413
+
414
+ ## 文件路径
415
+ 测试文件放在: src/test/java/{与ServiceImpl相同的包路径}/
416
+
417
+ ## 输出
418
+ - 每个 ServiceImpl 对应一个测试文件
419
+ - 包含完整可编译的测试代码
420
+ {{rules}}
421
+ output: test-gen.md
422
+
332
423
  - id: code
333
424
  name: 代码生成
334
425
  description: 按任务清单生成完整代码文件
335
- requires: [tasks]
426
+ requires: [tasks, test-gen]
336
427
  template: code-generation.md
337
428
  instruction: |
338
429
  按任务清单逐个生成代码文件。
339
430
 
431
+ 生成前请先读取本 schema 目录下的 references.md,了解可用的公共工具类和方法,优先复用已有方法,避免重复实现。
432
+
340
433
  ## 执行方式
341
434
  1. 按任务依赖顺序执行
342
435
  2. 每个任务:
@@ -348,14 +441,18 @@ artifacts:
348
441
  ## 代码规范检查
349
442
  生成的代码必须符合以下 MES 规范:
350
443
  - Java 8 语法,不使用 Java 9+ 特性
351
- - Lombok 注解(@Data, @Builder 等)
444
+ - Lombok 注解(@Data, @EqualsAndHashCode(callSuper=true) 等)
352
445
  - SLF4J 日志(@Slf4j),不用 System.out.println
353
446
  - LambdaQueryWrapper,不用字符串字段名
354
447
  - ResponseWrapper<T> 统一响应
355
- - BaseModel 继承
448
+ - BaseModel 继承(com.twsz.mom.ds.model.BaseModel)
449
+ - Entity: callSuper=true, 不重复声明 id/orgId/opt/审计字段
450
+ - Controller: @Autowired, @RequestMapping("/api/v1/kebab-case")
451
+ - 数据库字符串字段使用 NVARCHAR2
356
452
  - Vue 2 Options API
357
453
  - View Design 4 组件
358
- - indexPage mixin / master-sub 组件
454
+ - indexPage mixin / master-sub / tw-table 组件
455
+ {{rules}}
359
456
 
360
457
  ## 输出
361
458
  - 所有代码文件已写入磁盘
@@ -14,9 +14,13 @@
14
14
  | 后端包路径 | com.twsz.mom.{module}.{sub} |
15
15
  | 前端 API 目录 | src/api/{domain}/ |
16
16
  | 前端页面目录 | src/views/{domain}/{entity}/ |
17
+ | 表名前缀 | {prefix}_ |
18
+ | 数据表空间 | MES_DATA_xxx |
19
+ | 索引表空间 | MES_IDX_xxx |
17
20
  | 编号生成 | @AutoGenCode / 无 |
18
21
  | 状态流转 | 有 / 无 |
19
22
  | 已有代码影响 | <列表或无> |
23
+ | 索引策略 | <索引清单> |
20
24
 
21
25
  ---
22
26
 
@@ -49,3 +53,15 @@
49
53
  ### Q6: 特殊技术决策
50
54
 
51
55
  <对话内容>
56
+
57
+ ### Q7: 表名前缀
58
+
59
+ <对话内容 — 确认表的模块前缀,如 pc_、wm_、pd_ 等>
60
+
61
+ ### Q8: 表空间选择
62
+
63
+ <对话内容 — 根据业务模块确认数据表空间和索引表空间>
64
+
65
+ ### Q9: 索引策略
66
+
67
+ <对话内容 — 确认每张表需要哪些索引,字段列表,索引类型>
@@ -15,10 +15,25 @@
15
15
  - **操作**:
16
16
  ```sql
17
17
  CREATE TABLE <table_name> (
18
- ...
19
- );
18
+ id NUMBER(19) NOT NULL,
19
+ org_id NUMBER(19) NOT NULL,
20
+ -- 业务字段
21
+ <column_name> <oracle_type> <constraint>,
22
+ -- 审计字段
23
+ created_by VARCHAR2(64),
24
+ created_date DATE DEFAULT SYSDATE,
25
+ last_updated_by VARCHAR2(64),
26
+ last_updated_date DATE DEFAULT SYSDATE,
27
+ CONSTRAINT <tableName>_PK PRIMARY KEY (id) USING INDEX TABLESPACE <index_tablespace>
28
+ ) TABLESPACE <data_tablespace>;
29
+
30
+ -- 唯一索引(org_id 联合,命名: 表名_U1..U5)
31
+ CREATE UNIQUE INDEX <tableName>_U1 ON <table_name>(org_id, <业务字段>) TABLESPACE <index_tablespace>;
32
+
33
+ -- 普通索引(命名: 表名_N1..N5)
34
+ CREATE INDEX <tableName>_N1 ON <table_name>(<字段>) TABLESPACE <index_tablespace>;
20
35
  ```
21
- - **验证**: 表创建成功,字段和约束正确
36
+ - **验证**: 表创建成功,索引创建成功,表空间正确
22
37
  - **依赖**: 无
23
38
 
24
39
  ---
@@ -24,13 +24,42 @@
24
24
 
25
25
  ## 1. 数据库设计
26
26
 
27
- ### 1.1 新建表
27
+ ### 1.1 索引设计规范
28
+
29
+ **索引命名规范**(严格遵守):
30
+
31
+ | 索引类型 | 命名格式 | 示例 | 说明 |
32
+ |----------|----------|------|------|
33
+ | 主键索引 | `{tableName}_PK` | `pc_cad_data_PK` | 主键约束自动命名 |
34
+ | 普通索引 | `{tableName}_N1` ~ `N5` | `pc_cad_data_N1` | 按顺序编号,最多 5 个 |
35
+ | 唯一索引 | `{tableName}_U1` ~ `U5` | `pc_cad_data_U1` | 按顺序编号,最多 5 个 |
36
+
37
+ **唯一索引规范**:
38
+ - 必须以 `org_id` 作为首列,组成联合唯一索引
39
+ - 格式: `(org_id, 业务字段1, 业务字段2, ...)`
40
+ - 示例: `CREATE UNIQUE INDEX pc_cad_data_U1 ON pc_cad_data(org_id, material_code) TABLESPACE MES_IDX_COM;`
41
+
42
+ **表空间定义**:
43
+
44
+ | 表空间名称 | 用途 | 说明 |
45
+ |------------|------|------|
46
+ | MES_DATA_WORKS | 生产数据表空间 | 存储基础数据表、车间数据表 |
47
+ | MES_DATA_WMS | 仓库数据表空间 | 存储仓库表 |
48
+ | MES_DATA_COM | 基础数据表空间 | 存储基础数据表 |
49
+ | MES_IDX_WORKS | 生产索引表空间 | 存储基础数据、主车间索引 |
50
+ | MES_IDX_WMS | 仓库索引表空间 | 存储仓库表索引 |
51
+ | MES_IDX_COM | 基础数据表索引空间 | 存储基础数据索引 |
52
+
53
+ > 使用 confirm-scope 中确认的表空间。数据表使用 `MES_DATA_xxx`,索引使用 `MES_IDX_xxx`。
54
+
55
+ ### 1.2 新建表
28
56
 
29
57
  #### 表: <table_name>
30
58
 
31
59
  ```sql
32
60
  CREATE TABLE <table_name> (
33
61
  id NUMBER(19) NOT NULL,
62
+ org_id NUMBER(19) NOT NULL,
34
63
  -- 业务字段
35
64
  <column_name> <oracle_type> <constraint>,
36
65
  -- 审计字段
@@ -38,24 +67,40 @@ CREATE TABLE <table_name> (
38
67
  created_date DATE DEFAULT SYSDATE,
39
68
  last_updated_by VARCHAR2(64),
40
69
  last_updated_date DATE DEFAULT SYSDATE,
41
- CONSTRAINT pk_<table_name> PRIMARY KEY (id)
42
- );
70
+ CONSTRAINT <tableName>_PK PRIMARY KEY (id) USING INDEX TABLESPACE <index_tablespace>
71
+ ) TABLESPACE <data_tablespace>;
43
72
 
44
73
  COMMENT ON TABLE <table_name> IS '<表中文名>';
45
74
  COMMENT ON COLUMN <table_name>.id IS '主键';
75
+ COMMENT ON COLUMN <table_name>.org_id IS '组织ID';
46
76
  -- 其他字段注释
47
77
  ```
48
78
 
49
79
  **索引**:
50
80
 
51
- | 索引名 | 字段 | 类型 | 说明 |
52
- |--------|------|------|------|
53
- | | | UNIQUE / NORMAL | |
81
+ | 索引名 | 字段 | 类型 | 表空间 | 说明 |
82
+ |--------|------|------|--------|------|
83
+ | `<tableName>_U1` | org_id, <业务字段> | UNIQUE | `<index_tablespace>` | 联合唯一(org_id 首列) |
84
+ | `<tableName>_N1` | <字段> | NORMAL | `<index_tablespace>` | 普通索引 |
85
+
86
+ **索引创建语句**:
87
+
88
+ ```sql
89
+ -- 唯一索引(org_id 联合)
90
+ CREATE UNIQUE INDEX <tableName>_U1 ON <table_name>(org_id, <业务字段>) TABLESPACE <index_tablespace>;
91
+
92
+ -- 普通索引
93
+ CREATE INDEX <tableName>_N1 ON <table_name>(<字段>) TABLESPACE <index_tablespace>;
94
+ ```
54
95
 
55
96
  **建表方式**:
56
97
  - [ ] dbx MCP 工具(优先)
57
98
  - [ ] SQL 脚本执行
58
99
 
100
+ > **org_id 处理说明**: org_id 仅在 DDL 中定义(用于唯一索引组合列),**不在 Entity 中声明**。
101
+ > MyBatis-Plus `TenantLineInnerInterceptor` 已配置多租户,INSERT/SELECT/UPDATE/DELETE 自动注入 `org_id` 条件。
102
+ > 业务代码(Entity、Mapper XML、Service、Controller)无需显式处理 org_id。
103
+
59
104
  ### 1.2 修改表(如有)
60
105
 
61
106
  #### 表: <existing_table_name>
@@ -0,0 +1,77 @@
1
+ # 测试生成报告: {{changeName}}
2
+
3
+ > 输入: 任务清单 (tasks.md) 中的 ServiceImpl 代码
4
+ > 生成日期: {{date}}
5
+
6
+ ---
7
+
8
+ ## 1. 测试文件清单
9
+
10
+ | # | 测试文件 | 被测类 | Mock 依赖 | 测试方法数 |
11
+ |:-:|----------|--------|-----------|:----------:|
12
+ | 1 | `src/test/java/{package}/XxxServiceImplMockTest.java` | XxxServiceImpl | Mapper, DetailService | N |
13
+
14
+ ---
15
+
16
+ ## 2. 测试详情
17
+
18
+ ### 2.1 XxxServiceImplMockTest
19
+
20
+ **被测类**: `com.twsz.mom.xxx.service.impl.XxxServiceImpl`
21
+
22
+ **Mock 依赖**:
23
+ | # | 字段 | 类型 | 说明 |
24
+ |:-:|------|------|------|
25
+ | 1 | xxxMapper | XxxMapper | 主表 Mapper |
26
+ | 2 | detailService | XxxDetailService | 子表 Service |
27
+
28
+ **测试用例**:
29
+
30
+ | # | @Nested 分组 | 方法名 | 场景 | 断言 |
31
+ |:-:|-------------|--------|------|------|
32
+ | 1 | saveModel方法测试 | should_insert_when_idIsNull | 新增成功 | verify(mapper).insert(any) |
33
+ | 2 | saveModel方法测试 | should_update_when_idNotNull | 编辑成功 | verify(mapper).updateById(any) |
34
+ | 3 | saveModel方法测试 | should_throw_when_optimisticLockFails | 乐观锁冲突 | assertThrows(BaseException) |
35
+ | 4 | deleteByIds方法测试 | should_deleteByIds_when_givenIds | 批量删除 | verify(mapper).deleteBatchIds(any) |
36
+ | 5 | search方法测试 | should_returnPage_when_search | 分页查询 | assertNotNull(result) |
37
+ | 6 | list方法测试 | should_returnList_when_list | 列表查询 | assertNotNull(result) |
38
+
39
+ **测试代码结构**:
40
+
41
+ ```java
42
+ @ExtendWith(MockitoExtension.class)
43
+ class XxxServiceImplMockTest {
44
+
45
+ @Mock
46
+ private XxxMapper xxxMapper;
47
+
48
+ @Mock
49
+ private XxxDetailService detailService;
50
+
51
+ @InjectMocks
52
+ private XxxServiceImpl xxxService;
53
+
54
+ @BeforeEach
55
+ void setUp() {
56
+ ReflectionTestUtils.setField(xxxService, "baseMapper", xxxMapper);
57
+ }
58
+
59
+ @Nested
60
+ @DisplayName("saveModel方法测试")
61
+ class SaveModelTests {
62
+ @Test
63
+ @DisplayName("新增成功")
64
+ void should_insert_when_idIsNull() { ... }
65
+ }
66
+ }
67
+ ```
68
+
69
+ ---
70
+
71
+ ## 3. 统计
72
+
73
+ | 维度 | 数量 |
74
+ |------|:----:|
75
+ | 测试文件数 | N |
76
+ | 测试方法总数 | N |
77
+ | Mock 依赖总数 | N |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specpow/framework",
3
- "version": "0.5.23",
3
+ "version": "0.5.24",
4
4
  "description": "Spec-Powered AI Development Framework - 融合 OpenSpec 规范驱动 + Superpowers 执行引擎的企业级 AI 辅助编程框架",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",