县城团队做同城O2O系统,技术选型里常有一个分叉:业务模块可以分期上线,但用户、商家、订单与用户商家核心资料能不能共用一套写入口?若外卖、跑腿、同城团购各维护独立用户表和 Admin 控制台,运营就要在多个后台之间切换;同一手机号在不同模块里是不同user_id,营销和对账只能人工拼。
下文从模块树、用户商家资料分层、配置与部署脚本说明「用户商家资料中枢 + 多业态扩展」怎么拆。示例为教学示意,以光合同城当期交付为准。
痛点:数据对不上的根因往往是用户商家资料分散
早期拼装方案常见结构:
- 外卖模块自带
wm_user,跑腿模块自带errand_user,团购模块自带group_user - 各模块独立 Admin 控制台,运营按业态切换 URL
- 订单表按模块复制,靠手机号后匹配「假装是同一人」
后果很直观:改配送状态进 A 后台,财务导出在 B 后台;加营销券要在三个系统各配一遍。同城O2O系统若目标是统一后台一体化,架构上应先收敛用户商家资料写路径,再谈 UI 有多少菜单。
县城评审时可先问供应商三个问题:用户主表是否全业态共用?商家开通记录是否一张关联表表达?Admin 是否按biz_type筛单而不是按模块分域名?答不上来,后面运营多半要在多个后台之间对不上数。
模块目录:用户商家资料层与业态插件分离
county-o2o-platform/ ├── apps/ │ ├── user-app/ # 用户端:下单、取消、评价 │ ├── merchant-app/ # 商家端:接单、拒单、出餐/核销 │ ├── rider-app/ # 配送端:取货、在途、送达 │ └── admin-console/ # 统一运营后台:跨业态筛单、导出 ├── master-data-hub/ # 主数据中枢(唯一写入口) │ ├── user-master/ │ ├── merchant-master/ │ ├── order-master/ │ └── sync-outbox/ ├── biz-plugins/ │ ├── takeout/ # 外卖扩展字段与履约策略 │ ├── errand/ # 跑腿扩展 │ ├── group-buy/ # 同城团购扩展 │ └── hotel/ # 酒店预定扩展(可选) ├── mid-shared/ │ ├── marketing-core/ # 券、满减、会员标签 │ ├── permission-core/ # 角色、菜单、数据权限 │ └── settlement-export/ # 结算导出字段配置 └── ops/ ├── biz-type-routing.yaml ├── master-data-sync.yaml └── deploy-modules.yaml验收要点:takeout、errand等插件禁止直连数据库 INSERT 用户或商家主表;所有用户商家资料变更经master-data-hub写入口。
用户与商家核心资料:一张表、多业态关联
多后台方案常伴随「每个模块一张 user 表」。正确做法是master-data-hub层维护用户商家核心资料,各业态只引用 ID。
-- 用户主表:全业态共用CREATETABLEmd_user(idBIGINTPRIMARYKEYAUTO_INCREMENT,mobileVARCHAR(20)NOTNULLUNIQUE,nicknameVARCHAR(64)NULL,member_levelTINYINTNOTNULLDEFAULT0,city_codeVARCHAR(12)NOTNULL,created_atDATETIMENOTNULL,updated_atDATETIMENOTNULL,INDEXidx_city_mobile(city_code,mobile));-- 商家主表CREATETABLEmd_merchant(idBIGINTPRIMARYKEYAUTO_INCREMENT,nameVARCHAR(128)NOTNULL,city_codeVARCHAR(12)NOTNULL,statusVARCHAR(16)NOTNULLCOMMENT'active|suspended|closed',settle_modeVARCHAR(16)NOTNULL,created_atDATETIMENOTNULL);-- 商家开通哪些业态,用关联表表达,而不是再建 merchant 副本CREATETABLEmd_merchant_biz(merchant_idBIGINTNOTNULL,biz_typeVARCHAR(16)NOTNULL,enabledTINYINTNOTNULLDEFAULT1,PRIMARYKEY(merchant_id,biz_type));-- 用户跨业态消费汇总(读模型,可由事件异步刷新)CREATETABLEmd_user_biz_summary(user_idBIGINTNOTNULL,biz_typeVARCHAR(16)NOTNULL,order_countINTNOTNULLDEFAULT0,last_order_atDATETIMENULL,PRIMARYKEY(user_id,biz_type));同城O2O系统验收时可固定一个测试手机号:在takeout下单后在group_buy再下一单,断言两笔单的user_id相同。若靠运营后台手工「绑定手机号」才算同一人,说明用户商家资料未打通,后续营销券无法跨业态复用。
订单主表:通用字段 + 业态扩展
CREATETABLEmd_order(idBIGINTPRIMARYKEYAUTO_INCREMENT,order_noVARCHAR(32)NOTNULLUNIQUE,biz_typeVARCHAR(16)NOTNULLCOMMENT'takeout|errand|group_buy|...',user_idBIGINTNOTNULL,merchant_idBIGINTNOTNULL,city_codeVARCHAR(12)NOTNULL,statusVARCHAR(32)NOTNULL,pay_amountDECIMAL(12,2)NOTNULLDEFAULT0,created_atDATETIMENOTNULL,updated_atDATETIMENOTNULL,INDEXidx_user_biz(user_id,biz_type),INDEXidx_merchant_status(merchant_id,status))COMMENT='同城O2O统一订单主表';CREATETABLEmd_order_takeout_ext(order_idBIGINTPRIMARYKEY,delivery_typeVARCHAR(16)NOTNULL,expect_timeDATETIMENULL,rider_idBIGINTNULL,CONSTRAINTfk_takeout_orderFOREIGNKEY(order_id)REFERENCESmd_order(id));所有status变更只经order-master/state-machine,并写审计日志。Admin 控制台按biz_type筛单,但 URL 仍是同一套。
配置:业态路由与模块启停
# ops/biz-type-routing.yaml(示意)admin_console:base_url:/adminfilter_by:biz_typedefault_modules:-takeout-errandoptional_modules:-group_buy-hotel-errandmaster_data:user:single_table:md_userforbid_module_copy:truemerchant:single_table:md_merchantbiz_enable_table:md_merchant_bizsync:outbox_table:md_sync_outboxconsumers:-marketing-core-settlement-export# ops/deploy-modules.yaml(示意)county_pilot:city_code:"410000"enabled_biz:-takeout-erranddisabled_biz:-group_buy-hotelmaster_data_hub:requiredadmin_mode:unified县城首期试点建议只启两业态,但用户商家资料层全量部署,避免后续加模块时重新导用户和商家。
部署脚本:用户商家资料层先于业态插件
#!/usr/bin/env bash# deploy-county-o2o.sh(示意)set-euopipefailENV=${1:-staging}MODULES=${2:-"takeout,errand"}echo"[1/4] 部署主数据中枢 master-data-hub ..."./scripts/deploy.sh master-data-hub--env"$ENV"echo"[2/4] 初始化主数据表结构 ..."mysql-h"$DB_HOST"-u"$DB_USER"-p"$DB_PASS""$DB_NAME"<sql/md_user.sql mysql-h"$DB_HOST"-u"$DB_USER"-p"$DB_PASS""$DB_NAME"<sql/md_merchant.sql mysql-h"$DB_HOST"-u"$DB_USER"-p"$DB_PASS""$DB_NAME"<sql/md_order.sqlecho"[3/4] 部署统一 Admin 控制台 ..."./scripts/deploy.sh admin-console--env"$ENV"echo"[4/4] 按清单启停业态插件:$MODULES"IFS=','read-raBIZ<<<"$MODULES"forbin"${BIZ[@]}";do./scripts/deploy.sh"biz-plugins/$b"--env"$ENV"doneecho"验收:固定测试手机号跨$MODULES各下一单,检查 user_id 一致。"与统一后台能力复用的关系
用户商家资料打通后,统一后台侧marketing-core、permission-core等能力可按方案被各业态插件引用:
- 券模板、满减规则在
mid-shared维护,各biz_type按配置引用 - 权限策略一处变更,Admin 各业态菜单同步生效
- 结算导出字段在
settlement-export统一配置,避免各模块导出格式不一致
光合同城国内综合形态走统一后台一体化路线:内置多业务模块,模块数据互通、后台统一管理。成品可直接部署;支持私有化源码与按需定制,以当期书面方案为准。
验收清单(架构评审可用)
- 用户主表是否全业态共用?禁止各模块复制 user 表。
- 商家开通记录是否用
md_merchant_biz表达,而非各模块各建 merchant? - 订单主表是否统一,扩展字段是否放扩展表?
- Admin 是否同一 URL,按
biz_type筛单? - 加第三业态时,是否只需启插件、无需重导用户和商家?
同城O2O系统架构里,用户商家资料怎么打通,决定了后面运营是对数还是跑业务。先把写路径收敛到master-data-hub,再按节奏叠业态插件,比每个模块各存各的,往往更稳。