★ 产物以《2026-10-09-har-pick-dedup-retry-交付说明.md》为准(那篇含最新 jar/dist 指纹); 本文件 §二 / §六 记的 jar/dist 均已作废,部署只按那一篇。
需求原文(用户,指向《支付宝支付接口接入规范261008(1).docx》): 「门店新增了地区相关的字段,需要接上,因为支付宝异地收款会有风控,我们的 checkout 也要做一个调整, 在调起支付时要根据请求者 ip 自动选择对应地区的门店 id,这样才不会风控」
设计裁定(本批开工前已与你确认,未改):IP 归属地取 ipwho.is(进程内缓存,失败⇒空); 粒度 = 城市精确匹配、省作回退;匹配不上拒收(顾客语,不打上游、不落单);同城多店按 IP 稳定散列挑一家;城市未匹配则该省随机一家。
★ 这一批没有写计划文件(前几批是 .claude/plan/<日期>-<主题>.md),需求以 docx + 上述裁定
为准,落地过程与读数都在 logs/ 下的日志里 —— 这点与既有批次不同,先说明。
origin/java 停在 ed24480f。本地领先的提交 = 下表 6 个功能/文档刀 + 本说明自身的提交
(0a07a456 首次提交,以及本文件每次订正各一刀)—— 不写死总数:本说明自己也在数里,
写完再提交一次就 +1(要读数就现量 git rev-list --count origin/java..java)。
| 提交 | 内容 |
|---|---|
b1c8f6fb |
(一)目录快照 har-region-cn.json(31 省 / 342 地级 / 166 繁简对)+ HarRegionCatalog + HarIpRegionResolver + HarShopService#pickForRegion + 门店/创建请求带地区 |
32384b2b |
(二)结账腿接线:HarCheckoutController 按顾客 IP 挑店填 third_shop_code;挑不出来 400 拒收(两种文案);clientIp 四个头 + unknown 兜底 |
c47b0c63 |
(三)省市目录只读端点 GET /payment/har/region/provinces|cities + db/har-shop-region.sql |
8e352e7e |
(四)前端:门店表单省→市选择器、列表「地区」列、详情一行;开放 API 文档 §10.1 同步 |
ccc34414 |
文档 markdown 空行修正(引用块贴着 --- 会被当成懒惰续行) |
300a27f9 |
(五)编码填错统一 400:HarShopService#requireKnownCity 两处单参 BusinessException(默认码 500)改显式 400;两条地区拒收用例补码断言(先红后绿);开放文档 §10.1 补码值 |
★ b1c8f6fb 里夹带了一份 支付宝支付接口接入规范261008(1).docx(87512 字节) ——
那是开工时会话开始前就已经 git add 进索引的文件,提交时没有指定路径,于是跟着这一刀进去了。
我没有改写历史(改写会动到你已经能看到的提交)。你的裁定是保持现状(2026-10-08),
本批不再动它:留着无功能影响,摘掉需要 rebase/改写。后续提交都改成
「显式路径 git add + git commit -F <msg> -- <显式路径>」。
mvn test:1212 条,0 失败 0 错误 0 跳过,BUILD SUCCESS
(★ 两条读数:第一条 12:44:08 在 300a27f9 之前;400 订正后 12:51:21 复跑同数
= 1212/0/0/0,Total time: 21.430 s,日志 logs/_mvn-full-region-code-400.txt 的 mtime;
HarCheckoutControllerTest 24 + HarRegionControllerTest 5 都在内)| 变异体 | 结果 |
|---|---|
结账腿:4 个(挑到不填 third_shop_code / 两句拒收文案合并 / 不取 XFF 第一跳 / 去掉 unknown 兜底) |
预测红集 6,实测恰为那 6 条,其余 18 条作正对照仍绿;还原后 sha256 与变异前一致(24/0) |
目录端点:3 个(省码不校验 / 用空表代替 400 / 权限串只留 :create) |
预测红集 3,实测 cities_returnsOnlyThatProvincesCities、cities_unknownProvinceCode_400_notAnEmptyList、contract_pathAndPermissionAreTheOnesTheFrontendCalls;2 条对照仍绿;还原后 sha256 一致(5/0) |
前端(本仓无测试框架,ts:check 基线本就非零 ⇒ 只能同机差分):
pnpm ts:check:基线 289 条 error TS(exit 2)→ 改动后 289 条,逐条集合比对
新增 0、消失 0;★ 400 订正(只动一行 md)之后再量一次仍是 289(exit 2,
logs/_tscheck-region-code-400.txt)—— 文档经 ?raw 进来,不进类型检查pnpm build:exit 0(12:42 / 12:46 / ★ 400 订正后那次 56.39s、日志 mtime 12:53:05,共三次);
当时那次已回读产物确认新代码真进了包:
dist/js/index.x_9MozyF.js 含 region/provinces、dist/js/index.SNmFzXAj.js 含文档新句
「城市编码不属于该省」、province_code 出现在 3 个 chunk(★ 这三个 chunk 名只属于当时那一版,
12:53 那版全变了 —— 见上「产物指纹」)(注意:一开始我按 dist/assets/*.js
搜,3 个都查成 0 —— 是本仓产物在 dist/js/,是检索式错不是文件缺失,换路径后同一条搜索命中);select.mjs):loading: Boolean
「whether Select is loading data from server」、disabled: Boolean —— 不是凭记忆。产物指纹(★ 下面这版是要部署的,12:51/12:53 在 300a27f9 之后重打):
java/target/payment-platform-1.0.0.jar,143,146,853 字节,sha256 b6b64c8ad8455d4e1e0639d7d8ad04ea9db347292ec75bb81cb384213379068d,
重打于 12:51:49;开包验的是产物本身:从 jar 里取 HarShopService.class 反汇编
(javap -p -c),两处拒收都是 sipush 400 → BusinessException.<init>:(ILjava/lang/String;)Vfrontend/dist.zip,9,375,214 字节,sha256 80038a66ef9dd73678ec5d9158f66763eef6f4451feb9f3fd9b92d50a2468fa6,
12:53:49;281 个条目 = 277 文件 + 4 个目录条目(dist/、dist/css/、dist/img/、dist/js/,
与上一版同形:全为正斜杠、testzip() 无坏成员)977da280…(143,146,829 字节,12:45:02)、dist.zip dd4cf524…(9,375,221 字节)[hash] 含依赖哈希,
一处内容改动脉冲式传播),条目数没变(前后各 277 个文件)。两向读数:旧包里只有旧句
不属于该省的编码会被拒(在 js/index.SNmFzXAj.js),新包里只有新句 编码填错一律
(在 js/index.ruVK6GJ7.js),互不相见 —— 不是「重建后一律变」的空话java/src/main/resources/db/har-shop-region.sql(本工程未接 Flyway,只能你执行)。
★ 反了不是静默降级而是直接报错:实体带上 province_code/city_code 后,门店列表与
挑店的每一句 SELECT 都会取这两列 ⇒ 门店列表 500,且每一笔收款链接下单都失败。frontend/dist.zip。module_payment:har:shop:list / :create 权限串(@perm.hasAny 两条),菜单不动。province_code/city_code 为空的门店(2026-10-08 之前建的)在挑店时必然落选。
上游没有门店更新接口,补不上,要收款只能换新的 third_shop_id 重开。pay_url 仍然没有被真机扫过(上一批就挂着的问题,本批没变);挑店在真实顾客 IP
下的表现(真挑到哪个地区的哪家店)没有端到端跑过,只跑到「挑店逻辑 + 端口」这一层。HarCheckoutController 的类 javadoc
「已知的边界」里)。后果是把顾客导到收款方自己的另一家门店,不涉资金与租户边界,
要堵得在网关上 —— 本批不动。HarCheckoutController 三个偏差未改(都在代码注释里标了,属规范与现状不一致,
不是本次引入):shopName 我方 @Size(128) vs 规范 varchar(16);thirdShopCode 32 vs 30;
实体列宽按规范 16 建。300a27f9):编码不认识时,
HarRegionController 返回 code=400(两参构造),
而 HarShopService#requireKnownCity 返回 code=500(单参 BusinessException 的默认码)。
后者是对外开放 API 也会走到的路径 ⇒ 外部调用方传错码会看到「服务器错误」。
现在两处都是显式 400,文案一字未动。读数:给两条地区拒收用例各加一条
assertEquals(400, e.getCode()) 后实测红(expected: <400> but was: <500>,两条都红,
同类其余 16 条绿)→ 改完该件 18/18 绿 → 整包 1212/0/0/0(21.4s)。
开放文档 §10.1 那句从「只写 msg 形状、不写码值」改成写死 code=400(按上面的实测口径)。b1c8f6fb 里的 docx:保持现状(按裁定未动,历史未改写)。third_shop_id 重复仍是 code=500
—— 它与上面同类,但只有一处入口、且开放文档第 82 行已按 500 写给外部调用方,
改它等于动一条既有对外契约。要统一请单独说。另:probe 结论提醒 —— 顾客端拒收文案能上屏,是因为 GlobalExceptionHandler 对
BusinessException 不带 @ResponseStatus(HTTP 200 + body code),顾客页自建实例
读的是 res.data.msg;如果哪天有人把它改成真 4xx/5xx,顾客页会 fallback 成
「网络异常,请检查网络后重试」,那句「你所在地区未开通」就再也到不了顾客眼前。
需求(用户原文):「还有个点做了没?har的下单开放api,我看文档中没有让调用方传用户ip」 → 定案:「要么让调用方传用户地区也可以,省的我们解析了」→ 硬性要求: 「那就传地区,但是要在开放文档里写清楚地区的格式,要按照我们接收的格式来, 不然找不到门店就麻烦了」。
为什么不能按 IP:openapi 是服务器对服务器调用,请求 IP 是调用方机房的,不是顾客的 (只有收款链接那条腿能按 IP —— 顾客浏览器直连)。所以本次让调用方直传 GB/T 2260 省市码 (不传 IP、平台不解析)。
shop_code / third_shop_code 优先,两个地区字段被忽略(现状不变);third_shop_code
(实体与上游请求两处),响应回显挑中的那家;city_code 不给 province_code ⇒ 400(省码是地区定位的根);out_biz_no(同一 out_biz_no 重复调用挑到同一家)。origin/java 停在 f09e9cce;未推 = 2717f793(夹具,本批之前)+ 下表两刀 + 本节自身的提交及其订正;不写死总数 —— 一律以 git rev-list --count origin/java..java 现量为准)| 提交 | 内容 |
|---|---|
cb153add |
后端:DTO 两个选填字段;HarShopService#pickForOrder(校验 + 挑店 + 三种 400,抽出 requireKnownProvince 与建店共用同一句文案);HarOrderService#create 接线(挑店块 + builder 带 thirdShopCode);新契约测试类 HarOrderCreateJsonContractTest |
fcf026c7 |
开放文档 har-openapi.md 10.2:字段两行 + 挑店说明块(格式按接收口径逐字)+ 响应表 third_shop_code 语义订正 + 章首补句 |
logs/_tdd-region-red-1.txt):三个测试文件红于编译错 ——
pickForOrder 不存在(8 处)、setProvinceCode/setCityCode 不存在、
HarOrderService 期望 4 参实到 5 参。原因全部是「功能不存在」,无笔误。logs/_tdd-region-green-1.txt):86/0/0/0 =
挑店 22(13→22)+ 下单 61(54→61)+ 新契约 3。| 变异体 | 红集(预测=实测) | |---|---| | 城市码整个忽略 | 4 红(cityTier 挑成 SZ + 三条 400 文案/校验),82 对照绿 | | 显式门店码优先失效 | 2 红(两个 explicit 用例 NPE),84 对照绿 | | 挑不出来不拒收 | 3 红(三条 400 守卫,none thrown),83 对照绿 | | 散列种子换成 enterpriseId | 4 红(四条钉了精确种子的 create 用例),82 对照绿 | | 上游发 dto 的而不是挑中的 | 1 红(「挑中的店必须真的发到上游」NJ→null),85 对照绿 | | 无害重构(守卫提局部变量) | 全绿 86/86 ✓ |
还原复核:HarShopService 6341E642…、HarOrderService 41745D49…、
HarOrderCreateDTO 40165A56…(与变异前一致)。
logs/_mvn-full-region-order.txt;1232 = 上一批 1213 + 本批 19)。b6b64c8a… / 旧 dist 80038a66… 作废):
java/target/payment-platform-1.0.0.jar,143,147,907 字节,12:13:28,
sha256 2F51689B49F8B51D5B890973BEDDF30557A553AD04CA732DA5D5BC2B772133D5。
开包回读(logs/_jar-readback-region-order.py,只读 jar 里的 .class 常量池):
pickForOrder、requireKnownProvince、四条 400 文案前缀、
DTO 的 provinceCode/cityCode/getProvinceCode/setCityCode 全为 True。frontend/dist.zip,9,376,009 字节,12:15:03,
sha256 3cbbdfffae3d6638fc662af1985173b685aafa928caa10c3ea2c4796d0c247b6。
281 条目 = 277 文件 + 4 目录条目(与上一版同形)、全正斜杠、testzip() 无坏成员;
新文档句(「按地区自动挑店」「就必须一并传 province_code」「该地区没有可用门店」)
只出现在 dist/js/index.BRvi1q4X.js —— 构建产物回读,zip 内也回读到同一文件
(logs/_dist-zip-region-order.py)。?raw 导入、类型恒为 string),未新增前端代码 ——
与上批「md 改动不进类型检查」的同一条实测口径。10.2 请求表加 province_code/city_code 两行,其后一块说明写死:与 10.1 建店 GB/T 2260
同一套码(完整六位:省级 XX0000、地级 XXXX00);传代码不是地名;city_code 必须属于 province_code;
只在没传任何门店码时生效;先同市、市无店回退全省;同地区多店按 out_biz_no 稳定;
挑中回显在响应 third_shop_code;三种 400 文案逐字(省份编码不存在: 999999 /
城市编码不属于该省: 330100(省 320000) / 省份编码不能为空: 传了 city_code 就必须一并传 province_code /
该地区没有可用门店: 省 320000(带市码时 该地区没有可用门店: 320100(省 320000)))——
这四个字符串与测试里钉的完全同一批,文档不是手抄的。(示例码原为短码,2026-10-09 按 GB/T 2260 六位订正,见 680406f9。)
db/har-shop-region.sql(若尚未跑;跑过就不必重复);2F51689B… 的 jar + 3cbbdfff… 的 dist.zip;HarCashierService#createQr)不带门店码,本批未动 —— 它要不要一并接
「地区」或「挑店」,待你拍板(现状=不带,不是回归)。pay_url 真机扫码仍未做过(老账,与 §四.2 相同)。