# 第 6 批接口实现进度(设置模块 18 个接口) > 目标:把后台「设置」菜单跑通——配送模板、物流公司、快递打印、支付设置、支付模板。 > 验证方式:读接口双端逐字段对比(PHP 线上 vs Java 本地),写接口端到端断言(造数 → 调接口 → 查库 → 清理)。 ## 一、本批接口清单(18 个) | 分组 | 接口 | 说明 | |---|---|---| | 配送模板 | `GET /store/setting.delivery/detail` | 详情(含 rule 数组) | | | `POST /store/setting.delivery/add` | 新增模板 + 区域运费 | | | `POST /store/setting.delivery/edit` | 编辑(区域整体替换) | | | `POST /store/setting.delivery/delete` | 软删除(被商品使用时拦截) | | 物流公司 | `POST /store/setting.express/add` | 新增 | | | `POST /store/setting.express/edit` | 编辑 | | | `POST /store/setting.express/delete` | 软删除 | | 支付设置 | `GET /store/setting.payment/options` | 4 客户端 × 支付方式配置 | | | `POST /store/setting.payment/update` | 全量覆盖写入 | | 支付模板 | `GET /store/setting.payment.template/list` | 分页(15/页) | | | `GET /store/setting.payment.template/all` | 全部 | | | `GET /store/setting.payment.template/detail` | 详情(config 合并默认) | | | `POST /store/setting.payment.template/add` | multipart,含证书文件 | | | `POST /store/setting.payment.template/edit` | multipart | | | `POST /store/setting.payment.template/delete` | 被引用时拦截 | | 打印机 | `POST /store/setting.printer/add` | printer_config 存 JSON | | | `POST /store/setting.printer/edit` | | | | `POST /store/setting.printer/delete` | 软删除 | **进度:151 个接口已实现 122 个,剩余 29 个。** ## 二、新增 / 修改文件 **新增** - 实体:`Payment`、`PaymentTemplate` - Mapper:`PaymentMapper`、`PaymentTemplateMapper` - 服务:`PaymentSettingService`(支付配置 + 支付模板) - 工具:`PaymentConfig`(默认配置结构 + `resetOptions` 递归合并) - 控制器:`SettingPaymentController`、`SettingPaymentTemplateController` **修改** - `Delivery`:补 `rule` 关联(`@JsonInclude(NON_NULL)`,未装配时不输出该键) - `DeliveryRule`:`region` / `region_text` 库内 JSON 字符串 → 出参数组 - `Printer`:`printer_config` 改为 JSON 解析后出参(原来直接吐字符串,列表页会错) - `SettingService`:配送模板/物流公司/打印机的新增、编辑、删除 - `SettingDeliveryController` / `SettingExpressController` / `SettingPrinterController`:补充路由 ## 三、本批踩到的对齐细节 1. **`rule` 键的有无是有语义的** `setting.delivery/all` 的 PHP 输出**没有** `rule` 键,而 `list` 和 `detail` 有。 所以实体上加 `@JsonInclude(NON_NULL)`,只在 `list`/`detail` 里装配,`all` 保持不输出——不能用「一律返回空数组」糊过去。 2. **`@TableLogic` 字段不能用 `updateById` 置位** MyBatis-Plus 更新时会跳过逻辑删除字段,`setIsDelete(1)` + `updateById` 结果是「接口返回成功、库里没删」。 必须 `lambdaUpdate().set(IsDelete, 1).set(UpdateTime, ts)`。删除接口的 4 处均已改为显式 set。 3. **支付模板 config 是「默认结构 + 已存值」的递归合并** PHP `resetOptions($default, $data)`:键并集、关联数组递归合并、索引数组后者整体覆盖。 `add` 只把 `{method: config}` 写库,读取时再补上另一种支付方式的默认块(所以 detail 里一定同时有 `wechat` 和 `alipay`)。 4. **证书文件名规则** `{字段名}-{md5(uuid) 前8位}.{pem|crt}`,落盘目录 `payment/{method}/{storeId}/`(PHP 是 `data/`,Java 用 `yoshop.upload-path` 作 data 根)。 编辑时前端会把 detail 拿到的文件名原样回传,所以「不传文件」时配置里的文件名要靠前端带上;若传空串则会被清空——这点与 PHP 一致。 5. **支付设置是「全量覆盖」** `update` 先删当前商城的全部 `yoshop_payment` 记录再批量插入(每个客户端 × 每个支付方式一条,共 9 条)。不是增量更新。 6. **`payment_options` 的 `methods` 是数组不是对象** 默认数据里按 `"client-method"` 作键便于合并,输出前必须 `array_values` 转成索引数组,否则前端表格渲染不出来。 ## 四、验证结果 | 项目 | 结果 | |---|---| | 读接口双端对比(`tools/compare_setting_batch.py`) | **13 / 13 零差异** | | 写接口端到端断言(`tools/test_setting_writes.py`) | **66 / 66 通过** | | 订单链路回归(`tools/compare_order_batch.py`) | 10 / 10 PASS | | 既有接口回归(`tools/compare_batch.py`) | 28 / 28 PASS | 写测试覆盖:配送模板新增(含 2 条区域规则的 JSON 落库与回读)、空规则拦截、编辑时区域整体替换(旧 rule 物理删除)、删除(含「被 6 个商品使用」拦截)、物流公司增改删、打印机配置对象 ↔ JSON 互转、支付设置全量覆盖与读回、支付模板 multipart 上传证书(校验文件名规则与落盘)、编辑保留证书名、被引用时禁止删除、解除后删除成功。 测试数据已全部清理(`yoshop_payment` 恢复为空、TESTJ 记录物理删除、上传的证书文件已删)。 ## 五、仍未接入(切流前必须补) - `checkPrivilege` 角色权限校验(当前只校验登录态); - 微信支付 V3「平台证书」自动下载(`downloadPlatformCert`):需要商户证书 + 调微信接口,Java 侧目前显式报错「请改用公钥验签」,避免静默跳过; - 小票打印机驱动(飞鹅 / 365 云打印)的实际出票调用——本批只做了打印机的增删改查; - 微信小程序发货信息上报、第三方支付(微信/支付宝)退款、阿里云物流轨迹驱动。