# 酒店二维码模块开发文档
## 1. 模块概述
**模块名称**: `forge-hotel`
**包路径**: `com.mdframe.forge.business.core.hotel`
**位置**: `forge-server/forge-business/forge-hotel/`
### 1.1 功能范围
| 功能 | 说明 |
|------|------|
| 二维码批量生成 | 批量生成不绑定房间的二维码,支持 10/50/100/200 个一批 |
| 二维码绑定/重绑/解绑 | 员工通过 PC 后台扫码绑定房间,记录操作人和时间 |
| 二维码状态管理 | 启用/禁用二维码(字典 sys_normal_disable: 0=启用, 1=禁用) |
| 绑定日志追溯 | 完整记录每次绑定/重绑/解绑操作 |
| 批量删除 | 勾选多个二维码批量逻辑删除,已绑定房间的拒绝删除 |
| 批量下载ZIP | 勾选多个二维码生成 PNG 图片打包为 ZIP 下载 |
| 单条下载 | 操作列下载单个二维码 PNG 图片 |
| 房间管理 | 房间 CRUD,关联房型 |
| 房型管理 | 房型 CRUD |
| 扫码查询(开放接口) | 免登录接口,供顾客/钉钉H5/小程序调用 |
### 1.2 三端架构
```
PC 管理后台(Sa-Token) → 批量生成、列表、批量删除、批量下载ZIP、单条下载、绑定/重绑/解绑、禁用/启用
钉钉 H5(DingTalk SSO) → 员工扫码绑定房间、重绑、查看绑定状态
微信/支付宝小程序 → 顾客扫码点餐(免登录,调用 /hotel/open/scan)
```
---
## 2. 数据库设计
### 2.1 表结构
#### hotel_room_type(房型表)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键(雪花算法) |
| tenant_id | BIGINT | 租户ID,默认1 |
| type_name | VARCHAR(64) | 房型名称 |
| description | VARCHAR(256) | 房型描述 |
| sort_order | INT | 排序号 |
| del_flag | BIGINT | 逻辑删除标记 |
| create_by/create_time/create_dept | - | 审计字段 |
| update_by/update_time | - | 审计字段 |
#### hotel_room(房间表)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| tenant_id | BIGINT | 租户ID |
| room_no | VARCHAR(32) | 房间号(租户内唯一) |
| room_type_id | BIGINT | 房型ID |
| floor_no | VARCHAR(16) | 楼层 |
| status | VARCHAR(16) | 状态:VACANT/OCCUPIED/CLEANING/MAINTENANCE |
| sort_order | INT | 排序号 |
| remark | VARCHAR(256) | 备注 |
| del_flag | BIGINT | 逻辑删除标记 |
**唯一索引**: `UNIQUE (tenant_id, room_no, del_flag)`
#### hotel_qr_code(二维码表)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| tenant_id | BIGINT | 租户ID |
| short_code | VARCHAR(32) | 二维码唯一短码(8位大写字母+数字) |
| sign | VARCHAR(64) | MD5签名值 |
| room_id | BIGINT | 绑定房间ID(NULL=未绑定) |
| room_no | VARCHAR(32) | 冗余房间号 |
| status | VARCHAR(16) | 状态:0=启用, 1=禁用(对应字典 sys_normal_disable) |
| batch_no | VARCHAR(32) | 批次号(格式:QR+yyyyMMddHHmmss) |
| bind_user_id | BIGINT | 最近绑定人ID |
| bind_user_name | VARCHAR(64) | 最近绑定人姓名 |
| bind_time | DATETIME | 最近绑定时间 |
| unbind_user_id | BIGINT | 最近解绑人ID |
| unbind_user_name | VARCHAR(64) | 最近解绑人姓名 |
| unbind_time | DATETIME | 最近解绑时间 |
| del_flag | BIGINT | 逻辑删除标记 |
**唯一索引**: `UNIQUE (tenant_id, short_code)`
#### hotel_qr_bind_log(绑定日志表)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| tenant_id | BIGINT | 租户ID |
| qr_code_id | BIGINT | 二维码ID |
| short_code | VARCHAR(32) | 冗余短码 |
| room_id | BIGINT | 房间ID |
| room_no | VARCHAR(32) | 冗余房间号 |
| action_type | VARCHAR(16) | 操作类型:BIND/REBIND/UNBIND |
| operator_id | BIGINT | 操作人ID |
| operator_name | VARCHAR(64) | 操作人姓名 |
| operate_time | DATETIME | 操作时间 |
| remark | VARCHAR(256) | 备注 |
### 2.2 Flyway 迁移脚本
**文件**: `forge-server/db/migration/V1.0.86__add_hotel_qr_code_tables.sql`
包含内容:
- 4张业务表建表语句(均使用 `CREATE TABLE IF NOT EXISTS`)
- 菜单权限资源(`sys_resource`,`tenant_id=1`,带 `NOT EXISTS` 防重复)
- 字典数据(`sys_dict_type` + `sys_dict_data`,`tenant_id=1`)
### 2.3 字典数据
| 字典类型 | 字典值 | 标签 | 标签样式 |
|----------|--------|------|----------|
| sys_normal_disable | 0 | 正常 | success |
| sys_normal_disable | 1 | 停用 | danger |
| hotel_room_status | VACANT | 空闲 | success |
| hotel_room_status | OCCUPIED | 入住 | primary |
| hotel_room_status | CLEANING | 打扫中 | warning |
| hotel_room_status | MAINTENANCE | 维护中 | danger |
| hotel_qr_bind_action | BIND | 绑定 | success |
| hotel_qr_bind_action | REBIND | 重绑 | warning |
| hotel_qr_bind_action | UNBIND | 解绑 | danger |
---
## 3. 后端 API 接口
### 3.1 管理端接口(需 Sa-Token 登录)
#### 二维码管理
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/hotel/qrcode/page` | 分页查询二维码 |
| GET | `/hotel/qrcode/:id` | 查询二维码详情 |
| POST | `/hotel/qrcode/batch-generate` | 批量生成二维码 |
| POST | `/hotel/qrcode/:id/bind` | 绑定房间 |
| POST | `/hotel/qrcode/:id/rebind` | 重新绑定房间 |
| POST | `/hotel/qrcode/:id/unbind` | 解绑房间 |
| PUT | `/hotel/qrcode/:id/status` | 更新二维码状态 |
| POST | `/hotel/qrcode/batch-remove` | 批量删除二维码(逻辑删除) |
| POST | `/hotel/qrcode/batch-download` | 批量下载二维码ZIP |
| GET | `/hotel/qrcode/:id/bind-logs` | 查询绑定日志 |
#### 房间管理
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/hotel/room/page` | 分页查询房间 |
| GET | `/hotel/room/:id` | 查询房间详情 |
| POST | `/hotel/room` | 新增房间 |
| PUT | `/hotel/room` | 修改房间 |
| POST | `/hotel/room/remove/:id` | 删除房间 |
| GET | `/hotel/room/list-all` | 查询全部房间(下拉用) |
#### 房型管理
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/hotel/room-type/list` | 查询房型列表 |
| GET | `/hotel/room-type/:id` | 查询房型详情 |
| POST | `/hotel/room-type` | 新增房型 |
| PUT | `/hotel/room-type` | 修改房型 |
| POST | `/hotel/room-type/remove/:id` | 删除房型 |
### 3.2 开放接口(免登录)
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/hotel/open/scan?shortCode=xxx&sign=xxx` | 扫码查询二维码信息 |
**请求参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| shortCode | String | 是 | 二维码短码 |
| sign | String | 是 | 签名校验值 |
**响应示例(未绑定)**:
```json
{
"code": 200,
"data": {
"qrCodeId": 1234567890,
"shortCode": "AB12CD34",
"status": "0",
"bound": false,
"room": null,
"bindInfo": null
}
}
```
**响应示例(已绑定)**:
```json
{
"code": 200,
"data": {
"qrCodeId": 1234567890,
"shortCode": "AB12CD34",
"status": "0",
"bound": true,
"room": {
"roomId": 9876543210,
"roomNo": "301",
"roomTypeName": "标准双人间"
},
"bindInfo": {
"operatorName": "张三",
"bindTime": "2026-08-10 14:30:00"
}
}
}
```
---
## 4. 二维码 URL 格式
二维码内容为平台无关的 URL 格式:
```
{baseUrl}/hotel/scan?c={shortCode}&t={tenantId}&s={sign}
```
| 参数 | 说明 |
|------|------|
| c | 二维码短码(8位) |
| t | 租户ID |
| s | MD5签名值 = MD5(shortCode + tenantId + secret) |
**签名密钥配置**:
- 默认值: `hotel_qr_default_secret`
- 生产环境: 通过环境变量 `HOTEL_QR_SECRET` 覆盖
---
## 5. 前端页面
| 页面 | 路径 | 说明 |
|------|------|------|
| 二维码管理 | `forge-admin-ui/src/views/hotel/qrcode.vue` | AiCrudPage 配置化页面(批量生成、批量删除、批量下载ZIP、单条下载、绑定/重绑/解绑、状态切换、日志查看) |
| 房间管理 | `forge-admin-ui/src/views/hotel/room.vue` | AiCrudPage 配置化页面 |
| 房型管理 | `forge-admin-ui/src/views/hotel/roomType.vue` | AiCrudPage 配置化页面 |
| API 文件 | `forge-admin-ui/src/api/hotel.js` | 统一 API 封装 |
---
## 6. 菜单权限
| 资源ID | 名称 | 类型 | 权限标识 |
|--------|------|------|----------|
| 2000 | 酒店管理 | 菜单(1) | - |
| 2001 | 二维码管理 | 菜单(1) | hotel:qrcode:list |
| 2011 | 二维码生成 | 按钮(2) | hotel:qrcode:generate |
| 2012 | 二维码绑定 | 按钮(2) | hotel:qrcode:bind |
| 2013 | 二维码管理操作 | 按钮(2) | hotel:qrcode:manage |
| 2014 | 二维码导出 | 按钮(2) | hotel:qrcode:export |
| 2002 | 房间管理 | 菜单(1) | hotel:room:list |
| 2021 | 房间新增 | 按钮(2) | hotel:room:add |
| 2022 | 房间编辑 | 按钮(2) | hotel:room:edit |
| 2023 | 房间删除 | 按钮(2) | hotel:room:delete |
| 2003 | 房型管理 | 菜单(1) | hotel:roomtype:list |
| 2031 | 房型新增 | 按钮(2) | hotel:roomtype:add |
| 2032 | 房型编辑 | 按钮(2) | hotel:roomtype:edit |
| 2033 | 房型删除 | 按钮(2) | hotel:roomtype:delete |
---
## 7. Maven 依赖变更
### 7.1 forge-dependencies(BOM)
新增 ZXing 版本管理:
```xml
3.5.3
com.google.zxing
core
${zxing.version}
com.google.zxing
javase
${zxing.version}
```
新增 forge-hotel 版本声明:
```xml
com.mdframe.forge
forge-hotel
${revision}
```
### 7.2 forge-business/pom.xml
新增子模块:
```xml
forge-business-core
forge-hotel
```
### 7.3 forge-admin-server/pom.xml
新增依赖:
```xml
com.mdframe.forge
forge-hotel
${revision}
```
---
## 8. 框架层变更
### SaTokenConfig.java
在 Sa-Token 拦截器中新增 `/hotel/open/**` 路径排除(免登录):
```java
// 登录校验拦截器
.notMatch("/hotel/open/**")
// API权限拦截器
.excludePathPatterns("/hotel/open/**")
```
---
## 9. 环境变量
| 变量名 | 必填 | 默认值 | 说明 |
|--------|------|--------|------|
| HOTEL_QR_SECRET | 否 | hotel_qr_default_secret | 二维码签名密钥(生产环境必须修改) |
---
## 10. 后续扩展预留
| 扩展点 | 说明 |
|--------|------|
| 钉钉 H5 集成 | 通过 DingTalk SSO 获取员工身份,调用绑定/重绑接口 |
| 小程序 openid 绑定 | 在 `/hotel/open/` 下新增小程序专用接口 |
| 二维码导出打印 | 使用 EasyExcel 批量导出二维码图片(已实现批量下载ZIP) |
| 顾客点餐流程 | 扫码后进入点餐 H5/小程序页面 |
| 房间状态实时推送 | WebSocket 推送房间状态变更 |