案例研究 / 01
酒店运营平台
React / TypeScript / Node / SQL
一套已上线的运营系统,把悉尼三家酒店的房态、客房清洁、交接班、维修与资产流程连接起来。
← 全部项目在服务边界实现酒店间数据隔离
挑战
在不中断酒店日常运营的前提下,替换掉分散在纸张、表格与聊天工具里的工作流。
我的贡献
从运营一线出发,主导需求、工作流设计与交付。平台通过 AI 辅助的开发流程构建,业务规则、权限边界、测试与生产行为均由我评审。
- 104数据库表
- 594REST 端点
- 3,434测试用例
- 60 × 7权限键 × 岗位角色
- 3运营中的酒店
- 1开发者——从需求到运维
全部数字于 2026 年 9 月从代码库中统计得出。
问题
客房部靠纸、表格
和一个聊天群运转。
一张日清洁表、一张周深洁表、一个报房态的聊天群、一张算计件工资的表格,交接班记录放在 Microsoft Lists 里。没有一样能查询,没人能证明某间房查过,新增一种房型时,清洁工的工资会悄悄变成零。
这个平台为一家三店的精品酒店集团替换了以上全部:房态、清洁与查房派工、维修升级、公区排班、交接班、钥匙与失物台账,以及挂在这些之上的模块——计件工资、积分与奖励、电子签署的入职文件、库存盘点和面向业主的营收日报。每家酒店的数据各自隔离;每个页面和端点都受 7 个岗位角色、60 个权限键构成的矩阵控制。
七个角色,七套界面
每个岗位有自己的界面,
而不是同一套的过滤版。
权限在代码里的单一注册表中声明,启动时同步进数据库,管理员可在运行时重新分配——业务可以调整角色而无需改代码。
客房清洁员
- 在自己手机上看任务列表,按工作类型分页,大触控目标
- 开始 → 完成 → 拍照留证;最低照片数由服务端强制
- 「请勿打扰」与「免服务」是一等结果;被锁定的工作会解释原因而非隐藏
- 自己的计件收入、积分、奖励商城和待签文件
杂工(Houseman)
- 特殊任务队列:送物、倒垃圾、深洁项目
- 可自行完成的维修工单
- 维修工单与杂工任务的双向转换
- 手机上完成库存盘点与库存移动
查房员
- 已完成房间的查房队列,三种明确结果:可入住、Vacant Clean、返工
- 返工是原任务上的一轮,房间历史保持一条线,计件不重复
- 带评分、分项的房间审核与可打印报告
- 查房权限可按值班表授予,而不只按角色
前台
- 实时房态板,支持批量迁移,高风险操作有护栏
- 统一的派工入口;报告分流与快速退房申报队列
- 交接班看板:跟进、确认、导入旧 Excel 表
- 钥匙审计台账、失物登记、夜审核对、日用房处理、承包商自助登记
经理
- 前台的一切,加上审批、指派与升级的权限
- 计件工资视图与每日任务核对——这是工资依据,因此员工不能调整自己的
- 只限本店范围的审批收件箱
- 公区检查表、排班、查房轮值、公告
IT 运维
- 账号、酒店与房间配置
- PMS 集成健康度:Webhook、同步运行、水位、异常
- 通知总开关与内部 Wiki 编写
- 刻意不授予:工资、不可逆的「已支付」步骤、员工身份证件
业主
- 权限矩阵:运行时把任意权限重新分配给任意角色
- 定时营收日报;团队日报;公区周览
- 员工文件库:电子签名、PDF 字段标注器、表单构建器
- 面向员工的更新日志:461 条,用读者能懂的话写
架构
每个概念只有一条写路径。
所有 SQL 在同一层。
每个写操作端点都经过三道关:身份认证、权限键、然后在取出的行本身上断言酒店范围。
- 01桌面 SPA · 79 个路由 / 移动端外壳 · 30 个路由React 18 · TypeScript · Vite · Ant Design 令牌 · Zustand · TanStack Query · react-intl(中 / 英)
- 02Express API · 61 个路由模块 · 594 个端点authenticate (JWT) → requirePermission(key) → enforceHotelAccess(row.hotel_id)
- 0349 个服务 · 8 个调度器 · 审计日志业务规则、会抛错的资金不变量、只追加的台账
- 0445 个仓储模块——系统中唯一有 SQL 的地方服务组合仓储,路由组合服务;路由文件不构造查询
- 05SQLite(better-sqlite3,WAL)· 104 张表150+ 个有序幂等迁移,完整性校验,迁移前快照
- Nginx · PM2 · 每夜备份 cron
- 原子化前端切换:先构建到暂存目录再移动
- 同时容器化:多阶段构建、非 root、健康检查
最难的部分
状态迁移是事件,
不是快照。
PMS 掌握预订与房间状况,但不能直接驱动本地工作:它的版本号会倒退,响应里缺失不等于删除,一次静态的「vacant dirty」读数也不代表房间需要再清洁一次。
- 01PMS 事件Occupied → Vacant Dirty(Webhook + 增量轮询)
- 02镜像幂等 UPSERT,版本守卫,从不删除
- 03投影解释迁移事件;最多生成一个退房清洁周期
- 04客房作业派发任务、拍照留证,客人离店前保持锁定
- 05查房可入住 · Vacant Clean · 返工
- 06回写一种状态、一间房、三道门禁、按酒店的允许名单
与进行中、已支付或人工创建的工作发生冲突时,进入人工复核队列,而不是被覆盖。修复或重放源数据永远不会改写运营数据。
数据库设计
值得一读的
几个决定。
SQLite 中的 104 张表,只能通过 45 个仓储模块访问。每张业务表都带 hotel_id;多租户是一列字段,由一个辅助函数统一强制。
金额是整数分
工资金额曾是浮点数,一周工资总额是一次浮点 SUM。新增并回填了整数分列;所有求和都以分计算,只在边界转成元。
价格在完成时快照
完成的任务保存当时适用的费率。三个费率等级取代了按房型计价的表——正是后者让新增房型悄悄付零。
资金不变量直接抛错
带类型码的 PayrollIntegrityError 会让整个事务失败。「房间做完了却没人拿到钱」正是静默跳过造成的。
职责分离写进 schema
审批工资周期与标记「已支付」是两个不同的键;「已支付」键默认不授予任何角色,授予它永远是一次明确、可见的动作。
历史即重点之处只追加
审计日志、指派历史、房态事件、PMS 迁移、回写尝试、积分条目。重要的东西都不原地更新。
时间以酒店本地为准
SQLite 的 datetime('now') 是 UTC,曾让通知显示在十小时前。所有写路径都经过悉尼时钟辅助函数;测试也用同一函数取「今天」。
工程实践
为长期运维而建,
不只是交付。
3,434 个测试,以不变量为核心
覆盖资金与状态迁移的服务与仓储测试、路由与权限矩阵测试、组件与 Hook 测试、fast-check 属性测试。失败的测试从不被当作偶发。
权限即声明式数据
一个注册表文件;启动时同步;同一个键在前端通过 <Can> 控制界面,在后端通过 requirePermission 控制端点。下线一个权限键是一等操作。
真正的设计系统
源自客户品牌的令牌、所有模块页共用的 PageFrame、统一的表格语言、每页最多一个主操作、独立的移动端外壳。
端到端的发布工程
Nginx、PM2、原子化前端切换、迁移前快照、发布后验证、保留 14 天的每夜备份,以及 461 条面向员工的更新日志。
代码库
约 26 万行 TypeScript 的分布
生产代码约 19.5 万行;测试约 6.4 万行,分布在 340 个文件。
这个项目证明的能力
- 规模化全栈 TypeScript
- 关系建模与台账设计
- 线上 schema 演进
- API 设计与分层鉴权
- 第三方系统集成
- 权限架构
- 面向一线员工的移动端体验
- 设计系统
- 测试工程
- DevOps / SRE
- 产品发现
- 独立负责全程
完整指标表
| 生产 TypeScript / TSX(不含测试) | 约 195,000 行 |
|---|---|
| 含测试的 TypeScript 总量 | 约 260,000 行 |
| 数据库表 | 104 |
| 有序幂等迁移 | 150+(最新部署 #156) |
| REST 端点 | 594,分布在 61 个路由模块 |
| 仓储 / 服务模块 | 45 / 49(+26 个 PMS 集成模块) |
| 后台调度器 | 8 |
| 权限键 / 岗位角色 | 60(8 个模块)/ 7 |
| 桌面 / 移动端路由 | 79 / 30 |
| 测试文件 / 用例 | 340 / 3,434(约 64,000 行) |
| 面向员工的更新日志 | 461 条 |
| 运营中的酒店 | 3 |
| 界面语言 | 2(中文 / 英文) |
交付过程
这项工作
是如何成形的。
从对问题的第一个模型,到检验系统的实际行为。
观察工作本身
在决定软件要自动化什么之前,先梳理真实的前台、客房与维修交接。
划定边界
把角色、酒店范围与异常路径,转化为明确的权限与数据规则。
分片交付
逐步上线各个工作流,让员工在酒店照常运营的同时验证行为。
守住生产环境
用构建 / 静态检查 / 测试门禁、审计记录、部署控制与员工文档来降低运营风险。
工程证据
那些
真正重要的细节。
- 酒店间数据隔离与基于角色的权限
- 审计日志与回归测试覆盖
- 104 张表 · 594 个 REST 端点 · 3,434 个测试(截至 2026 年 9 月)
- 60 个权限键覆盖 7 个岗位角色,可在运行时重新分配
- 与 PMS 双向集成:只读镜像、投影层、受门禁保护的回写
- 500+ 次提交,全部经过构建 / 静态检查 / 测试门禁;150+ 个幂等迁移
- 以 PM2 与 Nginx 运行在 Linux 生产环境,每夜备份,原子化发布
决策细节 / 展开阅读
01把酒店边界放在服务层
酒店选择器方便导航,但每个请求还必须遵守用户被授权的酒店范围。把规则放在服务边界,即便操作绕过预期的界面流程直达 API,规则依然生效。
02让运营异常保持可见
房态、客房清洁与交接班各有不同的负责人。明确的状态让员工看得见未完成的工作与异常,也给软件提供了可验证的具体迁移。
03AI 辅助实现,人来评审
AI 工具帮助生成和迭代改动。需求、业务规则、集成、调试、测试评审,以及在真实环境中对结果的验收,由我负责。
04让改动可追溯
审计记录支撑事后调查;回归测试在发布前保护既有行为。当多家酒店依赖同一个系统时,两者都不可少。
结果
平台从个人构建走进多酒店的日常运营,用一个可审计的系统取代了彼此割裂的流程。
由 AI 辅助交付:我负责需求、业务规则、评审、集成、测试与上线。本案例仅使用架构描述;生产数据与源码不公开。
- React
- TypeScript
- Node.js
- Express
- SQL
- Linux
- PM2
- Nginx
下一个案例
02Power Platform 内部运营工具组→