--- 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 引用 - POM 工作流:`language/java/pom-factory-skill.md`;**Maven 治理**:[`rules/java-pom-governance.mdc`](../../../../../rules/java/java-pom-governance.mdc)。 - **Java CRUD 通用规范**:[`rules/java-crud-common.mdc`](../../../../../rules/java/java-crud-common.mdc)。 - **tf-ylsw 条线 MUST/SHOULD**:[`rules/java-tf-ylsw-backend.mdc`](../../../../../rules/java/java-tf-ylsw-backend.mdc)(与 ledger 实际写法冲突时,以 ledger 存量风格为准,并在输出中注明差异)。 - 当本文件与 **java-crud-common** 冲突时,本文件(ylsw-bw / ledger)优先。 ## 代码落点与包结构(对齐 `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`**(带泛型,禁止裸类型)。 - 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.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` / `MPJBaseServiceImpl`。 - Mapper:`MPJBaseMapper`;简单查询不写 XML。 - 复杂统计、窗口函数、批量状态更新:在 `*Mapper.xml` 编写,**禁止** Mapper 注解 SQL。 ### 实体与值对象(部门 / 用户 / 字典) - 继承 **`BaseEntity`**;主键 `@TableId(value = "ID", type = IdType.ASSIGN_ID)`(雪花)。 - Lombok:`@Data`、`@Accessors(chain = true)`、`@FieldDefaults(level = PRIVATE)`;字段 **`@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`**;字段上必须标注字典目录: ```java @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`**,`getKey()` / `getLabel()`,放 **`{domain}/enums/`**。 ### 注释规范 | 层级 | 要求 | |------|------| | **类** | JavaDoc:中文业务说明;`@author`、`@date`(与 ledger 存量一致) | | **public 方法(Service)** | JavaDoc:`@param`、`@return`;说明业务含义,非复述方法名 | | **关键代码** | 仅对**非显而易见**逻辑:状态流转、双写出入库、补偿、Wrappers 中每组条件的业务含义(参考 `MeterServiceImpl.list` 行尾 `//总公司/分公司`) | | **禁止** | 无信息注释(`// 查询`、`// 保存`);大量注释掩盖坏命名 | 示例(Service 条件注释,摘自 meter): ```java .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. **自检**(见下)。 ## 输出格式 ```markdown ## 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 ``` ## 参考 - 模板代码见 [examples-ylsw-bw.md](examples-ylsw-bw.md) - 仓库实现:`business/water-meter/src/main/java/com/tofly/wm/ledger/meter/`