# 技术方案 - REQ-集成-001 会议室预定 > **版本**: v1.0 > **日期**: 2026-07-15 > **REQ编号**: REQ-集成-001 > **关联PRD**: `01-产品文档/08-集成生态/PRD-REQ-集成-001-会议室预定-v1.0.md` > **状态**: 待评审 > **作者**: 高见远 (Gao) · 架构师 --- ## 目录 - [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: 项目基础设施 + 数据层
配置·模型·迁移·前端脚手架] T02[T02: 后端会议室API + Service层
企微API对接·缓存·REST路由] T03[T03: 终端绑定管理 + WebSocket
CRUD·WS终端连接池·回调适配] T04[T04: 终端前端页面
状态展示·快速预定·扫码登录·WS同步] T05[T05: H5端会议室页面 + 集成调试
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 覆盖 ```