🪟 第18章 Visual C++与SQL Server实现酒店管理系统
📄 一句话概括

菜单不是写死的,是按角色过滤出来的:管理员全看到、前台看业务、保洁只看房态。 菜单过滤只是「看不见」,不是「进不去」——每一层都要再校验一次。 主窗体上「底部显示连接状态」这条很土,但用户自己就能判断是断网还是程序崩了。

📝 本章和前面几章的关系

18.5 登录成功、拿到了角色,这一节用这个角色决定「主窗体长什么样」。 教材 18.6 分三小节:主窗体模块概述、技术分析、实现过程。 「概述」里那句「主窗体是系统的中枢」是对的——它管着菜单、会话、子窗体的生命周期。 沙箱画不出窗体,所以这一段用表格 + 逐角色打印菜单来表达, 重点是「怎么筛」而不是「怎么摆」。

一、菜单不是写死的,是按角色过滤出来的

菜单 允许的角色
客房管理 管理员、前台
入住登记 管理员、前台
退房结账 管理员、前台
房价维护 管理员
报表 管理员
房态看板 管理员、前台、保洁

三种角色过滤后的结果: 管理员全看到;前台看到客房、入住、退房、房态四项;保洁只看到房态看板一项。 这个设计比「隐藏按钮」可靠——因为菜单级的过滤拦住了「进不去这个页面」。 按钮权限是第二道,不是第一道。 菜单表存哪? 小系统可以直接写在代码里的数组(像这里); 要动态配置就建一张 tb_menu 表(菜单名、父菜单、需要的角色)。 后者更灵活,但要多一个「菜单维护」界面——一期通常不值。

二、主窗体还该显示什么

区域 内容 为什么
顶部 系统名、当前操作员、当前时间 出问题时知道「谁在用」
左侧 菜单树 主导航
右侧 房态概览(空闲几间、已住几间) 前台一屏就能报数
底部 数据库连接状态 连不上时立刻看得出来

「底部显示连接状态」这条很土,但很值。 桌面程序最常见的问题是「用了半小时突然报错」—— 如果底部一直显示连接状态,用户自己就能看出是网断了还是程序崩了。 「当前操作员」也一样。 前台交接班时经常两个人用同一台机器, 顶部显示「当前是谁登录的」能避免「用别人的账号干活」——而这种错在出问题时会让你查错方向。 右上角那一块「房态概览」是业务需要: 客人问「还有房吗」,前台不用点进任何页面就能答。 这就是「一屏能报数」的价值。

三、菜单过滤 ≠ 权限控制

层 校验什么
菜单层 这个角色看不看得到这个入口
窗体层 打开时再确认一次角色(防止被直接调用)
SQL 层 只给该角色能碰的表/视图的权限(16.3)

菜单只是「看不见」,不是「进不去」。 用户完全可以直接调用那个窗体的功能(改配置、发请求、写脚本)。 所以三层都要校验,只做第一层叫「界面美化」。 这也是为什么真实系统里「后端校验」永远不能省——前端能做的事,用户都能绕过。 第三层最容易被忽略,但它最硬: 即使程序逻辑被绕过, 数据库权限还在那儿挡着(16.3 的 GRANT/DENY)。 所以「最小权限」不只是代码规范,是最后一道物理防线。

四、主窗体的生命周期

登录成功 → 建主窗体(按角色生成菜单)→ 用户点菜单 → 打开子窗体 → 关闭子窗体 → 退出时释放连接。 最容易漏的是最后一步: 程序退出不释放连接, 连接池里的连接一直挂着——几个人反复开关之后,库就「连接数满了」。 第二个容易漏的是「子窗体重复打开」: 用户连点两下菜单,开了两个一样的窗口,改完互相覆盖。 常规做法是「已经开着就激活它,不再新建」。 第三个是「模态 vs 非模态」: 登录窗体应该是模态(不关就不能操作别的), 而业务窗体通常是非模态(可以同时看两个)。这个选择错了,用户体验会非常别扭。

五、动手 Demo:三种角色,三套菜单

这一段把菜单定义成一个数组(每个菜单标上允许的角色), 然后按三种角色各过滤一次,打印出各自看到的菜单。 逻辑只有一行——但这一行决定了「谁能进哪个页面」。

menus = [
    (u"客房管理", u"管理员", u"前台"),
    (u"入住登记", u"管理员", u"前台"),
    (u"退房结账", u"管理员", u"前台"),
    (u"房价维护", u"管理员"),
    (u"报表",     u"管理员"),
    (u"房态看板", u"管理员", u"前台", u"保洁"),
]

for role in (u"管理员", u"前台", u"保洁"):
    vis = [m[0] for m in menus if role in m[1:]]
    print(role, u"看到:", u"、".join(vis))

实跑输出:

--------------------------------------------------------------------
主窗体:菜单树与角色过滤
--------------------------------------------------------------------


  ① 菜单不是写死的,是按角色过滤出来的
  --------------------------------------------------------------------

  菜单表长这样(每个菜单标上「哪些角色能看」):

| 菜单 | 允许的角色 |
|---|---|
| 客房管理 | 管理员、前台 |
| 入住登记 | 管理员、前台 |
| 退房结账 | 管理员、前台 |
| 房价维护 | 管理员 |
| 报表 | 管理员 |
| 房态看板 | 管理员、前台、保洁 |

  管理员 看到:客房管理、入住登记、退房结账、房价维护、报表、房态看板
  前台 看到:客房管理、入住登记、退房结账、房态看板
  保洁 看到:房态看板
  三行输出就是「权限菜单」的全部逻辑:同一个窗体,按角色筛一遍菜单项。
  保洁只看到「房态看板」,前台看到业务三个,管理员全看到。
  这个设计比「隐藏按钮」可靠——因为菜单级的过滤拦住了「进不去这个页面」。

  ② 主窗体还该显示什么
  --------------------------------------------------------------------
| 区域 | 内容 | 为什么 |
|---|---|---|
| 顶部 | 系统名、当前操作员、当前时间 | 出问题时知道「谁在用」 |
| 左侧 | 菜单树 | 主导航 |
| 右侧 | 房态概览(空闲几间、已住几间) | 前台一屏就能报数 |
| 底部 | 数据库连接状态 | 连不上时立刻看得出来 |

  「底部显示连接状态」这条很土,但很值。
  桌面程序最常见的问题是「用了半小时突然报错」——如果底部一直显示连接状态,
  用户自己就能看出是网断了还是程序崩了。

  ③ 菜单过滤 ≠ 权限控制
  --------------------------------------------------------------------
  菜单只是「看不见」,不是「进不去」。
  用户完全可以直接调用那个窗体的功能(改配置、发请求、写脚本)。
  所以每一层都要校验:
| 层 | 校验什么 |
|---|---|
| 菜单层 | 这个角色看不看得到这个入口 |
| 窗体层 | 打开时再确认一次角色(防止被直接调用) |
| SQL 层 | 只给该角色能碰的表/视图的权限(16.3) |

  三层都做才叫「权限」,只做第一层叫「界面美化」。这也是为什么真实系统里「后端校验」永远不能省。

  ④ 主窗体的生命周期
  --------------------------------------------------------------------
  登录成功 → 建主窗体(按角色生成菜单)→ 用户点菜单 → 打开子窗体 → 关闭子窗体 → 退出时释放连接。
  最容易漏的是最后一步: 程序退出不释放连接,连接池里的连接一直挂着——几个人反复开关之后,
  库就「连接数满了」。

m[1:] 这一句是「跳过菜单名,取后面的角色列表」。 role in m[1:] 就是过滤条件——一行代码,三种结果。 注意「保洁」只看到一项。 这不是「简化」, 是「她确实只需要这个」——多余的功能对她是干扰,也是风险(点错了可能改到别人的数据)。 这个数组写死在代码里,好处是简单、坏处是要改就得重新编译。 小系统没问题;一旦「菜单要经常变」,就该搬到表里—— 判断标准:这个月改过几次?改过两次以上就该搬。 最后再强调一次:这只是一层。 真正要防的是「绕过菜单直接调功能」—— 所以在打开每个子窗体时,还得再确认一次角色(18.6 第 ③ 段)。

30 秒实验

  1. 给菜单数组加一个「报表—员工工资」项,只允许管理员。 前台和保洁的菜单不会变——这就是「加功能不影响别人」。
  2. 把 menus 改成从数据库表里读(建一张 tb_menu),再跑一遍。 结果一样,但「改菜单不用改代码」——付出的代价是多一张表和一个维护界面。
  3. 想一想:保洁需要「房态看板」,但她需不需要「看到客人姓名」? 不需要。 这说明同一张表、 不同角色该看到不同的列——那就是「视图 + 不同授权」的活(16.3)。

常见误区

  1. 「把菜单都显示出来,点不动的灰掉」。 灰掉的菜单等于告诉别人「这里有东西」,而且一旦前端被绕过就全暴露。 该隐藏就隐藏,该拒绝就拒绝。
  2. 「菜单过滤做了就等于有权限了」。 菜单只挡「看不见」,挡不住「直接调用」。 每一层都要校验。
  3. 「主窗体上多放点信息显得专业」。 信息多了就没人看。 一屏只放「操作员、时间、房态、连接状态」就够。
自测三问(点开对答案) 1. 菜单是怎么按角色过滤的? —— **每个菜单标上「允许哪些角色」,登录后按当前角色筛一遍。** **逻辑就一行 `role in m[1:]`。** 2. 为什么「菜单过滤」不等于「权限控制」? —— **因为菜单只是「看不见」,用户可以直接调用那个功能。** **所以窗体层要再校验、SQL 层还要有权限。** 3. 主窗体底部为什么建议显示连接状态? —— **让用户自己判断「是网断了还是程序崩了」。** **桌面程序最常见的故障就是这种,而报错信息往往看不懂。**

下一篇 18.7 讲业务模块:开房为什么必须用事务、退房账单怎么算, 以及一条「房态自查 SQL」怎么把「事务没做全」的脏数据抓出来。

相关笔记