本地生活 APP 开发:城市 × 品类 × 履约形态的三维网格与三套账

写在前面:本地生活平台的复杂度不来自功能数量,来自它是三个维度的叉乘。本文给出一套可落地的建模方法:城市作为租户的数据隔离方案、品类作为业务开关的配置化、三种履约形态的订单模型拆分,以及三套资金账与三条合规线。配套提供城市配置表建表 SQL、密度测算脚本、履约范围判定脚本与季度涉税报送聚合 SQL。适合平台型项目的产品与技术负责人。

一、三维网格:先定分叉,再谈功能

多数本地生活项目的问题不在功能,而在架构假设错了------把「一个 APP 覆盖全国」当前提,所有城市数据挤一张表,用 city_id 区分。

三个维度必须在第一版就定义清楚:

维度 本质 定错后的返工代价
城市 数据隔离的最小单元(租户) 价格、活动、结算无法独立;统计需全表扫
品类 业务模型开关(决定履约/资质/表单) 每加一个品类要改核心表
履约形态 订单模型的类型分叉 塞一张表后拆表,接近重做

城市作为租户的设计要点:

  1. 配置隔离:城市的营业规则、结算周期、抽佣比例、活动全部按 city_id 存储,总部只保留模板。
  2. 数据隔离:订单、商家、商品按城市分片(逻辑分片即可,city_id 作为分区键),避免跨城联表。
  3. 权限隔离:城市运营只能操作本城数据。
  4. 复制方式:扩城 = 复制一份配置模板 + 初始化基础数据,不是在同一池子里插数据。

品类作为开关 的落地方式:把品类抽象成「能力组合」,而不是 category_id 字段。

yaml 复制代码
# category_profile.yaml ------ 品类能力配置
retail_food:
  name: 餐饮外卖
  fulfillment: delivery          # 履约形态:delivery / pickup / onsite
  license_required: [食品经营许可证]
  sku_model: dish_with_spec      # 菜品带规格(辣度/份量/加料)
  order_extra: [餐具份数, 备注]
  inventory: false               # 不按库存扣减,按可售状态
beauty_salon:
  name: 美业到店
  fulfillment: onsite
  license_required: [营业执照, 卫生许可证]
  sku_model: service_with_staff  # 项目绑定技师
  order_extra: [指定技师, 到店时间]
  inventory: slot                # 按技师时段占用
home_service:
  name: 家政上门
  fulfillment: onsite
  license_required: [营业执照]
  sku_model: service_with_duration
  order_extra: [服务地址, 房屋面积]
  inventory: slot

三个品类对应三套 sku_model 与 inventory 策略------这是「品类不是分类,是开关」的具体含义。

二、三种履约形态的订单模型必须拆开

这是本地生活架构上第一个必须定死的分叉:

形态 售卖对象 核心链路 关键表
到店核销 券 / 时段 购券 → 到店 → 核销 券实例表 + 核销记录表
到家配送 时效 下单 → 派单 → 配送 → 签收 配送单表 + 骑手轨迹
上门服务 人 + 时段 下单 → 派单 → 服务 → 验收 排班表 + 时段占用表

把三者塞进一张 order 表的典型后果:

  • 做「超时赔付」时发现没有配送时间字段,只能加字段并回填历史数据;
  • 做「核销防重」时发现没有核销记录,无法判断券是否已被使用;
  • 做「技师排班冲突」时发现没有时段占用表,只能靠订单表反推,性能与正确性都不可接受。

正确的结构是主订单表 + 形态子表 :order 存公共字段(用户、金额、状态机主链),order_voucher / order_delivery / order_onsite 各存形态特有字段。查询时按形态 join,写入时按形态分别落表。

sql 复制代码
-- 主订单表:只放跨形态公共字段
CREATE TABLE biz_order (
    id           BIGSERIAL PRIMARY KEY,
    order_no     VARCHAR(32) NOT NULL UNIQUE,
    city_id      BIGINT      NOT NULL,          -- 租户键
    merchant_id  BIGINT      NOT NULL,
    user_id      BIGINT      NOT NULL,
    biz_type     SMALLINT    NOT NULL,          -- 1 到店 2 到家 3 上门
    total_amount NUMERIC(10,2) NOT NULL,
    state        SMALLINT    NOT NULL,          -- 公共状态机主链
    created_at   TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX idx_order_city_state ON biz_order (city_id, state, created_at DESC);

-- 到店:券实例 + 核销记录(核销幂等靠唯一键)
CREATE TABLE order_voucher (
    order_id     BIGINT PRIMARY KEY REFERENCES biz_order(id),
    voucher_code VARCHAR(32) NOT NULL UNIQUE,
    valid_from   DATE NOT NULL,
    valid_to     DATE NOT NULL,
    status       SMALLINT DEFAULT 0             -- 0未用 1已核销 2已过期 3已退
);

CREATE TABLE voucher_redeem (
    id         BIGSERIAL PRIMARY KEY,
    order_id   BIGINT NOT NULL REFERENCES biz_order(id),
    store_id   BIGINT NOT NULL,
    operator   VARCHAR(64) NOT NULL,            -- 核销操作人
    redeem_at  TIMESTAMPTZ DEFAULT now(),
    UNIQUE (order_id)                           -- 一券只能核销一次
);

-- 到家:配送单(一单可拆多段)
CREATE TABLE order_delivery (
    id            BIGSERIAL PRIMARY KEY,
    order_id      BIGINT NOT NULL REFERENCES biz_order(id),
    rider_id      BIGINT,
    dispatch_at   TIMESTAMPTZ,
    pickup_at     TIMESTAMPTZ,
    delivered_at  TIMESTAMPTZ,
    distance_m    INT,
    timeout_flag  BOOLEAN DEFAULT FALSE          -- 超时标记供赔付计算
);

-- 上门:时段占用(人员 × 日期 × 起始分钟 唯一)
CREATE TABLE order_onsite (
    order_id    BIGINT PRIMARY KEY REFERENCES biz_order(id),
    staff_id    BIGINT NOT NULL,
    serve_date  DATE   NOT NULL,
    start_min   SMALLINT NOT NULL,
    end_min     SMALLINT NOT NULL,
    UNIQUE (staff_id, serve_date, start_min)     -- 防同时段重复派单
);

三、供给密度:扩城的判定公式

本地生活最反直觉的一条:决定成败的是供给密度,不是功能完整度。

粗糙但有效的模型:

单城日承载量 = 可服务商家数 × 单店日均接单能力

单城日需求 = 日活用户 × 下单转化率 × 人均频次

健康度 = 承载量 / 需求(建议留 1.2 倍以上余量)

三个过程指标比结果更早预警:

指标 计算 告警含义
履约半径内商家数 3km 内可下单商家计数 低于阈值则体验必然差
平均接单时长 下单 → 商家接单 中位数 上涨早于用户流失
单城订单密度 单城日单量 / 商家数 过低则商家陆续退出
python 复制代码
# city_health.py ------ 单城健康度测算
# 用法: python city_health.py city_config.json metrics.json
import json, sys

def health(cfg: dict, m: dict) -> dict:
    capacity = m["active_merchants"] * cfg["avg_orders_per_merchant"]
    demand = m["dau"] * cfg["order_cvr"] * cfg["freq_per_user"]
    ratio = capacity / demand if demand else float("inf")
    radius_ok = m["merchants_in_3km_median"] >= cfg["min_merchants_3km"]
    return {
        "capacity": round(capacity),
        "demand": round(demand, 1),
        "ratio": round(ratio, 2),
        "radius_ok": radius_ok,
        "verdict": "可扩城" if ratio >= 1.2 and radius_ok else "先补供给",
    }

if __name__ == "__main__":
    cfg = json.load(open(sys.argv[1], encoding="utf-8"))
    m = json.load(open(sys.argv[2], encoding="utf-8"))
    print(json.dumps(health(cfg, m), ensure_ascii=False, indent=2))

阈值放 city_config.json,按城市覆盖:avg_orders_per_merchant(单店日均单量,餐饮 25---35)、order_cvr(0.1---0.15)、freq_per_user(人均月频次 2---3)、min_merchants_3km(60 起)。四个值随城市等级调整,一线与三线不应共用。

扩城判据不是「功能做完了」,而是「第一个城市 ratio 与 radius_ok 同时成立并稳定一到两个季度」。缺这条线,就会在亏损城市继续加投。

四、Geo 分层与地图合规

4.1 三层地理模型

层级 用途 存储
城市 租户键、结算单位 独立表
商圈/网格 运营投放、骑手调度 网格编码
POI 商家地址、用户地址 经纬度 + 结构化地址

配送范围用多边形围栏而非半径圆:直线距离一公里在实际地形里可能绕行三公里(河、高架、封闭小区)。围栏数据可由图商等时圈能力生成,或运营手动绘制。

python 复制代码
# geo_check.py ------ 点是否在配送围栏内(射线法,轻量场景够用)
def in_polygon(lng: float, lat: float, poly: list) -> bool:
    """poly: [(lng, lat), ...] 闭合或非闭合均可"""
    inside = False
    n = len(poly)
    j = n - 1
    for i in range(n):
        xi, yi = poly[i]
        xj, yj = poly[j]
        if (yi > lat) != (yj > lat):
            x_cross = (xj - xi) * (lat - yi) / (yj - yi + 1e-12) + xi
            if lng < x_cross:
                inside = not inside
        j = i
    return inside

def check_order(addr: dict, merchant: dict) -> dict:
    if addr["city_id"] != merchant["city_id"]:
        return {"ok": False, "reason": "跨城订单,下单前拦截"}
    if not in_polygon(addr["lng"], addr["lat"], merchant["fence"]):
        return {"ok": False, "reason": "超出配送范围"}
    return {"ok": True, "reason": ""}

要点:跨城订单在提交前拦截,别等支付完成再退------退一单成本远高于拦一单。

4.2 地图合规三条硬线

要求 依据 产品动作
互联网地图服务需测绘资质 《地图管理条例》第三十三条(国务院令第 664 号) 使用有资质图商服务,不自采 POI 对外展示
地图须经审核并标注审图号,有效期 2 年 同上,第三十八条及地图审核相关规定 页面左下角标注审图号,到期重新送审
地图数据服务器须在境内 同上,第三十四条 境内部署,跨境业务单独评估

另需注意:每六个月须将新增标注内容备案;地图安全审校人员需经省级以上培训考核。擅自使用未经审核批准的地图提供服务,罚款上限二十万,情节严重可停业整顿、降级或吊销资质。

五、三套账与资金合规

平台资金三个流向,须分账记录:

账户类型 内容 结算对象
平台佣金 交易抽成 平台
商家结算 扣佣后货款 平台内经营者
从业者劳务 骑手/技师/服务人员报酬 平台内从业人员

分账的三个理由:对账(三方各一份账)、资金合规(平台留存商户结算资金自行清分属「二清」,须走持牌机构分账)、税务(见下节)。

六、涉税信息报送:2025 年后的新义务

《互联网平台企业涉税信息报送规定》(国务院令第 810 号,2025 年 6 月 20 日公布并施行)要点:

条款 要求
第三条 施行之日起 30 日内(或开展业务起 30 日内)报送平台域名、业务类型、运营主体统一社会信用代码与名称
第四条 季度终了的次月内,报送平台内经营者与从业人员的身份信息及上季度收入信息
第四条(豁免) 从事配送、运输、家政等便民劳务活动,依法享受税收优惠或不需要纳税的从业人员,不报送其收入信息
第六条 平台对报送信息的真实性、准确性、完整性负责;已尽核验义务且因对方过错导致不实的,不追究平台责任
第十条 未按期报送或瞒报漏报:责令改正,逾期罚 2---10 万;情节严重责令停业整顿并罚 10---50 万

系统侧要提前准备的结构化能力:

  1. 商家身份档案:统一社会信用代码、经营者名称、联系方式、行政许可,落库为结构字段且可导出(同时满足《电子商务法》第二十七条的核验登记与定期更新义务)。
  2. 季度收入聚合:按商家 × 季度聚合流水,字段口径与报送模板对齐。
  3. 从业人员分类标记:区分商家经营者与自然人从业人员,并对配送/家政类打上豁免标记。
sql 复制代码
-- 季度涉税报送聚合(示例口径:按商家聚合已结算流水)
SELECT
    m.unified_code                       AS 统一社会信用代码,
    m.merchant_name                      AS 经营者名称,
    date_trunc('quarter', o.created_at)  AS 所属季度,
    count(*)                             AS 订单数,
    sum(o.total_amount)                  AS 交易总额,
    sum(o.commission_amount)             AS 平台佣金
FROM biz_order o
JOIN merchant m ON m.id = o.merchant_id
WHERE o.state = 4                        -- 已完成
  AND o.created_at >= date_trunc('quarter', CURRENT_DATE - INTERVAL '3 month')
  AND o.created_at <  date_trunc('quarter', CURRENT_DATE)
GROUP BY 1, 2, 3
ORDER BY 5 DESC;

七、功能优先级分层

层级 内容 判断标准
第一层 商家入驻与资质审核、商品上架、下单支付、订单状态、核销/配送/派单、退款 不做无法开门
第二层 商家端接单、骑手端/服务人员端、地址管理、评价售后、结算对账、消息 决定体验下限
第三层 券与活动、会员储值、分享分销、数据看板、内容 决定增长
第四层 智能调度、推荐排序、跨品类营销、供应链 做深了才有意义

常见错误是把第四层写进第一版,而第一层的资质审核与售后尚未扎实。第一层不扎实,第二层以上全部空转。

八、需求评审易漏八项

序号 易漏项 后期代价
1 城市级价格与活动是否独立 全局配置改一次影响所有城
2 券的核销幂等与跨店限制 重复核销无法追溯
3 配送超时的判定口径与赔付 无配送时间字段则无法计算
4 上门服务的时段冲突 无占用唯一键则重复派单
5 跨城订单拦截节点 支付后才发现,退款成本高
6 商家资质的有效期与到期提醒 证照过期仍在营业
7 从业人员的劳务结算与代扣 补做需重构结算链路
8 涉税报送字段的可导出性 报送时现拼,易漏易错

九、小结

本地生活平台的架构核心是三条:城市即租户 (配置与数据隔离)、品类即开关 (能力组合而非分类字段)、形态即子表(主订单 + 形态子表)。运营核心是一条:先算密度,再谈扩城。合规核心是三条线:平台核验与连带责任(《电子商务法》第二十七条、第三十八条)、涉税信息按季报送(国务院令第 810 号)、地图使用有资质图商(《地图管理条例》第三十三条)。

相关推荐
2301_776045234 小时前
山寨币与比特币之间的关系
大数据·人工智能·区块链
code2cat5 小时前
Java工具篇之Maven:从pom.xml到测试通过的JAR
xml·java·maven
一枕槐安48712 小时前
少样本提示(Few-shot)在业务场景的落地实践
大数据·人工智能·意图分类·mmr多样性·knn检索·动态示例选择
wuminyu12 小时前
JVM规范学习总结:解释器与JIT编译器如何处理JVM字节码
java·linux·c语言·jvm·c++
txxzjmzlh12 小时前
SpringBoot 配置⽂件
java·spring boot·spring
Wang's Blog12 小时前
Java 项目实战: 交友项目 - 搜索附近的功能概述和分析
java·开发语言·交友
骑着蜗牛撵大象32713 小时前
大模型 Agent 的沙箱护栏:gVisor 隔离、出口白名单与审计闭环
java·开发语言·jvm·安全·agent沙箱·gvisor·microvm
独泪了无痕13 小时前
Hutool之LocalDateTimeUtil:让时间间隔操作如虎添翼
java
roman_日积跬步-终至千里13 小时前
零售与数仓(二):从经营问题到可信证据,数仓怎样建设?
大数据·零售