java-ylsw-bw.md 19 KB


name: crud-by-controller-ylsw-bw

description: 基于仪陇表务(ylsw-bw / water-meter)ledger 模块风格生成 CRUD、台账查询与导出接口。参考 com.tofly.wm.ledger.meter 等既有实现,统一 ResultResponse、MPJ、PageHelper 与权限注解。用户提到表务、水表台账、ledger、出入库、生成接口时使用。

CRUD 生成 Skill(仪陇表务 ylsw-bw)

适用场景

当用户要求在 business/water-meter 或表务相关模块中新增/补全接口时,优先对齐 com.tofly.wm.ledger 下既有代码(首选参考 ledger/meter,台账类参考 ledger/purchase、ledger/arrive、ledger/verify)。

公共 Skill 引用

代码落点与包结构(对齐 com.tofly.wm)

模块根包 com.tofly.wm,按业务分层;新建代码必须落在与职责匹配的包下,禁止全部堆在 ledger 或单一层级。

包树与职责(参考 ledger / manage 存量)

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 还是 manage?

场景 推荐包
分页/列表/导出台账、跨表只读汇总 ledger.{domain}
主表 CRUD、流程、审批、写入业务表 manage.{业务域}
移动端/专项入口 ledger.{domain}.app 或 manage.*.app

ylsw-bw 专项约束

接口与返回

  • 返回体统一 ResultResponse<T>(带泛型,禁止裸类型)。
  • Controller:@RestController + @RequestMapping("/xxx") + @Api(tags = "...")。
  • 依赖注入:@RequiredArgsConstructor + @FieldDefaults(level = PRIVATE, makeFinal = true)(Controller 常见);Service 可用 @AllArgsConstructor 或 @Service 无接口实现类。

路由习惯(对齐 meter)

能力 方法 路径 说明
分页 GET /page 与 /list 共用 {Domain}Query(含 pageNum/pageSize)
列表 GET /list 与 /page 同一 Query、同一 Service 条件;不分页,返回 List
详情 GET /{id}
新增 POST / @RequestBody 实体
修改 POST /update 本模块用 POST 而非 PUT
删除 DELETE /{id}
批量删除 DELETE /deleteByIds ids 请求参数,逗号分隔主键
导出 GET /export 返回 void,EasyPoiUtil.download(台账类)

工具类与空值判断

场景 用法 禁止/避免
字符串为空 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
  • Hutool 包名:cn.hutool.core.*(StrUtil、CollUtil、BeanUtil 等),与项目存量一致。
  • 字符串用 Hutool;引用类型对象(实体字段、枚举、Long、OrganizationEntry 等)用 Objects。

分页与查询

  • 查询类继承 PageQuery,Swagger @ApiModel / @ApiModelProperty。
  • Query 字段类型:筛选用 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))。
  • 批量删除:Service deleteByIds(String ids) 解析逗号分隔主键后 removeByIds,需 @Transactional。
  • 多表台账只读:MPJWrappers.lambdaJoin() + selectJoinList(见 purchase/verify 台账),不强行拆 XML。
  • 关键条件行加简短行尾注释(见注释规范),如 //出厂编码、//需求部门。

Service / Mapper 基类

  • 实体 CRUD:MPJBaseService<Entity> / MPJBaseServiceImpl<Mapper, Entity>。
  • Mapper:MPJBaseMapper<Entity>;简单查询不写 XML。
  • 复杂统计、窗口函数、批量状态更新:在 *Mapper.xml 编写,禁止 Mapper 注解 SQL。

实体与 BaseEntity(强制)

与 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 / 会话上下文自动写入,业务代码禁止在子类重复声明上述字段。

持久层实体(强制)

  • 所有映射数据库表的实体类 extends BaseEntity(含主表、明细表、子表,如 PurchaseApplyList、GiveBackMeter)。
  • 主键仍在子类声明:@TableId(value = "ID", type = IdType.ASSIGN_ID) Long id。
  • 禁止在子类再次声明 createUser、createTime、updateUser、updateTime 及 createCompanyId~curDeptId 等 BaseEntity 已映射字段(避免覆盖、类型不一致,如 BigDecimal updateUser)。
  • Lombok:@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(或布尔映射等价条件)。

附件(FileEntry / FileEntryList)

场景 Java 类型 说明
多个附件 com.tofly.entity.pojo.FileEntryList 入库、到货、领用等常见,列名如 ATTACH
单个附件 com.tofly.entity.pojo.FileEntry 仅一个文件时使用
@TableField("ATTACH")
@ApiModelProperty("附件")
FileEntryList attach;
  • 禁止用 String 存逗号分隔文件 ID、List<Long> 裸 ID、JSON 字符串等替代(与公共文件服务对接由 FileEntry* 类型处理器完成)。
  • DTO/Vo 与 Entity 保持一致:接收、返回附件均用 FileEntryList 或 FileEntry。
  • 参考:PurchasePlanArr.attach、PurchaseClaim.attach。

自检(建表 / 新建实体)

  • DDL 已包含上表全部公共列及 COMMENT ON COLUMN
  • 实体 extends BaseEntity,且无重复审计字段(deleted 若父类已有则不重复)
  • 逻辑删除为 Boolean deleted + 列 DELETED,需要时含 @TableLogic
  • 附件字段为 FileEntry / FileEntryList,非字符串拼 ID
  • 明细表与主表均具备相同公共列(非「纯关系表」例外须在表注释说明)

实体与值对象(部门 / 用户 / 字典)

  • 主键 @TableId(value = "ID", type = IdType.ASSIGN_ID)(雪花)。
  • Lombok:@Accessors(chain = true)(按需);字段 @TableField 大写列名。

部门 / 公司(OrganizationEntry)

  • 仅持久层实体、Vo 使用 OrganizationEntry 存部门/公司(含 id、name 映射),禁止裸 Long deptId + String deptName 双字段落库。
  • Query / *LedgerQuery 中禁止使用 OrganizationEntry:按部门/公司筛选时用 Long(如 demandCompany、applyDeptId),按名称模糊筛选用 String(如 deptName,按需);与 PurchasePlanQuery.demandCompany 一致。
  • 新增/修改赋值(Entity):OrganizationEntry.of(部门或公司ID);会话上下文可用 ApplicationSession.getDepartmentId() / getCompanyId()。
  • Service 条件拼装:Query 的 Long 与实体 OrganizationEntry 字段对接,例如
    .eq(Objects.nonNull(q.getDemandCompany()), Entity::getDemandCompany, q.getDemandCompany())(见 PurchasePlanServiceImpl)。

用户(UserNameEntry)

  • Entity、Vo 上申请人、入库人等:UserNameEntry,禁止仅 Long userId 落库。
  • Query 中不使用 UserNameEntry;按用户筛选用 Long userId 或 String 用户名(如 inboundUser)。
  • 赋值:UserNameEntry.of(ApplicationSession.getUserId()) 或业务传入的用户 ID。

字典(DictEntry + @DictDirectory)

  • 字典项字段类型 DictEntry;字段上必须标注字典目录:

    @DictDirectory(MeterConstant.DICT_METER_JJCD)  // 字典目录码,定义在 MeterConstant
    @TableField("PRIORITY")
    @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())

Swagger 与日志

  • 类 @Api(tags);方法 @ApiOperation;路径参数 @ApiParam。
  • 写操作按 java-tf-ylsw-backend 补 @ToFlyAppLog(ledger/meter 存量未全覆盖时,新增写接口应补齐)。

生成步骤

  1. 识别域与参考类
    • 标准 CRUD → ledger/meter(MeterController ~ MeterMapper)。
    • 只读台账 + 导出 → ledger/purchase 或 ledger/verify。
  2. 生成 Query / Vo(分页查询、导出列)。
  3. 生成 Entity + Enum(表结构对齐 TF_WM_*)。
  4. 生成 Mapper + Service(Impl)(单表 Wrappers,多表 MPJJoin)。
  5. 生成 Controller(ResultResponse、权限、路由)。
  6. 按需生成 Mapper.xml(仅复杂 SQL)。
  7. 自检(见下)。

输出格式

## CRUD 结果(ylsw-bw)
- 域包:`com.tofly.wm.ledger.{domain}`
- 参考模板:`ledger/meter` | `ledger/purchase` | ...
- 新增/修改文件:`...`

## 接口清单
- `GET /{resource}/page`
- `GET /{resource}/list`
- `DELETE /{resource}/deleteByIds`
- ...

## 规范检查
- [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] `Boolean deleted` + `DELETED` 列;附件为 `FileEntry` / `FileEntryList`

参考

  • 模板代码见 examples-ylsw-bw.md
  • 仓库实现:business/water-meter/src/main/java/com/tofly/wm/ledger/meter/