1. 项目拆解:宠物医院挂号预约到底在解决什么问题
1.1 线下宠物就医的真实痛点
先说一个我亲眼见过很多次的场景:某宠物医院早上九点开门,八点半门口已经蹲了七八只猫狗,主人怀里的毛孩子有的精神还好,有的明显蔫儿了。等医生来了,前台开始挨个手动登记,名字、品种、症状、联系电话,写个十分钟,然后再开始排队叫号。赶上周末,光是排队等号就能等两三个小时,有些主子实在等不住,只能带着宠物先回家,下午再来碰运气。
这个场景不是个例。宠物医院和人的医院不一样,大部分都是中小型机构,人力有限,前台往往同时承担挂号、收费、取药、接电话好几项工作。挂号环节看起来简单,但高峰期的排队压力是实打实的。更麻烦的是,宠物主人在没有预约机制的情况下,只能靠“到了再说”的方式碰运气,而医院方也没法预判今天到底会来多少病患、需要安排几个医生值班。
所以这个项目的核心问题很清楚:用一套线上系统,把“挂号”这个高频刚需从线下挪到线上,让宠物主人可以提前预约、按时间到院,让医院可以管理号源、统计排班、减少前台压力。
1.2 所谓“设计”和“实现”各指什么
项目标题里“设计与实现”这五个字,其实是这类毕业设计或者工程项目的通用说法,但很多刚开始做的人会低估它到底包含多少工作量。
“设计”至少包括三块:数据库结构设计、接口协议设计、前端页面交互设计。你在白纸上画个草图说“我要一个预约按钮”,那不叫设计;你得考虑清楚预约状态有几个、时间冲突怎么处理、医生歇班怎么办、爽约之后要不要锁号,这些才是设计里真正耗时间的部分。
“实现”也很啰嗦:后端框架的搭建、微信小程序的用户授权登录、前端页面和接口的联调、部署上线后的服务器配置……任何一个环节出问题,前面做得再好也白搭。
这个项目适合什么人做?如果你正在学 Python Web 开发,想找个能覆盖“数据库设计 + 后端接口 + 移动端页面”的综合练手项目,它会比单纯写一个 CRUD 管理系统有挑战得多。整个系统里,后端是两套可选的技术路线——Django 和 Flask,前端我用的是 UniApp,最终产物是微信小程序,整体链路完整,学完基本能摸清一个小型商业系统的全部构成。
1.3 我在这个项目里的整体预期
先交代一下我自己的技术背景和做这个项目时的预期:我之前用 Django 做过几个后台管理系统,对 Flask 也有一定的使用经验,UniApp 属于边做边学。这个项目我做之前给自己定了一个标准——不能只是“能跑”,而是从真实场景出发做完整的闭环:用户进小程序、创建宠物档案、选择科室和医生、预约时间段、到院核销、医生看诊记录、后台管理端排班和统计,每一条链路都要通。
实际做下来,我最大的感受是:这个项目复杂度的天花板并不在代码量,而在业务规则的梳理上。仅“一个用户能不能替朋友家的宠物挂号”“一个时段放几个号”“医生临时请假挂出去的号怎么处理”这三个问题,就够你反复改好几轮数据库表和接口逻辑。你要是也想做,强烈建议先把这些业务规则想清楚再动手写代码。
2. 技术选型拆解:Django、Flask、UniApp 到底怎么选
2.1 Django 和 Flask:同一道题的两种解法
标题里同时写了 Django 和 Flask,说明这不是二选一,而是给你提供两条可选的路线。很多人在选题的时候纠结“到底用哪个”,我的看法是:先看你要交付什么,再决定框架。
Django 的思路是“全家桶”,自带 Admin 后台、ORM、认证系统、表单校验这些模块。做这种预约系统,Django 的 Admin 后台几乎可以零成本变成一个基础的信息管理后台——医生排班的数据直接在里面维护,连单独写管理页面都省了。如果你想尽快看到进度、并且需要一个能用的“医生端后台”,Django 的体验会让你非常舒服。
Flask 的思路是“自由组合”,它只做最核心的路由和 WSGI 部分,其余全看你自己怎么装。Flask 的优势在你对项目结构有自己想法的时候尤其明显,比如你想把路由写得很干净、自己设计一个轻量级的服务层,Flask 不会拦着你;缺点是很多东西都要自己搭,用户登录、数据库迁移、参数校验这些在 Django 里一个命令搞定的事情,在 Flask 里你得手动选方案再手动集成。
我实际的操作是:用 Django 做了一套主版本,用 Flask 重新写了一遍核心接口。不是因为闲,而是这个系统的业务逻辑足够小,两个人做同样的事,对比下来你才能真正理解两个框架的边界在哪里。简单总结区别:
| 对比项 | Django | Flask |
|---|---|---|
| 上手平滑度 | 高,自带工具多 | 低,需要自己搭组件 |
| 适合的项目体量 | 中小型以上均可 | 小型、可定制化需求高 |
| 自带后台 | 有,可直接使用 | 无,需额外搭配 Admin 方案 |
| ORM 完善度 | 高,支持复杂查询 | 基础够用,复杂查询要自己写 |
| 适合的场景 | 管理后台 + 接口一体 | 纯接口服务、微服务拆分 |
2.2 UniApp 的优势:一次编写,多端输出
我选 UniApp 而不是原生微信小程序开发,理由很现实:项目本身就不只是给你一个人的手机用的,预览阶段我要同时在微信开发者工具里看效果,偶尔还要在手机浏览器里调试,原生小程序开发只能在微信环境里跑,调试效率低、复用性差。
UniApp 基于 Vue 语法,写的是 .vue 单文件组件,编译的时候可以根据你选择的目标平台——微信小程序、App、H5——分别打包。它的运行机制是这样的:通过条件编译和框架底层的事件映射,把 Vue 的模板和数据绑定关系转换成对应平台的代码。比如你在模板里写了一个按钮点击事件,编译成微信小程序后,它就变成微信的 bindtap 事件;编译成 H5 后,它就变成普通的 click。
比较关键的一点是:UniApp 不是简单地“套壳”微信小程序。它自己有完整的页面栈管理、路由系统、生命周期钩子,而且对微信小程序的 API 做了封装——比如uni.request就是 wx.request 的统一封装层。绝大多数业务代码,你只在 H5 环境里调试通过,再丢到微信开发者工具里验证一次兼容性,基本就可以确认能用了。
实际开发中我遇到的坑主要有两个:一是某些组件在不同平台上的样式表现不一致,比如scroll-view的滚动条样式在 H5 和微信里就不是一回事;二是微信小程序对包体积有 2M 主包限制,UniApp 编译出来的代码往往自带一套运行时,如果图片资源、第三方组件库不加控制,很容易超限。后面我会专门讲这块怎么处理。
2.3 整体系统架构怎么搭
这个项目的架构,一句话概括:前后端分离,后端提供接口,前端是小程序,数据库负责持久化,文件服务负责存图片。
服务端我推荐的结构是 Django 的 app 模块化或者 Flask 的 Blueprint 模块化,按业务垂直切分:
users:用户注册、微信授权登录、token 管理pets:宠物档案管理departments:科室信息doctors:医生信息与排班appointments:预约核心逻辑
为什么要垂直切分?因为预约这个业务天生就牵涉多个模块——用户要先登录、宠物档案要先存在、医生的排班要先配置好,最后才轮到预约动作。如果你把所有逻辑堆在一个文件里,联调的时候光是理清函数之间的调用关系就够你受的。
前端页面结构上,UniApp 的小程序端我规划了五个主 Tab 页:首页(医院介绍+科室入口)、预约(核心预约流程)、宠物(宠物档案管理)、消息(通知列表)、我的(个人信息与预约记录)。每个 Tab 对应一个独立的页面文件,页面之间通过 UniApp 的路由跳转来连通。
3. 数据库设计:预约系统的地基画不好,后面全是坑
3.1 核心表结构梳理
设计数据库的时候,我先把自己代入到“前台护士”的角色里,从她一天的工作流程倒推需要哪些数据表:用户进来先要认身份,所以要有用户表;用户带着宠物来,宠物有自己的信息,所以要有宠物表;宠物要挂号,挂的是某个科室某个医生的号,所以要有科室表、医生表;挂号要确定时间段,所以要有排班表;排班决定了时间段里能放几个号,所以要有预约表。
下面是我最终确定的几个核心表及其关键字段:
用户表(users)
openid:微信用户唯一标识nickname:昵称avatar:头像地址phone:联系电话create_time:注册时间
宠物档案表(pets)
user_id:所属用户name:宠物名字species:物种(猫/狗/其他)breed:品种gender:性别age:年龄weight:体重avatar:宠物照片
医生表(doctors)
name:医生姓名department_id:所属科室title:职称avatar:头像intro:简介status:在职状态
排班表(schedules)
doctor_id:医生 IDwork_date:出诊日期shift:班次(上午/下午/晚班)start_time:开始时间end_time:结束时间max_appointments:最大预约数appointed_count:已预约数
预约表(appointments)
appointment_no:预约单号user_id:预约用户pet_id:就诊宠物doctor_id:预约医生schedule_id:对应排班appointment_date:预约日期time_slot:时间段status:状态(待就诊/已完成/已取消/爽约)symptom:症状描述create_time:下单时间
你如果仔细看这个表结构会发现,我把“排班表”和“预约表”分开了,而不是直接在预约表里写医生和日期。这是整个设计里最关键的一个决策。
3.2 为什么必须要有独立的排班表
想象一下没有排班表的场景:用户预约的时候,系统要查询某个医生某天有没有空,只能遍历所有的预约记录,查已被占用的时间段,再和全天总时间段做差集。数据量小的时候没问题,但一旦医生数量多了、预约记录多了,这个查询会慢得让人抓狂。更麻烦的是,你很难做“放号上限”的控制——是每个小时放几个号,还是一天总共放几个号?这些规则没有排班表根本没法表达。
有了排班表,逻辑就变得很清爽:管理员先给医生配置一个排班,比如“周一上午 9:00-12:00,最大放 15 个号”。预约的时候,系统先查这张排班表,确认这个医生这一天这个班次还有没有号,有号就创建预约记录,同时把排班表里的appointed_count加 1;没号就直接提示“该时段已约满”。
这里牵扯到一个非常关键的技术点:预约和更新号源不能分开做,必须以事务的方式放在一起执行。用 Django 的 ORM 写大致是这样:
from django.db import transaction @transaction.atomic def create_appointment(user, pet, schedule_id): schedule = Schedule.objects.select_for_update().get(id=schedule_id) if schedule.appointed_count >= schedule.max_appointments: raise ServiceError("该时段已约满") appointment = Appointment.objects.create( user=user, pet=pet, doctor=schedule.doctor, schedule=schedule, status='pending' ) schedule.appointed_count += 1 schedule.save() return appointmentselect_for_update()在数据库层面为这一行记录加了锁,两个人同时抢最后一个号的时候,数据库会保证只有一个人能成功更新。Flask 里如果用 SQLAlchemy,思路完全一样,with db.session.begin():包住整个操作,再用with_for_update()加锁。这块不处理好,后面会出现超卖号源的情况——明明只剩一个号,两个用户却同时约成功了。
3.3 预约状态机的演进
预约单的状态看起来就五个:待就诊、已完成、已取消、爽约、进行中。但如果你真把这个系统用起来,会发现状态之间还有各种边界情况。
比如:用户在“待就诊”状态下想取消预约,号源要不要释放?我的答案是:可以释放,但要看时间。预约日当天上午 8 点之前取消,号源释放回池子里,其他用户可以继续约;过了这个时间点,号源锁定,取消操作不释放号源,系统直接记录一个“爽约”。
这样设计的逻辑是:宠物主人早上 9 点的预约,如果 8:50 才取消,其他用户不可能来得及赶到医院,这个号事实上已经废了;不如锁住它,让排班记录更真实地反映当天的实际到诊情况。医院在统计医生工作量的时候,看的是“实际就诊数”而不是“预约数”,把已锁定的号源算进医生工作量里,对医生才是公平的。
4. 后端接口设计:把业务能力安全地暴露给小程序
4.1 接口规范与返回格式统一
前后端分离之后,小程序端只能通过 HTTP 接口拿数据,所以接口设计是否清晰,直接决定联调阶段你是否会加班。
我自己的经验是,接口第一课永远是返回格式统一。不管请求成功还是失败,包里必须是一个固定结构的 JSON:
{ "code": 200, "message": "操作成功", "data": { "appointment_no": "AP20250318001", "status": "pending" } }code用数字标识业务状态:200 成功、400 参数错误、401 未登录、403 无权限、404 资源不存在、500 系统异常。小程序端写一个统一的请求封装函数,拦截响应之后先判断code,如果不是 200 就弹出message提示框。这样做的好处是,前端不用在每个接口里单独处理错误分支,逻辑会简化特别多。
接口命名上我习惯用 RESTful 风格,不需要花哨,但要有规律。预约模块下:
GET /api/appointments:获取当前用户的预约列表POST /api/appointments:创建预约GET /api/appointments/{id}:获取预约详情POST /api/appointments/{id}/cancel:取消预约POST /api/appointments/{id}/complete:完成就诊(核销)
4.2 微信登录与用户体系打通
小程序端不能像普通网站那样直接输入用户名密码登录,它的标准流程是:小程序调用wx.login()拿到一个临时code,把code发给后端;后端拿着code去向微信的服务器换取openid。
这里有个早年很多教程都还在用的做法,我必须让你避开:直接把code传给后端,让后端调微信接口换openid是标准流程,但千万不要在请求里带上appsecret。appsecret是后端才能持有的密钥,只能存在服务器上,一旦暴露在代码里,任何拿到代码的人都能冒充你的小程序调用微信接口。
Django 侧的实现思路:
def wx_login(request): code = request.data.get('code') appid = settings.WX_APPID secret = settings.WX_APPSECRET url = ( f"https://api.weixin.qq.com/sns/jscode2session" f"?appid={appid}&secret={secret}&js_code={code}&grant_type=authorization_code" ) resp = requests.get(url).json() # resp 里包含 openid 和 session_key user, created = User.objects.get_or_create(openid=resp['openid']) token = generate_token(user) return JsonResponse({"code": 200, "data": {"token": token, "user": user.to_dict()}})拿到openid之后,后端自己生成一个 token 返回给小程序。后续所有需要身份的请求,小程序在请求头里带上Authorization: Bearer <token>,后端通过中间件或者装饰器校验用户身份。注意 token 要设置过期时间,我设置的是 7 天,过期之后用户需要重新静默登录一次,体验上基本无感。
4.3 核心接口实现细节:预约与核销
预约接口是全场最核心的接口,没有之一。前端传四样东西:pet_id、schedule_id、symptom(症状描述)、appointment_date。后端要做的事情按顺序排:
- 校验用户登录状态,确认身份。
- 校验宠物属于当前用户,防止 A 用户拿 B 用户的宠物档案去预约。
- 校验排班日期和预约日期是否一致。
- 开启事务,锁定排班记录。
- 检查
appointed_count是否满额。 - 创建预约记录,更新排班计数。
- 提交事务,返回预约单号。
核销接口是医院前台或者医生使用的:医生在后台点“开始看诊”,前端调用完成就诊接口,预约状态从pending变成completed。核销的时候要防止重复操作——用一个状态判断就可以:如果当前状态已经不是pending,直接报错“该预约已被处理”。
5. UniApp 前端:从页面骨架到微信小程序适配
5.1 项目初始化与目录结构
UniApp 的初始化很简单,有 HBuilderX 的话直接在新建项目里选“UniApp 项目”,模板选“默认模板”就行;如果你更习惯命令行,也可以用vue create搭配@dcloudio/vite-plugin-uni。我个人的建议是:如果你对 Vue 还不熟,直接用 HBuilderX 附带模板,少折腾环境问题,把精力留在业务代码上。
初始化之后的目录结构:
src/ ├── pages/ # 页面文件 │ ├── index/ # 首页 │ ├── appoint/ # 预约 │ ├── pet/ # 宠物管理 │ ├── message/ # 消息 │ └── my/ # 我的 ├── static/ # 静态图片资源 ├── utils/ # 工具函数 │ ├── request.js # 请求封装 │ └── auth.js # token 管理 ├── App.vue # 应用入口 ├── main.js # 主入口 └── pages.json # 页面注册与 tabBar 配置pages.json 是一个会被新手忽视但非常重要的大文件,页面路径、导航栏样式、tabBar 都要在这里配。小程序的 tabBar 最多五个,我的五个页面刚好铺满,顺序按用户操作频率从高到低排:首页、预约、宠物、消息、我的。
5.2 请求封装的正确姿势
小程序里没有 axios,UniApp 提供的是uni.request。直接在每个页面里调uni.request会让代码变得特别散,所以我习惯封装一个request.js:
const BASE_URL = 'https://your-api-domain.com/api' export function request({ url, method = 'GET', data }) { return new Promise((resolve, reject) => { uni.request({ url: BASE_URL + url, method, data, header: { 'Content-Type': 'application/json', 'Authorization': uni.getStorageSync('token') }, success: (res) => { if (res.data.code === 200) { resolve(res.data.data) } else if (res.data.code === 401) { // token 失效,跳转登录 uni.navigateTo({ url: '/pages/login/login' }) } else { uni.showToast({ title: res.data.message, icon: 'none' }) reject(res.data) } }, fail: (err) => { uni.showToast({ title: '网络请求失败', icon: 'none' }) reject(err) } }) }) }封装之后,页面里调用接口就变成一行:
const list = await request({ url: '/appointments', method: 'GET' })这个封装的收益在项目做到一半的时候才会显现——你会在几十个页面里调用上百次接口,如果每一处都写uni.request的完整回调,后期想加一个统一的 token 刷新逻辑,光改代码就能让你崩溃。封装之后,只需要在一个文件里加逻辑,全站自动生效。
5.3 微信小程序适配中的几个大坑
UniApp 号称一套代码多端运行,但实际开发里,微信小程序和 H5 的差异点会实实在在消耗你的时间。我踩过最多的坑集中在三块。
第一是样式兼容。小程序不支持部分 CSS 选择器,比如*通配符在某些基础库版本里不生效;position: fixed的层级问题在小程序里也容易出现异常。我的应对方案是:写样式之前先查一下微信小程序的“样式支持列表”,能用 flex 布局解决的绝不碰奇怪的定位方式。
第二是生命周期差异。UniApp 的onShow在小程序里会在每次页面显示的时候触发,而 H5 里可能只在首次加载时触发一次。如果你是做“从预约详情页返回列表页刷新数据”这种操作,必须把刷新的逻辑放在onShow而不是onLoad里。
第三是图片资源与包体积。微信小程序主包限制 2M,UniApp 编译后的运行库大约占 200K 到 500K,剩下留给业务代码的空间其实很紧张。我的做法是:所有图片能压缩就压缩,能用云存储链接就不用本地图片;静态资源尽量走 CDN 或者腾讯云存储,本地只留必须的默认图。还有一个小技巧——把非核心页面设置为“分包加载”,主包里只放 Tab 页和核心流程页,其他页面全部挪进subPackages。
6. 核心业务流程与场景实现
6.1 预约全流程
我把预约流程拆成六个步骤,前端每一步都有对应的页面。
- 用户进入小程序,自动授权登录,后端返回 token 并缓存到本地。
- 用户如果还没有宠物档案,先进入“宠物管理”创建档案。
- 在首页选择科室,进入医生列表,选择目标医生。
- 进入排班页面,选择日期和剩余号源充足的时间段。
- 填写症状描述,确认预约信息,提交订单。
- 到院前可在“我的预约”里查看预约详情,到院后出示预约单号由前台核销。
这六个步骤里,第 4 步“选时间段”是用户体验最敏感的位置。你需要在这个页面明确展示“剩余号源数量”,比如“剩 3 个号”,用红色标记接近满号的时段。用户约满的时段直接置灰不可点,避免用户点了才发现没号。
6.2 医生排班的后台管理
医生排班是后台管理端最核心的功能。前端小程序只负责展示排班结果,排班动作必须由管理员在后台维护。我用 Django Admin 实现了一版基础排班:
class ScheduleAdmin(admin.ModelAdmin): list_display = ('doctor', 'work_date', 'shift', 'appointed_count', 'max_appointments') list_filter = ('work_date', 'doctor') actions = ['generate_week_schedule'] @admin.action(description='一键生成下周排班') def generate_week_schedule(self, request, queryset): # 以本周排班为模板,生成下周同名同班次的排班记录 ...这个“一键生成下周排班”是排班模块里特别提升工作效率的功能。医院排班往往是周期性重复的,周一谁出诊、周三谁出诊,两周之间变化不大。与其让管理员每天手动新建排班记录,不如提供一个“复制上周排班”的功能,然后再允许管理员微调。
6.3 消息通知与预约状态变更
预约成功之后,用户需要得到明确的反馈。我的实现是双通道:
第一通道是站内消息。预约创建成功、预约取消、预约前一天提醒,都会往消息表里写入一条数据,用户在小程序“消息”Tab 里可以看到列表。
第二通道是微信服务订阅消息。微信小程序有一个“订阅消息”能力,用户在特定动作之后可以授权接收一次模板消息。注意这里的限制很坑:用户点一次授权,只能给小程序一次下发机会,不能像公众号模板消息那样随便群发。所以我的策略是:用户在提交预约表单的时候弹一次订阅授权,授权成功之后,系统在预约状态变更时下发一条消息提醒。一次授权对应一次下发,刚好用完。
6.4 数据统计:让系统对管理者也有价值
预约系统不应该只是工具,还应该给医院管理者提供决策参考。我在后台加了一个简单的统计面板,展示每日预约量、各科室预约占比、宠物种类分布、爽约率这些指标。这些数据全部来源于预约表,SQL 聚合查询就可以完成,不需要引入额外的报表工具。
爽约率这个指标特别能说明问题:如果某位医生的爽约率持续偏高,大概率是这位医生的口碑或者预约提示有问题,可以考虑调整确认流程;如果某个科室的预约量长期爆满,就要考虑增加排班人数了。
7. 开发与上线中的常见问题排查
7.1 跨域问题:联调阶段的头号杀手
前端在 H5 环境开发时,浏览器会拦截跨域请求。UniApp 的 H5 端请求的是开发服务器地址,和后端端口不一致,就触发跨域。解决办法有两种:
一种是后端开启 CORS。Django 下用django-cors-headers,Flask 下用flask-cors,都只有几行配置的事:
# Django settings.py 中配置 CORS_ALLOWED_ORIGINS = [ "http://localhost:8000", # 前端开发地址 "https://your-frontend-domain.com", ]另一种是在 manifest.json 里配置 H5 的代理转发,把接口请求转发到后端地址。不过这套配置在微信开发者工具里不生效,所以我的建议是直接做好后端 CORS,一劳永逸。还有一点,微信小程序的请求不会走 CORS 这套规则,它验证的是域名合法性——必须在微信公众平台的后台把接口域名加入“request 合法域名”,否则开发者工具里直接报“域名不合法”。
7.2 小程序审核被拒:准备好被驳回一次
微信小程序上线前要经过审核,我的经验是审核不通过非常正常,甚至可以说“不被驳回反而不太正常”。宠物医院预约系统最常见的驳回理由有三个:
- 类目选择和实际功能不匹配。预约挂号属于“医疗/健康”相关类目,可能需要提供资质证明。
- 隐私政策缺失。小程序的用户协议和隐私政策里必须详细说明收集了哪些用户信息,尤其是手机号、位置这些敏感信息。
- 页面出现测试数据。审核人员看到页面上残留“测试用户”“阿猫阿狗”之类的假数据印象就很差,很容易被拒。
我的建议是:提交审核之前,把所有测试数据清除干净,该写的隐私政策写完整,页面遇到需要真实资质的模块就先用功能引导替代。提交之后被驳回了也不用慌,仔细看驳回理由,逐条修,按时处理,一般不会卡太久。
7.3 并发预约与数据库锁
在第 3 章我提过select_for_update()的用法,这里再展开聊聊并发场景。模拟一下:某医生某个时段放 10 个号,现在还剩最后 1 个,用户 A 和用户 B 同时点了“确认预约”。
如果没有锁,两个请求同时读到appointed_count=9,都判断小于 10,于是各自创建一条预约记录,把计数更新成 10,最终结果是放了 11 个号——超卖。加锁之后,第一个请求进来了,把排班那行锁住,第二个请求在锁外面等待,等第一个事务提交之后,它进去一查发现已经是 10,直接返回“约满了”。
实际压测的时候我用 20 个并发请求模拟抢号,加锁之后没有出现一条超卖数据。这个经验告诉你:数据库层面该加的锁不能省,特别是在预约这种有数量上限的业务场景。
7.4 图片上传与存储选型
宠物档案里的宠物照片、用户的头像,都需要上传能力。微信小程序里上传文件用uni.uploadFile,后端接收之后保存到本地,还是直接存到对象存储?我的建议是直接存对象存储。
原因是:小程序后端如果跑在云服务器上,本地磁盘空间是有限的,而且用户上传的图片如果直接放在服务器本地,备份、迁移的时候都麻烦。用对象存储,比如腾讯云 COS 或者阿里云 OSS,把上传请求的签名逻辑放在后端,小程序直接通过临时密钥上传,整个过程不需要占用后端带宽,性能和成本都可控。
8. 项目复盘:这套系统做完我到底得到了什么
这个项目从设计到开发再到部署,我前后花了一个多月的时间。回头复盘,最值钱的不是代码本身,而是三个认知层面的提升。
第一个认知是业务规则的复杂度远超技术实现的难度。写一个预约接口只需要一晚上,但想清楚“取消预约要不要释放号源”“爽约之后要不要锁号”“医生请假之后已预约用户怎么通知”这些规则,才是最花精力的地方。技术手段只是把这些规则变成代码而已——规则没想明白,代码写得再漂亮,上线也一定会出问题。
第二个认知是前后端联调是整个项目里最容易返工的环节。接口字段名不一致、返回格式不统一、状态码语义混淆,这些看起来很小的问题,联调时会让你浪费大量时间。最好的解决方案是把接口文档写在前端写代码之前,后端先把返回结构定死,前端直接照着文档开发,返工率能降一半以上。
第三个认知是部署上线才是项目完成的真正标志。本地能跑通和真正上线是两回事:本地接口随便调,上线之后要考虑 HTTPS 证书、域名备案、数据库备份、日志监控、服务器磁盘空间。我在部署阶段补的课比开发阶段还多,但这些内容在工作环境中反而是最值钱的。
如果你正打算做这个项目,我给的建议是:先花两天时间把业务规则全部列出来,再开始建表写代码。另外,不要把目光只盯在“做完”上——试着把排班统计、消息通知这些看起来不起眼的功能做到位,它们才是这个系统能不能被医院真正用起来的分水岭。
最后再分享一个小技巧:开发小程序的时候,微信开发者工具里有个“真机调试”功能,一定要用起来。模拟器里看着好用的页面,拿到真机上可能就变样了——字体大小、底部安全区、loading 状态,这些细节只有在真机上才能发现真相。做预约系统更是如此,用户拿的是真手机、真宠物,你不在真机上把流程走一遍,就不算真正做完。