地级市综合运营商做本地生活服务系统 ,技术评审里除了问「有哪些模块」,更该问「模块边界怎么划、结算字段放哪张表」。若外卖、跑腿、同城团购各维护独立结算导出逻辑,财务对账就要人工拼;加模块时结算规则又要重写一遍,专项定制预算很快烧在返工上。
下文从模块边界、结算表结构、Service 伪代码说明「统一结算中枢 + 业态扩展字段」怎么拆。示例为教学示意,以光合同城当期交付为准。

痛点:结算返工根因往往是字段散落各模块
早期拼装方案常见结构:
- 外卖模块自带
wm_settle_detail,跑腿模块自带errand_settle_detail - 各模块独立导出 CSV,列名、精度、状态枚举各不一致
- 加第三业态时,财务要求的核对列又要重新开发
后果很直观:周会前要对结算,运营从三个后台各导一份表再拼;改一条商务规则,要在多个系统各改一遍。本地生活服务系统 若走统一后台一体化,架构上应先收敛结算写路径与导出配置,再谈 UI 有多少菜单。
地级市评审时可先问:结算明细是否全业态共用主表?导出列是否可配置?加模块时结算规则是否可继承?答不上来,后面多半要反复花钱改字段。
模块边界:订单中枢、结算中枢、业态插件
text
local-life-service/
├── apps/
│ ├── user-app/
│ ├── merchant-app/
│ ├── rider-app/
│ └── admin-console/ # 统一运营后台
├── order-hub/ # 订单中枢(唯一写入口)
│ ├── command-api/
│ ├── state-machine/
│ └── read-model/
├── settlement-hub/ # 结算中枢(唯一写入口)
│ ├── settle-command/
│ ├── export-config/
│ ├── reconcile-read/
│ └── field-mapping.yaml
├── biz-plugins/
│ ├── takeout/
│ ├── errand/
│ └── group-buy/
├── mid-shared/
│ ├── user-master/
│ ├── merchant-master/
│ └── marketing-core/
└── ops/
├── settle-export-columns.yaml
└── biz-settle-rules.yaml
边界原则:
order-hub管订单状态,settlement-hub管结算明细与导出,业态插件禁止直连 UPDATE 结算主表。- 各
biz-plugin只提供业态扩展字段与合法状态分支,结算通用字段在settlement-hub统一维护。 - Admin 控制台结算导出走
export-config,列定义来自 YAML,而非各模块硬编码。
结算主表:通用字段 + 业态扩展
sql
-- 结算明细主表:全业态共用
CREATE TABLE st_settle_detail (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT NOT NULL,
order_no VARCHAR(32) NOT NULL,
biz_type VARCHAR(16) NOT NULL COMMENT 'takeout|errand|group_buy|...',
merchant_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
city_code VARCHAR(12) NOT NULL,
order_amount DECIMAL(12,2) NOT NULL COMMENT '订单原价',
discount_amount DECIMAL(12,2) NOT NULL DEFAULT 0 COMMENT '优惠抵扣',
platform_fee DECIMAL(12,2) NOT NULL DEFAULT 0 COMMENT '平台服务费(客户自定规则)',
merchant_income DECIMAL(12,2) NOT NULL COMMENT '商家应得',
rider_fee DECIMAL(12,2) NULL COMMENT '配送费(如有)',
settle_status VARCHAR(16) NOT NULL COMMENT 'pending|confirmed|paid|disputed',
settle_batch_no VARCHAR(32) NULL COMMENT '结算批次号',
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
INDEX idx_merchant_batch (merchant_id, settle_batch_no),
INDEX idx_biz_status (biz_type, settle_status)
) COMMENT='本地生活统一结算明细';
-- 外卖结算扩展
CREATE TABLE st_settle_takeout_ext (
settle_id BIGINT PRIMARY KEY,
delivery_subsidy DECIMAL(12,2) NULL COMMENT '配送补贴',
packaging_fee DECIMAL(12,2) NULL,
CONSTRAINT fk_takeout_settle FOREIGN KEY (settle_id) REFERENCES st_settle_detail(id)
);
-- 跑腿结算扩展
CREATE TABLE st_settle_errand_ext (
settle_id BIGINT PRIMARY KEY,
distance_km DECIMAL(8,2) NULL,
weight_surcharge DECIMAL(12,2) NULL,
CONSTRAINT fk_errand_settle FOREIGN KEY (settle_id) REFERENCES st_settle_detail(id)
);
-- 导出配置:列定义可配置,避免各模块硬编码
CREATE TABLE st_export_column_def (
id INT PRIMARY KEY AUTO_INCREMENT,
column_key VARCHAR(32) NOT NULL UNIQUE,
column_label VARCHAR(64) NOT NULL,
source_table VARCHAR(64) NOT NULL COMMENT 'st_settle_detail|ext',
source_field VARCHAR(64) NOT NULL,
sort_order INT NOT NULL DEFAULT 0,
enabled TINYINT NOT NULL DEFAULT 1
);
设计约束:
merchant_income、platform_fee等通用字段只在st_settle_detail维护,禁止各模块复制结算表。- 业态差异放
st_settle_*_ext,导出时 JOIN 主表与扩展表。 - 商务结算规则由客户确定;系统提供可配置核对字段与导出项,不作收益承诺。
导出列配置:YAML 驱动,少返工
yaml
# ops/settle-export-columns.yaml(示意)
default_export:
columns:
- key: order_no
label: 订单号
source: st_settle_detail.order_no
- key: biz_type
label: 业态
source: st_settle_detail.biz_type
- key: merchant_name
label: 商家名称
source: md_merchant.name
join: merchant_id
- key: order_amount
label: 订单金额
source: st_settle_detail.order_amount
- key: discount_amount
label: 优惠抵扣
source: st_settle_detail.discount_amount
- key: platform_fee
label: 平台服务费
source: st_settle_detail.platform_fee
- key: merchant_income
label: 商家应得
source: st_settle_detail.merchant_income
- key: settle_status
label: 结算状态
source: st_settle_detail.settle_status
- key: settle_batch_no
label: 结算批次
source: st_settle_detail.settle_batch_no
takeout_extra:
- key: delivery_subsidy
label: 配送补贴
source: st_settle_takeout_ext.delivery_subsidy
when_biz: takeout
errand_extra:
- key: distance_km
label: 配送公里
source: st_settle_errand_ext.distance_km
when_biz: errand
财务临时要求加一列核对项时,优先在 YAML 和 st_export_column_def 配置,而非各模块改硬编码导出逻辑。这是减少本地生活服务系统后期返工的关键。
业态结算规则:分支在配置,不在代码复制
yaml
# ops/biz-settle-rules.yaml(示意)
common:
platform_fee_mode: percent # percent|fixed|tiered(客户配置)
platform_fee_rate: 0.05 # 示意值,客户自定
merchant_income_formula: "order_amount - discount_amount - platform_fee"
takeout:
inherit: common
extra_fields:
- packaging_fee
- delivery_subsidy
rider_fee_source: order_ext.rider_fee
errand:
inherit: common
extra_fields:
- distance_km
- weight_surcharge
rider_fee_source: calculated_by_distance
group_buy:
inherit: common
extra_fields:
- verify_code
- verify_time
加第三业态时,只需在 YAML 增加 inherit: common 与扩展字段清单,结算主表结构不变。
Service 伪代码:结算唯一写入口
java
@Service
public class SettlementCommandService {
public SettleDetail confirmSettle(ConfirmSettleCmd cmd, Operator op) {
Order order = orderRepo.findById(cmd.getOrderId());
SettleRule rule = ruleLoader.load(order.getBizType());
SettleDetail detail = new SettleDetail();
detail.setOrderId(order.getId());
detail.setOrderNo(order.getOrderNo());
detail.setBizType(order.getBizType());
detail.setMerchantId(order.getMerchantId());
detail.setOrderAmount(order.getPayAmount());
detail.setDiscountAmount(calcDiscount(order));
detail.setPlatformFee(calcPlatformFee(order, rule));
detail.setMerchantIncome(calcMerchantIncome(order, rule));
detail.setSettleStatus("pending");
settleRepo.save(detail);
saveBizExtIfNeeded(detail, order, rule);
auditLog.append("settle_confirmed", detail.getId(), op);
return detail;
}
private BigDecimal calcPlatformFee(Order order, SettleRule rule) {
if ("percent".equals(rule.getPlatformFeeMode())) {
return order.getPayAmount()
.multiply(rule.getPlatformFeeRate())
.setScale(2, RoundingMode.HALF_UP);
}
// fixed / tiered 分支略
return BigDecimal.ZERO;
}
}
java
@Service
public class SettlementExportService {
public ExportFile exportByBatch(String batchNo, ExportProfile profile) {
List<ColumnDef> columns = columnDefRepo.load(profile);
List<Map<String, Object>> rows = reconcileRead.query(batchNo, columns);
return csvWriter.write(rows, columns);
}
}
所有结算明细生成只经 SettlementCommandService;导出只经 SettlementExportService,Admin 控制台不得各模块各自写导出 SQL。
与统一后台的关系
结算中枢与订单中枢、用户核心资料同属统一后台层:
- 统一后台: 结算导出、批次确认、争议标记在同一 Admin 入口,按
biz_type筛选。 - 数据互通: 结算明细关联统一
order_id、merchant_id、user_id,禁止各模块独立结算副本。 - 能力共享: 新增导出列、调整核对字段,在
settlement-hub/export-config一处变更,各业态同步生效。
光合同城国内综合形态走统一后台一体化路线:内置多业务模块,模块数据互通、后台统一管理。成品可直接部署;支持私有化源码与按需定制,商务结算规则由客户自行确定。
验收清单(技术评审可用)
- 结算主表是否全业态共用?禁止各模块复制
settle_detail。 - 导出列是否 YAML 可配置?财务临时加列是否不必改各模块代码?
- 加第三业态时,结算规则是否
inherit: common即可? - 结算写路径是否唯一?禁止业态插件直连 UPDATE 结算表。
- 统一 Admin 是否一处导出,而非每个业态各导一份再拼?
本地生活服务系统里,模块边界与结算字段怎么拆,决定了后期是改配置还是反复返工。先把结算中枢和导出配置收敛,再按节奏叠业态插件,比每个模块各存各的结算逻辑,往往更稳。