报名写入去掉活动行锁
原来提交一进来就 SELECT … FOR UPDATE 锁住活动行,全场排一条队。改成凭证号用数据库序列发号、同手机号重复交给部分唯一索引兜底,只有配了名额才加锁。
Case Study · 2026.08 – 2026.10
民生医药成立 100 周年庆典的线上系统。员工在企业微信里收邀请函、登记服装尺码、查座位、现场打卡;外部嘉宾扫码即开,不用登录就能接受邀请、查座位;会务组在后台管报名、排座、发通知、看现场。3,409 人登记参会,经历了三次全员规模的访问高峰,庆典当天零宕机。
Staff · 企业微信 H5
员工从企业微信工作台进入,身份走 OAuth 静默授权,不用再注册。每个页面都按真实流程设计:收邀请、确认尺码、看议程、查座位、到场打卡。







左右滑动查看全部 · 点图看大图
Guest · 微信扫码即开
外部嘉宾不在企业微信里,扫邀请物料上的二维码或打开专属邀请链接就能进,不用注册登录。页面和员工端基本是同一套,身份由所在端决定:嘉宾端没有服装尺码,多了「是否参加晚宴」,着装要求换成嘉宾版。



1,056 位嘉宾里,687 位通过嘉宾端报名,其中 227 位由 87 位对接人代为申领;其余由会务组按名单导入或在后台补录。
On Site · 现场工作台
庆典当天会务人员不坐在电脑前,常用功能都做成了手机能操作的版本;嘉宾扫会场物料上的二维码就能查自己的座位,不用登录。



Drawn in Code · SVG
大会座位图、晚宴桌位图、地铁出站图、落客点示意图,都是 Vue 组件按配置画出来的 SVG。员工端、嘉宾端、座位总览和离线导出用的是同一套组件,数据一改,所有地方的图跟着变。




场馆给的看台牌号图 8191×5791、13 MB,直接发给手机又慢又费流量。改成一份配置描述每段座位的起点、方向和间距,座位位置由代码算出来。
拖动缩放改的是 SVG 的 viewBox,不用 CSS transform:transform 放大的是已经栅格化的位图,viewBox 变了会按新尺度重新矢量渲染。手机横过来,整层转 90°。
每个区的座位合成一条 path,拖动缩放时不用逐个比对几千个节点;缩略图只画各区色块,座位小到不到半个像素时改画定位圈。
员工、嘉宾看「我的座位」;座位总览在同一张图上按公司着色、写姓名、搜人跳转。晚宴图按每桌实际座数画椅子:A 桌 30 座、主桌 16 座、普通桌 13 座。
「A区3排5号」「208B区2排12座」都能解析,场馆区号 107–112 与字母区 H–L2 互认,导入的座位文本不用统一格式。
座位总览可以一键导出离线网页:两张图离屏渲染成带姓名的 SVG,搜索、拖动缩放、点椅子看人都在本地运行,现场网络不稳时用浏览器直接打开。
Console · PC 管理后台
报名审核与补录、座位导入、入场记录、尺码统计、短信与企业微信推送、定时任务,都在这里。列表里的手机号默认脱敏,需要时再切换显示。




Architecture · 架构
后端分层为 entity → repository → service → router,依赖注入统一管理。Nginx 通过 upstream 在两套后端实例之间切换,新版本健康检查通过后再切流量,发版不停服。
Vue 3 + Vite,企业微信与浏览器内打开;构建产物带哈希、长缓存
TLS、HTTP/2、gzip;静态资源与对象存储直出,API 反代到活跃的一侧
upstream 5758 ⇄ 5759分层架构 + dependency-injector;请求级 UnitOfWork 管事务
260 个接口序列发号、部分唯一索引、pg_trgm 模糊搜索
登录令牌、座位总览缓存(写后按版本失效)
邀请函海报、图片、视频
OAuth 网页授权、通讯录同步、JS-SDK 签名、在线文档回写
定时短信、企业微信推送每分钟扫描,名单比对每 20 分钟回写在线文档;执行记录可在后台查看
systemd 模板单元做蓝绿实例;连接池泄漏追踪开关,借出超 60 s 的连接按调用栈打日志
Production · 真实流量
来得最急的一次不在庆典当天,而在服装尺码登记通知发出的那一分钟:接口调用从每分钟 70 次左右跳到 2,079 次,2,000 多名员工几乎同时第一次登录。
数据来自 Nginx 与后端访问日志、数据库业务表的逐秒还原;压测流量已按时间窗剔除。实际峰值离压测上限很远:最急的那一秒也只用到只读容量的 3%。
Engineering Notes · 工程笔记
第一轮生产压测时系统 43 rps 就封顶,过载后不是降级而是雪崩。之后每轮只改一个瓶颈,用同口径的压测验收,前后十余轮。
原来提交一进来就 SELECT … FOR UPDATE 锁住活动行,全场排一条队。改成凭证号用数据库序列发号、同手机号重复交给部分唯一索引兜底,只有配了名额才加锁。
scoped_session 从未 remove(),连接池被慢慢吞掉;providers.Singleton 不加锁,进程启动时多线程并发会造出两个 engine。改为请求结束释放会话、ThreadSafeSingleton。
依赖注入的 Factory 让每个需要登录的请求都重建整条 service 链,还要读一次磁盘上的配置,单次 12.8 ms。改为单例后,扫码核销与姓名搜索的吞吐都翻了一倍。
一个接口给齐 3,279 人的座位,单次计算 1.6–1.8 s,5 rps 就排队超时。加 Redis 缓存,13 个写接口提交后按版本失效,超管可手动刷新。
登录回调先查库再同步调企业微信,企微一慢,事务就空转超过 idle_in_transaction_session_timeout 被 PostgreSQL 断开,线程上的会话从此报错且不会自愈。改为先调企微再开事务,Redis 加超时,常驻调度线程兜底释放会话。
两套后端实例轮流上线:新版本先在空闲端口启动,健康检查通过后改 upstream 切流量,旧实例处理完手上的请求再退出;回滚只要把 upstream 指回去。
Stack · 技术栈
Python · FastAPI · SQLAlchemy 2.0 · dependency-injector · APScheduler · Pydantic
PostgreSQL(序列、部分唯一索引、pg_trgm)· Redis · MinIO
Vue 3 · Vite · Pinia · Naive UI · Tailwind CSS · SVG 绘图 · html2canvas
Nginx · systemd 蓝绿实例 · vegeta 压测 · sysstat · 企业微信开放平台