接口实现进度-第4批.md 5.8 KB

第 4 批接口移植:商品保存链路 + 文件上传

时间:2026-10-17 范围:商品新增/编辑(含多规格)、上下架、删除、按 ID 取列表、商品基础信息、规格列表、图片/视频上传

一、本批完成的接口

接口 方法 说明
/store/goods/listByIds GET 按 ID 集取商品(field(goods_id, ids) 顺序)
/store/goods/basic GET/POST 商品基础信息(含 skuList / goods_images)
/store/goods/add POST 新增商品(单规格 / 多规格)
/store/goods/edit POST 编辑商品(sku 重建、关联差集)
/store/goods/state POST 批量上下架
/store/goods/delete POST 批量软删除
/store/goods.spec/list GET 商品规格属性列表(多级控制器点号)
/store/upload/image POST 图片上传(字段名 iFile)
/store/upload/video POST 视频上传

store 模块进度:151 个接口已实现 91 个,剩余 60 个(tools/check_progress.py 可随时盘点)。

二、关键实现细节(对齐 PHP 的行为)

1. 单规格 / 多规格的价格与库存

  • 单规格:goods_price_min = goods_price_max = goods_price,line_price 同理,stock_total = stock_num,SKU 只写一条且 goods_sku_id = "0"。
  • 多规格:stock_total = Σ stock_num;价格区间取 sku 列表 array_filter 后(过滤 0 与空值)的 min/max,全空则 [0, 0]。
  • 多规格 SKU 落库前做兜底:goods_price ?: 0.01、line_price ?: 0、stock_num ?: 0(与 PHP increasedFroMulti 一致)。

2. 规格组 / 规格值复用

对照 PHP Spec::getNewSpecList / SpecValue::getNewValueList:先按名称(含 store_id)查已有记录并复用 ID,不存在才 insert。这样同一规格名在 yoshop_spec 里不会重复堆积。

goods_sku_id 由规格值 ID 用 _ 连接生成;goods_props 存 [{group:{name,id}, value:{name,id}}]。出参时这两个字段会被反解成数组(GoodsSku 上加了 @JsonIgnore + @JsonProperty 的 JSON getter),与 PHP 的获取器行为一致。

3. 关联表更新策略

  • 分类 / 图片 / 服务与承诺:新增走「批量插入」,编辑走「差集」(只删多余的、只插新增的),保证关联行 id 稳定。
  • 规格关联 / SKU:编辑时「全删再插」(对齐 PHP GoodsSpecRel::updates、GoodsSku::edit)。

4. 其它易错点

  • alone_grade_equity:前端键是 grade_id:10001,落库前去掉前缀并保持为对象(PHP 非连续索引数组 json_encode 后是对象,不是数组);is_enable_grade=0 时强制 is_alone_grade=0;开启单独折扣但传空时报「请先添加会员等级后再设置会员折扣价」。
  • delivery_type 库内是 JSON 字符串([10]),实体走 JacksonTypeHandler。
  • 商品删除是软删除(is_delete=1),Goods 实体的 @TableLogic(value="0", delval="1") 与全局 10/30 配置不同,不能混用。

5. 文件上传

  • 字段名 iFile、分组走 query groupId,与 PHP Upload::FORM_NAME 一致。
  • 落盘路径 {storeId}/{yyyyMMdd}/{md5}.{ext},写 yoshop_upload_file(storage=local、domain 留空)。
  • 出参 fileInfo 含 preview_url / external_url,域名取 yoshop.base-url。
  • 新增配置:yoshop.upload-path(默认 runtime/uploads),并把 /uploads/** 映射为该目录的静态资源,便于本地联调;线上建议仍由 Nginx 托管。
  • 上传大小上限调到 100MB(spring.servlet.multipart)。

三、验证结果

1. 写接口端到端(tools/test_goods_writes.py,42 项断言全通过)

覆盖:新增单规格 → 库内校验(价格/库存/关联/sku)→ 读回校验 → 编辑为多规格(sku 重建、规格入库、价格区间)→ 读回校验 → 下架/上架 → 软删除 → 删除后不可见 → 数据清理。

2. 读接口逐字段对比(tools/compare_live.py,7/7 PASS,0 差异)

goods/basic、goods/listByIds、goods.spec/list、goods/detail(单规格 + 2 个多规格商品)全部与线上 PHP 一致。

3. 回归

tools/compare_batch.py 28 个既有接口 28/28 PASS(GoodsSku 的 JSON 字段改造没有破坏详情接口)。

四、⚠️ 环境发现:本地库与线上库不是强一致

测试中确认了一个影响后续方案的事实:

  • 线上 PHP 写入的数据,本地 Java 能读到(例:通过 PHP 接口上传的「泰国金枕榴莲」在两边都在);
  • Java 写入的数据,线上 PHP 读不到(新建商品后 PHP detail 报「商品信息不存在」、list?goodsName= 返回空,等待 9 秒仍如此)。

也就是说 192.168.1.177/shop 与线上 PHP 的库之间是单向同步(PHP → 本地),Java 的写操作只落在本地副本。

影响:

  1. 写接口无法用「PHP 读回」做交叉验证,本批改为「库内断言 + Java 读回」自洽校验(已覆盖价格、库存、关联、sku、软删除等所有落库结果);
  2. 上线前必须把 Java 的数据源指向线上主库,否则后台改的数据在线上不生效;
  3. 第 3 批的写测试(用户/角色/页面)同样写到了本地副本,不影响线上数据,但也验证不到线上。

五、下一步

剩余 61 个接口,按页面可用性排序:

  1. 订单事件:改价、备注、改地址、取消、发货、发货单列表/详情/物流轨迹、退款审核/收货;
  2. 设置类 CRUD:支付设置(options/update/模板 CRUD)、配送模板、运费模板、打印机;
  3. 营销:优惠券 CRUD/领取、充值方案 CRUD、会员等级 CRUD;
  4. 其余:商品评论、客户端设置(h5/wxapp)、统计接口、controller/initialize。

另外 checkPrivilege(按角色校验 API 权限)仍未实现,切流前必须补。