2026-08-07 22:31:32 +08:00
|
|
|
|
# 技术方案 - REQ-集成-001 会议室预定
|
2026-07-11 23:13:10 +08:00
|
|
|
|
|
|
|
|
|
|
> **版本**: v1.0
|
|
|
|
|
|
> **日期**: 2026-07-15
|
2026-08-07 22:31:32 +08:00
|
|
|
|
> **REQ编号**: REQ-集成-001
|
|
|
|
|
|
> **关联PRD**: `01-产品文档/08-集成生态/PRD-REQ-集成-001-会议室预定-v1.0.md`
|
2026-07-11 23:13:10 +08:00
|
|
|
|
> **状态**: 待评审
|
2026-08-07 22:31:32 +08:00
|
|
|
|
> **作者**: 高见远 (Gao) · 架构师
|
2026-07-11 23:13:10 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 目录
|
|
|
|
|
|
|
|
|
|
|
|
- [Part A: System Design](#part-a-system-design)
|
|
|
|
|
|
- [1. 实现方案 + 框架选型](#1-实现方案--框架选型)
|
|
|
|
|
|
- [2. 文件列表及相对路径](#2-文件列表及相对路径)
|
|
|
|
|
|
- [3. 数据结构和接口](#3-数据结构和接口)
|
|
|
|
|
|
- [4. 程序调用流程](#4-程序调用流程)
|
|
|
|
|
|
- [5. 待明确事项](#5-待明确事项)
|
|
|
|
|
|
- [Part B: Task Decomposition](#part-b-task-decomposition)
|
|
|
|
|
|
- [6. 依赖包列表](#6-依赖包列表)
|
|
|
|
|
|
- [7. 任务列表](#7-任务列表)
|
|
|
|
|
|
- [8. 共享知识](#8-共享知识)
|
|
|
|
|
|
- [9. 任务依赖图](#9-任务依赖图)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Part A: System Design
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 实现方案 + 框架选型
|
|
|
|
|
|
|
|
|
|
|
|
#### 1.1 核心技术挑战
|
|
|
|
|
|
|
|
|
|
|
|
| # | 挑战 | 分析 | 方案 |
|
|
|
|
|
|
|---|------|------|------|
|
|
|
|
|
|
| C1 | **企微会议室 access_token 独立管理** | 企微会议室API需要独立的"会议室"secret获取token,不同于现有应用secret和通讯录secret。现有 `WecomService` 已有 `get_access_token()` 和 `get_contact_access_token()` 双token模式 | 在 `WecomService` 中新增 `get_meetingroom_access_token()` 方法,复用 Redis缓存+内存降级模式,缓存key=`wecom:meetingroom_access_token`,TTL=6900秒 |
|
|
|
|
|
|
| C2 | **企微API为唯一数据源 + 本地不维护预定数据** | 所有预定状态查询、预定、取消都直接调用企微API,后端仅做代理+缓存+鉴权。这与现有系统中本地维护会话/消息数据的模式不同 | 后端 `MeetingroomService` 作为薄代理层:所有写操作直接转发企微API,读操作加Redis短缓存(30s)减少API调用。`terminal_room_binding` 表仅存终端↔会议室映射关系,不存预定数据 |
|
|
|
|
|
|
| C3 | **终端页面横屏大屏适配 + 深色主题** | 小鱼易联终端是物理大屏设备(1920×1080横屏),需要大字号触控交互,与现有H5端(Vant4移动端)和坐席端(Element Plus桌面端)UI风格完全不同 | 新建独立前端项目 `frontend-terminal`,使用 Vue3 + Tailwind CSS(不引入Vant/Element),自定义深色主题组件 |
|
|
|
|
|
|
| C4 | **终端身份识别 — 企微扫码登录** | 终端是公共设备,员工预定时需要身份识别。现有系统已有企微OAuth扫码登录流程(`auth_wecom_sso.py`),但终端场景需要适配(终端无企微内置浏览器UA) | 复用现有扫码登录 API (`/api/auth/qrcode`),终端页面通过轮询获取扫码结果。token存储在 Redis key=`terminal:token:{token}`,TTL=8小时 |
|
|
|
|
|
|
| C5 | **WebSocket 实时同步 + 轮询降级** | 终端需要实时显示会议室状态变更。现有WS有 agent/employee 双连接池,但终端是第三种连接类型 | 在 `ConnectionManager` 中新增 `terminal_connections` 字典,新增 `/ws/terminal/{terminal_sn}` 端点。WS断线时终端自动降级为30秒轮询 |
|
|
|
|
|
|
| C6 | **小鱼易联终端管理API集成(方案A为主,方案B预留)** | 用户确认终端型号/环境不确定,架构按方案A(浏览器全屏访问URL)设计,同时预留方案B(API推送URL)的接口 | `XyLinkService` 封装终端管理API,提供 `push_url_to_terminal()` 和 `get_terminal_status()` 方法。P0阶段终端直接浏览器访问URL,P1阶段可通过API推送 |
|
|
|
|
|
|
|
|
|
|
|
|
#### 1.2 框架与库选型
|
|
|
|
|
|
|
|
|
|
|
|
**后端(复用现有技术栈)**:
|
|
|
|
|
|
|
|
|
|
|
|
| 组件 | 选型 | 理由 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| Web框架 | FastAPI (现有) | 复用现有架构,异步支持好,自动生成OpenAPI文档 |
|
|
|
|
|
|
| ORM | SQLAlchemy 2.0 + async (现有) | 复用现有 `Mapped` / `mapped_column` 模式 |
|
|
|
|
|
|
| 数据库 | PostgreSQL (现有) | 复用现有数据库连接 |
|
|
|
|
|
|
| 缓存 | Redis (现有) | 复用 `settings.create_redis_client()` 和 `cache_service` |
|
|
|
|
|
|
| HTTP客户端 | httpx.AsyncClient (现有) | 与 `WecomService` 一致的异步HTTP调用模式 |
|
|
|
|
|
|
| 数据库迁移 | Alembic (现有) | 新增 `011_` 迁移脚本 |
|
|
|
|
|
|
|
|
|
|
|
|
**前端终端页面(新建项目)**:
|
|
|
|
|
|
|
|
|
|
|
|
| 组件 | 选型 | 理由 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| 框架 | Vue 3.4+ | 与现有前端技术栈一致,团队熟悉 |
|
|
|
|
|
|
| 构建工具 | Vite 5+ | 与现有前端一致,HMR开发体验好 |
|
|
|
|
|
|
| CSS方案 | Tailwind CSS 3+ | 深色主题 + 大字号自定义灵活,不需要UI组件库的样式约束 |
|
|
|
|
|
|
| 状态管理 | Pinia 2+ | 与现有前端一致 |
|
|
|
|
|
|
| 路由 | Vue Router 4+ | 与现有前端一致 |
|
|
|
|
|
|
| HTTP客户端 | Axios 1+ | 与现有前端一致 |
|
|
|
|
|
|
| TypeScript | TypeScript 5+ | 类型安全,与现有前端一致 |
|
|
|
|
|
|
|
|
|
|
|
|
**前端H5端(现有项目扩展)**:
|
|
|
|
|
|
|
|
|
|
|
|
| 组件 | 选型 | 理由 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| UI组件库 | Vant 4 (现有) | 复用现有组件 |
|
|
|
|
|
|
| 其他 | 同现有 frontend-h5 | 仅新增页面和路由 |
|
|
|
|
|
|
|
|
|
|
|
|
#### 1.3 架构模式
|
|
|
|
|
|
|
|
|
|
|
|
采用**分层架构 + 中间层代理模式**:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
┌──────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ 系统架构分层 │
|
|
|
|
|
|
│ │
|
|
|
|
|
|
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────────────────┐ │
|
|
|
|
|
|
│ │ 终端前端 │ │ H5前端(扩展) │ │ 管理后台(扩展) │ │
|
|
|
|
|
|
│ │ Vue3+Tailwind│ │ Vue3+Vant4 │ │ Vue3+Element+Tailwind │ │
|
|
|
|
|
|
│ │ frontend- │ │ frontend-h5/ │ │ frontend-admin/ │ │
|
|
|
|
|
|
│ │ terminal/ │ │ (现有) │ │ (现有) │ │
|
|
|
|
|
|
│ └──────┬───────┘ └──────┬───────┘ └───────────┬───────────────┘ │
|
|
|
|
|
|
│ │ │ │ │
|
|
|
|
|
|
│ │ REST API │ REST API │ REST API │
|
|
|
|
|
|
│ │ WebSocket │ │ │
|
|
|
|
|
|
│ └────────┬────────┴───────────────────────┘ │
|
|
|
|
|
|
│ │ │
|
|
|
|
|
|
│ ┌───────────────▼─────────────────────────────────────────────────┐ │
|
|
|
|
|
|
│ │ 后端 API 层 (FastAPI) │ │
|
|
|
|
|
|
│ │ app/api/meetingroom.py — 会议室预定REST API │ │
|
|
|
|
|
|
│ │ app/api/admin/terminal_binding.py — 终端绑定管理CRUD │ │
|
|
|
|
|
|
│ │ app/api/ws.py (扩展) — WebSocket终端端点 │ │
|
|
|
|
|
|
│ └───────────────┬─────────────────────────────────────────────────┘ │
|
|
|
|
|
|
│ │ │
|
|
|
|
|
|
│ ┌───────────────▼─────────────────────────────────────────────────┐ │
|
|
|
|
|
|
│ │ Service 业务逻辑层 │ │
|
|
|
|
|
|
│ │ app/services/meetingroom_service.py — 会议室预定业务(缓存+代理) │ │
|
|
|
|
|
|
│ │ app/services/xylink_service.py — 小鱼易联终端管理 │ │
|
|
|
|
|
|
│ │ app/services/wecom_service.py (扩展) — 企微API封装+token管理 │ │
|
|
|
|
|
|
│ │ app/services/ws_manager.py (扩展) — WS连接管理(新增终端池) │ │
|
|
|
|
|
|
│ └───────────────┬─────────────────────────────────────────────────┘ │
|
|
|
|
|
|
│ │ │
|
|
|
|
|
|
│ ┌───────────────▼─────────────────────────────────────────────────┐ │
|
|
|
|
|
|
│ │ Data 数据层 │ │
|
|
|
|
|
|
│ │ app/models/terminal_room_binding.py — 终端绑定模型 │ │
|
|
|
|
|
|
│ │ app/models/meetingroom_booking_snapshot.py (P2) — 预定快照模型 │ │
|
|
|
|
|
|
│ │ Redis: 缓存 + token + WS会话 │ │
|
|
|
|
|
|
│ │ PostgreSQL: terminal_room_binding 表 │ │
|
|
|
|
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
|
|
|
|
│ │ │
|
|
|
|
|
|
│ ┌───────────────▼─────────────────────────────────────────────────┐ │
|
|
|
|
|
|
│ │ 外部 API 层 (External APIs) │ │
|
|
|
|
|
|
│ │ 企微会议室API (meetingroom/list, book, cancel, get_booking_info)│ │
|
|
|
|
|
|
│ │ 小鱼易联API (终端管理, Web SDK) │ │
|
|
|
|
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
|
|
|
|
└──────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**关键设计决策**:
|
|
|
|
|
|
|
|
|
|
|
|
1. **企微API为唯一数据源**:不在本地维护独立的预定数据。`MeetingroomService` 作为薄代理层,所有写操作直接转发企微API,读操作加Redis短缓存。
|
|
|
|
|
|
2. **终端绑定关系本地存储**:`terminal_room_binding` 表存储 terminal_sn ↔ meetingroom_id 映射,这是企微API不提供的关系数据。
|
|
|
|
|
|
3. **P0降级策略**:P0阶段终端使用30秒轮询保证基本实时性;P1阶段升级为WebSocket+企微回调。
|
|
|
|
|
|
4. **前端独立项目**:终端页面UI需求(横屏大屏、深色主题、大字号触控)与现有H5/管理后台差异大,新建独立项目 `frontend-terminal`。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 文件列表及相对路径
|
|
|
|
|
|
|
|
|
|
|
|
> **路径约定**:后端代码物理路径为 `backend/app/xxx`,但 Python 模块导入路径为 `from app.xxx import yyy`(运行目录为 `backend/`)。下方"模块路径"列标注导入路径。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2.1 后端 — 新建文件
|
|
|
|
|
|
|
|
|
|
|
|
| # | 物理路径 | 模块路径 | 职责 |
|
|
|
|
|
|
|---|---------|---------|------|
|
|
|
|
|
|
| 1 | `backend/app/models/terminal_room_binding.py` | `app.models.terminal_room_binding` | 终端-会议室绑定模型(SQLAlchemy ORM) |
|
|
|
|
|
|
| 2 | `backend/app/models/meetingroom_booking_snapshot.py` | `app.models.meetingroom_booking_snapshot` | 预定记录快照模型(P2统计用) |
|
|
|
|
|
|
| 3 | `backend/app/schemas/meetingroom.py` | `app.schemas.meetingroom` | Pydantic 请求/响应模型定义 |
|
|
|
|
|
|
| 4 | `backend/app/services/meetingroom_service.py` | `app.services.meetingroom_service` | 会议室预定业务逻辑(缓存+企微API代理) |
|
|
|
|
|
|
| 5 | `backend/app/services/xylink_service.py` | `app.services.xylink_service` | 小鱼易联终端管理服务 |
|
|
|
|
|
|
| 6 | `backend/app/api/meetingroom.py` | `app.api.meetingroom` | 会议室预定REST API路由 |
|
|
|
|
|
|
| 7 | `backend/app/api/admin/terminal_binding.py` | `app.api.admin.terminal_binding` | 终端绑定管理CRUD路由 |
|
|
|
|
|
|
| 8 | `backend/alembic/versions/011_add_meetingroom_tables.py` | — | Alembic迁移:创建 terminal_room_binding 表 |
|
|
|
|
|
|
|
|
|
|
|
|
#### 2.2 后端 — 修改文件
|
|
|
|
|
|
|
|
|
|
|
|
| # | 物理路径 | 修改内容 |
|
|
|
|
|
|
|---|---------|---------|
|
|
|
|
|
|
| 9 | `backend/app/config.py` | 新增 `wecom_meetingroom_secret`、`xylink_enterprise_id`、`xylink_client_id`、`xylink_client_secret`、`xylink_api_base`、`xylink_ext_id` 配置项 |
|
|
|
|
|
|
| 10 | `backend/app/services/wecom_service.py` | 新增 `get_meetingroom_access_token()` 方法 + `get_meetingroom_list()` / `get_booking_info()` / `book_meetingroom()` / `cancel_booking()` / `get_booking_detail()` 方法 |
|
|
|
|
|
|
| 11 | `backend/app/services/ws_manager.py` | 新增 `terminal_connections` 字典 + `connect_terminal()` / `disconnect_terminal()` / `send_to_terminal()` 方法 |
|
|
|
|
|
|
| 12 | `backend/app/api/ws.py` | 新增 `/ws/terminal/{terminal_sn}` WebSocket端点 |
|
|
|
|
|
|
| 13 | `backend/app/api/router.py` | 注册 meetingroom 路由和 terminal_binding 路由 |
|
|
|
|
|
|
| 14 | `backend/app/models/__init__.py` | 导入并注册 `TerminalRoomBinding` 和 `MeetingroomBookingSnapshot` 模型 |
|
|
|
|
|
|
|
|
|
|
|
|
#### 2.3 前端终端页面 — 新建文件
|
|
|
|
|
|
|
|
|
|
|
|
| # | 路径 | 职责 |
|
|
|
|
|
|
|---|------|------|
|
|
|
|
|
|
| 15 | `frontend-terminal/package.json` | 依赖声明(Vue3, Vite, Tailwind, Pinia, Vue Router, Axios) |
|
|
|
|
|
|
| 16 | `frontend-terminal/vite.config.ts` | Vite配置(base=/terminal/, proxy, port=5176) |
|
|
|
|
|
|
| 17 | `frontend-terminal/tailwind.config.js` | Tailwind配置(深色主题色板、大字号断点) |
|
|
|
|
|
|
| 18 | `frontend-terminal/tsconfig.json` | TypeScript配置 |
|
|
|
|
|
|
| 19 | `frontend-terminal/postcss.config.js` | PostCSS配置(Tailwind + Autoprefixer) |
|
|
|
|
|
|
| 20 | `frontend-terminal/index.html` | HTML入口 |
|
|
|
|
|
|
| 21 | `frontend-terminal/src/main.ts` | 应用入口(挂载Vue + Pinia + Router) |
|
|
|
|
|
|
| 22 | `frontend-terminal/src/App.vue` | 根组件 |
|
|
|
|
|
|
| 23 | `frontend-terminal/src/router/index.ts` | 路由配置(/terminal/:sn → StatusView) |
|
|
|
|
|
|
| 24 | `frontend-terminal/src/stores/terminal.ts` | Pinia状态管理(会议室状态、登录状态、WS连接) |
|
|
|
|
|
|
| 25 | `frontend-terminal/src/api/meetingroom.ts` | API调用封装(Axios) |
|
|
|
|
|
|
| 26 | `frontend-terminal/src/composables/useWebSocket.ts` | WebSocket连接管理(自动重连+降级轮询) |
|
|
|
|
|
|
| 27 | `frontend-terminal/src/composables/useAuth.ts` | 扫码登录逻辑(创建二维码+轮询状态) |
|
|
|
|
|
|
| 28 | `frontend-terminal/src/views/StatusView.vue` | 终端状态页(主页面) |
|
|
|
|
|
|
| 29 | `frontend-terminal/src/views/BookingView.vue` | 快速预定弹窗 |
|
|
|
|
|
|
| 30 | `frontend-terminal/src/components/Timeline.vue` | 时间轴组件(横向滚动,当前时间标记) |
|
|
|
|
|
|
| 31 | `frontend-terminal/src/components/StatusBadge.vue` | 状态标识组件(空闲/使用中/即将开始) |
|
|
|
|
|
|
| 32 | `frontend-terminal/src/components/QrLogin.vue` | 扫码登录组件(显示二维码+轮询) |
|
|
|
|
|
|
| 33 | `frontend-terminal/src/styles/main.css` | 全局样式(Tailwind指令 + 深色主题变量) |
|
|
|
|
|
|
| 34 | `frontend-terminal/src/types/meetingroom.ts` | TypeScript类型定义 |
|
|
|
|
|
|
|
|
|
|
|
|
#### 2.4 前端H5端 — 新建/修改文件
|
|
|
|
|
|
|
|
|
|
|
|
| # | 路径 | 职责 |
|
|
|
|
|
|
|---|------|------|
|
|
|
|
|
|
| 35 | `frontend-h5/src/views/MeetingroomView.vue` | H5端会议室预定页面(新建) |
|
|
|
|
|
|
| 36 | `frontend-h5/src/api/meetingroom.ts` | H5端会议室API调用(新建) |
|
|
|
|
|
|
| 37 | `frontend-h5/src/router/index.ts` | 添加会议室路由(修改) |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 3. 数据结构和接口
|
|
|
|
|
|
|
|
|
|
|
|
#### 3.1 类图(Mermaid classDiagram)
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
classDiagram
|
|
|
|
|
|
direction TB
|
|
|
|
|
|
|
|
|
|
|
|
%% ===== 数据模型层 =====
|
|
|
|
|
|
class TerminalRoomBinding {
|
|
|
|
|
|
+Integer id
|
|
|
|
|
|
+String terminal_sn
|
|
|
|
|
|
+String terminal_name
|
|
|
|
|
|
+Integer meetingroom_id
|
|
|
|
|
|
+String meetingroom_name
|
|
|
|
|
|
+String location
|
|
|
|
|
|
+Boolean is_active
|
|
|
|
|
|
+DateTime created_at
|
|
|
|
|
|
+DateTime updated_at
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class MeetingroomBookingSnapshot {
|
|
|
|
|
|
+Integer id
|
|
|
|
|
|
+Integer meetingroom_id
|
|
|
|
|
|
+String booking_id
|
|
|
|
|
|
+String subject
|
|
|
|
|
|
+String booker
|
|
|
|
|
|
+DateTime start_time
|
|
|
|
|
|
+DateTime end_time
|
|
|
|
|
|
+Integer status
|
|
|
|
|
|
+Date snapshot_date
|
|
|
|
|
|
+DateTime created_at
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
%% ===== Service 层 =====
|
|
|
|
|
|
class WecomService {
|
|
|
|
|
|
-Optional~Redis~ redis
|
|
|
|
|
|
-AsyncClient client
|
|
|
|
|
|
-str _token_cache
|
|
|
|
|
|
-str _contact_token_cache
|
|
|
|
|
|
-str _meetingroom_token_cache
|
|
|
|
|
|
+get_access_token() str
|
|
|
|
|
|
+get_contact_access_token() str
|
|
|
|
|
|
+get_meetingroom_access_token() str
|
|
|
|
|
|
+get_meetingroom_list(city, building, floor) list
|
|
|
|
|
|
+get_booking_info(meetingroom_id, date) list
|
|
|
|
|
|
+book_meetingroom(meetingroom_id, subject, start, end, booker, attendees) dict
|
|
|
|
|
|
+cancel_booking(booking_id) dict
|
|
|
|
|
|
+get_booking_detail(meetingroom_id, booking_id) dict
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class MeetingroomService {
|
|
|
|
|
|
-WecomService wecom_service
|
|
|
|
|
|
-Redis redis
|
|
|
|
|
|
+get_room_list(city, building, floor) list
|
|
|
|
|
|
+get_room_status(meetingroom_id, date) list
|
|
|
|
|
|
+book_room(meetingroom_id, subject, start, end, booker, attendees) dict
|
|
|
|
|
|
+cancel_booking(booking_id, meetingroom_id) dict
|
|
|
|
|
|
+get_booking_detail(meetingroom_id, booking_id) dict
|
|
|
|
|
|
+invalidate_room_cache(meetingroom_id) void
|
|
|
|
|
|
+get_current_status(meetingroom_id) dict
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class XyLinkService {
|
|
|
|
|
|
-str enterprise_id
|
|
|
|
|
|
-str client_id
|
|
|
|
|
|
-str client_secret
|
|
|
|
|
|
-str api_base
|
|
|
|
|
|
-AsyncClient client
|
|
|
|
|
|
+get_terminal_status(sn) dict
|
|
|
|
|
|
+push_url_to_terminal(sn, url) dict
|
|
|
|
|
|
+list_terminals() list
|
|
|
|
|
|
+generate_signature(params, secret) str
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class ConnectionManager {
|
|
|
|
|
|
+Dict active_connections
|
|
|
|
|
|
+Dict employee_connections
|
|
|
|
|
|
+Dict terminal_connections
|
|
|
|
|
|
+connect(agent_id, ws) void
|
|
|
|
|
|
+connect_employee(employee_id, ws) void
|
|
|
|
|
|
+connect_terminal(terminal_sn, ws) void
|
|
|
|
|
|
+disconnect_terminal(terminal_sn) void
|
|
|
|
|
|
+send_to_terminal(terminal_sn, data) void
|
|
|
|
|
|
+broadcast_to_terminals(terminal_sns, data) void
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
%% ===== API Schema 层 =====
|
|
|
|
|
|
class MeetingroomListResponse {
|
|
|
|
|
|
+int meetingroom_id
|
|
|
|
|
|
+String name
|
|
|
|
|
|
+int capacity
|
|
|
|
|
|
+String location
|
|
|
|
|
|
+List devices
|
|
|
|
|
|
+int need_approval
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class BookingInfoResponse {
|
|
|
|
|
|
+String booking_id
|
|
|
|
|
|
+String subject
|
|
|
|
|
|
+String booker
|
|
|
|
|
|
+String start_time
|
|
|
|
|
|
+String end_time
|
|
|
|
|
|
+int status
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class BookRequest {
|
|
|
|
|
|
+int meetingroom_id
|
|
|
|
|
|
+String subject
|
|
|
|
|
|
+String start_time
|
|
|
|
|
|
+String end_time
|
|
|
|
|
|
+String booker
|
|
|
|
|
|
+List attendees
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class TerminalBindingCreate {
|
|
|
|
|
|
+String terminal_sn
|
|
|
|
|
|
+String terminal_name
|
|
|
|
|
|
+int meetingroom_id
|
|
|
|
|
|
+String meetingroom_name
|
|
|
|
|
|
+String location
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class TerminalBindingResponse {
|
|
|
|
|
|
+int id
|
|
|
|
|
|
+String terminal_sn
|
|
|
|
|
|
+String terminal_name
|
|
|
|
|
|
+int meetingroom_id
|
|
|
|
|
|
+String meetingroom_name
|
|
|
|
|
|
+String location
|
|
|
|
|
|
+bool is_active
|
|
|
|
|
|
+String created_at
|
|
|
|
|
|
+String updated_at
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
%% ===== 关系 =====
|
|
|
|
|
|
TerminalRoomBinding <|-- MeetingroomService : uses
|
|
|
|
|
|
MeetingroomService --> WecomService : delegates API calls
|
|
|
|
|
|
MeetingroomService --> ConnectionManager : triggers WS push
|
|
|
|
|
|
XyLinkService --> TerminalRoomBinding : manages terminals
|
|
|
|
|
|
ConnectionManager --> TerminalRoomBinding : routes by terminal_sn
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 3.2 REST API 接口定义
|
|
|
|
|
|
|
|
|
|
|
|
> **路由前缀约定**:会议室API使用 `/itportal/meetingroom/`,终端绑定管理API使用 `/itportal/admin/terminal-bindings/`,与现有系统 `/itportal/automation/` 保持一致。
|
|
|
|
|
|
|
|
|
|
|
|
##### 3.2.1 会议室预定 API
|
|
|
|
|
|
|
|
|
|
|
|
| 方法 | 路径 | 说明 | 请求参数 | 响应 |
|
|
|
|
|
|
|------|------|------|---------|------|
|
|
|
|
|
|
| GET | `/itportal/meetingroom/list` | 会议室列表 | `?city=&building=&floor=` (可选过滤) | `{code:0, data:{rooms:[...]}}` |
|
|
|
|
|
|
| GET | `/itportal/meetingroom/{meetingroom_id}/booking` | 预定状态查询 | `?date=YYYY-MM-DD` | `{code:0, data:{bookings:[...]}}` |
|
|
|
|
|
|
| GET | `/itportal/meetingroom/{meetingroom_id}/status` | 当前实时状态 | — | `{code:0, data:{status:"free"/"busy", current_meeting:{...}, next_meeting:{...}}}` |
|
|
|
|
|
|
| POST | `/itportal/meetingroom/book` | 预定会议室 | `{meetingroom_id, subject, start_time, end_time, booker, attendees?}` | `{code:0, data:{booking_id:"..."}}` |
|
|
|
|
|
|
| DELETE | `/itportal/meetingroom/booking/{booking_id}` | 取消预定 | `?meetingroom_id=` | `{code:0, data:{}}` |
|
|
|
|
|
|
| GET | `/itportal/meetingroom/booking/{booking_id}/detail` | 预定详情 | `?meetingroom_id=` | `{code:0, data:{subject, booker, attendees, start_time, end_time}}` |
|
|
|
|
|
|
| GET | `/itportal/meetingroom/terminal/{terminal_sn}/binding` | 根据终端SN查询绑定 | — | `{code:0, data:{meetingroom_id, meetingroom_name, ...}}` |
|
|
|
|
|
|
|
|
|
|
|
|
##### 3.2.2 终端绑定管理 API
|
|
|
|
|
|
|
|
|
|
|
|
| 方法 | 路径 | 说明 | 请求参数 | 响应 |
|
|
|
|
|
|
|------|------|------|---------|------|
|
|
|
|
|
|
| GET | `/itportal/admin/terminal-bindings` | 绑定列表 | `?page=&size=&keyword=` | `{code:0, data:{list:[...], total:N}}` |
|
|
|
|
|
|
| POST | `/itportal/admin/terminal-bindings` | 新增绑定 | `{terminal_sn, terminal_name, meetingroom_id, meetingroom_name, location}` | `{code:0, data:{id:N}}` |
|
|
|
|
|
|
| PUT | `/itportal/admin/terminal-bindings/{id}` | 更新绑定 | `{terminal_name?, meetingroom_id?, location?}` | `{code:0, data:{}}` |
|
|
|
|
|
|
| DELETE | `/itportal/admin/terminal-bindings/{id}` | 删除绑定 | — | `{code:0, data:{}}` |
|
|
|
|
|
|
|
|
|
|
|
|
##### 3.2.3 小鱼易联终端管理 API
|
|
|
|
|
|
|
|
|
|
|
|
| 方法 | 路径 | 说明 | 请求参数 | 响应 |
|
|
|
|
|
|
|------|------|------|---------|------|
|
|
|
|
|
|
| GET | `/itportal/admin/xylink/terminals/{sn}/status` | 查询终端状态 | — | `{code:0, data:{online:bool, ...}}` |
|
|
|
|
|
|
| POST | `/itportal/admin/xylink/terminals/{sn}/push-url` | 推送页面URL | `{url}` | `{code:0, data:{}}` |
|
|
|
|
|
|
|
|
|
|
|
|
#### 3.3 WebSocket 接口定义
|
|
|
|
|
|
|
|
|
|
|
|
##### 3.3.1 终端 WebSocket 端点
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
端点: /ws/terminal/{terminal_sn}
|
|
|
|
|
|
认证: Sec-WebSocket-Protocol: bearer.{token} (与现有坐席/H5端一致)
|
|
|
|
|
|
或 Authorization: Bearer {token}
|
|
|
|
|
|
或 ?token={token} (向后兼容)
|
|
|
|
|
|
|
|
|
|
|
|
注意: 终端WS的token认证使用 terminal:token:{token} Redis key 查询
|
|
|
|
|
|
终端状态查看不需要登录(访客可查看),但预定操作需要登录
|
|
|
|
|
|
WS连接时token可选,无token时仅允许接收状态推送,不允许发送预定指令
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
##### 3.3.2 WebSocket 消息格式
|
|
|
|
|
|
|
|
|
|
|
|
**服务端 → 终端(推送消息)**:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"type": "room_status_update",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"meetingroom_id": 123,
|
|
|
|
|
|
"status": "free",
|
|
|
|
|
|
"current_meeting": null,
|
|
|
|
|
|
"next_meeting": {
|
|
|
|
|
|
"booking_id": "xxx",
|
|
|
|
|
|
"subject": "技术方案讨论",
|
|
|
|
|
|
"start_time": "2026-07-15T15:00:00+08:00",
|
|
|
|
|
|
"end_time": "2026-07-15T16:00:00+08:00",
|
|
|
|
|
|
"booker": "王五"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**终端 → 服务端(客户端消息)**:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
// 心跳
|
|
|
|
|
|
{"type": "ping"}
|
|
|
|
|
|
|
|
|
|
|
|
// 请求状态刷新(可选,通常由服务端主动推送)
|
|
|
|
|
|
{"type": "request_status", "data": {"meetingroom_id": 123}}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 3.4 Service 层接口定义
|
|
|
|
|
|
|
|
|
|
|
|
##### MeetingroomService
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
class MeetingroomService:
|
|
|
|
|
|
"""会议室预定业务服务 — 企微API代理 + Redis缓存。"""
|
|
|
|
|
|
|
|
|
|
|
|
# Redis 缓存 key 前缀
|
|
|
|
|
|
CACHE_KEY_ROOM_LIST = "meetingroom:room_list" # TTL=600s
|
|
|
|
|
|
CACHE_KEY_BOOKING_INFO = "meetingroom:booking:{room_id}:{date}" # TTL=30s
|
|
|
|
|
|
CACHE_KEY_BOOKING_DETAIL = "meetingroom:detail:{booking_id}" # TTL=300s
|
|
|
|
|
|
|
|
|
|
|
|
async def get_room_list(self, city=None, building=None, floor=None) -> list[dict]:
|
|
|
|
|
|
"""获取会议室列表(缓存10分钟)。"""
|
|
|
|
|
|
|
|
|
|
|
|
async def get_room_status(self, meetingroom_id: int, date: str) -> list[dict]:
|
|
|
|
|
|
"""获取指定日期的预定状态(缓存30秒)。"""
|
|
|
|
|
|
|
|
|
|
|
|
async def get_current_status(self, meetingroom_id: int) -> dict:
|
|
|
|
|
|
"""获取当前实时状态(综合判断空闲/使用中/即将开始)。"""
|
|
|
|
|
|
|
|
|
|
|
|
async def book_room(self, meetingroom_id, subject, start_time, end_time,
|
|
|
|
|
|
booker, attendees=None) -> dict:
|
|
|
|
|
|
"""预定会议室(成功后清除该会议室的预定状态缓存)。"""
|
|
|
|
|
|
|
|
|
|
|
|
async def cancel_booking(self, booking_id: str, meetingroom_id: int) -> dict:
|
|
|
|
|
|
"""取消预定(成功后清除缓存)。"""
|
|
|
|
|
|
|
|
|
|
|
|
async def get_booking_detail(self, meetingroom_id: int, booking_id: str) -> dict:
|
|
|
|
|
|
"""获取预定详情(缓存5分钟)。"""
|
|
|
|
|
|
|
|
|
|
|
|
async def invalidate_room_cache(self, meetingroom_id: int, date: str = None) -> None:
|
|
|
|
|
|
"""清除指定会议室的缓存(预定/取消后调用)。"""
|
|
|
|
|
|
|
|
|
|
|
|
async def notify_terminal_update(self, terminal_sn: str, meetingroom_id: int) -> None:
|
|
|
|
|
|
"""状态变更后通过WebSocket通知终端。"""
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
##### XyLinkService
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
class XyLinkService:
|
|
|
|
|
|
"""小鱼易联终端管理服务。"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, enterprise_id, client_id, client_secret, api_base, ext_id):
|
|
|
|
|
|
"""初始化小鱼易联API客户端。"""
|
|
|
|
|
|
|
|
|
|
|
|
def _generate_signature(self, params: dict, timestamp: str) -> str:
|
|
|
|
|
|
"""生成API签名(支持签名1.0和2.0)。"""
|
|
|
|
|
|
|
|
|
|
|
|
async def get_terminal_status(self, sn: str) -> dict:
|
|
|
|
|
|
"""查询终端在线状态。"""
|
|
|
|
|
|
|
|
|
|
|
|
async def push_url_to_terminal(self, sn: str, url: str) -> dict:
|
|
|
|
|
|
"""推送自定义页面URL到终端(方案B预留)。"""
|
|
|
|
|
|
|
|
|
|
|
|
async def list_terminals(self) -> list[dict]:
|
|
|
|
|
|
"""查询终端列表。"""
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 4. 程序调用流程
|
|
|
|
|
|
|
|
|
|
|
|
#### 4.1 终端页面加载流程
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
sequenceDiagram
|
|
|
|
|
|
participant Terminal as 终端浏览器
|
|
|
|
|
|
participant Backend as FastAPI后端
|
|
|
|
|
|
participant Redis as Redis
|
|
|
|
|
|
participant WecomAPI as 企微API
|
|
|
|
|
|
|
|
|
|
|
|
Terminal->>Terminal: 访问 /terminal/{sn}
|
|
|
|
|
|
Terminal->>Terminal: Vue Router 解析 sn 参数
|
|
|
|
|
|
Terminal->>Backend: GET /itportal/meetingroom/terminal/{sn}/binding
|
|
|
|
|
|
activate Backend
|
|
|
|
|
|
Backend->>Backend: 查询 TerminalRoomBinding (by terminal_sn)
|
|
|
|
|
|
alt 绑定存在
|
|
|
|
|
|
Backend-->>Terminal: {code:0, data:{meetingroom_id, meetingroom_name, location}}
|
|
|
|
|
|
Terminal->>Backend: GET /itportal/meetingroom/{meetingroom_id}/status
|
|
|
|
|
|
activate Backend
|
|
|
|
|
|
Backend->>Redis: GET meetingroom:booking:{room_id}:{today}
|
|
|
|
|
|
alt 缓存命中
|
|
|
|
|
|
Redis-->>Backend: 预定列表
|
|
|
|
|
|
else 缓存未命中
|
|
|
|
|
|
Backend->>WecomAPI: POST /cgi-bin/oa/meetingroom/get_booking_info
|
|
|
|
|
|
activate WecomAPI
|
|
|
|
|
|
WecomAPI-->>Backend: 预定列表
|
|
|
|
|
|
deactivate WecomAPI
|
|
|
|
|
|
Backend->>Redis: SETEX meetingroom:booking:{room_id}:{today} 30
|
|
|
|
|
|
end
|
|
|
|
|
|
Backend->>Backend: 计算当前状态(free/busy/starting_soon)
|
|
|
|
|
|
Backend-->>Terminal: {code:0, data:{status, current_meeting, next_meeting, bookings}}
|
|
|
|
|
|
deactivate Backend
|
|
|
|
|
|
Terminal->>Terminal: 渲染 StatusView (状态+时间轴)
|
|
|
|
|
|
Terminal->>Backend: WS /ws/terminal/{sn} (建立WebSocket连接)
|
|
|
|
|
|
activate Backend
|
|
|
|
|
|
Backend-->>Terminal: WS连接已建立
|
|
|
|
|
|
deactivate Backend
|
|
|
|
|
|
else 绑定不存在
|
|
|
|
|
|
Backend-->>Terminal: {code:0, data:null}
|
|
|
|
|
|
Terminal->>Terminal: 显示"请联系IT管理员绑定会议室"提示页
|
|
|
|
|
|
end
|
|
|
|
|
|
deactivate Backend
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 4.2 快速预定流程
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
sequenceDiagram
|
|
|
|
|
|
participant User as 用户(终端)
|
|
|
|
|
|
participant Terminal as 终端页面
|
|
|
|
|
|
participant Backend as FastAPI后端
|
|
|
|
|
|
participant Redis as Redis
|
|
|
|
|
|
participant WecomAPI as 企微API
|
|
|
|
|
|
participant WS as WebSocket管理器
|
|
|
|
|
|
|
|
|
|
|
|
User->>Terminal: 点击时间轴空闲时段
|
|
|
|
|
|
Terminal->>Terminal: 检查登录状态(token)
|
|
|
|
|
|
|
|
|
|
|
|
alt 未登录
|
|
|
|
|
|
Terminal->>Backend: POST /api/auth/qrcode (创建扫码票据)
|
|
|
|
|
|
activate Backend
|
|
|
|
|
|
Backend->>Redis: SET terminal:qrcode:{ticket} 300s
|
|
|
|
|
|
Backend-->>Terminal: {ticket, qr_url}
|
|
|
|
|
|
deactivate Backend
|
|
|
|
|
|
Terminal->>Terminal: 显示QrLogin组件(二维码)
|
|
|
|
|
|
loop 轮询(每2秒)
|
|
|
|
|
|
Terminal->>Backend: GET /api/auth/scan/status?ticket={ticket}
|
|
|
|
|
|
Backend->>Redis: GET terminal:qrcode:{ticket}
|
|
|
|
|
|
alt 已扫码确认
|
|
|
|
|
|
Backend-->>Terminal: {status:"confirmed", token:"xxx", userid:"zhangsan"}
|
|
|
|
|
|
Terminal->>Terminal: 保存token, 关闭二维码
|
|
|
|
|
|
else 等待中
|
|
|
|
|
|
Backend-->>Terminal: {status:"waiting"}
|
|
|
|
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
Terminal->>Terminal: 弹出 BookingView (输入主题+选择时长)
|
|
|
|
|
|
User->>Terminal: 输入会议主题, 选择时长, 点击确认
|
|
|
|
|
|
Terminal->>Backend: POST /itportal/meetingroom/book
|
|
|
|
|
|
activate Backend
|
|
|
|
|
|
Note over Backend: Headers: Authorization: Bearer {token}
|
|
|
|
|
|
Backend->>Redis: 验证 terminal:token:{token} → userid
|
|
|
|
|
|
Backend->>Backend: 时间按30分钟取整
|
|
|
|
|
|
Backend->>WecomAPI: POST /cgi-bin/oa/meetingroom/book
|
|
|
|
|
|
activate WecomAPI
|
|
|
|
|
|
alt 预定成功
|
|
|
|
|
|
WecomAPI-->>Backend: {errcode:0, booking_id:"xxx"}
|
|
|
|
|
|
Backend->>Redis: DEL meetingroom:booking:{room_id}:{today}
|
|
|
|
|
|
Backend->>WS: send_to_terminal(sn, {type:"room_status_update"})
|
|
|
|
|
|
Backend-->>Terminal: {code:0, data:{booking_id:"xxx"}}
|
|
|
|
|
|
Terminal->>Terminal: 显示"预定成功"动画 → 3秒后返回状态页
|
|
|
|
|
|
else 时间冲突
|
|
|
|
|
|
WecomAPI-->>Backend: {errcode:xxx, errmsg:"时间冲突"}
|
|
|
|
|
|
Backend-->>Terminal: {code:3001, message:"该时段已被预定"}
|
|
|
|
|
|
Terminal->>Terminal: 显示错误提示 → 返回预定弹窗
|
|
|
|
|
|
end
|
|
|
|
|
|
deactivate WecomAPI
|
|
|
|
|
|
deactivate Backend
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 4.3 实时同步流程(P1 — WebSocket + 企微回调)
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
sequenceDiagram
|
|
|
|
|
|
participant WecomApp as 企微客户端
|
|
|
|
|
|
participant WecomCallback as 企微回调
|
|
|
|
|
|
participant Backend as FastAPI后端
|
|
|
|
|
|
participant Redis as Redis
|
|
|
|
|
|
participant WS as WebSocket管理器
|
|
|
|
|
|
participant Terminal as 终端页面
|
|
|
|
|
|
|
|
|
|
|
|
Note over WecomApp,Terminal: 场景:员工在企微客户端预定/取消会议室
|
|
|
|
|
|
|
|
|
|
|
|
WecomApp->>WecomApp: 用户在企微客户端预定会议室
|
|
|
|
|
|
WecomApp->>WecomCallback: 企微触发预定变更事件回调
|
|
|
|
|
|
activate WecomCallback
|
|
|
|
|
|
WecomCallback->>Backend: POST /api/wecom/callback (事件回调)
|
|
|
|
|
|
activate Backend
|
|
|
|
|
|
Backend->>Backend: 解析回调事件类型(meetingroom_booked/cancelled)
|
|
|
|
|
|
Backend->>Redis: DEL meetingroom:booking:{room_id}:{today}
|
|
|
|
|
|
Backend->>Backend: 查询 TerminalRoomBinding (by meetingroom_id)
|
|
|
|
|
|
alt 终端已绑定
|
|
|
|
|
|
Backend->>WS: send_to_terminal(terminal_sn, room_status_update)
|
|
|
|
|
|
activate WS
|
|
|
|
|
|
WS->>Terminal: WS推送 {type:"room_status_update", data:{...}}
|
|
|
|
|
|
deactivate WS
|
|
|
|
|
|
Terminal->>Terminal: 更新状态页显示
|
|
|
|
|
|
end
|
|
|
|
|
|
Backend-->>WecomCallback: {code:0}
|
|
|
|
|
|
deactivate Backend
|
|
|
|
|
|
deactivate WecomCallback
|
|
|
|
|
|
|
|
|
|
|
|
Note over Terminal: 终端收到WS推送后自动刷新
|
|
|
|
|
|
|
|
|
|
|
|
alt WS断线降级
|
|
|
|
|
|
Terminal->>Terminal: WS连接断开 → 启动30秒轮询
|
|
|
|
|
|
loop 每30秒
|
|
|
|
|
|
Terminal->>Backend: GET /itportal/meetingroom/{room_id}/status
|
|
|
|
|
|
Backend-->>Terminal: 最新状态
|
|
|
|
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 4.4 企微扫码登录流程(终端适配)
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
sequenceDiagram
|
|
|
|
|
|
participant User as 用户手机
|
|
|
|
|
|
participant WecomApp as 企微App
|
|
|
|
|
|
participant Terminal as 终端页面
|
|
|
|
|
|
participant Backend as FastAPI后端
|
|
|
|
|
|
participant Redis as Redis
|
|
|
|
|
|
participant WecomAPI as 企微API
|
|
|
|
|
|
|
|
|
|
|
|
Note over Terminal: 终端是公共设备,无企微内置浏览器UA
|
|
|
|
|
|
|
|
|
|
|
|
Terminal->>Backend: POST /api/auth/qrcode (创建扫码登录票据)
|
|
|
|
|
|
activate Backend
|
|
|
|
|
|
Backend->>Backend: 生成 ticket + state
|
|
|
|
|
|
Backend->>Redis: SET terminal:qrcode:{ticket} {state, status:"waiting"} TTL=300s
|
|
|
|
|
|
Backend->>Backend: 构建企微OAuth2授权URL
|
|
|
|
|
|
Backend-->>Terminal: {ticket, qr_url, oauth_url}
|
|
|
|
|
|
deactivate Backend
|
|
|
|
|
|
|
|
|
|
|
|
Terminal->>Terminal: QrLogin组件显示二维码(qr_url)
|
|
|
|
|
|
|
|
|
|
|
|
User->>WecomApp: 企微App扫描二维码
|
|
|
|
|
|
WecomApp->>WecomAPI: OAuth2授权(用户确认)
|
|
|
|
|
|
WecomAPI->>Backend: GET /api/auth/oauth2/callback?code=xxx&state=xxx
|
|
|
|
|
|
activate Backend
|
|
|
|
|
|
Backend->>WecomAPI: 用code换userid
|
|
|
|
|
|
activate WecomAPI
|
|
|
|
|
|
WecomAPI-->>Backend: {userid:"zhangsan"}
|
|
|
|
|
|
deactivate WecomAPI
|
|
|
|
|
|
Backend->>Redis: SET terminal:qrcode:{ticket} {status:"confirmed", userid:"zhangsan"} TTL=60s
|
|
|
|
|
|
Backend->>Redis: SET terminal:token:{token} {userid, name, dept} TTL=28800s (8h)
|
|
|
|
|
|
Backend-->>WecomAPI: 302 重定向到终端页面
|
|
|
|
|
|
deactivate Backend
|
|
|
|
|
|
|
|
|
|
|
|
loop 轮询(每2秒)
|
|
|
|
|
|
Terminal->>Backend: GET /api/auth/scan/status?ticket={ticket}
|
|
|
|
|
|
activate Backend
|
|
|
|
|
|
Backend->>Redis: GET terminal:qrcode:{ticket}
|
|
|
|
|
|
alt status == "confirmed"
|
|
|
|
|
|
Backend-->>Terminal: {status:"confirmed", token:"xxx", userid:"zhangsan", name:"张三"}
|
|
|
|
|
|
Terminal->>Terminal: 保存token到Pinia store + localStorage
|
|
|
|
|
|
Terminal->>Terminal: 关闭QrLogin组件, 打开BookingView
|
|
|
|
|
|
else status == "waiting"
|
|
|
|
|
|
Backend-->>Terminal: {status:"waiting"}
|
|
|
|
|
|
end
|
|
|
|
|
|
deactivate Backend
|
|
|
|
|
|
end
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 5. 待明确事项
|
|
|
|
|
|
|
|
|
|
|
|
| # | 问题 | 影响范围 | 当前假设 | 建议确认方式 |
|
|
|
|
|
|
|---|------|---------|---------|-------------|
|
|
|
|
|
|
| U1 | **小鱼易联终端型号及浏览器能力** | 终端页面技术选型、WebView兼容性 | 按方案A(浏览器全屏访问URL)设计,假设终端内置浏览器支持现代ES模块和WebSocket | 实际终端上测试页面渲染效果;若浏览器版本过低,考虑加polyfill或降级为iframe方案 |
|
|
|
|
|
|
| U2 | **企微会议室API的errcode错误码映射** | 错误处理和用户提示 | 假设时间冲突返回特定errcode,需实际调用确认 | 开发阶段用企微API测试工具验证各场景的errcode |
|
|
|
|
|
|
| U3 | **企微回调事件的数据结构** | P1企微回调实现 | 假设企微会议室预定变更会触发事件回调,回调数据包含meetingroom_id和booking_id | 查阅企微官方文档确认回调事件类型和字段;若无回调能力,P1仅依赖定时刷新+WS |
|
|
|
|
|
|
| U4 | **小鱼易联API签名的具体算法** | XyLinkService实现 | 假设支持HMAC-SHA256签名1.0和2.0,但具体参数拼接方式未确认 | 参考小鱼易联官方文档 https://openapi.xylink.com/ 的签名说明 |
|
|
|
|
|
|
| U5 | **终端页面的nginx路由配置** | 部署方案 | 假设在现有nginx中新增 `location /terminal/` 指向 `frontend-terminal/dist` | 部署时在nginx配置中添加路由规则 |
|
|
|
|
|
|
| U6 | **终端WS认证策略** | 安全性 | 假设终端查看状态不需要token认证(访客可查看),但预定操作需要token。WS连接时token可选 | 确认是否允许未认证终端建立WS连接(仅接收推送) |
|
|
|
|
|
|
| U7 | **多终端绑定同一会议室** | 实时同步推送 | 假设一个会议室可绑定多个终端(如门口+室内各一个),WS推送时需遍历所有绑定该会议室的终端 | 确认是否需要支持多终端绑定同一会议室 |
|
|
|
|
|
|
| U8 | **H5端会议室入口的权限控制** | H5端页面 | 假设所有企微员工都可以通过H5端预定会议室,无需额外角色权限 | 确认是否需要限制预定权限(如仅特定部门可预定) |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Part B: Task Decomposition
|
|
|
|
|
|
|
|
|
|
|
|
### 6. 依赖包列表
|
|
|
|
|
|
|
|
|
|
|
|
#### 6.1 Python 后端(新增到 `backend/requirements.txt`)
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
# 以下包均已存在于现有项目中,无需新增安装:
|
|
|
|
|
|
# - fastapi (现有)
|
|
|
|
|
|
# - uvicorn (现有)
|
|
|
|
|
|
# - sqlalchemy[asyncio] (现有)
|
|
|
|
|
|
# - asyncpg (现有)
|
|
|
|
|
|
# - redis[asyncio] (现有)
|
|
|
|
|
|
# - httpx (现有)
|
|
|
|
|
|
# - pydantic-settings (现有)
|
|
|
|
|
|
# - alembic (现有)
|
|
|
|
|
|
|
|
|
|
|
|
# 无需新增 Python 依赖包 — 所有功能均可通过现有依赖实现
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 6.2 前端终端页面(`frontend-terminal/package.json`)
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"dependencies": {
|
|
|
|
|
|
"vue": "^3.4.0",
|
|
|
|
|
|
"vue-router": "^4.3.0",
|
|
|
|
|
|
"pinia": "^2.1.0",
|
|
|
|
|
|
"axios": "^1.7.0"
|
|
|
|
|
|
},
|
|
|
|
|
|
"devDependencies": {
|
|
|
|
|
|
"vite": "^5.3.0",
|
|
|
|
|
|
"@vitejs/plugin-vue": "^5.0.0",
|
|
|
|
|
|
"typescript": "^5.5.0",
|
|
|
|
|
|
"vue-tsc": "^2.0.0",
|
|
|
|
|
|
"tailwindcss": "^3.4.0",
|
|
|
|
|
|
"postcss": "^8.4.0",
|
|
|
|
|
|
"autoprefixer": "^10.4.0",
|
|
|
|
|
|
"@types/node": "^20.0.0"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 6.3 前端H5端(新增到 `frontend-h5/package.json`)
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
# 无需新增 npm 依赖包 — H5端会议室页面使用现有 Vue3 + Vant4 + Axios 技术栈即可实现
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 7. 任务列表
|
|
|
|
|
|
|
|
|
|
|
|
> **分组原则**:按功能模块/层次分组,每个任务包含3+相关文件。
|
|
|
|
|
|
> **依赖原则**:尽量仅依赖T01,减少线性依赖链。
|
|
|
|
|
|
|
|
|
|
|
|
#### T01: 项目基础设施 + 数据层
|
|
|
|
|
|
|
|
|
|
|
|
| 项 | 内容 |
|
|
|
|
|
|
|----|------|
|
|
|
|
|
|
| **任务ID** | T01 |
|
|
|
|
|
|
| **任务名称** | 项目基础设施 + 数据层(配置 + 模型 + 迁移 + 前端脚手架) |
|
|
|
|
|
|
| **优先级** | P0 |
|
|
|
|
|
|
| **依赖** | 无 |
|
|
|
|
|
|
|
|
|
|
|
|
**涉及文件**:
|
|
|
|
|
|
|
|
|
|
|
|
后端:
|
|
|
|
|
|
- `backend/app/config.py` (修改) — 新增 `wecom_meetingroom_secret`、`xylink_enterprise_id`、`xylink_client_id`、`xylink_client_secret`、`xylink_api_base`、`xylink_ext_id` 配置项
|
|
|
|
|
|
- `backend/app/models/terminal_room_binding.py` (新建) — TerminalRoomBinding 模型
|
|
|
|
|
|
- `backend/app/models/meetingroom_booking_snapshot.py` (新建) — MeetingroomBookingSnapshot 模型(P2)
|
|
|
|
|
|
- `backend/app/models/__init__.py` (修改) — 注册新模型
|
|
|
|
|
|
- `backend/alembic/versions/011_add_meetingroom_tables.py` (新建) — 创建 terminal_room_binding 表
|
|
|
|
|
|
|
|
|
|
|
|
前端:
|
|
|
|
|
|
- `frontend-terminal/package.json` (新建)
|
|
|
|
|
|
- `frontend-terminal/vite.config.ts` (新建) — base=`/terminal/`, port=5176
|
|
|
|
|
|
- `frontend-terminal/tailwind.config.js` (新建) — 深色主题色板
|
|
|
|
|
|
- `frontend-terminal/tsconfig.json` (新建)
|
|
|
|
|
|
- `frontend-terminal/postcss.config.js` (新建)
|
|
|
|
|
|
- `frontend-terminal/index.html` (新建)
|
|
|
|
|
|
- `frontend-terminal/src/main.ts` (新建)
|
|
|
|
|
|
- `frontend-terminal/src/App.vue` (新建)
|
|
|
|
|
|
- `frontend-terminal/src/styles/main.css` (新建) — Tailwind指令 + 深色主题变量
|
|
|
|
|
|
- `frontend-terminal/src/types/meetingroom.ts` (新建) — TypeScript类型定义
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准**:
|
|
|
|
|
|
- 后端启动无报错,新配置项可从环境变量读取
|
|
|
|
|
|
- `alembic upgrade head` 成功创建 `terminal_room_binding` 表
|
|
|
|
|
|
- `frontend-terminal` 项目 `pnpm dev` 可正常启动,显示空白Vue页面
|
|
|
|
|
|
- Tailwind CSS 生效(深色背景)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### T02: 后端会议室API + Service层
|
|
|
|
|
|
|
|
|
|
|
|
| 项 | 内容 |
|
|
|
|
|
|
|----|------|
|
|
|
|
|
|
| **任务ID** | T02 |
|
|
|
|
|
|
| **任务名称** | 后端会议室预定API + Service层 + 企微API对接 |
|
|
|
|
|
|
| **优先级** | P0 |
|
|
|
|
|
|
| **依赖** | T01 |
|
|
|
|
|
|
|
|
|
|
|
|
**涉及文件**:
|
|
|
|
|
|
- `backend/app/services/wecom_service.py` (修改) — 新增 `get_meetingroom_access_token()` + 会议室API方法(list/booking_info/book/cancel/detail)
|
|
|
|
|
|
- `backend/app/services/meetingroom_service.py` (新建) — 会议室预定业务逻辑(缓存管理 + 企微API代理 + 状态计算)
|
|
|
|
|
|
- `backend/app/services/xylink_service.py` (新建) — 小鱼易联终端管理服务(签名 + 终端状态 + 推送URL)
|
|
|
|
|
|
- `backend/app/schemas/meetingroom.py` (新建) — Pydantic请求/响应模型
|
|
|
|
|
|
- `backend/app/api/meetingroom.py` (新建) — 会议室预定REST API路由
|
|
|
|
|
|
- `backend/app/api/router.py` (修改) — 注册 meetingroom 路由
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准**:
|
|
|
|
|
|
- `GET /itportal/meetingroom/list` 返回企微会议室列表
|
|
|
|
|
|
- `GET /itportal/meetingroom/{id}/booking?date=YYYY-MM-DD` 返回预定状态
|
|
|
|
|
|
- `GET /itportal/meetingroom/{id}/status` 返回当前实时状态(free/busy/starting_soon)
|
|
|
|
|
|
- `POST /itportal/meetingroom/book` 成功预定并清除缓存
|
|
|
|
|
|
- `DELETE /itportal/meetingroom/booking/{id}` 成功取消并清除缓存
|
|
|
|
|
|
- `GET /itportal/meetingroom/terminal/{sn}/binding` 返回终端绑定的会议室信息
|
|
|
|
|
|
- access_token 使用 Redis 缓存(key=`wecom:meetingroom_access_token`),不重复请求
|
|
|
|
|
|
- Swagger 文档中可见所有新接口
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### T03: 终端绑定管理 + WebSocket实时同步
|
|
|
|
|
|
|
|
|
|
|
|
| 项 | 内容 |
|
|
|
|
|
|
|----|------|
|
|
|
|
|
|
| **任务ID** | T03 |
|
|
|
|
|
|
| **任务名称** | 终端绑定管理CRUD + WebSocket终端连接池 + 企微回调适配 |
|
|
|
|
|
|
| **优先级** | P0(绑定管理) / P1(WS+回调) |
|
|
|
|
|
|
| **依赖** | T01 |
|
|
|
|
|
|
|
|
|
|
|
|
**涉及文件**:
|
|
|
|
|
|
- `backend/app/api/admin/terminal_binding.py` (新建) — 终端绑定CRUD路由
|
|
|
|
|
|
- `backend/app/api/admin/__init__.py` (修改) — 注册 terminal_binding 子路由
|
|
|
|
|
|
- `backend/app/api/router.py` (修改) — 注册 terminal_binding 路由
|
|
|
|
|
|
- `backend/app/services/ws_manager.py` (修改) — 新增 `terminal_connections` + `connect_terminal()` / `disconnect_terminal()` / `send_to_terminal()` / `broadcast_to_terminals()`
|
|
|
|
|
|
- `backend/app/api/ws.py` (修改) — 新增 `/ws/terminal/{terminal_sn}` WebSocket端点
|
|
|
|
|
|
- `backend/app/services/meetingroom_service.py` (修改) — 新增 `notify_terminal_update()` 方法(状态变更后WS推送)
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准**:
|
|
|
|
|
|
- `GET/POST/PUT/DELETE /itportal/admin/terminal-bindings` CRUD完整可用
|
|
|
|
|
|
- 终端可建立 `/ws/terminal/{sn}` WebSocket连接
|
|
|
|
|
|
- 预定/取消后,绑定的终端通过WS收到 `room_status_update` 推送
|
|
|
|
|
|
- WS断线后不影响后端服务
|
|
|
|
|
|
- 终端SN查询绑定关系接口可用
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### T04: 终端前端页面
|
|
|
|
|
|
|
|
|
|
|
|
| 项 | 内容 |
|
|
|
|
|
|
|----|------|
|
|
|
|
|
|
| **任务ID** | T04 |
|
|
|
|
|
|
| **任务名称** | 终端前端页面(状态展示 + 快速预定 + 扫码登录 + WS实时同步) |
|
|
|
|
|
|
| **优先级** | P0(状态+预定) / P1(WS) |
|
|
|
|
|
|
| **依赖** | T01 |
|
|
|
|
|
|
|
|
|
|
|
|
**涉及文件**:
|
|
|
|
|
|
- `frontend-terminal/src/router/index.ts` (新建) — 路由配置
|
|
|
|
|
|
- `frontend-terminal/src/stores/terminal.ts` (新建) — Pinia状态管理
|
|
|
|
|
|
- `frontend-terminal/src/api/meetingroom.ts` (新建) — API调用封装
|
|
|
|
|
|
- `frontend-terminal/src/composables/useWebSocket.ts` (新建) — WS连接管理(自动重连+降级轮询)
|
|
|
|
|
|
- `frontend-terminal/src/composables/useAuth.ts` (新建) — 扫码登录逻辑
|
|
|
|
|
|
- `frontend-terminal/src/views/StatusView.vue` (新建) — 终端状态页(主页面)
|
|
|
|
|
|
- `frontend-terminal/src/views/BookingView.vue` (新建) — 快速预定弹窗
|
|
|
|
|
|
- `frontend-terminal/src/components/Timeline.vue` (新建) — 时间轴组件
|
|
|
|
|
|
- `frontend-terminal/src/components/StatusBadge.vue` (新建) — 状态标识组件
|
|
|
|
|
|
- `frontend-terminal/src/components/QrLogin.vue` (新建) — 扫码登录组件
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准**:
|
|
|
|
|
|
- 终端页面在1920×1080横屏下正常渲染,深色主题
|
|
|
|
|
|
- 页面加载3秒内显示会议室状态(空闲/使用中)+ 时间轴
|
|
|
|
|
|
- 点击空闲时段弹出预定弹窗
|
|
|
|
|
|
- 未登录时显示企微扫码二维码,扫码后自动登录
|
|
|
|
|
|
- 预定成功后显示成功动画,3秒后返回状态页
|
|
|
|
|
|
- WebSocket连接建立后,状态变更实时更新(P1)
|
|
|
|
|
|
- WS断线后自动降级为30秒轮询(P1)
|
|
|
|
|
|
- 当前时间用竖线标记在时间轴上
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### T05: H5端会议室页面 + 集成调试
|
|
|
|
|
|
|
|
|
|
|
|
| 项 | 内容 |
|
|
|
|
|
|
|----|------|
|
|
|
|
|
|
| **任务ID** | T05 |
|
|
|
|
|
|
| **任务名称** | H5端会议室预定页面 + nginx路由配置 + 端到端集成调试 |
|
|
|
|
|
|
| **优先级** | P1(H5页面) / P0(集成) |
|
|
|
|
|
|
| **依赖** | T02, T04 |
|
|
|
|
|
|
|
|
|
|
|
|
**涉及文件**:
|
|
|
|
|
|
- `frontend-h5/src/views/MeetingroomView.vue` (新建) — H5端会议室预定页面
|
|
|
|
|
|
- `frontend-h5/src/api/meetingroom.ts` (新建) — H5端API调用
|
|
|
|
|
|
- `frontend-h5/src/router/index.ts` (修改) — 添加会议室路由
|
|
|
|
|
|
- `frontend-h5/src/views/HomeView.vue` (修改) — 首页新增「会议室」入口卡片
|
|
|
|
|
|
|
|
|
|
|
|
**集成调试范围**:
|
|
|
|
|
|
- 终端页面 → 后端API → 企微API 全链路验证
|
|
|
|
|
|
- H5端预定 → 终端页面实时更新验证
|
|
|
|
|
|
- 终端预定 → 企微客户端显示验证
|
|
|
|
|
|
- nginx `/terminal/` 路由配置验证
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准**:
|
|
|
|
|
|
- H5端可查看所有会议室列表和状态
|
|
|
|
|
|
- H5端可选择会议室 → 查看时间轴 → 预定空闲时段
|
|
|
|
|
|
- H5端预定后,终端页面在30秒内(P0轮询)/ 5秒内(P1 WS)显示更新
|
|
|
|
|
|
- 终端预定后,H5端刷新可见最新状态
|
|
|
|
|
|
- nginx正确路由 `/terminal/` 到终端前端静态文件
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 8. 共享知识
|
|
|
|
|
|
|
|
|
|
|
|
#### 8.1 API 路由前缀约定
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
# 现有路由前缀(不变):
|
|
|
|
|
|
/api/ — 坐席端、H5端、管理后台共用API
|
|
|
|
|
|
/itportal/automation/ — 自动化闭环API
|
|
|
|
|
|
|
|
|
|
|
|
# 新增路由前缀:
|
|
|
|
|
|
/itportal/meetingroom/ — 会议室预定API(终端+H5共用)
|
|
|
|
|
|
/itportal/admin/terminal-bindings/ — 终端绑定管理API(管理后台)
|
|
|
|
|
|
/itportal/admin/xylink/ — 小鱼易联终端管理API(管理后台)
|
|
|
|
|
|
/ws/terminal/{terminal_sn} — 终端WebSocket端点
|
|
|
|
|
|
|
|
|
|
|
|
# 注意:nginx 已经 strip /api 前缀,后端路由不包含 /api。
|
|
|
|
|
|
# /itportal/ 前缀的请求,nginx 直接透传给后端(与 /itportal/automation/ 一致)。
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 8.2 Redis 缓存 key 命名规范
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
# Token 缓存(与现有模式一致)
|
|
|
|
|
|
wecom:access_token — 普通应用token (TTL=6900s)
|
|
|
|
|
|
wecom:contact_access_token — 通讯录token (TTL=6900s)
|
|
|
|
|
|
wecom:meetingroom_access_token — 会议室token (TTL=6900s) 【新增】
|
|
|
|
|
|
|
|
|
|
|
|
# 会议室数据缓存
|
|
|
|
|
|
meetingroom:room_list — 会议室列表缓存 (TTL=600s)
|
|
|
|
|
|
meetingroom:room_list:{city}:{building} — 按位置过滤的列表缓存 (TTL=600s)
|
|
|
|
|
|
meetingroom:booking:{room_id}:{date} — 指定会议室指定日期的预定状态 (TTL=30s)
|
|
|
|
|
|
meetingroom:detail:{booking_id} — 预定详情缓存 (TTL=300s)
|
|
|
|
|
|
meetingroom:status:{room_id} — 当前实时状态缓存 (TTL=10s,极短缓存防刷)
|
|
|
|
|
|
|
|
|
|
|
|
# 终端认证缓存
|
|
|
|
|
|
terminal:token:{token} — 终端登录token → {userid, name, dept} (TTL=28800s/8h)
|
|
|
|
|
|
terminal:qrcode:{ticket} — 扫码登录票据 → {status, userid} (TTL=300s)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 8.3 错误处理规范
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
# 错误码分配(与现有体系一致:1000+通用/2000+企微/3000+业务)
|
|
|
|
|
|
# 会议室模块使用 3100-3199 段:
|
|
|
|
|
|
|
|
|
|
|
|
3101: "会议室不存在"
|
|
|
|
|
|
3102: "该时段已被预定(时间冲突)"
|
|
|
|
|
|
3103: "会议室需要审批,暂不支持快速预定"
|
|
|
|
|
|
3104: "预定开始时间已过,无法预定"
|
|
|
|
|
|
3105: "非预定人无法取消"
|
|
|
|
|
|
3106: "终端未绑定会议室"
|
|
|
|
|
|
3107: "终端绑定不存在"
|
|
|
|
|
|
|
|
|
|
|
|
3201: "小鱼易联API调用失败"
|
|
|
|
|
|
3202: "终端不在线"
|
|
|
|
|
|
|
|
|
|
|
|
2001: "企微API返回错误" # 复用现有
|
|
|
|
|
|
2002: "企微access_token获取失败" # 复用现有
|
|
|
|
|
|
|
|
|
|
|
|
# 响应格式(与现有一致):
|
|
|
|
|
|
# 成功: {"code": 0, "data": {...}, "message": "success"}
|
|
|
|
|
|
# 失败: {"code": 3102, "data": null, "message": "该时段已被预定"}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 8.4 前后端接口契约
|
|
|
|
|
|
|
|
|
|
|
|
**终端页面 → 后端**:
|
|
|
|
|
|
|
|
|
|
|
|
| 场景 | 方法 | 路径 | 认证 | 备注 |
|
|
|
|
|
|
|------|------|------|------|------|
|
|
|
|
|
|
| 页面加载 | GET | `/itportal/meetingroom/terminal/{sn}/binding` | 无需认证 | 访客可查看 |
|
|
|
|
|
|
| 状态查询 | GET | `/itportal/meetingroom/{id}/status` | 无需认证 | 访客可查看 |
|
|
|
|
|
|
| 预定列表 | GET | `/itportal/meetingroom/{id}/booking?date=` | 无需认证 | 访客可查看 |
|
|
|
|
|
|
| 预定详情 | GET | `/itportal/meetingroom/booking/{id}/detail?meetingroom_id=` | 无需认证 | 访客可查看 |
|
|
|
|
|
|
| 预定 | POST | `/itportal/meetingroom/book` | Bearer token | 需扫码登录 |
|
|
|
|
|
|
| 取消 | DELETE | `/itportal/meetingroom/booking/{id}?meetingroom_id=` | Bearer token | 需扫码登录 |
|
|
|
|
|
|
| 扫码登录 | POST | `/api/auth/qrcode` | 无需认证 | 创建票据 |
|
|
|
|
|
|
| 扫码轮询 | GET | `/api/auth/scan/status?ticket=` | 无需认证 | 轮询状态 |
|
|
|
|
|
|
| WebSocket | WS | `/ws/terminal/{sn}` | token可选 | 无token仅接收推送 |
|
|
|
|
|
|
|
|
|
|
|
|
**H5端 → 后端**:
|
|
|
|
|
|
|
|
|
|
|
|
| 场景 | 方法 | 路径 | 认证 | 备注 |
|
|
|
|
|
|
|------|------|------|------|------|
|
|
|
|
|
|
| 会议室列表 | GET | `/itportal/meetingroom/list` | H5 token | 现有H5认证 |
|
|
|
|
|
|
| 预定状态 | GET | `/itportal/meetingroom/{id}/booking?date=` | H5 token | |
|
|
|
|
|
|
| 预定 | POST | `/itportal/meetingroom/book` | H5 token | |
|
|
|
|
|
|
| 取消 | DELETE | `/itportal/meetingroom/booking/{id}?meetingroom_id=` | H5 token | |
|
|
|
|
|
|
|
|
|
|
|
|
#### 8.5 前端终端页面设计规范
|
|
|
|
|
|
|
|
|
|
|
|
```css
|
|
|
|
|
|
/* 深色主题色板(Tailwind 自定义颜色) */
|
|
|
|
|
|
:root {
|
|
|
|
|
|
--color-bg-primary: #1a1a2e; /* 主背景 */
|
|
|
|
|
|
--color-bg-card: #16213e; /* 卡片背景 */
|
|
|
|
|
|
--color-bg-card-hover: #1a2744; /* 卡片悬停 */
|
|
|
|
|
|
--color-status-free: #07C160; /* 空闲(企微绿) */
|
|
|
|
|
|
--color-status-busy: #FF6B6B; /* 使用中(红色) */
|
|
|
|
|
|
--color-status-soon: #FFA502; /* 即将开始(橙色) */
|
|
|
|
|
|
--color-text-primary: #FFFFFF; /* 主文本 */
|
|
|
|
|
|
--color-text-secondary: #A0A0B0; /* 次要文本 */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 字号规范(大屏适配) */
|
|
|
|
|
|
.text-status-main { font-size: 96px; } /* 当前状态 */
|
|
|
|
|
|
.text-room-name { font-size: 36px; } /* 会议室名称 */
|
|
|
|
|
|
.text-timeline { font-size: 20px; } /* 时间轴 */
|
|
|
|
|
|
.text-button { font-size: 24px; } /* 操作按钮 */
|
|
|
|
|
|
|
|
|
|
|
|
/* 终端页面部署路径 */
|
|
|
|
|
|
/* 生产: https://itsupport.servyou.com.cn/terminal/{sn} */
|
|
|
|
|
|
/* 开发: http://localhost:5176/terminal/{sn} */
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 8.6 数据库模型约定
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
# TerminalRoomBinding 模型定义规范
|
|
|
|
|
|
# - 表名: terminal_room_binding(单数,与现有 agents/conversations 等一致)
|
|
|
|
|
|
# - 主键: Integer 自增(与现有 system_configs 等一致,非UUID)
|
|
|
|
|
|
# - terminal_sn: 唯一索引(一个终端只能绑定一个会议室)
|
|
|
|
|
|
# - meetingroom_id: 普通索引(一个会议室可被多个终端绑定)
|
|
|
|
|
|
# - created_at/updated_at: 服务器默认时间(与现有模型一致)
|
|
|
|
|
|
# - 使用 SQLAlchemy 2.0 Mapped 语法(与现有 Agent 等模型一致)
|
|
|
|
|
|
|
|
|
|
|
|
# Alembic 迁移编号: 011_add_meetingroom_tables
|
|
|
|
|
|
# down_revision: 010_add_agent_otp
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 9. 任务依赖图
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
graph TD
|
|
|
|
|
|
T01[T01: 项目基础设施 + 数据层<br/>配置·模型·迁移·前端脚手架]
|
|
|
|
|
|
T02[T02: 后端会议室API + Service层<br/>企微API对接·缓存·REST路由]
|
|
|
|
|
|
T03[T03: 终端绑定管理 + WebSocket<br/>CRUD·WS终端连接池·回调适配]
|
|
|
|
|
|
T04[T04: 终端前端页面<br/>状态展示·快速预定·扫码登录·WS同步]
|
|
|
|
|
|
T05[T05: H5端会议室页面 + 集成调试<br/>H5预定页面·nginx·端到端验证]
|
|
|
|
|
|
|
|
|
|
|
|
T01 --> T02
|
|
|
|
|
|
T01 --> T03
|
|
|
|
|
|
T01 --> T04
|
|
|
|
|
|
T02 --> T05
|
|
|
|
|
|
T04 --> T05
|
|
|
|
|
|
|
|
|
|
|
|
style T01 fill:#07C160,color:#fff
|
|
|
|
|
|
style T05 fill:#FFA502,color:#fff
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**依赖说明**:
|
|
|
|
|
|
- **T01** 是所有任务的基础(配置、模型、前端脚手架),必须最先完成
|
|
|
|
|
|
- **T02** 和 **T03** 仅依赖 T01,可并行开发(不同人员分别负责API层和WS层)
|
|
|
|
|
|
- **T04** 仅依赖 T01(前端可基于API Mock先行开发),与 T02/T03 并行
|
|
|
|
|
|
- **T05** 依赖 T02(H5端需要后端API)和 T04(集成调试需要终端页面),是最终集成阶段
|
|
|
|
|
|
|
|
|
|
|
|
**建议开发顺序**:
|
|
|
|
|
|
```
|
|
|
|
|
|
第1阶段: T01(1-2天)
|
|
|
|
|
|
第2阶段: T02 + T03 + T04 并行(3-4天)
|
|
|
|
|
|
第3阶段: T05(1-2天)
|
|
|
|
|
|
总计: 5-8天(P0核心功能)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 附录: 与现有系统的集成点
|
|
|
|
|
|
|
|
|
|
|
|
### A1. WecomService 扩展
|
|
|
|
|
|
|
|
|
|
|
|
在现有 `WecomService` 类中新增以下方法,复用现有的 Redis缓存+内存降级模式:
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
# 现有方法(不变):
|
|
|
|
|
|
get_access_token() → cache_key = "wecom:access_token"
|
|
|
|
|
|
get_contact_access_token() → cache_key = "wecom:contact_access_token"
|
|
|
|
|
|
|
|
|
|
|
|
# 新增方法:
|
|
|
|
|
|
get_meetingroom_access_token() → cache_key = "wecom:meetingroom_access_token"
|
|
|
|
|
|
→ 使用 settings.wecom_meetingroom_secret
|
|
|
|
|
|
→ TTL = 6900s (提前300s刷新)
|
|
|
|
|
|
→ 内存降级: self._meetingroom_token_cache
|
|
|
|
|
|
|
|
|
|
|
|
get_meetingroom_list() → POST /cgi-bin/oa/meetingroom/list
|
|
|
|
|
|
get_booking_info() → POST /cgi-bin/oa/meetingroom/get_booking_info
|
|
|
|
|
|
book_meetingroom() → POST /cgi-bin/oa/meetingroom/book
|
|
|
|
|
|
cancel_booking() → POST /cgi-bin/oa/meetingroom/cancel_book
|
|
|
|
|
|
get_booking_detail() → POST /cgi-bin/oa/meetingroom/bookinfo/get
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### A2. ConnectionManager 扩展
|
|
|
|
|
|
|
|
|
|
|
|
在现有 `ConnectionManager` 类中新增终端连接池:
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
# 现有连接池(不变):
|
|
|
|
|
|
active_connections: Dict[str, WebSocket] # agent_id → WS
|
|
|
|
|
|
employee_connections: Dict[str, WebSocket] # employee_id → WS
|
|
|
|
|
|
|
|
|
|
|
|
# 新增连接池:
|
|
|
|
|
|
terminal_connections: Dict[str, WebSocket] # terminal_sn → WS
|
|
|
|
|
|
|
|
|
|
|
|
# 新增方法:
|
|
|
|
|
|
connect_terminal(terminal_sn, ws) # 接受终端WS连接
|
|
|
|
|
|
disconnect_terminal(terminal_sn) # 清理终端WS连接
|
|
|
|
|
|
send_to_terminal(terminal_sn, data) # 向指定终端推送
|
|
|
|
|
|
broadcast_to_terminals(terminal_sns, data) # 向多终端推送
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### A3. 路由注册
|
|
|
|
|
|
|
|
|
|
|
|
在 `app/api/router.py` 中新增:
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
# 会议室预定 API
|
|
|
|
|
|
from app.api.meetingroom import router as meetingroom_router
|
|
|
|
|
|
api_router.include_router(meetingroom_router, tags=["会议室预定"])
|
|
|
|
|
|
|
|
|
|
|
|
# 终端绑定管理 API
|
|
|
|
|
|
from app.api.admin.terminal_binding import router as terminal_binding_router
|
|
|
|
|
|
api_router.include_router(terminal_binding_router, tags=["终端绑定管理"])
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
在 `app/main.py` 中无需修改 — WS路由已在 `ws.py` 中定义,`ws_router` 已被 include。
|
|
|
|
|
|
|
|
|
|
|
|
### A4. 模型注册
|
|
|
|
|
|
|
|
|
|
|
|
在 `app/models/__init__.py` 中新增:
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
from app.models.terminal_room_binding import TerminalRoomBinding
|
|
|
|
|
|
from app.models.meetingroom_booking_snapshot import MeetingroomBookingSnapshot
|
|
|
|
|
|
|
|
|
|
|
|
__all__ = [
|
|
|
|
|
|
# ... 现有模型 ...
|
|
|
|
|
|
"TerminalRoomBinding",
|
|
|
|
|
|
"MeetingroomBookingSnapshot",
|
|
|
|
|
|
]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### A5. nginx 路由配置(部署时新增)
|
|
|
|
|
|
|
|
|
|
|
|
```nginx
|
|
|
|
|
|
# 终端前端页面
|
|
|
|
|
|
location /terminal/ {
|
|
|
|
|
|
alias /app/frontend-terminal/dist/;
|
|
|
|
|
|
try_files $uri $uri/ /terminal/index.html;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 终端WebSocket (与现有 /ws/ 一致,无需额外配置)
|
|
|
|
|
|
# /ws/terminal/{sn} 已被现有的 /ws/ location 覆盖
|
|
|
|
|
|
```
|