name: crud-by-controller-ylsw-bw
当用户要求在 business/water-meter 或表务相关模块中新增/补全接口时,优先对齐 com.tofly.wm.ledger 下既有代码(首选参考 ledger/meter,台账类参考 ledger/purchase、ledger/arrive、ledger/verify)。
language/java/pom-factory-skill.md;Maven 治理:rules/java-pom-governance.mdc。rules/java-crud-common.mdc。rules/java-tf-ylsw-backend.mdc(与 ledger 实际写法冲突时,以 ledger 存量风格为准,并在输出中注明差异)。com.tofly.wm)模块根包 com.tofly.wm,按业务分层;新建代码必须落在与职责匹配的包下,禁止全部堆在 ledger 或单一层级。
com.tofly.wm
├── cons/ # 模块常量(字典目录码、业务编码前缀等)
│ └── MeterConstant.java
├── ledger/ # 台账、出入库、只读汇总(少写或不写主数据)
│ ├── meter/ # 水表出入库 CRUD
│ │ ├── enums/ # 域内枚举(BoundTypeEnum、CompanyTypeEnum…)
│ │ └── app/ # 子场景(App 入库、领用等),*Controller/*Service/*Vo
│ ├── purchase/ # 采购台账(Vo + Query + Service,无独立 Entity 表)
│ ├── arrive/ # 到货台账
│ └── verify/ # 落地检台账
├── manage/ # 管理端业务写操作、主数据维护
│ ├── puachase/ # 采购(存量包名 puachase,新建子包沿用勿改拼写)
│ │ ├── apply/ # 采购申请
│ │ │ └── list/ # 申请明细子表
│ │ ├── plan/ # 采购计划
│ │ │ ├── arrive/ # 到货单
│ │ │ └── arriveList/ # 到货明细
│ │ └── claim/ # 领用
│ ├── brand/ # 厂家
│ ├── verify/ # 检定管理(含 giveback 等子包)
│ └── warehouse/
├── workorder/ # 工单
└── api/ # 对外/第三方接口(verify 等)
ledger/meter 为准)| 类型 | 命名 | 位置 |
|---|---|---|
| 接口 | {Domain}Controller |
域包根目录 |
| 服务接口/实现 | {Domain}Service / {Domain}ServiceImpl |
域包根目录;台账可无接口,仅 {Domain}LedgerService |
| 实体 | {Domain} |
域包根目录 |
| 查询 | {Domain}Query / {Domain}LedgerQuery |
域包根目录 |
| 展示 | {Domain}Vo / {Domain}LedgerVo |
域包根目录 |
| Mapper | {Domain}Mapper |
域包根目录 |
| 枚举 | *Enum |
{domain}/enums/ |
| App/专项 | App*、Claim* |
{domain}/app/ 等子包 |
controller/service/entity 再分子包(与 ledger 一致)。list、arriveList 等,类名与主表关联清晰。com.tofly.wm.cons.MeterConstant,禁止魔法字符串。TF_WM_*,业务表多为 _W;mapper.xml → src/main/resources/mapper/。| 场景 | 推荐包 |
|---|---|
| 分页/列表/导出台账、跨表只读汇总 | ledger.{domain} |
| 主表 CRUD、流程、审批、写入业务表 | manage.{业务域} |
| 移动端/专项入口 | ledger.{domain}.app 或 manage.*.app |
ResultResponse<T>(带泛型,禁止裸类型)。@RestController + @RequestMapping("/xxx") + @Api(tags = "...")。@RequiredArgsConstructor + @FieldDefaults(level = PRIVATE, makeFinal = true)(Controller 常见);Service 可用 @AllArgsConstructor 或 @Service 无接口实现类。| 能力 | 方法 | 路径 | 说明 |
|---|---|---|---|
| 分页 | GET | /page |
与 /list 共用 {Domain}Query(含 pageNum/pageSize) |
| 列表 | GET | /list |
与 /page 同一 Query、同一 Service 条件;不分页,返回 List |
| 详情 | GET | /{id} |
|
| 新增 | POST | / |
@RequestBody 实体 |
| 修改 | POST | /update |
本模块用 POST 而非 PUT |
| 删除 | DELETE | /deleteByIds |
ids 请求参数,逗号分隔主键;禁止单条 DELETE /{id} |
| 导出 | GET | /export |
返回 void,EasyPoiUtil.download(台账类) |
DELETE /{id}、removeById 对外接口;删除统一走 DELETE /deleteByIds(单条删除传 ids=1 即可)。@DeleteMapping("/{id}") 模板代码。编制或生成各 PRD-04 功能需求说明 §接口设计(研发)、公共接口说明 时:
page、list、{id}、POST /、update、deleteByIds),不得因 PRD 未写或业务暂未实现而省略。GET /page、GET /list(若适用)、GET /{id}(若适用)、GET /export;不写 POST/deleteByIds 时在文档中标注「台账只读,无写接口」。DELETE /{resource}/{id};删除统一写 DELETE /{resource}/deleteByIds。| 场景 | 用法 | 禁止/避免 |
|---|---|---|
| 字符串为空 | StrUtil.isNotBlank(s) / StrUtil.isBlank(s) |
手写 s != null && !s.isEmpty() |
| 集合为空 | CollUtil.isEmpty(list) / CollUtil.isNotEmpty(list) |
仅 list == null |
| 对象拷贝 | BeanUtil.copyProperties(...) |
大段手动 set(无业务差异时) |
| 对象是否为 null | Objects.nonNull(x) / Objects.isNull(x) |
对象用 == null 写在条件首参外且无一致性时 |
| 枚举入库条件 | Optional.ofNullable(enum).map(Enumerable::getKey).orElse(null) |
直接 .eq(field, enum) 未取 key |
| 范围时间 | .between(Objects.nonNull(q.getStartDate()), ...) |
未判空即 between |
cn.hutool.core.*(StrUtil、CollUtil、BeanUtil 等),与项目存量一致。Objects。PageQuery,Swagger @ApiModel / @ApiModelProperty。Long/String/枚举/LocalDate 等基础类型;OrganizationEntry、UserNameEntry、DictEntry 仅出现在 Entity/Vo,不出现在 Query(字典筛选可用字典 code 的 String)。Wrappers.<Entity>lambdaQuery(),条件首参遵循上表;枚举用 Enumerable.getKey()。list(query) 与分页共用 buildWrapper;page(query) 内部 PageHelper.startPage(query, () -> list(query))。deleteByIds(String ids) 解析逗号分隔主键后 removeByIds,需 @Transactional。MPJWrappers.lambdaJoin() + selectJoinList(见 purchase/verify 台账),不强行拆 XML。//出厂编码、//需求部门。MPJBaseService<Entity> / MPJBaseServiceImpl<Mapper, Entity>。MPJBaseMapper<Entity>;简单查询不写 XML。*Mapper.xml 编写,禁止 Mapper 注解 SQL。与
database-design.mdc的关系:公司通用规范写create_user/is_delete等;本仓库(ylsw-bw / Oracle 11)以com.tofly.entity.pojo.BaseEntity映射为准,逻辑删除列名为DELETED(NUMBER(1)),创建/更新人为NUMBER(19)。建表、实体、Skill 自检均以本节为准。
新建 TF_WM_* 主表、明细表时,在 CREATE TABLE 语句内与主键 ID、业务字段一并声明下列列(Oracle 11),不要单独用 ALTER TABLE ... ADD 事后补列。
CREATE TABLE TF_WM_XXX_W (
ID NUMBER(19) NOT NULL,
-- BaseEntity 公共列(主表/明细表均须包含)
CREATE_USER NUMBER(19),
CREATE_TIME TIMESTAMP,
UPDATE_USER NUMBER(19),
UPDATE_TIME TIMESTAMP,
DELETED NUMBER(1) DEFAULT 0,
CREATE_COMPANY_ID NUMBER(19),
CREATE_COMPANY_NAME VARCHAR2(200),
CREATE_DEPT_ID NUMBER(19),
CREATE_DEPT_NAME VARCHAR2(200),
CREATE_POST_ID NUMBER(19),
CREATE_POST_NAME VARCHAR2(200),
CREATE_USER_NAME VARCHAR2(100),
CUR_COMPANY_ID NUMBER(19),
CUR_DEPT_ID NUMBER(19),
-- 业务字段 …
CONSTRAINT PK_TF_WM_XXX_W PRIMARY KEY (ID)
);
COMMENT ON TABLE TF_WM_XXX_W IS '表说明';
COMMENT ON COLUMN TF_WM_XXX_W.ID IS '主键';
COMMENT ON COLUMN TF_WM_XXX_W.CREATE_USER IS '创建人';
COMMENT ON COLUMN TF_WM_XXX_W.CREATE_TIME IS '创建时间';
COMMENT ON COLUMN TF_WM_XXX_W.UPDATE_USER IS '更新人';
COMMENT ON COLUMN TF_WM_XXX_W.UPDATE_TIME IS '更新时间';
COMMENT ON COLUMN TF_WM_XXX_W.DELETED IS '删除状态:0未删除 1已删除';
COMMENT ON COLUMN TF_WM_XXX_W.CREATE_COMPANY_ID IS '创建公司ID';
COMMENT ON COLUMN TF_WM_XXX_W.CREATE_COMPANY_NAME IS '创建公司名称';
COMMENT ON COLUMN TF_WM_XXX_W.CREATE_DEPT_ID IS '创建部门ID';
COMMENT ON COLUMN TF_WM_XXX_W.CREATE_DEPT_NAME IS '创建部门名称';
COMMENT ON COLUMN TF_WM_XXX_W.CREATE_POST_ID IS '创建岗位ID';
COMMENT ON COLUMN TF_WM_XXX_W.CREATE_POST_NAME IS '创建岗位名称';
COMMENT ON COLUMN TF_WM_XXX_W.CREATE_USER_NAME IS '创建用户名称';
COMMENT ON COLUMN TF_WM_XXX_W.CUR_COMPANY_ID IS '当前公司ID';
COMMENT ON COLUMN TF_WM_XXX_W.CUR_DEPT_ID IS '当前部门ID';
-- 业务列 COMMENT 逐列补充
| 列名 | 类型 | 说明 |
|---|---|---|
| CREATE_USER | NUMBER(19) | 创建人 ID |
| CREATE_TIME | TIMESTAMP | 创建时间 |
| UPDATE_USER | NUMBER(19) | 更新人 ID |
| UPDATE_TIME | TIMESTAMP | 更新时间 |
| DELETED | NUMBER(1) | 逻辑删除:0 未删,1 已删(Java 侧见下节 deleted) |
| CREATE_COMPANY_ID / CREATE_COMPANY_NAME | 创建时公司 | |
| CREATE_DEPT_ID / CREATE_DEPT_NAME | 创建时部门 | |
| CREATE_POST_ID / CREATE_POST_NAME | 创建时岗位 | |
| CREATE_USER_NAME | VARCHAR2(100) | 创建人姓名 |
| CUR_COMPANY_ID / CUR_DEPT_ID | NUMBER(19) | 当前公司/部门(数据权限) |
填充由平台 MetaObjectHandler / 会话上下文自动写入,业务代码禁止在子类重复声明上述字段。
凡 关联表 ID 列(通常为 *_id,以及 BaseEntity 的 create_user/update_user),COMMENT ON COLUMN 须写明 业务含义 + 关联目标:
COMMENT ON COLUMN wssmet_meterlife_w.operator_id IS '操作人ID,关联tf_user表的id字段';
COMMENT ON COLUMN wssmet_meter_w.warehouse_id IS '仓库ID,关联wssmet_warehouse_w表的id字段';
COMMENT ON COLUMN wssmet_purapplylist_w.apply_id IS '采购申请ID,关联wssmet_purapply_w表的id字段';
| 关联类型 | 目标表示例 | 说明 |
|---|---|---|
| 平台用户 | tf_user.id |
UserNameEntry、操作人/执行人/签收人等 |
| 平台组织 | tf_org.id |
OrganizationEntry、部门/公司/管辖组织等 |
| 平台岗位 | tf_post.id |
BaseEntity create_post_id |
| 本模块业务表 | wssmet_{x2}_w.id |
水表、仓库、申请主表、任务主表、明细父表等 |
| workflow/外部 | 注释单独说明 | 如 process_instance_id,不写伪外键 |
ref_table_name + ref_table_id)须说明与 ref_table_name 配合解析。business/water-meter/doc/design/数据库设计-V1.0.md 须与本节一致。凡 字典(DictEntry + @DictDirectory)或 业务枚举(Java *Enum implements Enumerable)落库列(多为 VARCHAR2 的 *_code、*_status),COMMENT ON COLUMN 须标明类型:
-- 字典:目录码与 MeterConstant 一致
COMMENT ON COLUMN wssmet_meter_w.caliber_code IS '口径编号,字典类型,目录码METER_DIAMETER';
COMMENT ON COLUMN wssmet_meter_w.meter_type_code IS '水表类型编号,字典类型,目录码METER_TYPE';
-- 枚举:写 Java 枚举类名
COMMENT ON COLUMN wssmet_purapply_w.approve_status IS '审批状态,枚举类型,ApproveStatusEnum';
COMMENT ON COLUMN wssmet_purplan_w.arrive_status_code IS '到货状态编号,枚举类型,ArriveStatusEnum';
| 类型 | COMMENT 必含 | Java 侧 |
|---|---|---|
| 字典 | 字典类型,目录码{目录码} |
DictEntry + @DictDirectory(MeterConstant.*) |
| 枚举 | 枚举类型,{EnumClassName} |
{domain}/enums/*Enum implements Enumerable |
VARCHAR2(apply_code、meter_code、task_code 等)为普通字段,不要求字典/枚举标注。数据库设计文档须含 §2.1 非普通字段清单(按表):仅列出字典列与枚举列,用表格标注 字典 / 枚举、说明(字段业务含义)、引用(目录码或 Java 枚举类名);字典项与枚举取值明细须单独维护于设计目录 字典与枚举说明-V1.0.md。
§2.1 必须排除(不得出现在按表清单中)
| 排除项 | 列示例 | 说明 |
|---|---|---|
| BaseEntity 公共列 | create_user、create_dept_id、cur_company_id、deleted 等 |
见设计文档 §2;COMMENT 仍按 §逻辑外键列注释 编写 |
| 主键 / 外键关联列 | id、meter_id、apply_id、apply_dept_id 等 *_id |
关联关系在 DDL COMMENT 中说明,不入 §2.1 表 |
| 普通业务列 | apply_code、meter_code、数量/日期/备注 |
非字典/枚举 |
§2.1 应列入(仅此两类)
| 类型 | 列示例 | 说明列 | 引用列 |
|---|---|---|---|
| 字典 | caliber_code、meter_type_code |
口径、水表类型等业务含义 | 目录码,如 METER_DIAMETER |
| 枚举 | task_status_code、approve_status |
任务状态、审批状态等业务含义 | Java 枚举类名,如 TaskStatusEnum |
### wssmet_purapply_w — 采购申请
| 字段 | 类型 | 说明 | 引用 |
| process_status_code | 枚举 | 流程状态 | ProcessStatusEnum |
| approve_status | 枚举 | 审批状态 | ApproveStatusEnum |
(caliber_code 等字典列:说明填业务含义,引用填目录码;禁止在说明列写 DictEntry、@DictDirectory、implements Enumerable 等实现细节。枚举/字典取值见 字典与枚举说明-V1.0.md。)
COMMENT ON COLUMN 一致;维护时手工同步 §2.1 与 DDL。extends BaseEntity(含主表、明细表、子表,如 PurchaseApplyList、GiveBackMeter)。@TableId(value = "ID", type = IdType.ASSIGN_ID) Long id。createUser、createTime、updateUser、updateTime 及 createCompanyId~curDeptId 等 BaseEntity 已映射字段(避免覆盖、类型不一致,如 BigDecimal updateUser)。@Data;子类可用 @EqualsAndHashCode(callSuper = true)(按需);@FieldDefaults(level = PRIVATE);业务列 @TableField 大写列名。deleted / Boolean)| 层 | 约定 |
|---|---|
| 数据库 | 列名 DELETED,NUMBER(1) DEFAULT 0(0 未删,1 已删) |
| Java 实体 | 属性名 deleted,类型 Boolean(禁止 Integer/Number/is_delete/del_flag) |
| 映射 | @TableField("DELETED") |
| MyBatis-Plus | 须逻辑删除时加 @TableLogic(参考 PurchasePlan 存量写法) |
BaseEntity 且父类已提供 Boolean deleted 时,子类禁止重复声明。父类无 deleted 时,在实体中显式声明(默认值 false):
@TableLogic
@TableField("DELETED")
@ApiModelProperty("删除状态")
Boolean deleted = false;
查询默认过滤已删除数据:依赖 @TableLogic 或 Wrapper 自动带 DELETED = 0;手写 SQL/XML 须显式 DELETED = 0(或布尔映射等价条件)。
| Java 类型 | 数据库类型 | 实体注解 | 说明 |
|---|---|---|---|
OrganizationEntry |
NUMBER(19) |
@TableField("APPLY_DEPT_ID") 等 |
仅存组织/部门/公司 ID;name 由平台解析,禁止再建 *_name 快照列 |
UserNameEntry |
NUMBER(19) |
@TableField("APPLY_USER_ID") 等 |
仅存用户 ID;禁止再建 *_name 快照列 |
DictEntry |
VARCHAR2 |
@TableField("PRIORITY_CODE") + @DictDirectory |
存字典 code(如 METER_JJCD 项编码),列名常用 *_code |
FileEntry / FileEntryList |
无列 | @TableField(exist = false) |
DDL 不建字段;附件由平台文件服务按业务主键关联 |
_id(如 apply_dept_id、verify_user_id);Java 属性仍用 OrganizationEntry applyDept、UserNameEntry verifyUser。_code(如 priority_code、meter_type_code);与 @DictDirectory(MeterConstant.*) 成对出现。create_company_name、create_user_name 等 平台自动填充,与业务字段上的 Entry 规则无关。FileEntry / FileEntryList)| 场景 | Java 类型 | 说明 |
|---|---|---|
| 多个附件 | com.tofly.entity.pojo.FileEntryList |
接口入参/出参、Entity/Vo 展示 |
| 单个附件 | com.tofly.entity.pojo.FileEntry |
仅一个文件时使用 |
@TableField(exist = false)
@ApiModelProperty("附件")
FileEntryList attach;
CREATE TABLE 中声明 attach/photos/evidence_files 等 CLOB 列;禁止用 String 存逗号分隔文件 ID、List<Long> 裸 ID、JSON 字符串等替代。FileEntryList 或 FileEntry,并标注 @TableField(exist = false)(映射表字段时)。extends BaseEntity,且无重复审计字段(deleted 若父类已有则不重复)Boolean deleted + 列 DELETED,需要时含 @TableLogicNUMBER(19) 单列;字典 Entry 对应 VARCHAR2;附件 @TableField(exist = false) 且无库列@TableId(value = "ID", type = IdType.ASSIGN_ID)(雪花)。@Accessors(chain = true)(按需);字段 @TableField 大写列名。OrganizationEntry 表示部门/公司;库列类型 NUMBER(19)(如 apply_dept_id),禁止裸 Long deptId + String deptName 双字段落库,禁止为 Entry 再建 apply_dept_name 等快照列。Query / *LedgerQuery 中禁止使用 OrganizationEntry:按部门/公司筛选时用 Long(如 demandCompany、applyDeptId),按名称模糊筛选用 String(如 deptName,按需);与 PurchasePlanQuery.demandCompany 一致。OrganizationEntry.of(部门或公司ID);会话上下文可用 ApplicationSession.getDepartmentId() / getCompanyId()。Long 与实体 OrganizationEntry 字段对接,例如.eq(Objects.nonNull(q.getDemandCompany()), Entity::getDemandCompany, q.getDemandCompany())(见 PurchasePlanServiceImpl)。UserNameEntry;库列类型 NUMBER(19)(如 apply_user_id),禁止 Long userId + String userName 双字段落库。UserNameEntry;按用户筛选用 Long userId 或 String 用户名(如 inboundUser)。UserNameEntry.of(ApplicationSession.getUserId()) 或业务传入的用户 ID。字典项字段类型 DictEntry;库列类型 VARCHAR2,存字典项 code;字段上必须标注字典目录:
@DictDirectory(MeterConstant.DICT_METER_JJCD) // 字典目录码,定义在 MeterConstant
@TableField("PRIORITY_CODE")
@ApiModelProperty("紧急程度")
DictEntry priority;
目录常量集中在 com.tofly.wm.cons.MeterConstant,例如:
DICT_METER_JJCD — 紧急程度DICT_METER_TYPE — 水表类型DICT_METER_DIAMETER — 口径DICT_WORKER_ORDER — 工单类型新建字典目录时:先在 MeterConstant 增加常量 → 再在实体/Vo 上使用 @DictDirectory。
按字典编码写死业务分支时:DictEntry.of(MeterConstant.PURCHASE_APPLY) 等(见 PurchaseApplyServiceImpl)。
Query 中若仅按字典 code 筛选,可用 String + eq;回显/入库仍用实体上的 DictEntry。
Enumerable<T>,getKey() / getLabel(),放 {domain}/enums/。| 层级 | 要求 |
|---|---|
| 类 | JavaDoc:中文业务说明;@author、@date(与 ledger 存量一致) |
| public 方法(Service) | JavaDoc:@param、@return;说明业务含义,非复述方法名 |
| 关键代码 | 仅对非显而易见逻辑:状态流转、双写出入库、补偿、Wrappers 中每组条件的业务含义(参考 MeterServiceImpl.list 行尾 //总公司/分公司) |
| 禁止 | 无信息注释(// 查询、// 保存);大量注释掩盖坏命名 |
示例(Service 条件注释,摘自 meter):
.eq(Objects.nonNull(q.getCompanyType()), Meter::getCompanyType,
Optional.ofNullable(q.getCompanyType()).map(CompanyTypeEnum::getKey).orElse(null))
//出厂编码
.like(StrUtil.isNotBlank(q.getFactoryCode()), Meter::getFactoryCode, q.getFactoryCode())
@Api(tags);方法 @ApiOperation;路径参数 @ApiParam。java-tf-ylsw-backend 补 @ToFlyAppLog(ledger/meter 存量未全覆盖时,新增写接口应补齐)。ledger/meter(MeterController ~ MeterMapper)。ledger/purchase 或 ledger/verify。TF_WM_*)。ResultResponse、权限、路由;无 DELETE /{id})。## CRUD 结果(ylsw-bw)
- 域包:`com.tofly.wm.ledger.{domain}`
- 参考模板:`ledger/meter` | `ledger/purchase` | ...
- 新增/修改文件:`...`
## 接口清单
- `GET /{resource}/page`
- `GET /{resource}/list`
- `GET /{resource}/{id}`
- `POST /{resource}/`
- `POST /{resource}/update`
- `DELETE /{resource}/deleteByIds`(无 `DELETE /{resource}/{id}`)
- ...
## 规范检查
- [x] ResultResponse 带泛型
- [x] 包路径符合 ledger/manage 职责划分
- [x] 字符串用 StrUtil,对象空值用 Objects.nonNull/isNull
- [x] Entity/Vo 使用 OrganizationEntry、UserNameEntry、DictEntry + @DictDirectory;Query 不用 Entry 对象
- [x] 字典目录常量来自 MeterConstant
- [x] 类/方法/关键逻辑注释符合规范
- [x] 分页使用 PageHelper + PageQuery
- [x] 简单查询用 Wrappers / MPJ,复杂 SQL 在 XML
- [x] Mapper 无注解 SQL
- [x] 表含 BaseEntity 全部公共列;实体 `extends BaseEntity` 且无重复审计字段
- [x] 关联列 COMMENT 含「关联{表名}表的{字段}字段」
- [x] 字典/枚举列 COMMENT 含「字典类型,目录码…」或「枚举类型,…Enum」
- [x] 数据库设计 §2.1 仅含字典/枚举列,与 DDL COMMENT 一致
- [x] 无 `DELETE /{id}`;删除仅 `deleteByIds`
- [x] 接口文档已按模板全量列出 CRUD 路径
business/water-meter/src/main/java/com/tofly/wm/ledger/meter/