
简介面向“互联网”大学生创新创业大赛的参赛团队创业计划书聚焦“大学生爱创共享厨房”项目完整呈现从项目背景、项目内容到项目管理的一整套方案适合需要撰写或优化共享经济类商业计划书的学生参考。计划书基于共享经济与校园生活需求设计了线上预约、食材购买、烹饪课程等增值服务并明确了盈利模式、团队构成、短期与长期目标规模管理、质量管理、风险管理、财务规划和营销策略等章节均有具体做法项目性质、主要业务、成员组成、设备与安全管理等细节也一并覆盖目录从摘要、项目内容、项目简介到项目管理层层展开可直接套用或按校赛/省赛要求调整。资源共1个docx文件压缩包仅841KB虽为单文档资料但内容完整、结构清晰。目前已有1338人学习下载对希望快速搭好比赛计划书框架的学生来说是一份高性价比的参考底稿。1. 把“大学生爱创共享厨房”从口号拆成最小系统很多参赛团队把“共享厨房”理解成一个带二维码的小程序比赛时演示了选时段、付钱、开门禁之后就被评委追问到卡壳。真正的难点不在 UI而在订单链路的两头一头是厨房设备这类有限资源如何按时间片分配另一头是计费、押金、退款、超时占用这些资金相关字段怎么在数据库里闭环。互联网大学生创业大赛评审看的是“资源是真实的钱是能算清的”不是只看界面美观度。我建议技术负责人先把项目标题里的“共享”转化为可调度的资源模型灶台数量、营业时间、单时段容量、计价规则、违约规则。再把这个模型落到数据库表和接口里才算完成一份经得起追问的创业计划书。这篇内容按可复现路径展开定义最小系统边界用数据模型固定预约和计费给出后端最小接口和硬件联动方式最后是一套答辩前能复现的部署与验证脚本。适合理工类参赛团队的技术成员也适合想从演示原型往真实系统靠的学生团队参考。2. 创业计划书里的订单链路共享厨房的状态机与边界2.1 按角色拆功能边界区分真实需求和演示需求共享厨房面向的三类角色很简单学生用户是使用方厨房场地方或店主是供给方平台管理员是仲裁方。写计划书时最容易犯的错是把三类角色的功能全部塞进一个“我的”页面里。拆边界时我会先列一张角色功能表角色必须有的功能可以放到二期大赛演示不必要学生用户查看可用时段、预约、支付、开门、结算、申请退款拼单、菜谱社区、社交关系链个性化推荐算法店主维护厨房信息、设置可用时段和价格、查看日结算设备故障上报、员工排班经营趋势大屏平台管理员审核厨房资质、处理退款仲裁、查看交易流水营销活动配置、优惠券系统风控模型这样拆分后系统的最小闭环就清楚多了用户选择一个时间片系统锁住一个灶台资源支付成功后生成订单使用结束后按实际时长结算押金在确认无损坏后原路退回。整个流程里最关键的技术对象不是“厨房”而是“订单”和“时段库存”。2.2 订单状态机预约、使用、结算、退款我一般会把订单状态设计成一组可穷举的枚举而不是靠字段含义模糊表达。共享厨房订单至少要包含待支付、已支付、使用中、待退押金、已完成、已取消这六个状态。取消动作发生在支付前和支付后两条路径上支付前取消不产生任何费用支付后取消要区分“使用开始前多久”和“是否已被店主接单”。这里给出一个适合写进计划书的状态迁移关系当前状态允许迁移到触发条件待支付已支付 / 已取消支付成功回调用户主动取消或超时关闭已支付使用中 / 已取消用户扫码开门使用开始前超过免扣费时间取消使用中待退押金用户关门并点击结束系统开始计算费用待退押金已完成 / 已取消退押金成功店主仲裁判定需扣除押金已取消终态退款已原路退回或无需退款这六个状态必须落在订单表的 status 字段中后续所有计费、退款、对账都以此为依据。计划书答辩时评委大概率会问“用户取消了怎么办”“用了一半不付钱怎么办”有这张状态表可以直接回答。2.3 用状态迁移代码堵住“评审追问”里的逻辑漏洞光有状态表还不够接口层必须校验状态迁移的合法性。我在原型项目里会写一个很小的状态机校验函数避免后面接支付回调时出现“已完成订单又退款”这类事故from datetime import datetime, timedelta ALLOWED_TRANSITIONS { PENDING: {PAID, CANCELLED}, PAID: {IN_USE, CANCELLED}, IN_USE: {DEPOSIT_PENDING}, DEPOSIT_PENDING: {DONE, CANCELLED}, CANCELLED: set(), DONE: set(), } def can_transition(order, target_status: str) - bool: if target_status not in ALLOWED_TRANSITIONS.get(order.status, set()): return False if order.status PAID and target_status CANCELLED: cancel_deadline order.slot_start - timedelta(hours1) return datetime.now() cancel_deadline if order.status DEPOSIT_PENDING and target_status CANCELLED: return order.fine_cents 0 return True这段代码做了三件事先校验目标状态是否在合法迁移集合里再针对支付后取消的场景增加时间条件最后把押金仲裁场景单独拎出来。参数说明里最值得关注的是timedelta(hours1)这个免扣费取消窗口要跟支付页面的提示文案保持一致否则用户会投诉。很多团队把取消规则写死在前端后端不留记录评审一问就答不上来。3. 共享厨房数据模型时段、资金和履约字段怎么落库3.1 为什么订单表不能替代时段库存表共享厨房的库存不是商品件数而是时间片。一个厨房上午十点只有一个灶台空闲此时来了两个订单要有一个机制保证只有一个订单成功。如果直接把 start_time、end_time 存进订单表数据库只能通过“查询是否存在时间段重叠”来判断并发稍微一高就会出现双卖。我会把“库存”单独建模成一张时段表字段包含时段开始、时段结束、剩余名额和版本号。下订单时不直接操作订单表而是先锁定时段表里的某一行扣减库存后再生成订单。这种方式在计划书里写起来也更直观把时段表看作是“可售资源”订单表看作是“售卖结果”。3.2 建表 SQL把金额字段全部设计成整数共享厨房的金额必须精确到分不能用 float。MySQL 里的 decimal 虽然可以但为了让所有语言的 SDK 都避免浮点误差我习惯直接用 int 存“分”。订单表、用户余额表、退款表统一遵守这个规则后续写对账逻辑会省很多事。下面是一组能直接放进项目初始化的建表语句CREATE TABLE users ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, openid VARCHAR(64) NOT NULL UNIQUE COMMENT 小程序 openid, phone VARCHAR(20) NULL, role TINYINT NOT NULL DEFAULT 0 COMMENT 0学生 1店主 2管理员, balance_cents INT NOT NULL DEFAULT 0 COMMENT 账户余额单位分, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE kitchens ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, address VARCHAR(255) NOT NULL, manager_id BIGINT UNSIGNED NOT NULL, open_time TIME NOT NULL DEFAULT 08:00:00, close_time TIME NOT NULL DEFAULT 22:00:00, max_concurrent INT NOT NULL DEFAULT 4 COMMENT 同时可用的灶台数, status TINYINT NOT NULL DEFAULT 1 COMMENT 1营业 0停业 ); CREATE TABLE time_slots ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, kitchen_id BIGINT UNSIGNED NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, price_cents_per_minute INT NOT NULL DEFAULT 50 COMMENT 每分钟价格单位分, stock INT NOT NULL DEFAULT 1 COMMENT 该时段剩余可用数, version INT NOT NULL DEFAULT 0 COMMENT 乐观锁版本号, UNIQUE KEY uk_kitchen_time (kitchen_id, start_time, end_time) ); CREATE TABLE orders ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, order_no VARCHAR(32) NOT NULL UNIQUE, user_id BIGINT UNSIGNED NOT NULL, kitchen_id BIGINT UNSIGNED NOT NULL, slot_id BIGINT UNSIGNED NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付 2使用中 3待退押金 4已完成 5已取消, total_cents INT NOT NULL COMMENT 订单总金额单位分, fine_cents INT NOT NULL DEFAULT 0 COMMENT 超时或清洁罚金, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, paid_at DATETIME NULL, KEY idx_user (user_id), KEY idx_slot (slot_id) );这段 SQL 里有几个参数值得留意。time_slots表用uk_kitchen_time唯一键约束同一个厨房的同一时段不会重复插入这是避免“演示时手滑创建两条相同库存”的兜底手段。stock默认值是 1因为最小模型里一个厨房在一个时间段只有一个可用资源如果实体厨房有多个灶台就把它改成实际数量。version字段是给乐观锁预留的后面接口层用UPDATE ... WHERE version ?来实现并发控制。3.3 三个常见参数坑时区、费率、并发第一个坑是时区。数据库里的DATETIME默认不带时区如果服务器是 UTC小程序端是东八区时间戳就会差八个小时。我的做法是全链路统一存 UTC 或统一存东八区接口入参只接收“本地时间时区偏移量”服务端做一次转换后落库。第二个坑是费率设计。price_cents_per_minute是每分钟单价很多团队会在计划书写“按小时收费”但实际按分钟计费更容易处理超时。结算时使用ceil((实际结束时间 - 实际开始时间) / 60)计算分钟数再乘以单价。这里的取整方向必须提前约定否则用户会觉得被多收钱。第三个坑是并发。两个用户同时抢最后一个时段时单纯读 stock 再写订单一定会超卖。我习惯在事务里执行SELECT ... FOR UPDATE锁住时段行或者使用版本号更新。下面这段伪代码展示的是乐观锁更新逻辑UPDATE time_slots SET stock stock - 1, version version 1 WHERE id 1 AND stock 0 AND version 0;如果影响行数为 0说明这个时段已经被别人抢走了接口直接返回“该时段已约满”。把这段逻辑写进计划书的“技术方案”章节比写一百句“高并发设计”更有说服力。4. 用 FastAPI 实现共享厨房预约接口和设备联动4.1 技术选型FastAPI、MySQL、Redis、MQTT共享厨房的后端不需要很复杂关键是要能快速把订单链路跑通。我一般会选 FastAPI 作为 API 层原因是它自带 OpenAPI 文档比赛演示时可以直接打开/docs页面给评委看接口列表比用 Postman 现抓包体面得多。MySQL 负责订单和时段库存Redis 用来做分布式锁和支付回调幂等MQTT 用来控制门锁和智能插座。如果你所在团队更熟悉 Node.js改成 Express 或 NestJS 也可以但下面的接口逻辑思路是通用的。选 FastAPI 还有一层考虑大多数参赛团队的技术文档都是用 Python 写的答辩时讲代码更顺畅。4.2 预约接口用事务锁防止同一灶台超卖预约接口是共享厨房最核心的接口它在一次请求里要完成三件事校验时段存在和库存充足、锁住时段、创建订单。下面给出一个精简版实现from fastapi import FastAPI, HTTPException from pydantic import BaseModel from sqlalchemy import text app FastAPI() class ReserveRequest(BaseModel): user_id: int slot_id: int app.post(/api/v1/reserve) def reserve(req: ReserveRequest, db: Session): result db.execute( text( UPDATE time_slots SET stock stock - 1, version version 1 WHERE id :slot_id AND stock 0 ), {slot_id: req.slot_id}, ) if result.rowcount 0: raise HTTPException(status_code409, detail该时段已被约满) order_no fK{req.user_id}{req.slot_id}{int(time.time())} db.execute( text( INSERT INTO orders (order_no, user_id, kitchen_id, slot_id, status) SELECT :order_no, :user_id, kitchen_id, :slot_id, 0 FROM time_slots WHERE id :slot_id ), {order_no: order_no, user_id: req.user_id, slot_id: req.slot_id}, ) db.commit() return {order_no: order_no, status: PENDING_PAYMENT}这段代码的关键在UPDATE time_slots语句它把“检查库存是否充足”和“扣除库存”合并成一步数据库的 rowcount 就是我们判断成败的唯一依据。rowcount 0表示库存不足直接返回 409 冲突。插入订单时从time_slots反查kitchen_id避免前端传来不匹配的数据。需要注意这里的db是通过依赖注入传入的 Session生产环境还需要包一层事务边界。Demo 阶段最容易被忽略的是异常回滚如果订单插入失败前面扣掉的库存必须还原所以这段代码应该放进 try/except异常时调用db.rollback()。我在答辩前会专门测试这个场景用两个并发请求打同一个 slot确认只有一个返回成功。4.3 设备联动门禁和灶台的 MQTT 控制命令共享厨房区别于普通餐饮系统的地方在于需要硬件联动用户支付成功后门锁要能打开、电磁炉要能通电。比赛现场很难把真硬件搬过去但计划书和接口设计里必须包含这部分。我建议使用 MQTT 协议设备端订阅kitchen/{kitchen_id}/command服务端发布控制消息。演示时可以本地安装 mosquitto 客户端手动发布一条开门指令mosquitto_pub \ -h 127.0.0.1 \ -p 1883 \ -t kitchen/12/command \ -m {action:door_open,order_no:K100120260101,expire_at:1714550400}这条命令的逻辑是服务端向厨房 12 发布一个开门动作订单号用于设备端确认订单是否有效expire_at是 Unix 时间戳表示用户必须在这个时间点之前进门超过该时间指令作废。设备端收到消息后先校验 order_no 前缀再判断当前时间是否早于 expire_at最后才驱动继电器打开门锁。智能插座的控制命令与此类似区别是动作名改为power_on和power_off并且要携带灶台编号。比如{action:power_on,stove:2,order_no:...}。需要注意 MQTT payload 最好统一用 JSON且所有字段都要有默认值否则某台设备固件版本旧一点就直接忽略消息。设备端回复应发布到kitchen/{kitchen_id}/telemetry服务端订阅该主题来更新订单状态。4.4 支付回调本地模拟 pay callback 的字段对照支付是答辩时最容易出问题的环节。真实微信支付回调要求内网穿透或者公网地址比赛场地网络受限时根本收不到回调。我的做法是在后端预留一个本地模拟回调接口生产环境用环境变量切换。回调接口接收的字段尽量对齐微信支付回调参数含义本地模拟时的值out_trade_no商户订单号订单号 order_notransaction_id支付平台流水号固定生成字符串模拟用total_fee支付金额单位分订单金额time_end支付完成时间当前时间sign签名本地模拟可忽略本地模拟接口长这样app.post(/api/v1/mock_pay_callback) def mock_pay_callback(payload: dict, db: Session): order_no payload[out_trade_no] order db.query(Order).filter_by(order_noorder_no).first() if order.status ! 0: return {code: SUCCESS} order.status 1 order.paid_at datetime.now() db.commit() return {code: SUCCESS}这里有两点容易踩坑。第一是幂等处理支付平台可能因为网络超时重试同一个回调所以处理前必须先判断订单是不是待支付状态已经支付的订单直接返回成功不能重复改状态。第二是金额校验真实环境里必须校验total_fee是否等于订单金额防止恶意构造回调。5. 答辩前部署一套可复现的共享厨房冒烟测试5.1 用 docker compose 拉起后端依赖比赛演示最怕的是到现场才装 MySQL、Redis。我会要求团队把整个依赖写进 docker compose答辩机器上只要装好 Docker 就能一键启动。下面是最小化的编排文件version: 3.8 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: demo MYSQL_DATABASE: kitchen ports: - 3306:3306 redis: image: redis:7-alpine ports: - 6379:6379 mqtt: image: eclipse-mosquitto:2 ports: - 1883:1883 api: build: ./api environment: DB_DSN: mysqlpymysql://root:demomysql:3306/kitchen REDIS_URL: redis://redis:6379/0 MQTT_URL: mqtt://mqtt:1883 ports: - 8000:8000 depends_on: - mysql - redis - mqtt这个编排文件把数据库、缓存、消息代理和业务 API 四个服务都定义在同一网络里。演示前的启动命令是docker compose up -d --build等待十几秒后访问http://127.0.0.1:8000/docs确认 API 文档可打开。需要留意depends_on只保证容器启动顺序不保证 MySQL 已经完成初始化所以 API 启动代码里要加重试逻辑。5.2 接口冒烟测试脚本我用一个 bash 脚本在答辩当天做快速验证覆盖健康检查、厨房列表和预约链路。脚本会在任何一个接口返回 4xx/5xx 时退出并给出红色标记#!/usr/bin/env bash set -euo pipefail base${BASE_URL:-http://127.0.0.1:8000} check() { local path$1 local code code$(curl -s -o /tmp/shared_kitchen_resp.json -w %{http_code} $base$path) if [ $code -ge 400 ]; then echo [FAIL] $path - $code exit 1 fi echo [OK] $path - $code } check /health check /api/v1/kitchens check /api/v1/slots?date2026-01-01脚本里base${BASE_URL:-http://127.0.0.1:8000}表示默认使用本地地址也可以通过环境变量覆盖set -euo pipefail保证任何一步失败都会中止执行避免带着半坏的接口上台演示。最后的/api/v1/slots?date2026-01-01是查询某个日期的可用时段这个接口能直接证明“时段库存”不是写死的假数据。5.3 答辩前 10 分钟的三步降级检查如果现场网络很差或者评委要求拔掉网线演示我会按下面三步降级第一步确认本地 API 可访问。访问http://127.0.0.1:8000/docs能打开说明 API 进程健康。第二步手动调一次预约接口确认库存能被扣减。第三步如果 MQTT 消息无法发出则进入降级模式直接改订单状态为“使用中”用于演示后续结算流程。这一步在代码里只需要一个维护接口把订单状态从已支付改成使用中即可评委并不会纠结设备联动但要看到业务闭环是完整的。输出中显示[OK]后再去做比赛答辩演示。本文还有配套的精品资源点击获取