预算有限搭建校园外卖小程序:校门中转幂等与高峰验收

简介: 预算有限时先按成熟SaaS跑通单校,软件、模块、部署和第三方费用分项询价,售后拆成系统售后与运营售后,用正常单、退款和高峰批量中转验收。本文给出收餐幂等状态机、表约束、核心代码和重复收餐反例。

预算有限搭建校园外卖小程序,可先按成熟SaaS跑通单校:软件套餐、增值模块、部署环境、第三方服务、定制开发、培训维护分项询价;将售后拆成系统售后与运营售后;稳定性不能只看演示后台,而要用正常单、退款和午晚高峰批量中转验收。校园高峰常见工程故障是同一订单重复扫码或网络重试,导致重复通知、重复记账。下面用可复现的收餐幂等状态机,说明数据模型、核心代码、异常反例和业务验收。

一、预算与售后先落成可验收边界

预算有限时,先定单校标准SaaS路线,再做费用分项。软件套餐、增值模块、部署环境、第三方服务、定制开发、培训与维护分开询问,同时确认小程序认证、短信、云资源和支付相关费用由谁承担、是一次性还是续费。历史成交价只对应当时配置,当前报价按模块和交付范围确认。先买单校跑通所需的基础模块,订单增长后再加多校区、分账或定制。

费用项 要问清的内容 验收凭证
软件套餐 版本、终端、订单量或校区限制 功能清单、演示账号
增值模块 营销、分账、多校区是否另收费 模块报价、开通条件
部署与第三方 云资源、认证、短信、支付通道由谁承担 费用归属表、开通清单
培训与维护 上线指导、版本维护、受理渠道 服务说明、合同条款

完整售后要拆成两层。系统售后负责故障受理、版本维护、配置指导和升级通知;运营售后负责商家、骑手、用户投诉、退款纠纷和排班。合同里写受理渠道、响应级别、升级路径、培训与故障处理边界;预算有限时,运营售后可用学生兼职客服加骑手组长,系统售后按服务商条款核对。营销功能按拉新、首单、复购和时段活动列清单,逐项询问版本、叠加规则、优惠承担方、退款后券处理和是否另收费。

二、数据模型:一单只能进一个中转批次

校门中转要把订单状态、批次、扫描记录、通知和结算分开建模。订单状态采用READY_FOR_CAMPUS→CAMPUS_RECEIVED→DELIVERED,退款走REFUNDED旁路;version做乐观锁。batch_order对order_id加唯一约束,保证一个订单只能进入一个中转批次;order_status_log和outbox_notify的idempotency_key唯一,保证一次状态迁移只产生一次日志和一次取餐通知;settlement_ledger的biz_key唯一,保证送达结算只记一次。

表 关键字段 约束与用途
campus_order order_id、status、version、paid_amount 主键;状态枚举;乐观锁防并发覆盖
sort_batch batch_id、state、station_id 批次仅OPEN可收餐
batch_order batch_id、order_id、scan_code order_id唯一;防跨批次重复收餐
order_status_log idempotency_key、from_status、to_status 幂等键唯一;审计状态迁移
outbox_notify idempotency_key、event_type、state 幂等键唯一;防重复通知
settlement_ledger biz_key、account_type、amount biz_key唯一;防重复结算
CREATE TABLE campus_order (
  order_id TEXT PRIMARY KEY,
  campus_id TEXT NOT NULL,
  building_id TEXT NOT NULL,
  status TEXT NOT NULL CHECK (status IN ('READY_FOR_CAMPUS','CAMPUS_RECEIVED','DELIVERED','REFUNDED')),
  version INTEGER NOT NULL DEFAULT 0,
  paid_amount NUMERIC NOT NULL DEFAULT 0,
  updated_at TEXT NOT NULL
);

CREATE TABLE sort_batch (
  batch_id TEXT PRIMARY KEY,
  campus_id TEXT NOT NULL,
  station_id TEXT NOT NULL,
  state TEXT NOT NULL CHECK (state IN ('OPEN','CLOSED'))
);

CREATE TABLE batch_order (
  batch_id TEXT NOT NULL,
  order_id TEXT NOT NULL UNIQUE,
  scan_code TEXT NOT NULL,
  created_at TEXT NOT NULL,
  PRIMARY KEY (batch_id, order_id),
  FOREIGN KEY (batch_id) REFERENCES sort_batch(batch_id),
  FOREIGN KEY (order_id) REFERENCES campus_order(order_id)
);

CREATE TABLE order_status_log (
  log_id INTEGER PRIMARY KEY AUTOINCREMENT,
  order_id TEXT NOT NULL,
  from_status TEXT NOT NULL,
  to_status TEXT NOT NULL,
  action TEXT NOT NULL,
  idempotency_key TEXT NOT NULL UNIQUE,
  created_at TEXT NOT NULL
);

CREATE TABLE outbox_notify (
  msg_id INTEGER PRIMARY KEY AUTOINCREMENT,
  order_id TEXT NOT NULL,
  event_type TEXT NOT NULL,
  idempotency_key TEXT NOT NULL UNIQUE,
  state TEXT NOT NULL DEFAULT 'PENDING',
  created_at TEXT NOT NULL
);

CREATE TABLE settlement_ledger (
  entry_id INTEGER PRIMARY KEY AUTOINCREMENT,
  order_id TEXT NOT NULL,
  account_type TEXT NOT NULL,
  amount NUMERIC NOT NULL,
  biz_key TEXT NOT NULL UNIQUE,
  created_at TEXT NOT NULL
);

三、核心实现:批量收餐幂等

收餐函数按七步执行:开启写事务;校验批次为OPEN;查订单状态与已有批次;同一批次重复扫描且状态已收,返回ALREADY_IN_BATCH,不新增通知;订单已在其他批次,抛DUPLICATE_RECEIVE_CONFLICT并回滚;合法订单用status和version做条件更新,再插入批次记录、状态日志和通知;提交或回滚。关键点是唯一约束与条件更新同时存在,既防重复扫描,也防并发覆盖。

微订校园配送按站点和宿舍汇总订单,展示倒计时与扫码收单入口。

微订校园配送按站点和宿舍汇总订单,展示倒计时与扫码收单入口。

import sqlite3
from datetime import datetime

def now():
    return datetime.utcnow().isoformat(timespec='seconds') + 'Z'

def connect():
    conn = sqlite3.connect('campus.db')
    conn.execute('PRAGMA foreign_keys=ON')
    return conn

def receive_to_batch(conn, batch_id, order_id, scan_code, operator='station-1'):
    conn.execute('BEGIN IMMEDIATE')
    try:
        batch = conn.execute('SELECT state FROM sort_batch WHERE batch_id=?', (batch_id,)).fetchone()
        if not batch or batch[0] != 'OPEN':
            raise RuntimeError('BATCH_NOT_OPEN')
        order = conn.execute('SELECT status,version FROM campus_order WHERE order_id=?', (order_id,)).fetchone()
        if not order:
            raise RuntimeError('ORDER_NOT_FOUND')
        existing = conn.execute('SELECT 1 FROM batch_order WHERE order_id=?', (order_id,)).fetchone()
        if existing:
            same = conn.execute('SELECT 1 FROM batch_order WHERE order_id=? AND batch_id=?', (order_id, batch_id)).fetchone()
            if same and order[0] == 'CAMPUS_RECEIVED':
                conn.commit()
                return {
   'result':'ALREADY_IN_BATCH','order_id':order_id,'batch_id':batch_id,'status':order[0],'notify_rows':0}
            raise RuntimeError('DUPLICATE_RECEIVE_CONFLICT')
        if order[0] != 'READY_FOR_CAMPUS':
            raise RuntimeError('ILLEGAL_STATUS')
        cur = conn.execute('UPDATE campus_order SET status=?,version=version+1,updated_at=? WHERE order_id=? AND status=? AND version=?',
                           ('CAMPUS_RECEIVED', now(), order_id, 'READY_FOR_CAMPUS', order[1]))
        if cur.rowcount != 1:
            raise RuntimeError('CONCURRENT_UPDATE')
        conn.execute('INSERT INTO batch_order(batch_id,order_id,scan_code,created_at) VALUES(?,?,?,?)',
                     (batch_id, order_id, scan_code, now()))
        key = 'receive:' + batch_id + ':' + order_id
        conn.execute('INSERT INTO order_status_log(order_id,from_status,to_status,action,idempotency_key,created_at) VALUES(?,?,?,?,?,?)',
                     (order_id, 'READY_FOR_CAMPUS', 'CAMPUS_RECEIVED', 'RECEIVE', key, now()))
        conn.execute('INSERT INTO outbox_notify(order_id,event_type,idempotency_key,created_at) VALUES(?,?,?,?)',
                     (order_id, 'PICKUP_READY', 'notify:' + key, now()))
        conn.commit()
        return {
   'result':'RECEIVED','order_id':order_id,'batch_id':batch_id,'status':'CAMPUS_RECEIVED','notify_rows':1}
    except Exception:
        conn.rollback()
        raise

def mark_delivered(conn, order_id):
    conn.execute('BEGIN IMMEDIATE')
    try:
        order = conn.execute('SELECT status,paid_amount FROM campus_order WHERE order_id=?', (order_id,)).fetchone()
        if not order or order[0] != 'CAMPUS_RECEIVED':
            raise RuntimeError('ILLEGAL_STATUS')
        cur = conn.execute('UPDATE campus_order SET status=?,updated_at=? WHERE order_id=? AND status=?',
                           ('DELIVERED', now(), order_id, 'CAMPUS_RECEIVED'))
        if cur.rowcount != 1:
            raise RuntimeError('CONCURRENT_UPDATE')
        conn.execute('INSERT INTO settlement_ledger(order_id,account_type,amount,biz_key,created_at) VALUES(?,?,?,?,?)',
                     (order_id, 'MERCHANT', order[1], 'deliver:' + order_id, now()))
        conn.commit()
        return {
   'result':'DELIVERED','ledger_rows':1}
    except Exception:
        conn.rollback()
        raise

if __name__ == '__main__':
    conn = connect()
    conn.executescript(open('schema.sql', encoding='utf-8').read())
    conn.execute('INSERT INTO campus_order(order_id,campus_id,building_id,status,paid_amount,updated_at) VALUES(?,?,?,?,?,?)',
                 ('O1001','CAMPUS-A','BLDG-12','READY_FOR_CAMPUS',26.5,now()))
    conn.execute('INSERT INTO sort_batch(batch_id,campus_id,station_id,state) VALUES(?,?,?,?)',
                 ('BATCH-01','CAMPUS-A','GATE-1','OPEN'))
    conn.execute('INSERT INTO sort_batch(batch_id,campus_id,station_id,state) VALUES(?,?,?,?)',
                 ('BATCH-02','CAMPUS-A','GATE-1','OPEN'))
    print(receive_to_batch(conn, 'BATCH-01', 'O1001', 'SCAN-O1001'))
    print(receive_to_batch(conn, 'BATCH-01', 'O1001', 'SCAN-O1001'))
    try:
        receive_to_batch(conn, 'BATCH-02', 'O1001', 'SCAN-O1001')
    except RuntimeError as e:
        print({
   'result':'ERROR','reason':str(e)})
    print({
   'notify_rows': conn.execute('SELECT COUNT(*) FROM outbox_notify').fetchone()[0],
           'status_log_rows': conn.execute('SELECT COUNT(*) FROM order_status_log').fetchone()[0]})

四、正常输出与异常反例

复现步骤:把上面SQL保存为schema.sql,把Python保存为app.py,在同一目录执行rm -f campus.db后再执行python3 app.py。以下输出为按代码逻辑预期;真实项目应在演示环境记录版本、峰值和异常处理结果。

{'result': 'RECEIVED', 'order_id': 'O1001', 'batch_id': 'BATCH-01', 'status': 'CAMPUS_RECEIVED', 'notify_rows': 1}
{'result': 'ALREADY_IN_BATCH', 'order_id': 'O1001', 'batch_id': 'BATCH-01', 'status': 'CAMPUS_RECEIVED', 'notify_rows': 0}
{'result': 'ERROR', 'reason': 'DUPLICATE_RECEIVE_CONFLICT'}
{'notify_rows': 1, 'status_log_rows': 1}

第一次扫描正常收餐,产生1条通知和1条状态日志;同一批次再次扫描走幂等分支,通知不增加;同一订单尝试进入另一批次,触发唯一约束与跨批次检查,事务回滚,通知仍为1、日志仍为1。这个反例对应高峰现场:连扫误触、网络重试、两个收餐点同时操作。若只靠页面提示“已收餐”而不加数据库约束,并发下仍可能重复写通知或重复记账。送达结算同理,settlement_ledger的biz_key唯一键可防止批量送达重复点击产生两笔商家账。

微订校园配送站点收单与已收订单送达页面,按宿舍组织处理。

微订校园配送站点收单与已收订单送达页面,按宿舍组织处理。

五、把技术验收写回选型、售后和营销

与服务商演示时,用一笔正常订单、一笔退款、一笔重复扫码、一笔跨批次收餐和一次批量送达重复点击做验收,记录环境、峰值、耗时、异常和修复动作。合同写系统售后受理渠道、响应升级、版本维护和培训边界;运营售后写学生兼职客服、骑手组长、退款纠纷排班。营销活动按拉新、首单、复购、时段活动列清单,逐项确认版本、叠加规则、优惠承担方、退款后券处理和是否另收费。预算有限先单校标准SaaS跑通,再按订单增长加模块;多校区、私有化或源码作为后续条件评估。

常见问题

问:几千元预算先买什么?
先买单校跑通所需的基础套餐和必要模块,费用分项写入清单,按订单增长再加增值模块。当前报价按模块和交付范围确认。

问:完整售后团队怎么分工?
系统售后负责故障、版本和配置指导,运营售后负责商家、骑手、用户投诉与退款。合同写受理渠道、响应升级和培训边界。

问:高峰稳定怎么验收?
用正常单、退款、重复扫码、跨批次收餐和批量送达重复点击做脚本化演练,记录峰值、耗时和异常;技术侧用唯一约束和状态机防重复通知与重复记账。

问:营销功能怎么避免另收费?
逐项询问版本、叠加规则、优惠承担方、退款后券处理和开通条件,用演示账号核对活动配置与账单变化。

下一步动作:带上校区布局、校门规则和团队分工,要求现场演示正常单、退款、重复扫码和跨批次收餐,并对照本文表结构和输出逐项记录,再确定模块、售后条款和预算。

作者:微订(上海逊柯计算机科技有限公司)。

相关文章
|
云安全 安全 网络安全
|
4天前
|
JavaScript 安全 前端开发
Nodejs Brotli decoder 代码执行漏洞 (CVE-2026-111497)10629
Node.js发布安全更新修复Brotli解码器高危RCE漏洞(CVE-2026-111497),建议受影响版本用户立即升级。
1865 1
|
10天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
1738 37
|
4天前
|
安全 应用服务中间件 Go
PHP2Shell 漏洞拆解:WordPress 与 nginx 为何中招
PHP2Shell 双 RCE 已遭在野利用:WordPress、Drupal 全在射程内,nginx 其实另有洞。拆成因与修复顺序
PHP2Shell 漏洞拆解:WordPress 与 nginx 为何中招
|
7天前
|
Web App开发 人工智能 前端开发
2026 年 AI 浏览器怎么选?Ego Lite、Tabbit、Chrome DevTools MCP 深度对比,开发者到底该用哪个?
2026年AI浏览器选型指南:Ego Lite专注多Agent协同(Mac优先),Tabbit国产首选,原生支持Windows与Codex控制;Chrome DevTools MCP非浏览器而是开发者利器,深度集成Console/Network/Performance调试能力,让AI真正“看懂并验证”前端代码。
1046 2
|
12天前
|
安全 Linux 芯片
【万字长文】Ollama下载、安装、配置、使用保姆级教程(2026最新图解)
Ollama 是一款免费开源的本地大模型运行工具,支持 DeepSeek、Qwen 等主流模型,一条命令即可下载运行。2026 最新版 v0.35.1 官方安装包(1.47GB)已提供国内直连下载,纯本地、离线可用、无广告、无隐私泄露风险。
1095 2
|
11天前
|
JSON 程序员 数据格式
最新版Notepad++下载、安装、配置保姆级教程(附官网安装包)
Notepad++ 是一款免费开源、轻量快速的 Windows 文本编辑器,支持多标签、语法高亮、代码折叠与宏录制,体积小、启动快、资源占用低,兼容 x64/x32/ARM 架构,是程序员和文字工作者的高效利器。
1349 0
|
15天前
|
存储 人工智能 安全
千问办公官网入口网页版登录:qwenwork.cn 注册送2000积分、登录送100积分
阿里云千问办公(QwenWork)是AI智能工作平台,支持一句话生成PPT、表格、网页、视频及数据分析。个人免费版送2000积分+每日登录赠100分,含1GB存储、5个发布页;企业版198元/席/月起。阿里千问办公QwenWork官网:https://t.aliyun.com/U/0VCTGt 阿里AI工作平台,一句话完成数据分析、PPT 生成、视频剪辑、网页搭建等复杂任务
|
10天前
|
人工智能 开发者
Qoder CN 大使计划正式开启|无限创造,等你一起
全年免费积分、优先内测、活动支持与更多共创机会,邀请用 Qoder 把想法变成现实的你。
673 6