
东南亚华人团队选型「多语言团购系统」时技术评审常卡在数据不通i18n 资源、套餐主档、券占用与门店核销是否落在同一部署域与同一主键体系。下文用模块树、i18n 配置、核销状态机与联调验收说明海外版团购成品如何基于中台底座落地。示例为教学示意侧重架构说明而非菜单演示。场景痛点多语言展示层 vs 业务主数据常见反模式是「前端一套 locale 文件 核销小工具另库 券平台第三方」。结果是sku_id在交易库与核销库不一致切语言时复制出多行套餐库存各自扣减复购券按手机号匹配失败两套user_id低成本试水可以少开城、少开门店但不能接受主数据分裂。模块目录树示意overseas-groupbuy/ ├── gateway/ │ └── i18n-filter # Accept-Language / locale 解析 ├── mid-platform/ │ ├── user-service # 全局用户主档 │ ├── merchant-service # 商家/门店主档 │ ├── marketing-service # 券模板与占用 │ └── authz-service # 分站/角色权限 ├── groupbuy/ │ ├── catalog-service # 套餐主档 多语言文案表 │ ├── trade-service # 下单/支付回调 │ └── redeem-service # 到店核销 ├── admin-api/ # 统一后台入口 └── deploy/ ├── values-sea.yaml └── i18n/ ├── zh-CN.json ├── en-US.json └── th-TH.json原则locale 文件只承载展示文案catalog主档与user/merchant主档在 mid-platform 侧唯一redeem-service不维护第二套用户表。技术栈边界示意网关Nginx / API Gateway注入X-Locale、X-Station-Id服务Java / Go 均可关键是同一事务边界内写订单与券占用存储MySQL业务主库 Redis核销短码缓存 对象存储导出配置按国家启用语言列表与币种不按语言复制 SKU 行套餐主档与文案分离示意CREATETABLEgb_sku(sku_idBIGINTPRIMARYKEY,merchant_idBIGINTNOTNULL,station_idBIGINTNOTNULL,stock_qtyINTNOTNULL,price_minorBIGINTNOTNULLCOMMENT最小货币单位,currencyCHAR(3)NOTNULL,statusTINYINTNOTNULLCOMMENT1上架 0下架);CREATETABLEgb_sku_i18n(sku_idBIGINTNOTNULL,localeVARCHAR(16)NOTNULL,titleVARCHAR(256)NOTNULL,detail_mdTEXT,PRIMARYKEY(sku_id,locale));查询时按sku_id取主档再按localejoin 文案。禁止「泰语一套 sku_id、英语另一套 sku_id」。核销状态机示意CREATED → PAID → READY_TO_REDEEM → REDEEMED ↘ REFUNDED ↘ EXPIRED// 示意核销必须带着订单域状态迁移publicRedeemResultredeem(Stringcode,longstoreId,longoperatorId){CouponHoldholdholdRepo.lockByCode(code);if(hold.getStatus()!HoldStatus.READY_TO_REDEEM){thrownewBizException(HOLD_STATE_INVALID);}OrderorderorderRepo.lock(hold.getOrderId());assertSameStation(order.getStationId(),storeId);hold.markRedeemed(storeId,operatorId,Instant.now());order.markRedeemed();eventBus.publish(newRedeemedEvent(order.getId(),hold.getUserId()));returnRedeemResult.ok(order.getId());}核销成功必须同时更新券占用与订单状态只改门店本地「已核」列表视为数据不通。券占用表与支付回调把「待核销」写成库事实东南亚试水若只有「支付成功」弹窗、没有券占用行扩城后必然对不上。建议支付回调与占用落库同事务边界或最终一致但可对账。CREATETABLEgb_order(order_idBIGINTPRIMARYKEY,order_noVARCHAR(32)NOTNULLUNIQUE,user_idBIGINTNOTNULL,sku_idBIGINTNOTNULL,station_idBIGINTNOTNULL,currencyCHAR(3)NOTNULL,amount_minorBIGINTNOTNULL,status_codeVARCHAR(32)NOTNULL,locale_prefVARCHAR(16)NULL,paid_atDATETIMENULL,redeemed_atDATETIMENULL,INDEXidx_user(user_id,created_at),INDEXidx_station_status(station_id,status_code));CREATETABLEgb_coupon_hold(hold_idBIGINTPRIMARYKEY,order_idBIGINTNOTNULL,user_idBIGINTNOTNULL,sku_idBIGINTNOTNULL,redeem_codeVARCHAR(64)NOTNULLUNIQUE,statusVARCHAR(32)NOTNULL,-- READY_TO_REDEEM/REDEEMED/EXPIRED/REFUNDEDstore_idBIGINTNULL,operator_idBIGINTNULL,expire_atDATETIMENOTNULL,redeemed_atDATETIMENULL);// 教学示意支付成功 - 待核销TransactionalpublicvoidonPaid(StringorderNo,longamountMinor,Stringcurrency){GbOrderorderorderRepo.lockByNo(orderNo);if(!order.getAmountMinor().equals(amountMinor)||!order.getCurrency().equals(currency)){thrownewPaymentMismatchException(orderNo);}if(READY_TO_REDEEM.equals(order.getStatusCode()))return;// 幂等transit(order,PAID);transit(order,READY_TO_REDEEM);StringcodecodeGen.next();holdRepo.insert(CouponHold.ready(order,code,Duration.ofDays(30)));redis.opsForValue().set(redeem:code,order.getOrderId(),Duration.ofDays(30));}验收重复回调不生成第二枚短码金额/币种不一致拒绝gb_coupon_hold与gb_order状态同时进入待核销。网关路由与权限拒绝测例多语言团购系统的入口层要同时处理 locale、模块开关与分站范围避免业务服务各自解析Accept-Language并各写一套权限。# gateway 片段教学示意routes:-id:gb-tradepath:/api/gb/trade/**uri:lb://trade-servicefilters:[Authn,LocaleInject,StationScope,ModuleEnabledoverseas_groupbuy]-id:gb-redeempath:/api/gb/redeem/**uri:lb://redeem-servicefilters:[Authn,StationScope,ModuleEnabledoverseas_groupbuy]-id:gb-adminpath:/api/gb/admin/**uri:lb://admin-apifilters:[Authn,Rbac,StationScope]// 教学示意邻城核销拒绝publicvoidassertSameStation(longorderStationId,longstoreStationId){if(orderStationId!storeStationId){thrownewAccessDeniedException(cross-station redeem denied);}}拒绝测例建议原样进附件模块关闭后/api/gb/**→403统一后台菜单同步隐藏分站账号导出邻城核销明细 →403READY_TO_REDEEM之外状态核销 → 业务错误门店端不得本地记「已核」短码过期后核销 →EXPIRED库存/报表可区分于退款无 Token 访问核销接口 →401不支持的 locale未在enabledLocales→ 明确错误或回落fallback方案二选一写死并测# 教学示意curl-sS-o/dev/null-w%{http_code}\n\-HAuthorization: Bearer$STATION_B_TOKEN\$BASE/api/gb/admin/redeems?stationId$STATION_A# 期望403curl-sS-HAccept-Language: th-TH-HAuthorization: Bearer$USER\$BASE/api/gb/trade/skus/$SKU|jq.sku_id,.title# 期望sku_id 与 en-US 相同title 为泰语文案中台主数据与分站隔离示意# values-sea.yaml示意midPlatform:user:globalUnique:phone_e164# 东南亚试水常用手机号 E.164station:enforceScope:truegroupbuy:i18n:enabledLocales:[zh-CN,en-US,th-TH]fallback:en-USredeem:requireOrderState:READY_TO_REDEEMwriteBackOrder:trueenforceScope: true时分站账号读取邻城订单应返回 403。中台营销能力升级如新券模板类型可由marketing-service统一发布海外版团购模块复用无需每个烟囱重做。用户主档与商家主档必须全局唯一同一phone_e164不得因语言包或城市场景再开第二行。复购券、会员标签都依赖这一点否则「多语言团购系统」只是多套前台皮肤。-- 教学示意中台用户与商家海外团购复用CREATETABLEmdm_user(user_idBIGINTPRIMARYKEY,phone_e164VARCHAR(20)NOTNULLUNIQUE,statusTINYINTNOTNULL);CREATETABLEmdm_merchant(merchant_idBIGINTPRIMARYKEY,station_idBIGINTNOTNULL,statusTINYINTNOTNULL);CREATETABLEmdm_store(store_idBIGINTPRIMARYKEY,merchant_idBIGINTNOTNULL,station_idBIGINTNOTNULL,statusTINYINTNOTNULL);对照验收无中台时核销工具自建用户表有中台时gb_order.user_id必须等于mdm_user.user_id导出联表禁止再靠手机号模糊匹配。库存扣减与下架联动套餐库存与上架状态属于主档不属于某一种语言。下单扣减、退款回滚、下架拒绝新购都应打在gb_sku上。TransactionalpubliclongplaceOrder(PlaceCmdcmd){GbSkuskuskuRepo.lock(cmd.getSkuId());if(sku.getStatus()!1||sku.getStockQty()0){thrownewBizException(SKU_UNAVAILABLE);}sku.setStockQty(sku.getStockQty()-1);skuRepo.update(sku);returnorderRepo.insert(GbOrder.created(cmd,sku));}验收status0后zh-CN/en-US/th-TH三个会话下单均失败退款成功后库存回滚可查。禁止「泰语端下架、英语端仍可买」。联调验收清单同一user_id切换zh-CN/en-US/th-THsku_id不变仅gb_sku_i18n变化。支付回调将订单推至READY_TO_REDEEM短码写入 Redis 并落库重放回调不产生第二码。核销成功后订单与券占用均为REDEEMED后台联表可导出。商家将gb_sku.status0后交易接口拒绝新购多语言会话一致。邻城storeId核销应失败station scope。客户 VPC 内可执行日级备份与订单导出私有化交付边界。模块关闭与越权读导出返回403菜单同步隐藏。过期短码进入EXPIRED与REFUNDED可区分统计。部署清单海外版团购试水# 教学示意客户 VPC 最小拉起dockercompose-fdeploy/overseas-gb.yml up-dmysql redisdockercompose up-dgateway mid-user mid-merchant marketing groupbuy-trade groupbuy-redeem admincurl-sS$BASE/health|jq.curl-sS-HAccept-Language: en-US$BASE/api/gb/trade/ping附件建议固定values-sea.yaml语言列表、fallback、核销回写开关网关路由与ModuleEnabledoverseas_groupbuy状态机表 非法迁移测例i18n 键/文案覆盖率上架 SKU 三种 locale 齐全支付回调幂等与金额校验测例「用户—订单—券—门店」联表导出样例分站 403 与模块关闭 403 日志备份恢复与配置回滚说明低成本试水可以少开城但不能少这八项缺项等于数据不通风险未关闭。联表导出与读模型验收材料长什么样总部周末对账不应再收截图。读模型建议最少包含用户、订单、占用、门店四段主键语言字段只作展示辅助。CREATETABLEgb_redeem_export_row(order_noVARCHAR(32)NOTNULL,user_idBIGINTNOTNULL,sku_idBIGINTNOTNULL,hold_idBIGINTNOTNULL,redeem_codeVARCHAR(64)NOTNULL,status_codeVARCHAR(32)NOTNULL,store_idBIGINTNULL,station_idBIGINTNOTNULL,currencyCHAR(3)NOTNULL,amount_minorBIGINTNOTNULL,paid_atDATETIMENULL,redeemed_atDATETIMENULL);导出抽样断言示意 1) status_codeREDEEMED 时 store_id/redeemed_at 非空 2) 同一 order_no 在 zh/en/th 导出会话下主键列完全一致 3) EXPIRED 与 REFUNDED 分行计数禁止合并成「关闭」无中台拼装时导出往往要跨三个库手工 join且user_id对不上。有中台时统一后台一次导出即可回答「谁买了、哪家店核了、券还剩什么状态」。这是多语言团购系统能否支撑低成本试水的硬标准。过期任务与退款回滚试水也要有短码过期与退款回滚是数据不通的高发点门店以为还能核总部以为已关闭。// 教学示意日批过期publicintexpireHolds(Instantnow){ListCouponHoldlistholdRepo.listReadyBefore(now);for(CouponHoldh:list){h.setStatus(HoldStatus.EXPIRED);orderRepo.transit(h.getOrderId(),EXPIRED);holdRepo.update(h);}returnlist.size();}TransactionalpublicvoidonRefunded(StringorderNo){GbOrderorderorderRepo.lockByNo(orderNo);transit(order,REFUNDED);CouponHoldholdholdRepo.lockByOrderId(order.getOrderId());hold.setStatus(HoldStatus.REFUNDED);skuRepo.increaseStock(order.getSkuId(),1);}验收过期后核销失败退款后库存回滚且短码失效导出可分列统计。低成本试水可以不做复杂营销玩法但不能缺这两条任务。非法状态迁移也要有测例REDEEMED - READY_TO_REDEEM必须拒绝EXPIRED订单不得再次核销退款中的占用不得被门店端扫码成功。把这三条与网关 403 测例并列扩城前才能说「主数据与状态机已冻结」。营销占用与团购订单同域海外版团购若再外接一套券平台最常见后果是coupon_id对不上订单。中台营销占用应写入同一部署域可追溯字段。placeOrder - marketing.lock(user, template, order_no) onPaid - hold READY_TO_REDEEM redeem - hold REDEEMED order REDEEMED marketing.consume refund - marketing.release stock验收锁定、核销、释放三段流水能按order_no串起来禁止团购库与营销库各记各的、靠手机号事后对。中台营销能力升级时海外版团购按方案复用模板类型无需每个国家 fork 引擎。产品落法边界光合同城海外版团购为成品系统支持私有化源码独立部署。多语言、币种、支付通道按国家配置与定制合规结论由客户侧确定。系统侧不抽成客户平台订单。中台一体化表现为统一后台与主数据互通而不是把业务库默认放在不可控多租户集群。适合东南亚华人小团队首期只启团购模块与必要中台服务语言先中英加当地一种核销与导出先验通再扩门店。专项定制额外支付、字段、玩法按书面范围推进并评估是否触碰状态机与主数据边界。为什么这样验收多语言团购系统的失败模式很少是「少一种语言」更多是主数据分裂与核销不回写。架构说明把模块树、占用表、网关拒绝、导出断言写进附件后试水结论才可复制到下一城。语言可以下周再加用户主键与核销状态机不能下周再合并。两周试水联调日历可写进附件第一周聚焦主数据与购买链D1 拉起部署与语言列表D2 注册同一 user_id 并切三种 locale 看 sku_idD3 支付回调生成待核销占用D4 导出未核销清单核对金额币种D5 模块关闭与邻站 403。第二周聚焦核销与对账D6 到店核销回写D7 下架联动三语会话D8 过期任务D9 退款回滚库存D10 联表导出五行抽样与分站越权复测。日历不是形式而是把「数据不通」拆成每天可失败的断言。任一天失败就停扩城避免用门店数量掩盖主数据分裂。纯技术小结多语言团购系统的架构重点是把 i18n 留在文案表把库存、券占用、核销状态留在唯一业务主档并让核销服务写回同一订单状态机。海外版团购基于中台用户/商家/营销底座东南亚低成本试水可以少开城但验收必须包含「切语言 ID 不变 核销回写 联表导出」三项并补齐网关拒绝、支付幂等、过期退款与部署清单缺任一项数据不通会在扩城前先爆。