案例研究 / 01

酒店运营平台

React / TypeScript / Node / SQL

一套已上线的运营系统,把悉尼三家酒店的房态、客房清洁、交接班、维修与资产流程连接起来。

← 全部项目
系统架构01 — 运营
01员工工作台React / TypeScript
02规则与权限Node / 范围隔离服务
03运营记录SQL / 审计日志

在服务边界实现酒店间数据隔离

角色需求、工作流设计与 AI 辅助交付
背景三家运营中的酒店共同使用的内部平台
状态已上线 / 私有

挑战

在不中断酒店日常运营的前提下,替换掉分散在纸张、表格与聊天工具里的工作流。

我的贡献

从运营一线出发,主导需求、工作流设计与交付。平台通过 AI 辅助的开发流程构建,业务规则、权限边界、测试与生产行为均由我评审。

  • 104数据库表
  • 594REST 端点
  • 3,434测试用例
  • 60 × 7权限键 × 岗位角色
  • 3运营中的酒店
  • 1开发者——从需求到运维

全部数字于 2026 年 9 月从代码库中统计得出。

问题

客房部靠纸、表格
和一个聊天群运转。

一张日清洁表、一张周深洁表、一个报房态的聊天群、一张算计件工资的表格,交接班记录放在 Microsoft Lists 里。没有一样能查询,没人能证明某间房查过,新增一种房型时,清洁工的工资会悄悄变成零。

这个平台为一家三店的精品酒店集团替换了以上全部:房态、清洁与查房派工、维修升级、公区排班、交接班、钥匙与失物台账,以及挂在这些之上的模块——计件工资、积分与奖励、电子签署的入职文件、库存盘点和面向业主的营收日报。每家酒店的数据各自隔离;每个页面和端点都受 7 个岗位角色、60 个权限键构成的矩阵控制。

七个角色,七套界面

每个岗位有自己的界面,
而不是同一套的过滤版。

权限在代码里的单一注册表中声明,启动时同步进数据库,管理员可在运行时重新分配——业务可以调整角色而无需改代码。

01 移动端

客房清洁员

  • 在自己手机上看任务列表,按工作类型分页,大触控目标
  • 开始 → 完成 → 拍照留证;最低照片数由服务端强制
  • 「请勿打扰」与「免服务」是一等结果;被锁定的工作会解释原因而非隐藏
  • 自己的计件收入、积分、奖励商城和待签文件
02 移动端

杂工(Houseman)

  • 特殊任务队列:送物、倒垃圾、深洁项目
  • 可自行完成的维修工单
  • 维修工单与杂工任务的双向转换
  • 手机上完成库存盘点与库存移动
03 移动端

查房员

  • 已完成房间的查房队列,三种明确结果:可入住、Vacant Clean、返工
  • 返工是原任务上的一轮,房间历史保持一条线,计件不重复
  • 带评分、分项的房间审核与可打印报告
  • 查房权限可按值班表授予,而不只按角色
04 桌面 + 移动

前台

  • 实时房态板,支持批量迁移,高风险操作有护栏
  • 统一的派工入口;报告分流与快速退房申报队列
  • 交接班看板:跟进、确认、导入旧 Excel 表
  • 钥匙审计台账、失物登记、夜审核对、日用房处理、承包商自助登记
05 桌面端

经理

  • 前台的一切,加上审批、指派与升级的权限
  • 计件工资视图与每日任务核对——这是工资依据,因此员工不能调整自己的
  • 只限本店范围的审批收件箱
  • 公区检查表、排班、查房轮值、公告
06 桌面端

IT 运维

  • 账号、酒店与房间配置
  • PMS 集成健康度:Webhook、同步运行、水位、异常
  • 通知总开关与内部 Wiki 编写
  • 刻意不授予:工资、不可逆的「已支付」步骤、员工身份证件
07 桌面端

业主

  • 权限矩阵:运行时把任意权限重新分配给任意角色
  • 定时营收日报;团队日报;公区周览
  • 员工文件库:电子签名、PDF 字段标注器、表单构建器
  • 面向员工的更新日志:461 条,用读者能懂的话写

架构

每个概念只有一条写路径。
所有 SQL 在同一层。

每个写操作端点都经过三道关:身份认证、权限键、然后在取出的行本身上断言酒店范围。

  1. 01
    桌面 SPA · 79 个路由 / 移动端外壳 · 30 个路由React 18 · TypeScript · Vite · Ant Design 令牌 · Zustand · TanStack Query · react-intl(中 / 英)
  2. 02
    Express API · 61 个路由模块 · 594 个端点authenticate (JWT) → requirePermission(key) → enforceHotelAccess(row.hotel_id)
  3. 03
    49 个服务 · 8 个调度器 · 审计日志业务规则、会抛错的资金不变量、只追加的台账
  4. 04
    45 个仓储模块——系统中唯一有 SQL 的地方服务组合仓储,路由组合服务;路由文件不构造查询
  5. 05
    SQLite(better-sqlite3,WAL)· 104 张表150+ 个有序幂等迁移,完整性校验,迁移前快照
酒店管理系统 PMS(外部)
① 只读镜像② 投影③ 受门禁的回写
  • Nginx · PM2 · 每夜备份 cron
  • 原子化前端切换:先构建到暂存目录再移动
  • 同时容器化:多阶段构建、非 root、健康检查

最难的部分

状态迁移是事件,
不是快照。

PMS 掌握预订与房间状况,但不能直接驱动本地工作:它的版本号会倒退,响应里缺失不等于删除,一次静态的「vacant dirty」读数也不代表房间需要再清洁一次。

  1. 01PMS 事件Occupied → Vacant Dirty(Webhook + 增量轮询)
  2. 02镜像幂等 UPSERT,版本守卫,从不删除
  3. 03投影解释迁移事件;最多生成一个退房清洁周期
  4. 04客房作业派发任务、拍照留证,客人离店前保持锁定
  5. 05查房可入住 · Vacant Clean · 返工
  6. 06回写一种状态、一间房、三道门禁、按酒店的允许名单

与进行中、已支付或人工创建的工作发生冲突时,进入人工复核队列,而不是被覆盖。修复或重放源数据永远不会改写运营数据。

数据库设计

值得一读的
几个决定。

SQLite 中的 104 张表,只能通过 45 个仓储模块访问。每张业务表都带 hotel_id;多租户是一列字段,由一个辅助函数统一强制。

01

金额是整数分

工资金额曾是浮点数,一周工资总额是一次浮点 SUM。新增并回填了整数分列;所有求和都以分计算,只在边界转成元。

02

价格在完成时快照

完成的任务保存当时适用的费率。三个费率等级取代了按房型计价的表——正是后者让新增房型悄悄付零。

03

资金不变量直接抛错

带类型码的 PayrollIntegrityError 会让整个事务失败。「房间做完了却没人拿到钱」正是静默跳过造成的。

04

职责分离写进 schema

审批工资周期与标记「已支付」是两个不同的键;「已支付」键默认不授予任何角色,授予它永远是一次明确、可见的动作。

05

历史即重点之处只追加

审计日志、指派历史、房态事件、PMS 迁移、回写尝试、积分条目。重要的东西都不原地更新。

06

时间以酒店本地为准

SQLite 的 datetime('now') 是 UTC,曾让通知显示在十小时前。所有写路径都经过悉尼时钟辅助函数;测试也用同一函数取「今天」。

三个生产环境故事

看判断,
不看体量。

01

冻结了五天的房间

防乱序守卫拒绝比已存版本更旧的镜像更新。源系统自己的版本号倒退了,于是一间房停止更新。从存储的时间戳诊断出来;修法是正确比较版本,而不是拆掉守卫。

02

付零工资的房型

费率按 PMS 的房型字符串索引,新房型产生了零值工资条目。改为三个费率等级、一个映射函数、一个查找函数——不改 schema,历史金额也永不重新映射。

03

客人走了,工作还锁着

退房清洁只在投影观察到退房迁移的那一刻解锁。错过那一刻就再无解锁路径,某酒店上线首日一间房锁了四个小时。现在每个周期都有一次核对扫描,恢复不再依赖抓住某个瞬间。

工程实践

为长期运维而建,
不只是交付。

3,434 个测试,以不变量为核心

覆盖资金与状态迁移的服务与仓储测试、路由与权限矩阵测试、组件与 Hook 测试、fast-check 属性测试。失败的测试从不被当作偶发。

权限即声明式数据

一个注册表文件;启动时同步;同一个键在前端通过 <Can> 控制界面,在后端通过 requirePermission 控制端点。下线一个权限键是一等操作。

真正的设计系统

源自客户品牌的令牌、所有模块页共用的 PageFrame、统一的表格语言、每页最多一个主操作、独立的移动端外壳。

端到端的发布工程

Nginx、PM2、原子化前端切换、迁移前快照、发布后验证、保留 14 天的每夜备份,以及 461 条面向员工的更新日志。

代码库

约 26 万行 TypeScript 的分布

生产代码约 19.5 万行;测试约 6.4 万行,分布在 340 个文件。

  • 后端:服务、仓储、路由522 个文件
  • 前端:组件与 Hook218 .tsx + 199 .ts
  • 测试340 个文件 · 3,434 个用例

这个项目证明的能力

  • 规模化全栈 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(中文 / 英文)

交付过程

这项工作
是如何成形的。

从对问题的第一个模型,到检验系统的实际行为。

01

观察工作本身

在决定软件要自动化什么之前,先梳理真实的前台、客房与维修交接。

02

划定边界

把角色、酒店范围与异常路径,转化为明确的权限与数据规则。

03

分片交付

逐步上线各个工作流,让员工在酒店照常运营的同时验证行为。

04

守住生产环境

用构建 / 静态检查 / 测试门禁、审计记录、部署控制与员工文档来降低运营风险。

工程证据

那些
真正重要的细节。

  • 酒店间数据隔离与基于角色的权限
  • 审计日志与回归测试覆盖
  • 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 内部运营工具组→