# 第 8 批接口实现进度:客户端设置、评论、导出、统计与打印(15 个,**收官**) > 时间:2026-10-17 > 进度:**151 个接口已全部实现,剩余 0**(`tools/check_progress.py` 复核:PHP 151 / Java 151 / 待实现 0) > 验证:读接口 **26/26 零差异**、写接口 **21/21 断言全通过**;订单 10/10、设置 13/13、营销 6/6 回归全 PASS ## 一、本批 15 个接口 | 分组 | 接口 | 方法 | 说明 | |---|---|---|---| | 客户端设置 | `/store/client.h5.setting/detail` | GET/POST | resetOptions 合并默认,未知 key 返回 `[]` | | | `/store/client.h5.setting/update` | POST | 先 update 后 insert(有则改、无则插) | | | `/store/client.wxapp.setting/detail` | GET/POST | 额外返回 `domain`(去端口) | | | `/store/client.wxapp.setting/update` | POST | 仅落库 4 个白名单字段 | | 商品评论 | `/store/goods.comment/list` | GET/POST | join 商品/订单子查询,page 15 | | | `/store/goods.comment/detail` | GET/POST | 含 images / imageIds / imageList | | | `/store/goods.comment/edit` | POST | 先删后插 comment_image,同步 is_picture | | | `/store/goods.comment/delete` | POST | 软删除 | | 订单导出 | `/store/order.export/list` | GET/POST | 导出记录分页 | | | `/store/order.export/exportOrder` | GET/POST | 生成 xlsx + 落导出记录(兼容 `export_order`) | | 统计 | `/store/statistics.data/data` | GET/POST | overview + tradeTrend + goodsRanking + userExpendRanking | | | `/store/statistics.data/survey` | GET/POST | 6 项指标,支持起止时间 | | | `/store/statistics.data/initialize` | GET/POST | 空响应体(void) | | 其他 | `/store/controller/initialize` | GET/POST | 空响应体(void) | | | `/store/order.event/printer` | POST | 小票打印(飞鹅 / 365 两种驱动) | ## 二、新增/改动文件 **实体**:`CommentImage`(新增);`Comment` 补 `user`/`orderGoods`/`imageIds`/`imageList`/`images`; `H5Setting`、`WxappSetting`、`OrderExport`(新增,主键与保留字列均加反引号映射)。 **Mapper**:`CommentImageMapper`、`H5SettingMapper`、`WxappSettingMapper`、`OrderExportMapper`(新增); `OrderGoodsMapper` 新增 `goodsRanking()`(销量 Top10,`@Select` 手写 SQL)。 **服务**:`CommentService`、`ClientSettingService`、`StatisticsService`、`OrderExportService`、`PrinterService`(新增)。 **控制器**:`GoodsCommentController`、`ClientH5SettingController`、`ClientWxappSettingController`、 `StatisticsController`、`StoreBaseController`、`OrderExportController`(新增);`OrderEventController` 增 `printer`。 **改造**:`OrderService` 抽出 `buildQuery`,新增 `listAll`(导出不分页)与 `findAssembled`(打印); `BizException` 增 `silent` 工厂 + `GlobalExceptionHandler` 对应分支; `WebMvcConfig` 加 `/downloads/**` 静态映射与 `yoshop.export-path`;`pom.xml` 加 `poi-ooxml 5.4.1`。 **工具脚本**:`seed_comment_golden.py`(评论种子,支持 `--cleanup`)、 `compare_final_batch.py`(26 读用例)、`test_final_writes.py`(21 写断言,含 Excel 逐格对比)。 ## 三、双跑验证结果 **读接口 `compare_final_batch.py`:26/26 PASS** | 分组 | 用例数 | 覆盖 | |---|---|---| | 评论列表 | 11 | 无参 / 分页 / goodsId / orderNo / userId / status / score / 组合 / 空结果 | | 评论详情 | 4 | 正常 / 含图 / 软删除记录 / 不存在报错 | | H5 设置 | 2 | 默认合并 / 未知 key | | 小程序设置 | 2 | 含 domain / 默认值 | | 导出列表 | 1 | 分页结构 | | 统计 | 4 | data / survey 无参 / 带区间 / 反向区间 | | initialize | 2 | 两个接口空响应体 | **写接口 `test_final_writes.py`:21/21 PASS**(PHP 跑 → 采样 → 复位 → Java 跑 → 采样 → 对照) - 评论编辑:改分数/内容/状态/排序 + 换图(先删后插,comment_image 行 id 递增但内容一致); - 评论清空图片:`is_picture` 由 1 → 0; - 评论软删除:`is_delete=1`,列表与详情同步消失; - 缺失记录报错文案:`Call to a member function edit() on null` / `setDelete() on null`; - H5 设置:首次新增 + 二次覆盖,**库内 JSON 字节完全一致**; - 小程序设置:4 字段白名单落库一致(多余字段被丢弃); - 订单导出:**Excel 单元格逐格完全一致**(28 列 × N 行,含商品明细多行文本与「元」后缀); - 导出 betweenTime:`start_time`/`end_time` 落库一致、文件名时间戳归一化后一致; - 打印:打印机不存在、驱动调用失败(真实请求飞鹅接口返回「参数错误 : 该帐号未注册.」)两端一致。 **回归**(本地 PHP 源):订单 10/10、设置 13/13、营销 6/6,全部 PASS。 ## 四、七个关键对齐点 1. **`renderError` 与 `throwError` 的 data 结构不同**:打印失败走 `renderError`,PHP 返回 `{"status":500,"message":"...","data":[]}`;而多数业务异常走 `throwError`,data 是 `{"isPrompt":true}`。 Java 侧新增 `BizException.silent()`(不置 prompt 标志)+ 全局处理器分支返回 `new Object[0]`, 否则前端 `data.isPrompt` 判断会走错分支。 2. **统计口径三处易错**:`consumeUsers` 的 PHP 是 `group('user_id')->count()`——**分组行数**, 不是 `count(*)` 也不是 `count(user_id)`,等价 SQL 是 `count(distinct user_id)`; 计数类走 `number_format`(千分位字符串),金额类走 `helper::number2`(两位小数), 两者都是**字符串**出参,不是数字。 3. **导出字典即列序**:PHP `Export::dictionary()` 用 `LinkedHashMap` 语义, Excel 列顺序必须与字典顺序一致(28 列),不能按实体字段顺序生成。 单元格值一律 `\t值\t` 包裹,金额加「元」,商品明细是「1.商品名称:…\n 商品规格:g: v; \n…」的多行模板, ` ` 是全角空格——这些细节逐字节影响 Excel 对比。 4. **H5/小程序设置落库要复刻 PHP `json_encode` 字节**:PHP 默认转义 `/` 与非 ASCII(`\uXXXX`)。 通用 Jackson 配置产出的 JSON 字节不同,虽然语义一样但会导致「落库不一致」。 已建专用 `PHP_JSON`(`ESCAPE_NON_ASCII`)+ `.replace("/", "\\/")`,实测字节完全一致。 5. **小程序 `domain` 去端口**:PHP `$request->host(true)` 第二参为 true 时**不带端口**; Java `getServerName()` 也不带端口,但为兼容反向代理场景仍统一 `replaceAll(":\\d+$", "")`。 6. **未知 setting key 的两种行为**:`detail` 传未知 key 时 PHP 走 `resetOptions` 后返回空数组 `[]`(不报错); 而 `update` 里取 `describe[$key]` 会抛 `Undefined array key`。两处分支不能合并。 7. **打印驱动的错误透传**:PHP 是 `$result['msg'] ?: '操作失败'`,业务失败也返回 200 之外的 status=500, 但 message 用驱动原文。Java 侧 `BizException.silent(error != null ? error : "操作失败")` 复刻。 ## 五、整体交付状态 | 指标 | 数值 | |---|---| | PHP 接口总数(store 模块) | 151 | | Java 已实现 | 151(100%) | | 控制器 | 43 个 | | 服务 | 30 个 | | 读接口双跑对比 | 累计 26 + 60 + 13 + 10 + 7 + 6 ≈ 122 项,0 差异 | | 写接口断言 | 累计 21 + 118 + 66 + 77 + 42 + 62 + 38 + 24 ≈ 448 项,全通过 | **自动化脚本**(`tools/`):`check_progress.py` 盘点进度、`compare_*.py` 双跑对比、 `test_*_writes.py` 写接口对照、`seed_*_golden.py` 造数、`backup_*.py` 测试前备份。 ## 六、仍未接入的能力(切流前必须补) 1. `checkPrivilege` 角色权限校验——当前所有 store 接口只校验登录态(token),未按 `yoshop_store_role_menu` 做接口级鉴权,**这是切流前的第一优先级**; 2. 微信小程序发货信息上报(`OrderDeliveryService` 内留空); 3. 第三方支付(微信/支付宝)退款接口调用; 4. 阿里云物流轨迹驱动(当前只落库物流单号,不拉轨迹); 5. 微信支付 V3 平台证书自动下载(现依赖手工放置证书)。 ## 七、测试数据清理与环境还原 - 删除 `yoshop_order` 10099(TESTORDER10099)、`yoshop_printer` 10001、 `yoshop_comment` 10001–10004 及 `yoshop_comment_image` 3 条、`yoshop_order_export` 3 条; 复核:订单表回到原始 2 条(10001/10002),其余 4 张表归零。 - 删除导出产物:`F:/shop/shop/public/downloads/10001/*.xlsx`、`yoshop-java/runtime/downloads/10001/*.xlsx`。 - `F:/shop/shop/.env` 已按原字节还原(备份 `/tmp/yoshop_env_backup.txt`,即 localhost/test)。 - 本地 PHP 站点(127.0.0.1:8899,PHP 8.2 便携包)已关闭;PHP 8.2 目录保留在 `C:/Users/Administrator/AppData/Local/Temp/php82/`,需要再跑双端对比时可直接复用。 - Java 服务(18080)启动命令:`mvn -q -o -DskipTests spring-boot:run -Dspring-boot.run.arguments="--server.port=18080"` (登录态已改为存 Redis,`yoshop.auth.mode` 默认 `redis`)。