feat: 2026-07-11 全量更新 - 代办集成+会议室预定+知识迭代修复+UI统一+Bug修复
== 已部署上线 (9项) == - 代办事项真实数据源集成 (企微审批API 8bug修复链) - H5/坐席端 Logo样式统一+绿色背景 - 视频引导页修复 (localStorage key v2) - 坐席端 v9 Vue版本修复 (ElMessage._context) - 截图按钮 v10 修复 (getDisplayMedia user gesture) - 扫码样式恢复+H5扫码登录跳转修复 - H5截图快捷键提示 == 代码完成待部署 (3项) == - 知识迭代3Bug修复 (#8 POST端点/#7 MERGE幂等/#6 过期检查) - 会议室预定-小鱼易联终端 (40文件, 40/40测试通过) - IT资产升级审批推送 (asset_service.py) == 需求文档 (2项) == - 坐席端AI辅助消息框-PRD (4项新功能确认) - 坐席端布局优化建议 v2.0 (7天计划) == 新增文档 == - 日报-2026-07-11.md - 知识迭代Bug修复报告-20260711.md - 会议室预定-部署指南.md - CHANGELOG.md 更新 == 测试 == - test_todo_integration.py: 40/40 - test_meetingroom.py: 40/40 - test_bugfix_ki_suggestions.py: 21/21
This commit is contained in:
+114
-4
@@ -1,10 +1,10 @@
|
||||
# 00 · 标准故障排查手册
|
||||
|
||||
> **版本**: v1.1 | **日期**: 2026-07-08 | **维护人**: 宋献 / 助理
|
||||
> **版本**: v1.4 | **日期**: 2026-07-10 | **维护人**: 宋献 / 助理
|
||||
> **定位**: 所有故障排查前**首先查看本手册**。
|
||||
> **最新**: 新增 CASE-20260708-01~06(OTP 路由404 / nginx 404 / 扫码角色 / 用户角色 / 员工端路由 / OTP列表结构)+ nginx 配错急救流程
|
||||
> **最新**: 方案 C(卷挂载)已上线,§1.4 更新为 volume 挂载验证 + CASE-20260710-02 标注根因已消除 + 错误码速查更新
|
||||
|
||||
| v1.1 | 2026-07-08 | 新增 6 天 7.8 案例 + nginx 急救流程 + 端到端验证更新 |
|
||||
| v1.4 | 2026-07-10 | 方案 C 上线:§1.4 改为 volume 挂载验证 + 错误码速查更新 + CASE-20260710-02 根因已消除标注 |
|
||||
> **前置阅读**: [运维手册(部署/回滚/备份/应急)](../01-项目总览/01-智能IT服务系统运维手册-20260704.md) · [SOP-04 应急响应](../10-项目管理/SOPs-标准流程/SOP-04-应急响应.md)
|
||||
|
||||
---
|
||||
@@ -24,13 +24,26 @@
|
||||
| 版本 | 日期 | 变更 |
|
||||
|------|------|------|
|
||||
| v1.0 | 2026-07-07 | 整合 9 份散落文档 + 新增 CASE-20260707-01(Redis urlparse 挂起)|
|
||||
| v1.1 | 2026-07-08 | 新增 CASE-20260708-01~06 + nginx 急救流程 + 端到端验证更新 |
|
||||
| v1.2 | 2026-07-10 | 新增 CASE-20260710-01(CSP 拦截内联 JS)+ Step 0 响应头检查 + sed -i inode 教训 |
|
||||
| v1.4 | 2026-07-10 | 方案 C 上线:§1.4 改为 volume 挂载验证 + 错误码速查更新 + CASE-20260710-02 根因已消除标注 |
|
||||
| v1.3 | 2026-07-10 | 新增 CASE-20260710-02(镜像缺文件导致 API 404)+ 判定矩阵扩展 + 部署前同步检查清单 |
|
||||
|
||||
---
|
||||
|
||||
## 1 快速诊断决策树
|
||||
|
||||
### 1.1 三步隔离法(通用)
|
||||
任何"页面打不开 / 网络连接失败 / 接口无响应 / 422"都先用三步隔离,定位是 nginx、后端、还是依赖(DB / Redis)的问题:
|
||||
任何"页面打不开 / 网络连接失败 / 接口无响应 / 422"都先用三步隔离,定位是 nginx、后端、还是依赖(DB / Redis)的问题。
|
||||
|
||||
#### Step 0:HTTP 响应头检查(页面 200 但功能异常时首先执行)
|
||||
|
||||
```bash
|
||||
# 检查响应头,特别关注 Content-Security-Policy / X-Frame-Options / X-Content-Type-Options
|
||||
curl -ksI https://itsupport.servyou.com.cn/h5/ | grep -iE 'content-security|x-frame|content-type'
|
||||
```
|
||||
|
||||
**为什么要有 Step 0**:页面返回 HTTP 200、HTML 正常加载,但 JavaScript 不执行——这类问题无法被三步隔离法捕获。根因通常是 CSP(Content-Security-Policy)头限制了 `script-src`,导致内联 `<script>` 被浏览器静默拦截。详见 CASE-20260710-01。
|
||||
|
||||
```bash
|
||||
# 第1步:nginx 层可达性(在服务器执行;浏览器走 HTTPS,故用 https 而非 localhost)
|
||||
@@ -56,6 +69,8 @@ docker compose exec postgres pg_isready -U wecom # 期望 accepting
|
||||
| 某端点 502 | ❌502 | ❌后端 down | — | 后端未起 / 缺 `PYTHONPATH=/app` |
|
||||
| /itdesk/ 200 但 /api/... 404 | ✅ | — | — | nginx 代理路径不匹配 |
|
||||
| 422 | ✅ | API 校验失败 | — | 请求体缺字段(见 §2)|
|
||||
| 页面 200 但 JS 不执行 | ✅200 | — | — | **CSP 拦截内联 JS**(检查 `Content-Security-Policy` 头的 `script-src` 是否含 `'unsafe-inline'`,见 Step 0 / CASE-20260710-01)|
|
||||
| API 404 但本地代码存在 | ✅200 | ❌404 | — | ~~镜像缺文件~~(方案 C 前根因)。卷挂载模式下检查 volume 挂载 + `./app/` 代码完整性(见 CASE-20260710-02 / §1.4)|
|
||||
|
||||
### 1.2 关键陷阱:URL 特殊字符导致依赖"静默挂起"
|
||||
详见案例 **CASE-20260707-01**。密码含 `@` `#` 时,`urlparse` 把它们当 URL 分隔符,连到不存在的 host,连接**无限挂起**(浏览器表现为"网络连接失败",curl 永远等不到返回)。这是最隐蔽的一类故障——容器全 Up、nginx 全 200、唯独业务接口卡死。
|
||||
@@ -70,6 +85,57 @@ python jms_ops.py exec -c "docker compose ps" -c "curl -ksI https://itsupport.se
|
||||
|
||||
> 原"服务器端跑诊断"的 3 种手工方式(PuTTY 跳堡垒机 / scp 上传 / 服务器下载)已不推荐,统一用上述 jumpserver-ops 自动化。
|
||||
|
||||
### 1.4 部署前检查清单(卷挂载模式 — 方案 C)
|
||||
|
||||
**触发条件**:任何涉及后端代码变更的部署(新增/修改 `.py` 文件、新增 Python 依赖)。
|
||||
|
||||
#### ⛔ 硬规则:后端代码部署方式(方案 C 卷挂载,2026-07-10 上线)
|
||||
|
||||
| 变更类型 | 正确命令 | ❌ 禁止操作 | 耗时 |
|
||||
|---------|---------|---------|------|
|
||||
| `.py` 文件变更 | `cd /opt/wecom-it-desk && docker compose restart backend` | ❌ `docker compose build` | ~15-30 秒 |
|
||||
| `requirements.txt` 变更 | `docker compose build backend && docker compose up -d backend` | — | ~60-90 秒 |
|
||||
| `.env` / `docker-compose.yml` 变更 | `docker compose up -d backend` | — | ~10 秒 |
|
||||
|
||||
> 代码通过 `./app:/app/app` volume 挂载到容器,不烘焙进镜像。改代码只需 restart 让 uvicorn 重新加载,无需重建镜像。`docker compose build` 只在 Python 依赖变化时才需要。
|
||||
|
||||
**背景**:方案 C(卷挂载)已于 2026-07-10 上线。代码不再烘焙进 Docker 镜像,而是通过 `./app:/app/app` volume 挂载到容器。`backend/app/` 旧代码目录已删除。部署前只需验证 `./app/` 代码目录完整性和 volume 挂载状态。
|
||||
|
||||
> ⚠️ **历史背景**(已消除):方案 C 前,服务器存在两份代码目录 `app/` 和 `backend/app/`,不同步导致镜像缺文件(见 CASE-20260710-02)。方案 C 消除了此根因。
|
||||
|
||||
**检查清单(经堡垒机执行)**:
|
||||
|
||||
```bash
|
||||
# 1. 验证 ./app/ 关键文件存在
|
||||
for f in app/__init__.py app/main.py app/api/auth.py; do
|
||||
[ -f "/opt/wecom-it-desk/$f" ] && echo "PASS: $f" || echo "FAIL: $f missing"
|
||||
done
|
||||
|
||||
# 2. 验证 volume 挂载正常(容器内可访问代码)
|
||||
docker exec wecom_it_backend ls /app/app/main.py
|
||||
|
||||
# 3. 验证代码一致性(宿主机与容器内 MD5 一致)
|
||||
HOST=$(md5sum /opt/wecom-it-desk/app/main.py | awk '{print $1}')
|
||||
CONTAINER=$(docker exec wecom_it_backend md5sum /app/app/main.py | awk '{print $1}')
|
||||
[ "$HOST" = "$CONTAINER" ] && echo "PASS: code match" || echo "FAIL: code mismatch"
|
||||
|
||||
# 4. 如有新依赖,检查 requirements.txt
|
||||
# diff 本地 requirements.txt 与服务器上的(如需要)
|
||||
|
||||
# 5. 代码更新后只需重启(无需重建镜像)
|
||||
cd /opt/wecom-it-desk && docker compose restart backend
|
||||
```
|
||||
|
||||
**简化版(用 jumpserver-ops 一键检查)**:
|
||||
```bash
|
||||
python jms_ops.py exec \
|
||||
-c "for f in app/__init__.py app/main.py app/api/auth.py; do [ -f /opt/wecom-it-desk/\$f ] && echo \"PASS: \$f\" || echo \"FAIL: \$f\"; done" \
|
||||
-c "docker exec wecom_it_backend ls /app/app/main.py && echo PASS_mount" \
|
||||
--reuse
|
||||
```
|
||||
|
||||
> ⚠️ 代码更新后 `docker compose restart` 即可(15-30 秒)。仅当 `requirements.txt` 有变化时才需要 `docker compose build`。
|
||||
|
||||
---
|
||||
|
||||
## 2 常见错误码速查(E5xx)
|
||||
@@ -82,6 +148,8 @@ python jms_ops.py exec -c "docker compose ps" -c "curl -ksI https://itsupport.se
|
||||
| **E403** | IP 白名单 / 无权限 | `grep allow /opt/wecom-it-desk/nginx/nginx.conf`;admin 角色不足 |
|
||||
| **E422** | 请求体校验失败(Pydantic)| 确认必填字段齐全(如登录需 `user_id`+`name`)|
|
||||
| **网络失败 / 连接挂起** | 依赖不可达(最常见 Redis 配置错)| 见 §1.2 / CASE-20260707-01 |
|
||||
| **页面 200 但 JS 不执行** | CSP 头 `script-src` 缺 `'unsafe-inline'`,内联 `<script>` 被浏览器静默拦截 | `curl -ksI <URL> \| grep content-security`;检查 `script-src` 是否含 `'unsafe-inline'`(见 CASE-20260710-01)|
|
||||
| **API 404 但本地代码存在** | ~~镜像缺文件~~(方案 C 前根因已消除)。卷挂载模式下检查:volume 是否正常挂载 + `./app/` 是否有该文件 | `docker exec <容器> ls /app/app/api/auth.py`;`docker exec <容器> md5sum /app/app/main.py` 对比宿主机(见 §1.4 / CASE-20260710-02)|
|
||||
|
||||
### 2.1 E500 常见根因速查
|
||||
- 数据库缺列 → `ALTER TABLE ... ADD COLUMN IF NOT EXISTS ...`
|
||||
@@ -133,6 +201,47 @@ docker logs wecom_it_backend | grep -i websocket
|
||||
|
||||
## 4 案例库(倒序,编号 CASE-YYYYMMDD-序号)
|
||||
|
||||
### CASE-20260710-02 · 坐席端/管理端登录"获取二维码失败"(Docker 镜像缺文件)⭐⭐
|
||||
- **现象**:坐席端和管理端登录均报"获取二维码失败",浏览器控制台显示 `WebSocket connection failed` + `/api/auth/qrcode` 返回 404。
|
||||
- **误判历程**:
|
||||
1. 误判为 Nginx 未 reload → `nginx -s reload` 后 `/auth_qrcode/create` 返回 200,但前端实际调用的是 `/auth/qrcode`(不同路由)
|
||||
2. 尝试 `docker cp` 临时复制 `auth.py` 到容器 → 重启后文件丢失(临时文件系统)
|
||||
3. 发现连锁依赖:`auth.py` 依赖 `schemas/auth.py` → `services/auth_service.py` → `models/login_log.py`,全部缺失
|
||||
4. **真正根因**:服务器存在两份代码——`/opt/wecom-it-desk/app/`(较新,开发用)和 `/opt/wecom-it-desk/backend/app/`(较旧,Docker 构建用)。新增的 `auth.py` 只放到了 `app/`,未同步到 `backend/app/`,导致镜像构建时打包的是旧代码。
|
||||
- **根因**:服务器上 `app/`(开发目录)与 `backend/app/`(Docker 构建目录)不同步,`docker compose build` 打包了旧代码。同时 `requirements.txt` 也未同步(新增 `neo4j` 依赖),首次重建后容器因缺包 crash。
|
||||
- **修复**:
|
||||
1. 用 Python 脚本同步 `/opt/wecom-it-desk/app/` → `/opt/wecom-it-desk/backend/app/`
|
||||
2. 上传最新 `requirements.txt`(含 `neo4j>=5.26.0`)到 `/opt/wecom-it-desk/backend/`
|
||||
3. `docker compose build backend`(约 65s)
|
||||
4. `docker compose up -d backend`
|
||||
5. 验证:后端日志显示 `GET /auth/qrcode → 200 OK`,容器状态 healthy
|
||||
- **⚠️ 教训**:
|
||||
1. **部署新功能时必须检查两份代码一致性**:`app/` 和 `backend/app/` 不同步 = 镜像缺文件。
|
||||
2. **`docker cp` 是临时方案**:容器重启后丢失。永久修复必须重建镜像。
|
||||
3. **全链路检查**:代码同步 → requirements 更新 → 镜像重建 → 容器重启,四步缺一不可。
|
||||
- **防护措施**:见 §1.4 部署前检查清单。
|
||||
- **✅ 根因已消除**(2026-07-10):方案 C(卷挂载)已上线。代码不再烘焙进 Docker 镜像,通过 `./app:/app/app` volume 挂载到容器。`backend/app/` 旧代码目录已删除,不再存在"两份代码不同步"问题。此案例保留作为历史参考。
|
||||
|
||||
### CASE-20260710-01 · 扫码登录成功页 JS 不执行(CSP 拦截内联脚本)⭐⭐
|
||||
- **现象**:企微扫码登录成功后,"登录成功"页面显示但不会自动关闭。页面停在"JS加载中…",所有内联 `<script>` 从未执行。后端返回 HTTP 200,HTML 内容正确。
|
||||
- **误判历程**(5 轮,供反思):
|
||||
1. 误判为未引入企微 JS-SDK → 添加后仍不工作
|
||||
2. 误判为 JSAPI 签名 URL 协议不一致(HTTP vs HTTPS)→ 修复 `X-Forwarded-Proto` 后仍不工作
|
||||
3. 误判为外部 JS-SDK 加载失败 → 移除外部依赖改用 `WeixinJSBridge` 仍不工作
|
||||
4. 误判为 `WeixinJSBridgeReady` 事件未触发 → 改为轮询检测仍不工作
|
||||
5. **真正根因**:Nginx CSP 头 `script-src 'self' 'unsafe-eval' https://res.wx.qq.com` 缺少 `'unsafe-inline'`,所有内联 `<script>` 被浏览器静默拦截。**JS 从未执行过**——前面 4 轮的代码修改全部白费。
|
||||
- **根因**:`Content-Security-Policy` 的 `script-src` 指令未包含 `'unsafe-inline'`,浏览器根据 CSP 规范静默拦截所有内联脚本(`<script>...</script>` 和 `onclick=` 等内联事件处理器)。页面 HTML 正常返回,但 JS 引擎从未解析执行任何脚本。
|
||||
- **修复**:Nginx 配置 CSP 头 `script-src` 加入 `'unsafe-inline'`:
|
||||
```
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://res.wx.qq.com; ..." always;
|
||||
```
|
||||
修改后 `docker restart wecom_it_nginx`(不能用 `reload`,见下方教训)。
|
||||
- **验证**:用户手机企微扫码 → 显示"登录成功"→ 1 秒后自动关闭返回企微 ✅
|
||||
- **⚠️ 教训**:
|
||||
1. **页面 200 + HTML 正确 ≠ JS 会执行**。排查前端功能异常时,Step 0 应检查 HTTP 响应头(CSP / X-Frame-Options),而非直接深入代码逻辑。
|
||||
2. **CSP 拦截是静默的**——浏览器控制台报错但页面无任何错误提示,后端日志也完全正常。如果不主动检查响应头,会一直在代码层面打转。
|
||||
3. **5 轮误判的共同盲点**:每次都在"JS 代码为什么不对"上做文章,从未怀疑"JS 根本没执行"。正确的排查路径应该是先在浏览器 F12 控制台输入 `1+1` 确认 JS 引擎可用,再看 Console 是否有 CSP 报错。
|
||||
|
||||
### CASE-20260707-01 · 管理后台登录"网络连接失败"(Redis 密码 URL 解析挂起)⭐
|
||||
- **现象**:浏览器登录 `/itadmin/` 一直转圈 / "网络连接失败";API 永远不返回;curl 超时。
|
||||
- **根因**:`REDIS_URL=redis://:R3d!s@2026#Secure@redis:6379/0`,密码含 `@` 和 `#`。`urlparse()` 把 `#` 当 fragment、`@` 当 host 分隔符 → 解析出 host=`2026`、password=`R3d!s` → 连到不存在的 host → **无限挂起**。后端 `token_service.create_token()` 调 `redis.setex` 时卡死。
|
||||
@@ -220,6 +329,7 @@ docker logs wecom_it_backend | grep -i websocket
|
||||
| 5 | 检查容器状态:`docker ps --filter name=wecom_it_nginx` |
|
||||
| 6 | 用 `agent-browser` 打开 URL 做端到端验证 |
|
||||
| 7 | 避免用 sed 修改 nginx 配置——`$uri`/`$host` 等变量会被 shell 解释;用 Python 脚本或直接上传文件 |
|
||||
| 8 | ⚠️ **`sed -i` 会创建新 inode**:Docker bind mount 不跟踪新 inode,导致容器内看到的仍是旧文件。用 `sed -i` 修改后必须 `docker restart`(而非 `nginx -s reload`),或改用 `sed` 不带 `-i` + 重定向写入原文件(保持 inode 不变) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,385 @@
|
||||
# 智能IT服务系统运维手册
|
||||
|
||||
> **版本**: v1.0 | **日期**: 2026-07-04 | **维护人**: 助理(小米)
|
||||
> **目标读者**: 运维工程师 / IT支持组
|
||||
|
||||
> **📖 关联文档**:
|
||||
> - [README.md](../README.md) — 项目快速入门
|
||||
> - [01-项目总览与部署手册](./01-项目总览与部署手册-20260704.md) — 完整架构设计
|
||||
> - [CHANGELOG.md](../CHANGELOG.md) — 版本变更概览
|
||||
> - [docs/archive/](./archive/) — 历史版本详情
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [系统概述](#一系统概述)
|
||||
2. [环境信息](#二环境信息)
|
||||
3. [部署操作](#三部署操作)
|
||||
4. [日常运维](#四日常运维)
|
||||
5. [故障排查](#五故障排查)
|
||||
6. [回滚方案](#六回滚方案)
|
||||
7. [备份恢复](#七备份恢复)
|
||||
8. [应急响应](#八应急响应)
|
||||
|
||||
---
|
||||
|
||||
## 一、系统概述
|
||||
|
||||
### 1.1 系统架构
|
||||
|
||||
```
|
||||
浏览器 ──→ itsupport.servyou.com.cn:443
|
||||
│
|
||||
▼
|
||||
┌─── nginx (容器) ───────────────┐
|
||||
│ │
|
||||
│ /itdesk/* → H5 员工端 SPA │
|
||||
│ /itagent/* → 坐席工作台 SPA │
|
||||
│ /itadmin/* → 管理后台 SPA │
|
||||
│ /itportal/* → Portal 选择页 │
|
||||
│ /api/* → backend:8000 │
|
||||
│ /ws/* → backend:8000 (WS)│
|
||||
│ │
|
||||
└──────────────┬───────────────────┘
|
||||
│ 本机 Docker 网络
|
||||
┌─────────────┼─────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ backend │ │ postgres │ │ redis │
|
||||
│ :8000 │ │ :5432 │ │ :6379 │
|
||||
└──────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
### 1.2 组件清单
|
||||
|
||||
| 服务 | 镜像 | 端口 | 说明 |
|
||||
|------|------|------|------|
|
||||
| nginx | nginx:alpine | 443→80 (对外) | 反向代理 + SSL |
|
||||
| backend | 自构建 | 8000 (内部) | FastAPI 后端 |
|
||||
| postgres | postgres:16 | 5432 (内部) | 数据库 |
|
||||
| redis | redis:7 | 6379 (内部) | 缓存 + Session |
|
||||
|
||||
### 1.3 访问端点
|
||||
|
||||
| 端点 | 说明 |
|
||||
|------|------|
|
||||
| `https://itsupport.servyou.com.cn/itdesk/` | H5 员工端 |
|
||||
| `https://itsupport.servyou.com.cn/itagent/` | 坐席工作台 |
|
||||
| `https://itsupport.servyou.com.cn/itadmin/` | 管理后台 |
|
||||
| `https://itsupport.servyou.com.cn/itportal/` | Portal 角色选择 |
|
||||
| `https://itsupport.servyou.com.cn/api/docs` | API Swagger 文档 |
|
||||
|
||||
---
|
||||
|
||||
## 二、环境信息
|
||||
|
||||
### 2.1 服务器信息
|
||||
|
||||
| 环境 | IP | 域名 | 用途 |
|
||||
|------|-----|------|------|
|
||||
| 生产 | 10.90.5.110 (内网) | itsupport.servyou.com.cn | 正式环境 |
|
||||
| 运维入口 | 10.212.189.210:2222 | - | 堡垒机 SSH |
|
||||
|
||||
### 2.2 关键配置
|
||||
|
||||
| 配置项 | 值 |
|
||||
|--------|-----|
|
||||
| 企微 CorpID | `ww...` (见 .env) |
|
||||
| 企微 AgentID | `1000xxx` |
|
||||
| 数据库 | PostgreSQL 16 |
|
||||
| 缓存 | Redis 7 |
|
||||
| 域名证书 | `*.servyou.com.cn` (GeoTrust/DigiCert) |
|
||||
|
||||
### 2.3 部署路径
|
||||
|
||||
```
|
||||
/opt/wecom-it-desk/
|
||||
├── docker-compose.yml
|
||||
├── .env # 环境变量(不提交 Git)
|
||||
├── backend/ # 后端代码
|
||||
├── frontend-h5/dist/ # H5 前端构建产物
|
||||
├── frontend-agent/dist/ # 坐席前端构建产物
|
||||
├── frontend-admin/dist/ # 管理后台构建产物
|
||||
├── frontend-portal/dist/ # Portal 构建产物
|
||||
├── nginx/ # Nginx 配置
|
||||
└── logs/ # 日志目录
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、部署操作
|
||||
|
||||
### 3.1 部署流程概览
|
||||
|
||||
```
|
||||
1. 打包代码 → 2. 上传服务器 → 3. 配置环境变量 → 4. 启动容器 → 5. 验证
|
||||
```
|
||||
|
||||
### 3.2 打包命令(本地)
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行
|
||||
cd D:\资料\03-项目开发\wecom_it_smart_desk
|
||||
|
||||
# 使用部署脚本打包
|
||||
powershell -File deploy-server\build-package.ps1
|
||||
|
||||
# 或手动打包
|
||||
tar czf deploy.tar.gz \
|
||||
backend/ frontend-h5/dist/ frontend-agent/dist/ \
|
||||
frontend-admin/dist/ frontend-portal/dist/ \
|
||||
nginx/ docker-compose.yml .env.production scripts/
|
||||
```
|
||||
|
||||
### 3.3 上传到服务器
|
||||
|
||||
> **注意**: 公司服务器只能通过堡垒机上传,无法直接从本地 scp
|
||||
|
||||
1. **通过堡垒机上传到 `/tmp/`**:
|
||||
- 使用 SFTP 或 Web 界面上传到堡垒机
|
||||
2. **SSH 登录服务器**:
|
||||
```bash
|
||||
# 堡垒机: sxn@10.212.189.210:2222
|
||||
ssh sxn@10.90.5.110 # 跳转目标服务器
|
||||
```
|
||||
|
||||
3. **移动到目标目录**:
|
||||
```bash
|
||||
mv /tmp/deploy.tar.gz /opt/wecom-it-desk/
|
||||
cd /opt/wecom-it-desk/
|
||||
tar xzf deploy.tar.gz
|
||||
```
|
||||
|
||||
### 3.4 配置环境变量
|
||||
|
||||
```bash
|
||||
# 创建环境配置
|
||||
cp .env.production .env
|
||||
vim .env # 编辑真实配置
|
||||
|
||||
# 必填项:
|
||||
# - WECOM_CORP_ID
|
||||
# - WECOM_AGENT_ID
|
||||
# - WECOM_SECRET
|
||||
# - WECOM_TOKEN
|
||||
# - WECOM_ENCODING_AES_KEY
|
||||
# - POSTGRES_PASSWORD
|
||||
# - REDIS_PASSWORD
|
||||
```
|
||||
|
||||
### 3.5 启动服务
|
||||
|
||||
```bash
|
||||
# 启动所有容器
|
||||
docker compose up -d --build
|
||||
|
||||
# 或分步启动
|
||||
docker compose up -d postgres redis # 先启动基础服务
|
||||
docker compose up -d backend # 再启动后端
|
||||
docker compose up -d nginx # 最后启动前端
|
||||
```
|
||||
|
||||
### 3.6 验证部署
|
||||
|
||||
```bash
|
||||
# 1. 检查容器状态
|
||||
docker compose ps
|
||||
# 预期:4 个容器全部 Up/healthy
|
||||
|
||||
# 2. 健康检查
|
||||
curl -ksI https://itsupport.servyou.com.cn/api/health
|
||||
|
||||
# 3. 各端点验证
|
||||
curl -ksI https://itsupport.servyou.com.cn/itdesk/
|
||||
curl -ksI https://itsupport.servyou.com.cn/itagent/
|
||||
curl -ksI https://itsupport.servyou.com.cn/itadmin/
|
||||
curl -ksI https://itsupport.servyou.com.cn/itportal/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、日常运维
|
||||
|
||||
### 4.1 日常检查
|
||||
|
||||
```bash
|
||||
# 每日必做检查
|
||||
docker compose ps # 容器状态
|
||||
docker compose logs --tail=50 backend # 后端日志
|
||||
docker compose logs --tail=50 nginx # 前端日志
|
||||
df -h # 磁盘空间
|
||||
```
|
||||
|
||||
### 4.2 常用操作
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 重启后端 | `docker compose restart backend` |
|
||||
| 重启 nginx | `docker compose restart nginx` |
|
||||
| 查看实时日志 | `docker compose logs -f backend` |
|
||||
| 进入后端容器 | `docker compose exec backend bash` |
|
||||
| 查看容器资源 | `docker stats` |
|
||||
|
||||
### 4.3 监控指标
|
||||
|
||||
| 指标 | 阈值 | 说明 |
|
||||
|------|------|------|
|
||||
| CPU 使用率 | < 80% | 主机层面 |
|
||||
| 内存使用率 | < 80% | 主机层面 |
|
||||
| 磁盘使用率 | < 70% | 主机层面 |
|
||||
| 容器状态 | 全部 Up | docker compose ps |
|
||||
| API 响应时间 | P95 < 500ms | 业务层面 |
|
||||
|
||||
### 4.4 日志位置
|
||||
|
||||
| 服务 | 日志命令 |
|
||||
|------|----------|
|
||||
| 后端 | `docker compose logs backend` |
|
||||
| Nginx | `docker compose logs nginx` |
|
||||
| PostgreSQL | `docker compose logs postgres` |
|
||||
| Redis | `docker compose logs redis` |
|
||||
|
||||
---
|
||||
|
||||
## 五、故障排查
|
||||
|
||||
> **本章已整合至标准故障排查手册**:[09-部署运维/00-标准故障排查手册.md](../09-部署运维/00-标准故障排查手册.md)
|
||||
>
|
||||
> 手册涵盖:三步隔离法、错误码速查(500/502/503/403/422/网络挂起)、诊断脚本与命令、案例库(含 Redis urlparse 挂起、502、各类修复记录)、端到端验证完成标准(含"宣布修复前必须提供真实浏览器截图"硬规则)。
|
||||
>
|
||||
> **日常排故请直接打开该手册**,本文档不再重复故障排查细节。
|
||||
|
||||
---
|
||||
|
||||
## 六、回滚方案
|
||||
|
||||
### 6.1 快速回滚
|
||||
|
||||
```bash
|
||||
# 停止当前版本
|
||||
docker compose down
|
||||
|
||||
# 恢复上一个版本(需提前备份)
|
||||
# 方法1: 从 Git 拉取上一个 commit
|
||||
git checkout {上一个commit-hash}
|
||||
# 重新构建部署
|
||||
|
||||
# 方法2: 保留上一个版本的部署包
|
||||
cd /opt/wecom-it-desk-backup
|
||||
tar xzf deploy-v0.x.x.tar.gz
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 6.2 回滚检查清单
|
||||
|
||||
- [ ] 确认上一个版本可用
|
||||
- [ ] 通知相关人员
|
||||
- [ ] 记录当前版本问题
|
||||
- [ ] 执行回滚
|
||||
- [ ] 验证回滚后功能正常
|
||||
- [ ] 发送回滚通知
|
||||
|
||||
---
|
||||
|
||||
## 七、备份恢复
|
||||
|
||||
### 7.1 备份策略
|
||||
|
||||
| 备份对象 | 方法 | 频率 | 保留 |
|
||||
|---------|------|------|------|
|
||||
| PostgreSQL | pg_dump | 每日凌晨 | 7 天 |
|
||||
| Redis | redis-cli SAVE | 每日凌晨 | 7 天 |
|
||||
| 配置文件 | tar 归档 | 每次部署 | 4 个版本 |
|
||||
| 日志文件 | logrotate | 每周 | 4 周 |
|
||||
|
||||
### 7.2 备份命令
|
||||
|
||||
```bash
|
||||
# 备份数据库
|
||||
docker compose exec postgres pg_dump -U postgres wecom_it > /tmp/backup_$(date +%Y%m%d).sql
|
||||
|
||||
# 备份 Redis
|
||||
docker compose exec redis redis-cli SAVE
|
||||
cp /var/lib/docker/volumes/wecom-it-desk_redis_data/_data/dump.rdb /tmp/redis_$(date +%Y%m%d).rdb
|
||||
|
||||
# 备份配置
|
||||
tar czf /tmp/config_$(date +%Y%m%d).tar.gz /opt/wecom-it-desk/.env /opt/wecom-it-desk/nginx/
|
||||
```
|
||||
|
||||
### 7.3 恢复命令
|
||||
|
||||
```bash
|
||||
# 恢复数据库
|
||||
docker compose exec -T postgres psql -U postgres wecom_it < backup_20260701.sql
|
||||
|
||||
# 恢复 Redis
|
||||
docker compose exec -T redis redis-cli FLUSHALL
|
||||
# 停止服务后复制 dump.rdb 到数据目录
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、应急响应
|
||||
|
||||
### 8.1 事件分级
|
||||
|
||||
| 等级 | 场景 | 响应时间 |
|
||||
|------|------|----------|
|
||||
| 🔴 P0 | 鉴权漏洞 / 数据泄露 / 服务全停 | 5 min |
|
||||
| 🟠 P1 | 功能故障 / 单服务降级 | 30 min |
|
||||
| 🟡 P2 | 性能问题 / UI 异常 | 4 h |
|
||||
| 🟢 P3 | 体验优化 | 1 周 |
|
||||
|
||||
### 8.2 P0 应急流程
|
||||
|
||||
#### 立即止血
|
||||
|
||||
```bash
|
||||
# 1. 关闭外网访问
|
||||
sudo iptables -A INPUT -p tcp --dport 443 -j DROP
|
||||
|
||||
# 2. 停可疑服务
|
||||
docker compose stop backend
|
||||
|
||||
# 3. 保留现场(不删文件)
|
||||
docker compose logs backend > /tmp/incident-backend.log
|
||||
docker compose logs nginx > /tmp/incident-nginx.log
|
||||
```
|
||||
|
||||
#### 通知
|
||||
|
||||
- 微信/电话通知项目负责人
|
||||
- 邮件通知:`wecom-it-desk-incident@servyou-it.com`
|
||||
|
||||
### 8.3 应急联系
|
||||
|
||||
| 角色 | 联系人 |
|
||||
|------|--------|
|
||||
| 项目负责人 | 宋献 |
|
||||
| 运维 | IT 支持组 |
|
||||
| 企微技术支持 | 企微客服 |
|
||||
|
||||
---
|
||||
|
||||
## 附录
|
||||
|
||||
### 版本历史
|
||||
|
||||
| 版本 | 日期 | 更新内容 |
|
||||
|------|------|----------|
|
||||
| v1.0 | 2026-07-04 | 初始版本,整合部署/运维/故障排查文档 |
|
||||
|
||||
### 相关文档
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| `docs/01-项目总览/01-项目总览与部署手册-20260704.md` | 完整项目背景与架构设计 |
|
||||
| `docs/RELEASE_NOTES_v0.7.1.md` | 版本发布说明 |
|
||||
| `docs/SOPs/SOP-004-应急响应.md` | 详细应急响应流程 |
|
||||
| `09-部署运维/00-标准故障排查手册.md` | 标准故障排查手册(故障排查唯一入口)|
|
||||
|
||||
---
|
||||
|
||||
> **维护说明**: 本文档由助理(小米)维护,随每次发布更新。
|
||||
> 如有更新,请同步更新本文档的版本号和日期。
|
||||
@@ -0,0 +1,731 @@
|
||||
# 企微智能IT支持服务台 — 项目总览与部署手册
|
||||
|
||||
> **版本**: v2.2 | **日期**: 2026-07-04 | **编制**: 宋献(IT支持组组长)
|
||||
> **目标读者**: **管理者 / 架构师 / 运维** — 了解项目全貌、架构决策、部署与运维操作
|
||||
|
||||
> **📖 与 README.md 的关系**: 本文是 [README.md](../README.md) 的**详细版本**,侧重完整的架构设计和部署运维。README 适合新人快速入门,本文适合深入了解。
|
||||
|
||||
> **⚠️ 运维手册更新**: 部署与运维操作已整合到独立文档 [智能IT服务系统运维手册](./智能IT服务系统运维手册.md),该文档由助理(小米)维护,随每次发布更新。本文档保留架构设计与背景信息。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [项目概述](#一项目概述)
|
||||
2. [系统架构](#二系统架构)
|
||||
3. [三步演进路径](#三三步演进路径)
|
||||
4. [现有系统复用评估](#四现有系统复用评估)
|
||||
5. [正式环境部署方案](#五正式环境部署方案)
|
||||
6. [部署操作手册](#六部署操作手册)
|
||||
7. [运维管理](#七运维管理)
|
||||
8. [开发交付状态](#八开发交付状态)
|
||||
9. [附录](#九附录)
|
||||
|
||||
---
|
||||
|
||||
## 一、项目概述
|
||||
|
||||
### 1.1 背景与痛点
|
||||
|
||||
公司约 **6000 人**,全国设分子机构,使用企业微信作为内部 IM。当前 IT 服务存在三大痛点:
|
||||
|
||||
| 痛点 | 现状 | 影响 |
|
||||
|------|------|------|
|
||||
| 员工绕过 AI 直接找人工 | 可通过关键词直通人工坐席,首次后永久记忆 | AI 筛选率极低,人工成本高 |
|
||||
| AI 转人工需另开窗口 | 跳转到企微"员工服务"模块,与 AI 对话割裂 | 体验差,员工困惑 |
|
||||
| 无法跨主体共享 | 企微"员工服务"不支持互联企业应用共享 | 跨企业服务不可达 |
|
||||
|
||||
### 1.2 核心方案
|
||||
|
||||
**自研 IT 服务坐席系统**,替代企微内置的"员工服务"模块:
|
||||
- 基于企微自建应用消息 API,所有消息由自己的服务器接管
|
||||
- 分三步渐进式构建:M1 消息接管 → M2 AI 接入 → M3 知识库闭环
|
||||
- 当前处于 **M1(消息接管 + 极简坐席)开发完成,部署配置中**
|
||||
|
||||
### 1.3 核心设计理念
|
||||
|
||||
传统"串行排队"改为**"并行协作"**——AI 全程在线,人工随时介入:
|
||||
|
||||
| 角色 | 工作方式 |
|
||||
|------|---------|
|
||||
| AI | 全程在线,所有对话可见 |
|
||||
| 坐席 | 随时介入,AI 始终在旁辅助 |
|
||||
| 员工 | 同一窗口,AI 和人工无缝切换 |
|
||||
|
||||
---
|
||||
|
||||
## 二、系统架构
|
||||
|
||||
### 2.1 部署架构总览(预生产环境)
|
||||
|
||||
> **当前阶段**:预生产环境。智能咨询系统与 IT 数据查询平台**分别部署在不同主机**,通过 Nginx 路径路由共用域名 `it-dataquery.dc.servyou-it.com`。正式环境将迁移到 K8s 集群。
|
||||
|
||||
```
|
||||
浏览器 ──→ it-dataquery.dc.servyou-it.com:80
|
||||
│
|
||||
▼
|
||||
┌─── nginx (本系统主机) ───────────────┐
|
||||
│ │
|
||||
│ /itdesk/* → H5 员工端 SPA │
|
||||
│ /itagent/* → 坐席工作台 SPA │
|
||||
│ /api/* → backend:8000 (FastAPI) │
|
||||
│ /ws/* → backend:8000 (WS) │
|
||||
│ /* → 数据平台主机(远程IP) │ ← 跨主机代理
|
||||
│ │
|
||||
└──────────────┬───────────────────────┘
|
||||
│ 本机 Docker 网络
|
||||
┌─────────────┼─────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ backend │ │ postgres │ │ redis │
|
||||
│ :8000 │ │ :5432 │ │ :6379 │
|
||||
└──────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
| 对比项 | 预生产(当前) | 正式环境(未来) |
|
||||
|--------|-------------|---------------|
|
||||
| 部署方式 | Docker Compose(单主机) | K8s 集群(高可用) |
|
||||
| 与数据平台关系 | 不同主机,Nginx 远程代理 | 独立 K8s 集群 |
|
||||
| 域名 | 共用 `it-dataquery.dc.servyou-it.com` | 独立域名或 K8s Ingress |
|
||||
|
||||
### 2.2 技术栈
|
||||
|
||||
| 层级 | 技术选型 | 说明 |
|
||||
|------|---------|------|
|
||||
| 反向代理 | Nginx | 统一入口、路径路由、WebSocket 代理 |
|
||||
| 后端框架 | FastAPI (Python 3.12) | 异步、自动 OpenAPI 文档、类型安全 |
|
||||
| 数据库 | PostgreSQL 16 | 会话/消息/坐席/配置 持久化(9 张表) |
|
||||
| 缓存 | Redis 7 | access_token 缓存(TTL 7200s)、JWT 会话 |
|
||||
| ORM | SQLAlchemy 2.0 (async) | 异步 session、声明式模型 |
|
||||
| 数据库迁移 | Alembic | 所有表结构变更通过迁移脚本管理 |
|
||||
| 坐席前端 | Vue3 + ElementPlus + Pinia | 企业级组件库,三栏工作台 |
|
||||
| 员工 H5 | Vue3 + Vant4 + Pinia | 移动端组件库,企微 WebView 兼容 |
|
||||
| 容器化 | Docker + Docker Compose | 4 容器一键启停 |
|
||||
|
||||
### 2.3 数据库核心表(9 张)
|
||||
|
||||
| 表名 | 用途 | 关键字段 |
|
||||
|------|------|---------|
|
||||
| `conversations` | 会话主表 | employee_id, status, urgency_score(1-5), tags(JSON), is_vip, participants(JSON) |
|
||||
| `messages` | 消息记录 | sender_type(employee/agent/ai/system), content, msg_type |
|
||||
| `agents` | 坐席信息 | user_id, status(online/offline/busy), current_load |
|
||||
| `quick_reply_templates` | 快速回复模板 | category, title, content(支持 {变量}) |
|
||||
| `system_configs` | 系统配置 | config_key, config_value(关键词/阈值/话术等) |
|
||||
| `funny_phrases` | 趣味话术 | scene(6 种场景), content, tone, is_active |
|
||||
| `approval_links` | 审批流程链接 | category(IT/HR/行政/财务), title, url |
|
||||
| `software_downloads` | 软件下载入口 | category, name, version, platform, download_url |
|
||||
| `agent_notes` | 坐席备注 | conversation_id, agent_id, content |
|
||||
|
||||
### 2.4 API 接口分组
|
||||
|
||||
| 分组 | 路径前缀 | 核心接口 |
|
||||
|------|---------|---------|
|
||||
| 企微回调 | `/api/wecom/callback` | GET 验证 URL、POST 接收消息 |
|
||||
| 会话管理 | `/api/conversations` | 列表/详情/状态/置顶/代办/接单/邀请/退出/移除参与者 |
|
||||
| 消息管理 | `/api/conversations/{id}/messages` | 消息列表/发送 |
|
||||
| 坐席管理 | `/api/agents` | 列表/登录/状态切换 |
|
||||
| H5 用户端 | `/api/h5/*` | 会话/摇人/审批链接/软件下载/OAuth |
|
||||
| WebSocket | `/ws/{agent_id}` | 实时推送(坐席端) |
|
||||
|
||||
统一响应格式:`{ "code": 0, "data": {}, "message": "success" }`
|
||||
|
||||
### 2.5 消息收发全链路
|
||||
|
||||
```
|
||||
员工发消息 → 企微回调解密 → 消息路由 → 评分标记 → 入库 → 坐席 WS 推送 → 坐席回复 → 企微主动推送 → 员工同一窗口收到
|
||||
```
|
||||
|
||||
**坐席端通信**:已升级为 WebSocket 实时推送(2026-06-03),替代原计划的短轮询:
|
||||
- 心跳保活:前端每 30s 发 ping,后端回 pong
|
||||
- 断线重连:指数退避(1s→2s→4s→...→30s 上限)
|
||||
- 降级策略:WS 断连时自动降级为 3s 轮询
|
||||
|
||||
### 2.6 会话排序与评分规则
|
||||
|
||||
**排序**: 紧急 → 举手 → 需介入 → 活跃 → AI处理中 → 已结单(同级按时间倒序)
|
||||
|
||||
**紧急度评分**: `基础分(关键词) + 情绪加成 + VIP加成 + 重复追问加成`,范围 1-5
|
||||
|
||||
**标记系统**:
|
||||
|
||||
| 标记 | 图标 | 触发条件 |
|
||||
|------|------|---------|
|
||||
| VIP | 红色 | 企微通讯录规则匹配 |
|
||||
| 举手 | 黄色 | 员工说关键词或点击摇人按钮 |
|
||||
| 需介入 | 橙红 | 同一问题追问 >3 轮 |
|
||||
| 情绪 | 红色 | 关键词匹配(急/崩溃/投诉等) |
|
||||
|
||||
---
|
||||
|
||||
## 三、三步演进路径
|
||||
|
||||
| 里程碑 | 周期 | 核心交付 | 状态 |
|
||||
|--------|------|---------|------|
|
||||
| **M1** 消息接管 + 极简坐席 | 6-8 周 | 企微 API 链路验证 · 坐席三栏工作台 · 员工 H5 双栏 · 邀请功能(多人会话协作) | ✅ 代码完成,部署中 |
|
||||
| **M2** AI 机器人接入 | M1 后 4-6 周 | 千问/Dify/RAGFlow 接入 · AI 前置筛选 · 排队系统 | 📋 计划中 |
|
||||
| **M3** 知识库闭环迭代 | M2 后 4-6 周 | 坐席标注系统 · 千问自动分析 · 知识库自优化 | 📋 计划中 |
|
||||
|
||||
### M1 当前进度(2026-06-03)
|
||||
|
||||
| 模块 | 状态 |
|
||||
|------|------|
|
||||
| PRD + 架构设计 | ✅ 完成 |
|
||||
| 后端代码(45+ 文件,7 API 组) | ✅ 完成 |
|
||||
| 坐席前端(三栏工作台 + WebSocket) | ✅ 完成 |
|
||||
| 员工 H5(双栏 + 摇人按钮 + 呼叫坐席) | ✅ 完成 |
|
||||
| 邀请功能(多人会话协作 — PRD §21) | 📋 计划中(M1 范围) |
|
||||
| 前端功能联调验证 | ✅ 完成(2026-06-03) |
|
||||
| 测试用例(116 条 pytest) | ✅ 完成 |
|
||||
| Alembic 数据库迁移 | ✅ 完成 |
|
||||
| 前端构建产物(dist/) | ✅ 完成 |
|
||||
| 远程服务器部署 | 🔧 待 SSH 账号 |
|
||||
|
||||
### M2 核心改动
|
||||
|
||||
只改路由层逻辑,其余不动:
|
||||
```
|
||||
M1: 新会话 → 坐席队列
|
||||
M2: 新会话 → AI 先回答 → AI判断/用户触发 → 坐席队列
|
||||
```
|
||||
新增:千问对话模型、RAGFlow 知识库检索、Dify 编排平台、排队系统。目标:AI 首答率 ≥ 80%。
|
||||
|
||||
### M3 知识库迭代闭环
|
||||
|
||||
```
|
||||
坐席日常标注 ──→ 千问分析 ──→ 自动处理 ──→ 知识库增强
|
||||
(正确/错误) (缺文档/过时) │
|
||||
↑ │
|
||||
└──────────── 持续循环 ─────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、现有系统复用评估
|
||||
|
||||
### 4.1 核心复用(直接影响新系统架构)
|
||||
|
||||
| # | 资源 | 复用方式 | 新系统对应 |
|
||||
|---|------|---------|-----------|
|
||||
| 1 | Dify Workflow | 直接复用,M2 阶段接入 AI 回复 | 坐席助手 AI 面板 + 自动回复 |
|
||||
| 2 | dify2openai 桥接 | 直接复用 API | 后端调用 AI 的入口 |
|
||||
| 3 | RAGFlow 知识库 | 直接复用,M3 阶段混合标注迭代 | 知识库管理 + 标注闭环 |
|
||||
| 4 | Qwen3-30B 大模型 | 直接复用 | AI 对话底层模型 |
|
||||
| 5 | bge-m3 向量模型 | RAGFlow 内置,直接复用 | 知识库检索向量化 |
|
||||
| 6 | Dify 数据库(只读) | 读取 messages 表,同步历史数据 | 历史会话数据迁移 + 统计 |
|
||||
| 7 | 企微自建应用 | 直接复用应用凭证 | 消息收发的企微入口 |
|
||||
|
||||
### 4.2 基础设施复用(零耦合)
|
||||
|
||||
| 资源 | 复用方式 | 耦合度 |
|
||||
|------|---------|--------|
|
||||
| 企微自建应用凭证 | 配置文件引用(只读) | 零耦合 |
|
||||
| Dify Workflow API | HTTP 调用 | 外部依赖 |
|
||||
| RAGFlow 知识库 | HTTP 调用 | 外部依赖 |
|
||||
| Qwen3-30B 大模型 | HTTP 调用 | 外部依赖 |
|
||||
| SSL 证书文件 | Nginx 挂载只读 | 零耦合 |
|
||||
|
||||
### 4.3 关键结论
|
||||
|
||||
> 代码层面复用率约 15%(主要是业务逻辑和 SQL 查询),基础设施和 AI 能力复用率约 70%。
|
||||
> 新系统用 **FastAPI + SQLAlchemy 2.0**,不沿用旧 Django 代码。底层业务逻辑可参考移植。
|
||||
|
||||
---
|
||||
|
||||
## 五、正式环境部署方案
|
||||
|
||||
### 5.1 核心决策原则
|
||||
|
||||
基于四个约束条件:
|
||||
|
||||
| # | 约束 | 推导原则 |
|
||||
|---|------|---------|
|
||||
| 1 | 对现有正式环境架构影响最小 | **物理隔离 > 逻辑隔离** |
|
||||
| 2 | 避免变更影响现有服务 | **独立 Nginx 入口** |
|
||||
| 3 | 减少服务依赖 | **最小化外部依赖** |
|
||||
| 4 | 避免责任不清 | **独立数据库 + 独立 Redis** |
|
||||
|
||||
> **一句话**:新系统作为**独立服务单元**部署,与现有智能 IT 数据平台(Django)在物理资源层面完全解耦,仅通过 HTTP API 调用共享 AI 能力。
|
||||
|
||||
### 5.2 关键隔离策略
|
||||
|
||||
| 隔离层面 | 方案 | 效果 |
|
||||
|---------|------|------|
|
||||
| 服务器级 | 独立 VM,不共用宿主机 | 挂了不影响旧系统 |
|
||||
| 网络级 | Docker 内部网络,PG/Redis 不暴露宿主机端口 | 外部无法直连数据库 |
|
||||
| 存储级 | 独立命名卷,不共用 Volume | 数据完全隔离 |
|
||||
| 域名级 | 路径路由(共用域名) + 独立 Nginx 容器 | `/itdesk/`、`/itagent/`、`/api/` 归属本系统 |
|
||||
| 认证级 | JWT + 独立 Redis | 账户体系独立 |
|
||||
| 依赖级 | 仅 HTTP 调用外部 AI 服务 | 外部服务故障只影响 M2 功能 |
|
||||
|
||||
### 5.3 与现有系统的解耦修正
|
||||
|
||||
原复用评估中的部分共享方案已修正为独立部署:
|
||||
|
||||
| 原建议 | 修正方案 | 理由 |
|
||||
|--------|---------|------|
|
||||
| 同机部署于 10.80.0.86 | 独立服务器/VM(或同机端口分离+独立 compose) | 避免端口冲突、资源争抢 |
|
||||
| Redis 复用同实例 | 独立 Redis 容器 | FLUSHDB 误操作、内存 OOM 互相影响 |
|
||||
| 使用旧系统 Nginx | 独立 Nginx 容器 | 变更反代配置不影响旧系统路由 |
|
||||
| 复用旧 PG 实例 | 独立 PostgreSQL 容器 | 数据库是责任边界核心 |
|
||||
|
||||
### 5.4 Docker Compose 服务清单
|
||||
|
||||
| 服务 | 镜像 | 端口 | 健康检查 |
|
||||
|------|------|------|---------|
|
||||
| postgres | postgres:16 | 5432(内部) | pg_isready |
|
||||
| redis | redis:7 | 6379(内部) | redis-cli ping |
|
||||
| backend | 自构建 Dockerfile | 8000(内部) | GET /health |
|
||||
| nginx | nginx:alpine | 18080:80(对外) | GET /health |
|
||||
|
||||
Docker 网络:`it-desk-internal`(内部,连接 backend/postgres/redis)
|
||||
|
||||
### 5.5 资源需求
|
||||
|
||||
| 资源 | 配置 | 说明 |
|
||||
|------|------|------|
|
||||
| 服务器 | 4C8G + 100GB SSD(最低)/ 8C16G + 200GB SSD(推荐) | Docker Engine 环境 |
|
||||
| 域名 | `it-dataquery.dc.servyou-it.com`(已就绪,共用) | 路径路由 `/itdesk/` `/itagent/` `/api/` |
|
||||
| 企微自建应用 | 1 个(已创建) | CorpID/AgentID/Secret/Token/EncodingAESKey |
|
||||
| 防火墙 | 办公网→服务器:80/443, 企微→服务器:443 | 出站: 企微 API/ Dify/ RAGFlow/ Qwen |
|
||||
|
||||
### 5.6 风险矩阵
|
||||
|
||||
| 风险 | 概率 | 影响 | 缓解措施 |
|
||||
|------|------|------|---------|
|
||||
| 新服务器申请被拒/延迟 | 中 | 部署延期 | 退化方案:旧服务器端口分离+独立 compose |
|
||||
| SSL 证书到期 | 低 | HTTPS 不可用 | 复用现有通配符证书 |
|
||||
| 企微应用配置变更 | 低 | 双系统消息中断 | 建立变更通知机制 |
|
||||
| Dify/RAGFlow 不可用 | 中 | M2 AI 功能不可用 | 降级:纯坐席模式仍正常工作 |
|
||||
| Docker 宿主机故障 | 低 | 新系统全宕 | Compose 配置即代码,重建快 |
|
||||
|
||||
---
|
||||
|
||||
## 六、部署操作手册
|
||||
|
||||
> **预生产部署**:本系统与数据平台部署在**不同主机**,通过 Nginx 路径路由共用域名。数据平台请求通过远程 IP 反代(非 Docker 网络)。正式环境将迁移到 K8s。
|
||||
|
||||
### 6.1 前置条件
|
||||
|
||||
- 服务器已安装 Docker Engine 24+ + Docker Compose v2
|
||||
- IT 数据查询平台已部署运行
|
||||
- 有 SSH 登录权限
|
||||
|
||||
### 6.2 配置数据平台反代地址
|
||||
|
||||
预生产环境中,数据平台部署在**独立主机**。部署前需修改 `nginx/nginx.conf` 中的数据平台上游地址:
|
||||
|
||||
```nginx
|
||||
# nginx/nginx.conf — 将 DATAQUERY_HOST 替换为数据平台主机的实际 IP
|
||||
upstream dataquery {
|
||||
server 10.80.0.86:80; # ← 替换为数据平台实际 IP:端口
|
||||
}
|
||||
```
|
||||
|
||||
> **为什么不创建 Docker 共享网络?** 预生产两台主机不在同一 Docker Engine,无法使用 `docker network create` 互联。正式环境迁移 K8s 后由 Ingress/Service 处理路由。
|
||||
|
||||
### 6.3 上传部署包
|
||||
|
||||
在本地(Windows)执行打包上传:
|
||||
|
||||
```bash
|
||||
# 方式 A:使用 deploy.sh 打包
|
||||
bash scripts/deploy.sh --pack
|
||||
scp it-smart-desk-*.tar.gz user@server:/opt/
|
||||
|
||||
# 方式 B:手动打包
|
||||
tar czf deploy.tar.gz \
|
||||
backend/ frontend-h5/dist/ frontend-agent/dist/ \
|
||||
nginx/ docker-compose.yml .env.production scripts/
|
||||
scp deploy.tar.gz user@server:/opt/it-smart-desk/
|
||||
```
|
||||
|
||||
### 6.4 服务器配置与启动
|
||||
|
||||
```bash
|
||||
ssh user@server
|
||||
cd /opt/it-smart-desk
|
||||
tar xzf it-smart-desk-*.tar.gz
|
||||
|
||||
# 创建环境配置
|
||||
cp .env.production .env
|
||||
vim .env # 填入真实企微凭证
|
||||
```
|
||||
|
||||
`.env` 必填项:
|
||||
|
||||
| 配置项 | 说明 | 获取位置 |
|
||||
|--------|------|---------|
|
||||
| `WECOM_CORP_ID` | 企业 ID | 企微管理后台 > 我的企业 |
|
||||
| `WECOM_AGENT_ID` | 应用 AgentId | 企微管理后台 > 应用管理 |
|
||||
| `WECOM_SECRET` | 应用 Secret | 企微管理后台 > 应用管理 |
|
||||
| `WECOM_TOKEN` | 回调 Token | 企微管理后台 > 接收消息 |
|
||||
| `WECOM_ENCODING_AES_KEY` | 回调 AES 密钥 | 企微管理后台 > 接收消息 |
|
||||
| `POSTGRES_PASSWORD` | 数据库密码 | 自定义强密码 |
|
||||
| `CORS_ORIGINS` | `http://it-dataquery.dc.servyou-it.com` | CORS 白名单 |
|
||||
|
||||
启动:
|
||||
|
||||
```bash
|
||||
bash scripts/deploy.sh
|
||||
# 自动执行:检查前置条件 → 构建后端镜像 → 启动所有容器 → 运行数据库迁移
|
||||
```
|
||||
|
||||
### 6.5 验证部署
|
||||
|
||||
```bash
|
||||
# 检查容器状态
|
||||
docker compose ps
|
||||
# 预期:4 个容器全部 Up/healthy
|
||||
|
||||
# 健康检查
|
||||
curl http://localhost:18080/api/health
|
||||
|
||||
# 浏览器验证
|
||||
# http://it-dataquery.dc.servyou-it.com/itdesk/ → H5 员工咨询页面
|
||||
# http://it-dataquery.dc.servyou-it.com/itagent/ → 坐席工作台登录页
|
||||
# http://it-dataquery.dc.servyou-it.com/ → IT 数据查询平台(不变)
|
||||
# http://it-dataquery.dc.servyou-it.com/api/docs → FastAPI Swagger 文档
|
||||
```
|
||||
|
||||
### 6.6 常见问题
|
||||
|
||||
**nginx 启动失败,报 `host not found in upstream "dataquery"`**
|
||||
→ `nginx/nginx.conf` 中 `DATAQUERY_HOST` 未替换为数据平台的实际 IP。确保已在部署前完成替换。
|
||||
|
||||
**nginx 启动但数据平台页面 502**
|
||||
→ 本系统主机无法访问数据平台主机 IP。检查防火墙策略是否放行两台主机间的 80 端口。
|
||||
|
||||
**访问 `/itdesk/` 返回 404**
|
||||
→ 检查前端 dist 是否正确挂载:`docker exec wecom_it_nginx ls -la /usr/share/nginx/html/itdesk/`
|
||||
|
||||
**API 返回 CORS 错误**
|
||||
→ 检查 `.env` 中 `CORS_ORIGINS` 是否包含 `http://it-dataquery.dc.servyou-it.com`
|
||||
|
||||
**数据库迁移失败**
|
||||
→ PostgreSQL 可能未就绪,等 30 秒后执行:`docker compose restart backend`
|
||||
|
||||
### 6.7 更新部署
|
||||
|
||||
```bash
|
||||
# 仅更新前端
|
||||
bash scripts/deploy.sh --build
|
||||
docker compose restart nginx
|
||||
|
||||
# 仅更新后端
|
||||
docker compose build backend
|
||||
docker compose up -d backend
|
||||
|
||||
# 全量更新
|
||||
bash scripts/deploy.sh --down
|
||||
bash scripts/deploy.sh
|
||||
```
|
||||
|
||||
### 6.8 回滚
|
||||
|
||||
```bash
|
||||
docker compose down # 停止新系统所有容器
|
||||
# 旧系统不受任何影响(独立资源)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、运维管理
|
||||
|
||||
### 7.1 责任矩阵
|
||||
|
||||
| 运维操作 | 影响范围 | 备注 |
|
||||
|---------|---------|------|
|
||||
| 重启 PostgreSQL | 仅新系统 | 独立实例 |
|
||||
| 重启 Redis | 仅新系统 | 独立实例 |
|
||||
| 修改 Nginx 配置 | 仅新系统路由 | 独立容器 |
|
||||
| 更新后端/前端代码 | 仅新系统 | 独立容器 |
|
||||
| 企微应用配置变更 | **双系统** | ⚠️ 唯一共享点,需通知双方 |
|
||||
|
||||
### 7.2 监控指标
|
||||
|
||||
```yaml
|
||||
主机层面:
|
||||
- CPU 使用率 < 80%
|
||||
- 内存使用率 < 80%
|
||||
- 磁盘使用率 < 70%
|
||||
|
||||
容器层面:
|
||||
- docker compose ps 全部 "Up" 状态
|
||||
- Nginx 健康检查: GET /health → 200
|
||||
- Backend 健康检查: GET /health → 200
|
||||
|
||||
业务层面(后续接入):
|
||||
- 企微消息回调成功率 > 99%
|
||||
- API 响应时间 P95 < 500ms
|
||||
```
|
||||
|
||||
### 7.3 备份策略
|
||||
|
||||
| 备份对象 | 方法 | 频率 | 保留 |
|
||||
|---------|------|------|------|
|
||||
| PostgreSQL 数据 | `pg_dump` + 卷快照 | 每日凌晨 | 7 天 |
|
||||
| Redis 数据 | `SAVE` + 复制 dump.rdb | 每日凌晨 | 7 天 |
|
||||
| Docker 卷 | `tar czf` 归档 | 每周 | 4 周 |
|
||||
|
||||
### 7.4 关键对接参数(M2 阶段)
|
||||
|
||||
| 参数 | 值 | 用途 |
|
||||
|------|-----|------|
|
||||
| dify2openai API | `http://yw-dify.dc.servyou-it.com/dify2openai/v1/chat/completions` | AI 对话 |
|
||||
| RAGFlow | `http://10.80.0.85:8080` | 知识库管理 |
|
||||
| Qwen3-30B | `http://10.80.0.49:5000/api/llm/servyou/v1/chat/completions` | 大模型 |
|
||||
| Dify DB(生产只读) | `10.80.128.40:5432` DB=dify User=difyro | 历史数据同步 |
|
||||
| 数据平台 | `http://it-dataquery.dc.servyou-it.com` (10.80.0.86) | 部署服务器 |
|
||||
|
||||
### 7.5 应急预案可选技术项
|
||||
|
||||
> **评估日期**: 2026-06-03 | **来源**: 企微原生1对1方案(PRD §3.2 方式五)可行性评估
|
||||
|
||||
#### 7.5.1 备用方案概述
|
||||
|
||||
当 H5 WebView 方案(当前主方案)出现以下情况时,可切换至**企微原生1对1方案**作为降级/备用:
|
||||
|
||||
| 应急场景 | 当前方案症状 | 备用方案动作 |
|
||||
|---------|------------|------------|
|
||||
| H5 前端服务不可用 | Nginx 静态文件丢失/构建产物损坏 | 员工直接在企微与应用1对1聊天,走 `/message/send` 回复 |
|
||||
| H5 页面性能问题 | WebView 加载慢/白屏/兼容性问题 | 放弃 H5 入口,改用企微原生聊天窗口交互 |
|
||||
| OAuth2 鉴权异常 | 静默授权失败,H5 无法获取员工身份 | 原生方案无需 OAuth2,回调自带 UserID |
|
||||
| 跨平台接入需求 | 需接入钉钉/飞书/浏览器用户 | **不适合切换**——原生方案无法跨平台,此时应修复 H5 |
|
||||
| 外部专家协作 | 坐席需要拉入第三方专家协助 | 启用 `/appchat/create` 创建临时群聊 |
|
||||
|
||||
#### 7.5.2 备用方案技术架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ 员工端(企微原生1对1聊天窗口) │
|
||||
│ │
|
||||
│ 员工 ←─消息─→ 自建应用(IT智能助手) │
|
||||
│ │ │
|
||||
│ ├─ AI回复 → /message/send → 同一窗口 │
|
||||
│ ├─ 坐席回复 → /message/send → 同一窗口 │
|
||||
│ └─ 外援 → /appchat/create → 新群聊窗口 │
|
||||
│ │
|
||||
│ 坐席工作台(保留,不变) │
|
||||
│ ├─ WebSocket 接收员工消息 │
|
||||
│ ├─ 坐席回复 → 后端 → /message/send → 员工窗口 │
|
||||
│ └─ 外援指令 → 后端 → /appchat/create → 新群聊 │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**核心能力**:项目**已经具备**备用方案所需的全部后端代码:
|
||||
- `wecom_callback.py`:接收企微回调 ✅
|
||||
- `message_router._try_ai_reply()` → `wecom_service.send_text_message()`:AI回复走 `/message/send` ✅
|
||||
- `scoring_service.detect_hand_raise()`:关键词举手检测 ✅
|
||||
- `wecom_service.send_text_message()`:应用消息推送 ✅
|
||||
|
||||
**仅需新增**:
|
||||
- 交互卡片消息发送(`msgtype="template_card"`)— 用于"转人工"按钮、满意度评分
|
||||
- AppChat API 封装(`/cgi-bin/appchat/*`)— 用于外援群聊场景
|
||||
- AI/人工身份区分前缀(如 `🤖 AI回复:` / `👨💻 人工坐席(张三):`)
|
||||
|
||||
#### 7.5.3 切换流程
|
||||
|
||||
**从 H5 方案切换到原生1对1方案**:
|
||||
|
||||
| 步骤 | 操作 | 负责人 | 预计耗时 |
|
||||
|------|------|--------|---------|
|
||||
| 1 | 确认企微回调 URL 已配置且可达(H5 方案已配置则无需改动) | 运维 | 0 min |
|
||||
| 2 | 确认 `message_router._try_ai_reply()` 走 `/message/send`(已实现) | 开发 | 0 min |
|
||||
| 3 | 通知员工:直接在企微与应用聊天即可,不再进入 H5 | 运维 | 5 min |
|
||||
| 4 | (可选)关闭 H5 入口:Nginx 配置注释 `/itdesk/` 路由 | 运维 | 2 min |
|
||||
| 5 | (可选)启用交互卡片:部署 template_card 消息发送代码 | 开发 | 1-2 天 |
|
||||
|
||||
> **关键点**:步骤1-3 **零代码改动**即可完成基本切换,因为核心回调+消息推送链路已在运行。
|
||||
|
||||
**从原生1对1方案切回 H5 方案**:
|
||||
|
||||
| 步骤 | 操作 | 负责人 |
|
||||
|------|------|--------|
|
||||
| 1 | 恢复 Nginx `/itdesk/` 路由(如已注释) | 运维 |
|
||||
| 2 | 确认 H5 构建产物存在且可访问 | 运维 |
|
||||
| 3 | 通知员工:点击应用 → 进入 H5 咨询页面 | 运维 |
|
||||
|
||||
#### 7.5.4 企微 API 限制与容量评估
|
||||
|
||||
| API | 限制 | 当前业务量(月均 188 次 AI 会话/天) | 风险 |
|
||||
|-----|------|--------------------------------|------|
|
||||
| `/message/send` | ≤账号上限×200人次/天,同一人≤30次/分 | 预估 < 500 人次/天 | ✅ 充裕 |
|
||||
| `/appchat/create` | ≤1000群/天 | 外援场景低频(预估 < 10群/天) | ✅ 充裕 |
|
||||
| `/appchat/send` | ≤2万人次/分,同一人≤200条/分 | 群内消息量极小 | ✅ 充裕 |
|
||||
| 回调消息 | 无硬限制 | 企微服务器推送到回调 URL | ✅ 无风险 |
|
||||
|
||||
#### 7.5.5 备用方案局限性与适用边界
|
||||
|
||||
| 局限 | 说明 | 影响 |
|
||||
|------|------|------|
|
||||
| **无法跨主体企微** | 企微原生1对1仅限同一企微主体内员工 | 无法服务供应商/外包人员 |
|
||||
| **无法跨平台** | 原生方案绑定企微,无法嵌入钉钉/飞书/浏览器 | H5 扩展场景不可用 |
|
||||
| **AI/人工区分不直观** | 都以应用身份推送,需内容前缀区分 | 体验不如 H5 的丰富身份标识 |
|
||||
| **交互卡片需开发** | "转人工"按钮、满意度评分需 template_card 消息类型 | 降级期可用关键词替代("转人工") |
|
||||
| **群聊外援需审批** | appchat API 要求可见范围=根部门 | 需企微管理员配合 |
|
||||
|
||||
> **决策建议**:当 H5 不可用且影响范围仅限企微主体内员工时,**立即切换**原生1对1方案(零代码改动);当需要跨平台/跨主体服务时,**优先修复 H5**,不切换原生方案。
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
## 八、开发交付状态
|
||||
|
||||
### TL;DR
|
||||
|
||||
企微智能IT支持服务台第一步(消息接管 + 极简坐席台)全部代码已完成并通过测试,共 **110+ 文件**,**116/116 测试全部通过**,覆盖后端 API、坐席工作台、用户端 H5 三个子系统。
|
||||
|
||||
### 交付状态
|
||||
|
||||
| 阶段 | 状态 | 产出 |
|
||||
|------|------|------|
|
||||
| PRD | ✅ 完成 | `PRD.md` — 31 需求(P0/P1/P2),7 用户故事 |
|
||||
| 架构设计 | ✅ 完成 | `docs/03-技术架构/00-系统架构设计文档-v1.3.md` — 9 表 DDL,7 API 组,4 时序图,5 任务分解 |
|
||||
| T01 项目脚手架 | ✅ 完成 | 57 文件 — docker-compose, nginx, .env, 后端/前端骨架 |
|
||||
| T02 后端核心服务 | ✅ 完成 | 16 文件 — 企微加解密, 消息路由, 评分, 会话, 趣味话术, 7 API 路由 |
|
||||
| T03 坐席工作台 | ✅ 完成 | 25 文件 — 三栏布局, 会话管理, 聊天, AI助手面板(5Tab) |
|
||||
| T04 用户端H5 | ✅ 完成 | 12 文件 — 聊天面板, 摇人按钮, AI助手, 审批链接, 软件下载 |
|
||||
| QA 测试用例 | ✅ 完成 | 8 文件, 116 测试用例(原 93 + 新增 23) |
|
||||
| Bug 修复 | ✅ 完成 | 7 个 Bug 修复(详见下方) |
|
||||
| PostgreSQL/SQLite兼容 | ✅ 完成 | 9 个模型文件全部兼容 SQLite |
|
||||
| database.py 懒加载 | ✅ 完成 | 避免测试导入时连接 PostgreSQL |
|
||||
| WecomCrypto 懒加载 | ✅ 完成 | 避免默认 AES Key 导入报错 |
|
||||
| **pytest 全量验证** | **✅ 116/116 通过** | 1.71 秒完成,0 失败 |
|
||||
|
||||
### 关键文件
|
||||
|
||||
```
|
||||
wecom_it_smart_desk/
|
||||
├── README.md # 项目主文档(GitHub 首页)
|
||||
├── docker-compose.yml # Docker Compose 容器编排
|
||||
├── .env # 环境变量(数据库密码等,不提交 Git)
|
||||
├── backend/ # FastAPI 后端服务
|
||||
│ ├── app/
|
||||
│ │ ├── main.py # FastAPI 应用入口
|
||||
│ │ ├── config.py # 配置管理(从 .env 读取)
|
||||
│ │ ├── database.py # 懒加载数据库引擎
|
||||
│ │ ├── models/ # 11 个 ORM 模型(兼容 PostgreSQL/SQLite)
|
||||
│ │ ├── schemas/ # Pydantic Schema(请求/响应校验)
|
||||
│ │ ├── utils/
|
||||
│ │ │ └── wecom_crypto.py # 企微消息加解密(AES-CBC-256)
|
||||
│ │ ├── services/
|
||||
│ │ │ ├── wecom_service.py # 企微回调处理
|
||||
│ │ │ ├── message_router.py # 消息路由 + 评分 + 举手检测
|
||||
│ │ │ ├── scoring_service.py # 紧急度评分引擎
|
||||
│ │ │ ├── session_service.py # 会话生命周期管理
|
||||
│ │ │ └── funny_phrase_service.py # 摇人趣味话术生成
|
||||
│ │ └── api/ # 8 个 API 路由模块
|
||||
│ └── tests/ # 116+ 个测试用例
|
||||
├── frontend-agent/ # 坐席工作台(Vue 3 + Element Plus)
|
||||
│ └── src/
|
||||
│ ├── views/ # LoginView + WorkspaceView
|
||||
│ ├── components/
|
||||
│ │ ├── TopBar/ # 顶部栏(主题切换 + 用户信息)
|
||||
│ │ ├── conversation/ # 会话列表 + 会话条目
|
||||
│ │ ├── chat/ # 聊天区 + 消息气泡 + 输入框
|
||||
│ │ ├── assistant/ # AI 推荐内联组件
|
||||
│ │ ├── troubleshooting/ # 排查步骤栏(FlowchartNode)
|
||||
│ │ ├── quickreply/ # 快速回复面板(三层导航)
|
||||
│ │ └── todo/ # 待办面板 + 任务详情视图
|
||||
│ ├── stores/ # Pinia Store(conversation/agent/quickReply/theme/todo)
|
||||
│ └── api/ # API 调用模块
|
||||
├── frontend-h5/ # 员工端 H5(Vue 3 + Vant)
|
||||
│ └── src/
|
||||
│ ├── views/ # ChatView
|
||||
│ └── components/ # ChatPanel + 摇人按钮 + AI助手
|
||||
├── nginx/ # Nginx 反向代理配置
|
||||
│ └── nginx.conf
|
||||
├── scripts/ # 部署和运维脚本
|
||||
│ ├── start_backend.bat # Windows 快速启动后端(相对路径)
|
||||
│ └── restart_backend.ps1 # Windows 重启后端(自动查找 PG/Redis/Python)
|
||||
└── docs/ # 项目文档(全部文档统一存放)
|
||||
├── PRD.md # 产品需求文档 v1.0
|
||||
├── PRD-v53-incremental.md # v5.3 增量需求
|
||||
├── ARCHITECTURE.md # 系统架构设计(合并版)
|
||||
├── 01-项目总览与部署手册.md # 管理者视角部署手册
|
||||
├── 开发交付概览.md # 开发交付状态总览
|
||||
├── 智能IT支持服务台-项目迁移文档.md # 工作区迁移记录
|
||||
├── testing/ # 测试报告目录
|
||||
│ └── QA_COMPREHENSIVE_REPORT.md # 综合 QA 报告
|
||||
├── diagrams/ # Mermaid 图表
|
||||
│ ├── sequence-diagram.mermaid
|
||||
│ ├── sequence-shake.mermaid
|
||||
│ ├── sequence-scoring.mermaid
|
||||
│ ├── sequence-polling.mermaid
|
||||
│ └── class-diagram.mermaid
|
||||
└── prototypes/ # 原型文件
|
||||
├── agent-workspace-v5_3.html # 当前锁定版本(v5.3)
|
||||
├── qr_data_full.json # 快速回复数据(180条)
|
||||
└── archive/ # 历史原型归档
|
||||
```
|
||||
|
||||
### Bug 修复清单(7 个)
|
||||
|
||||
| # | 文件 | 问题 | 修复 |
|
||||
|---|------|------|------|
|
||||
| 1 | `message_router.py` | `calculate_urgency()` 是 async 但未 `await` | 添加 `await` |
|
||||
| 2 | `app/main.py` | 中文引号 `""` 嵌入 Python 双引号字符串,SyntaxError | 转义引号 |
|
||||
| 3 | `wecom_callback.py` | `WecomCrypto` 模块级初始化,默认 AES Key 不合法导致 `binascii.Error` | 改为懒加载单例 `_get_wecom_crypto()` |
|
||||
| 4 | `tests/conftest.py` | `aioredis.from_url` mock 路径错误 | 修正为 `redis.asyncio.from_url` |
|
||||
| 5 | `tests/conftest.py` | `create_test_conversation()` 缺少 `is_pinned`/`is_todo` 参数 | 添加可选参数 |
|
||||
| 6 | `session_service.py` | `conversation_id` UUID 对象 vs String(36) 列类型不匹配 | 先转字符串再查询 |
|
||||
| 7 | `scoring_service.py` | 关键词大小写不敏感缺失 + `_check_vip` 缺短路 | `.lower()` + 短路返回 |
|
||||
|
||||
### 用户下一步操作
|
||||
|
||||
1. **(已验证)pytest 全量通过**:116/116 测试已在开发环境验证通过,本地无需再跑
|
||||
|
||||
2. **配置企微应用凭证**:
|
||||
- 复制 `.env.example` 为 `.env`
|
||||
- 填入企微应用的 CorpID、AgentID、Secret、Token、EncodingAESKey
|
||||
|
||||
3. **Docker Compose 启动**(需 PostgreSQL + Redis):
|
||||
```powershell
|
||||
cd C:\Users\simon\wecom_it_smart_desk
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
4. **前端开发启动**:
|
||||
```powershell
|
||||
# 坐席工作台
|
||||
cd frontend-agent && npm install && npm run dev
|
||||
# 用户端 H5
|
||||
cd frontend-h5 && npm install && npm run dev
|
||||
```
|
||||
|
||||
5. **企微回调配置**:在企微管理后台配置消息回调 URL 指向你的服务器
|
||||
|
||||
|
||||
## 九、附录
|
||||
|
||||
### 8.1 需要团队协助的事项
|
||||
|
||||
| # | 事项 | 需要谁 | 紧急度 |
|
||||
|---|------|--------|--------|
|
||||
| 1 | **服务器 SSH 账号**:用于 Docker 部署 | 运维 | 🔴 高(当前阻塞) |
|
||||
| 2 | **企微通讯录权限**:确认 API 权限(VIP 功能依赖) | 运维/企微管理员 | 中(M1 可用 mock) |
|
||||
| 3 | **千问/Dify/RAGFlow 环境**(M2 阶段) | 架构/开发 | 低(M2 前准备) |
|
||||
|
||||
### 8.2 项目文件索引
|
||||
|
||||
```
|
||||
wecom_it_smart_desk/
|
||||
├── README.md # 入口索引
|
||||
├── PRD.md # 产品需求文档
|
||||
├── docs/
|
||||
│ ├── 01-项目总览与部署手册.md # ← 本文档(运维/架构/管理者)
|
||||
│ ├── 02-技术架构与开发指南.md # 开发者文档
|
||||
│ └── 03-测试验证文档.md # 测试文档
|
||||
├── backend/ # FastAPI 后端
|
||||
├── frontend-agent/ # 坐席工作台前端
|
||||
├── frontend-h5/ # 员工 H5 前端
|
||||
├── nginx/nginx.conf # Nginx 反代配置
|
||||
├── docker-compose.yml # Docker Compose 编排
|
||||
├── .env.production # 生产环境变量模板
|
||||
└── scripts/ # 部署/构建脚本
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> 本文档合并自原 `docs/团队沟通文档-架构消息知识库.md`、`docs/正式环境独立部署架构方案.md`、`docs/DEPLOY_NAS.md`。详细技术规格见 `docs/02-技术架构与开发指南.md`。
|
||||
@@ -0,0 +1,93 @@
|
||||
%% IT 智能服务台 — 架构组件图(卷挂载重构前后对比)
|
||||
%% 文件: class-diagram.mermaid
|
||||
|
||||
classDiagram
|
||||
class HostServer {
|
||||
+String path: /opt/wecom-it-desk/
|
||||
+String ip: 10.90.5.110
|
||||
+String domain: itsupport.servyou.com.cn
|
||||
}
|
||||
|
||||
class AppDirectory {
|
||||
+String path: /opt/wecom-it-desk/app/
|
||||
+String role: 唯一代码源
|
||||
+List~File~ pythonFiles
|
||||
+Boolean hasAuthPy
|
||||
}
|
||||
|
||||
class BackendDirectory {
|
||||
+String path: /opt/wecom-it-desk/backend/
|
||||
+File dockerfile
|
||||
+File requirementsTxt
|
||||
+String note: 不再包含 app/ 子目录
|
||||
}
|
||||
|
||||
class DockerImage {
|
||||
+String name: wecom-it-desk-backend:latest
|
||||
+String base: python:3.12-slim
|
||||
+String pythonVersion: 3.12
|
||||
+Boolean containsCode: false
|
||||
+Boolean containsDeps: true
|
||||
+String envPythondontWriteBytecode: "1"
|
||||
}
|
||||
|
||||
class DockerContainer {
|
||||
+String name: wecom_it_backend
|
||||
+String workdir: /app
|
||||
+String command: uvicorn app.main:app
|
||||
+Integer port: 8000
|
||||
+Boolean healthy
|
||||
}
|
||||
|
||||
class VolumeMount {
|
||||
+String hostPath: /opt/wecom-it-desk/app/
|
||||
+String containerPath: /app/app/
|
||||
+String type: bind
|
||||
+String mode: rw
|
||||
}
|
||||
|
||||
class UploadVolume {
|
||||
+String name: backend-uploads
|
||||
+String containerPath: /app/uploads/
|
||||
+String type: named_volume
|
||||
}
|
||||
|
||||
class LogMount {
|
||||
+String hostPath: /var/log/wecom-it-desk/
|
||||
+String containerPath: /app/logs/
|
||||
+String type: bind
|
||||
}
|
||||
|
||||
class DockerCompose {
|
||||
+String file: docker-compose.yml
|
||||
+String buildContext: ./backend
|
||||
+List~Service~ services
|
||||
}
|
||||
|
||||
class HealthCheck {
|
||||
+String endpoint: /health
|
||||
+Integer interval: 30
|
||||
+Integer timeout: 10
|
||||
+Integer retries: 3
|
||||
+Integer startPeriod: 40
|
||||
}
|
||||
|
||||
HostServer --> AppDirectory : contains
|
||||
HostServer --> BackendDirectory : contains
|
||||
HostServer --> DockerCompose : contains
|
||||
|
||||
DockerCompose --> DockerImage : builds
|
||||
DockerCompose --> DockerContainer : runs
|
||||
DockerCompose --> VolumeMount : configures
|
||||
DockerCompose --> UploadVolume : configures
|
||||
DockerCompose --> LogMount : configures
|
||||
|
||||
DockerImage --> DockerContainer : basis for
|
||||
VolumeMount --> DockerContainer : mounts code to /app/app/
|
||||
UploadVolume --> DockerContainer : mounts uploads to /app/uploads/
|
||||
LogMount --> DockerContainer : mounts logs to /app/logs/
|
||||
|
||||
AppDirectory --> VolumeMount : source of
|
||||
DockerContainer --> HealthCheck : monitored by
|
||||
|
||||
BackendDirectory --> DockerImage : provides Dockerfile + requirements.txt
|
||||
@@ -0,0 +1,52 @@
|
||||
%% IT 智能服务台 — 部署时序图(卷挂载重构)
|
||||
%% 文件: sequence-diagram.mermaid
|
||||
|
||||
sequenceDiagram
|
||||
participant Ops as 运维人员
|
||||
participant Host as 宿主机 10.90.5.110
|
||||
participant Docker as Docker Engine
|
||||
participant Container as Backend 容器
|
||||
|
||||
Note over Ops,Container: S1: 前置验证与备份
|
||||
Ops->>Host: curl /health 验证当前状态
|
||||
Host-->>Ops: 200 OK
|
||||
Ops->>Host: 备份 docker-compose.yml → .bak.{TIMESTAMP}
|
||||
Ops->>Host: 备份 Dockerfile → .bak.{TIMESTAMP}
|
||||
Ops->>Host: 写入 .rollback-info 文件
|
||||
|
||||
Note over Ops,Container: S2: 代码验证
|
||||
Ops->>Host: 检查 app/__init__.py, app/main.py
|
||||
Ops->>Host: 检查 app/api/auth.py(关键!)
|
||||
Host-->>Ops: 全部存在
|
||||
|
||||
Note over Ops,Container: S3: 修改 Dockerfile
|
||||
Ops->>Host: 重写 Dockerfile
|
||||
Note right of Host: 删除 COPY . .<br/>新增 ENV PYTHONDONTWRITEBYTECODE=1
|
||||
Ops->>Host: grep 验证 COPY . . 已删除
|
||||
|
||||
Note over Ops,Container: S4: 修改 docker-compose.yml
|
||||
Ops->>Host: sed 插入 ./app:/app/app 卷挂载
|
||||
Ops->>Host: grep 验证卷挂载已添加
|
||||
|
||||
Note over Ops,Container: S5: 重建镜像并重启
|
||||
Ops->>Docker: docker compose build backend
|
||||
Docker->>Docker: 构建镜像(仅 site-packages)
|
||||
Note right of Docker: 镜像不含业务代码
|
||||
Ops->>Docker: docker compose up -d backend
|
||||
Docker->>Container: 创建容器
|
||||
Docker->>Host: 挂载 ./app → /app/app (bind mount)
|
||||
Container->>Host: 运行时读取 /opt/wecom-it-desk/app/ 代码
|
||||
Container->>Container: uvicorn app.main:app 启动
|
||||
|
||||
Note over Ops,Container: S6: 部署后验证
|
||||
Ops->>Container: curl http://localhost:8000/health
|
||||
Container-->>Ops: 200 OK
|
||||
Ops->>Container: docker exec ... from app.auth import router
|
||||
Container-->>Ops: auth module: OK
|
||||
Ops->>Host: md5sum app/main.py
|
||||
Ops->>Container: docker exec md5sum /app/app/main.py
|
||||
Note right of Ops: 对比 hash 一致 → 卷挂载正常
|
||||
|
||||
Note over Ops,Container: S7: 清理(48小时后)
|
||||
Ops->>Host: rm -rf backend/app/
|
||||
Ops->>Host: 确认服务仍正常
|
||||
@@ -0,0 +1,76 @@
|
||||
# 部署运维工具箱
|
||||
|
||||
> **版本**: v1.0 | **日期**: 2026-07-10 | **维护人**: 宋献
|
||||
> **定位**: 部署运维过程中可复用的脚本、配置模板和调试工具的统一存放点。
|
||||
> **规则**: 每次故障排查或部署完成后,可复用的工具应归档到此目录并在本 README 中登记。
|
||||
|
||||
---
|
||||
|
||||
## 工具索引
|
||||
|
||||
### 上传部署工具
|
||||
|
||||
| 工具 | 用途 | 使用方式 |
|
||||
|------|------|----------|
|
||||
| `fast_upload.py` | 堡垒机大文件快速上传(8000-char base64 分块,比 jms_ops.py 的 500-char 快 16 倍) | `python fast_upload.py <本地文件> <远程路径>` |
|
||||
| `deploy_to_container.py` | 一键部署到 Docker 容器(打包→上传→cp→重启) | `python deploy_to_container.py <服务名> <本地路径> <容器路径>` |
|
||||
|
||||
### Nginx 配置模板
|
||||
|
||||
| 工具 | 用途 | 使用方式 |
|
||||
|------|------|----------|
|
||||
| `nginx-access-control.conf` | 三端访问控制配置模板(企微 UA OR IP 白名单双条件放行 + CSP 头) | 上传到服务器 `/opt/wecom-it-desk/nginx/nginx.conf` 后 `docker restart wecom_it_nginx` |
|
||||
|
||||
### 调试检查工具
|
||||
|
||||
| 工具 | 用途 | 使用方式 |
|
||||
|------|------|----------|
|
||||
| `check_html.py` | Python f-string HTML 花括号平衡检查器(排查 `{{ }}` 转义问题) | `python check_html.py <file.py>` |
|
||||
| `render_test.py` | 容器内 HTML 渲染验证(模拟 f-string 渲染并输出实际 HTML/JS) | `docker exec wecom_it_backend python /tmp/render_test.py` |
|
||||
| `extract_html.py` | 从 Python f-string 中提取 HTML 模板到独立文件 | `python extract_html.py <file.py>` |
|
||||
|
||||
### 历史修复脚本(archive/)
|
||||
|
||||
以下脚本为一次性修复用途,保留在 `archive/` 子目录中供参考,不建议直接复用。
|
||||
|
||||
| 脚本 | 修复场景 | 日期 |
|
||||
|------|----------|------|
|
||||
| `fix_admin_role.py` | 修复扫码登录角色写死为 agent 的问题 | 2026-07-08 |
|
||||
| `fix_compose_redis.py` / `fix_compose_redis2.py` | 修复 docker-compose Redis 密码 URL 编码 | 2026-07-07 |
|
||||
| `fix_itdesk.py` / `fix_itdesk2.py` / `fix_itdesk3.py` | 修复 nginx /itdesk/ 路由问题 | 2026-07-08 |
|
||||
| `patch.py` / `patch-mini.py` / `patch-redis-url.py` | Redis 密码 URL 编码补丁 | 2026-07-02 |
|
||||
| `update_password.py` | 数据库密码更新脚本 | 2026-07-05 |
|
||||
| `check_logs.py` / `check_roles.py` / `verify_logs.py` | 日志和角色检查(一次性诊断) | 2026-07-08~09 |
|
||||
| `upload_chunked.py` / `upload_split.py` | 早期分块上传方案(已被 fast_upload.py 替代) | 2026-07-08 |
|
||||
| `nginx_*.conf` / `itdesk-nginx-block.conf` | 历史 nginx 配置快照 | 2026-07-06~08 |
|
||||
| `extract_and_migrate.py` / `_ctrt_transform.py` | 数据迁移和格式转换 | 2026-07-06 |
|
||||
|
||||
---
|
||||
|
||||
## 工具沉淀流程
|
||||
|
||||
1. **排查完成** → 评估是否有可复用的脚本/配置模板
|
||||
2. **归档** → 复制到 `toolbox/`(活跃工具)或 `toolbox/archive/`(历史脚本)
|
||||
3. **登记** → 在本 README 的工具索引表中添加条目
|
||||
4. **清理** → 删除项目根目录的临时文件(渲染输出、中间产物等)
|
||||
|
||||
## 堡垒机使用说明
|
||||
|
||||
所有工具涉及服务器操作时,通过堡垒机(`sxn@10.212.189.210:2222`)执行:
|
||||
- 统一使用 `jms_ops.py`(jumpserver-ops 技能)或 `fast_upload.py`(大文件上传)
|
||||
- 详见 `docs/09-部署运维/11-堡垒机运维工具.md`
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
| 文档 | 位置 |
|
||||
|------|------|
|
||||
| 标准故障排查手册 | `docs/09-部署运维/00-标准故障排查手册.md` |
|
||||
| 堡垒机运维工具 | `docs/09-部署运维/11-堡垒机运维工具.md` |
|
||||
| 项目管理 SOP | `docs/10-项目管理/IT智能服务台-标准作业流程SOP.md` |
|
||||
| Nginx 基线配置 | `docs/09-部署运维/01-nginx-prod-baseline-20260708.conf` |
|
||||
|
||||
---
|
||||
|
||||
> **维护说明**: 新增工具时请同步更新本 README。archive/ 中的脚本仅供历史参考,不保证可用性。
|
||||
@@ -0,0 +1,7 @@
|
||||
fp='/opt/wecom-it-desk/nginx/nginx.conf'
|
||||
c=open(fp).read()
|
||||
p='\n # 真实 IP 还原(2026-06-15 v0.5.1)\n set_real_ip_from 10.0.0.0/8;\n set_real_ip_from 172.16.0.0/12;\n set_real_ip_from 192.168.0.0/16;\n set_real_ip_from 10.212.0.0/16;\n real_ip_header X-Forwarded-For;\n real_ip_recursive on;\n'
|
||||
o='error_log /var/log/nginx/error.log warn;'
|
||||
n=c.replace(o,o+p,1)
|
||||
open(fp,'w').write(n)
|
||||
print('patched, +%d bytes'%(len(n)-len(c)))
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
修复 docker-compose.yml 中 REDIS_URL 默认值的 URL-encode 问题。
|
||||
|
||||
背景:
|
||||
- 2026-06-15 故障:REDIS_URL 里的密码 R3d!s@2026#Secure 含 @ # 两个 URL 保留字符,
|
||||
Python redis 库解析时密码被截断成 R3d!s,导致鉴权失败 → Redis 连接超时
|
||||
- 修复:把密码 URL-encode(@→%40, #→%23, !→%21)
|
||||
- ⚠️ 关键: 只 URL-encode REDIS_URL 那行的密码,redis-server --requirepass
|
||||
和 healthcheck 的 redis-cli -a 都必须保持**明文**(否则 Redis 容器启动失败/鉴权失败)
|
||||
|
||||
用法:
|
||||
sudo python3 /tmp/patch-redis-url.py
|
||||
"""
|
||||
fp = '/opt/wecom-it-desk/docker-compose.yml'
|
||||
|
||||
# 读取当前内容
|
||||
with open(fp, encoding='utf-8') as f:
|
||||
c = f.read()
|
||||
|
||||
# 旧值(精确匹配 REDIS_URL 那一行,带 redis://://@ 上下文,避免误改 --requirepass)
|
||||
old = 'REDIS_URL=redis://:${REDIS_PASSWORD:-R3d!s@2026#Secure}@redis:6379/0'
|
||||
|
||||
# 新值:URL-encode 后的密码(!→%21, @→%40, #→%23),仅 REDIS_URL 这一行
|
||||
new = 'REDIS_URL=redis://:${REDIS_PASSWORD:-R3d%21s%402026%23Secure}@redis:6379/0'
|
||||
|
||||
# 检查是否已经修复过(幂等性)
|
||||
if old in c:
|
||||
print('[OK] 检测到未编码版本,准备修复...')
|
||||
c2 = c.replace(old, new, 1) # 只替换第一次出现(更安全)
|
||||
with open(fp, 'w', encoding='utf-8') as f:
|
||||
f.write(c2)
|
||||
delta = len(c2) - len(c)
|
||||
print('[OK] 已修复:REDIS_URL 行的密码已 URL-encode')
|
||||
print(f'[OK] 文件长度变化:{delta:+d} 字节')
|
||||
elif new in c:
|
||||
print('[OK] 已经修复过,跳过(幂等性 OK)')
|
||||
else:
|
||||
print('[ERROR] 既没找到旧值也没找到新值,请人工检查 docker-compose.yml')
|
||||
print('---')
|
||||
print('当前 REDIS_URL 相关配置:')
|
||||
import subprocess
|
||||
result = subprocess.run(['grep', '-n', 'REDIS_URL\\|REDIS_PASSWORD\\|--requirepass\\|redis-cli', fp],
|
||||
capture_output=True, text=True)
|
||||
print(result.stdout)
|
||||
exit(1)
|
||||
|
||||
# 验证:确保 --requirepass 和 redis-cli 仍然是明文(没被误改)
|
||||
import subprocess
|
||||
result = subprocess.run(['grep', '-nE', 'REDIS_URL|--requirepass|redis-cli.*-a', fp],
|
||||
capture_output=True, text=True)
|
||||
print('---')
|
||||
print('当前所有密码相关行(应只有 REDIS_URL 一行是 URL-encoded,其他保持明文):')
|
||||
print(result.stdout)
|
||||
@@ -0,0 +1,20 @@
|
||||
fp = '/opt/wecom-it-desk/nginx/nginx.conf'
|
||||
with open(fp) as f:
|
||||
c = f.read()
|
||||
patch = '''
|
||||
# ------------------------------------------------------------------
|
||||
# 真实 IP 还原(2026-06-15 v0.5.1 修复)
|
||||
# ------------------------------------------------------------------
|
||||
set_real_ip_from 10.0.0.0/8;
|
||||
set_real_ip_from 172.16.0.0/12;
|
||||
set_real_ip_from 192.168.0.0/16;
|
||||
set_real_ip_from 10.212.0.0/16;
|
||||
real_ip_header X-Forwarded-For;
|
||||
real_ip_recursive on;
|
||||
'''
|
||||
old = 'error_log /var/log/nginx/error.log warn;'
|
||||
new = old + patch
|
||||
new_c = c.replace(old, new, 1)
|
||||
with open(fp, 'w') as f:
|
||||
f.write(new_c)
|
||||
print('patched, +{} bytes'.format(len(new_c) - len(c)))
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import psycopg2
|
||||
|
||||
# 从环境变量获取 DATABASE_URL
|
||||
db_url = os.environ.get('DATABASE_URL')
|
||||
if not db_url:
|
||||
# 尝试从 docker-compose 生成的变量拼接
|
||||
db_url = "postgresql://wecom_user:wecom_pass@10.90.5.110:5432/wecom_it_desk"
|
||||
|
||||
conn = psycopg2.connect(db_url)
|
||||
cur = conn.cursor()
|
||||
|
||||
# A表
|
||||
cur.execute("SELECT COUNT(*) FROM config_change_logs")
|
||||
a_count = cur.fetchone()[0]
|
||||
|
||||
# B表 audit_logs 中 action='config_change' 的数量
|
||||
cur.execute("SELECT COUNT(*) FROM audit_logs WHERE action = 'config_change'")
|
||||
b_config_change_count = cur.fetchone()[0]
|
||||
|
||||
# B表总数
|
||||
cur.execute("SELECT COUNT(*) FROM audit_logs")
|
||||
b_total = cur.fetchone()[0]
|
||||
|
||||
print(f"config_change_logs (A): {a_count}")
|
||||
print(f"audit_logs (B) - config_change events: {b_config_change_count}")
|
||||
print(f"audit_logs (B) - total: {b_total}")
|
||||
|
||||
conn.close()
|
||||
@@ -0,0 +1,19 @@
|
||||
import re
|
||||
|
||||
with open(
|
||||
r'D:\资料\03-项目开发\wecom_it_smart_desk\backend\app\api\auth_qrcode.py',
|
||||
'r',
|
||||
encoding='utf-8',
|
||||
) as f:
|
||||
content = f.read()
|
||||
|
||||
match = re.search(r'html = f"""(.+?)"""', content, re.DOTALL)
|
||||
if match:
|
||||
html = match.group(1)
|
||||
print('HTML length:', len(html))
|
||||
out_path = r'D:\资料\03-项目开发\wecom_it_smart_desk\tmp_scan_html.html'
|
||||
with open(out_path, 'w', encoding='utf-8') as out:
|
||||
out.write(html)
|
||||
print('Saved to', out_path)
|
||||
else:
|
||||
print('HTML template not found')
|
||||
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
快速 base64 上传脚本 — 大分块版本
|
||||
将本地文件通过 JumpServer PTY base64 通道上传到远程服务器
|
||||
使用 8000 字符/块(远大于 jms_ops.py 的 500 字符),大幅减少命令数
|
||||
"""
|
||||
import sys
|
||||
import base64
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
|
||||
# 添加 jms_ops.py 所在目录
|
||||
SKILL_DIR = Path(r"C:\Users\simon\.workbuddy\skills\jumpserver-ops\scripts")
|
||||
sys.path.insert(0, str(SKILL_DIR))
|
||||
|
||||
# 导入 jms_ops 中的核心函数
|
||||
from jms_ops import get_connection_tokens, PlinkSession
|
||||
|
||||
|
||||
def fast_upload(local_path: str, remote_path: str, chunk_size: int = 8000):
|
||||
"""大分块 base64 上传"""
|
||||
local_file = Path(local_path)
|
||||
if not local_file.exists():
|
||||
print(f"ERROR: file not found: {local_path}")
|
||||
return False
|
||||
|
||||
local_data = local_file.read_bytes()
|
||||
local_md5 = hashlib.md5(local_data).hexdigest()
|
||||
b64_data = base64.b64encode(local_data).decode("ascii")
|
||||
|
||||
# 分块
|
||||
chunks = [b64_data[i:i+chunk_size] for i in range(0, len(b64_data), chunk_size)]
|
||||
total_chunks = len(chunks)
|
||||
|
||||
print(f"File: {local_file.name}")
|
||||
print(f"Size: {len(local_data)} bytes")
|
||||
print(f"Base64: {len(b64_data)} chars")
|
||||
print(f"Chunks: {total_chunks} x {chunk_size} chars")
|
||||
print(f"MD5: {local_md5}")
|
||||
print(f"Target: {remote_path}")
|
||||
print()
|
||||
|
||||
# 获取 token + 启动会话
|
||||
tokens = get_connection_tokens(1)
|
||||
if not tokens:
|
||||
print("ERROR: failed to get connection tokens")
|
||||
return False
|
||||
|
||||
token_id, token_secret = tokens[0]
|
||||
session = PlinkSession(f"JMS-{token_id}", token_secret)
|
||||
if not session.connect():
|
||||
print("ERROR: failed to connect session")
|
||||
return False
|
||||
|
||||
try:
|
||||
# 1. 清空目标文件
|
||||
print("Clearing target file...")
|
||||
session.run_command(f"> {remote_path}", timeout=5)
|
||||
|
||||
# 2. 逐块追加
|
||||
for i, chunk in enumerate(chunks):
|
||||
# 用 printf 避免 echo 的换行符问题
|
||||
cmd = f"printf '%s' '{chunk}' >> {remote_path}.b64"
|
||||
r = session.run_command(cmd, timeout=10)
|
||||
if not r["success"]:
|
||||
print(f" FAIL chunk {i+1}/{total_chunks}")
|
||||
return False
|
||||
|
||||
# 进度报告
|
||||
if (i+1) % 20 == 0 or (i+1) == total_chunks:
|
||||
pct = (i+1) * 100 // total_chunks
|
||||
print(f" [{pct:3d}%] chunk {i+1}/{total_chunks}")
|
||||
|
||||
# 3. base64 解码
|
||||
print(f"\nDecoding base64 -> {remote_path}...")
|
||||
r = session.run_command(f"base64 -d {remote_path}.b64 > {remote_path}", timeout=30)
|
||||
if not r["success"]:
|
||||
print(f" WARN decode result: {r}")
|
||||
|
||||
# 4. 验证大小
|
||||
print("Verifying size...")
|
||||
r = session.run_command(f"wc -c < {remote_path}", timeout=5)
|
||||
if r["success"]:
|
||||
remote_size_str = r["output"].strip()
|
||||
remote_size = int(remote_size_str) if remote_size_str.isdigit() else -1
|
||||
if remote_size == len(local_data):
|
||||
print(f" OK size match: {remote_size} bytes")
|
||||
else:
|
||||
print(f" SIZE MISMATCH: local={len(local_data)}, remote={remote_size}")
|
||||
return False
|
||||
else:
|
||||
print(f" WARN cannot verify size: {r}")
|
||||
return True # 仍然认为成功
|
||||
|
||||
# 5. MD5 验证
|
||||
print("Verifying MD5...")
|
||||
r = session.run_command(f"md5sum {remote_path}", timeout=10)
|
||||
if r["success"]:
|
||||
remote_md5 = r["output"].split()[0]
|
||||
if remote_md5 == local_md5:
|
||||
print(f" OK MD5 match: {remote_md5}")
|
||||
else:
|
||||
print(f" MD5 MISMATCH: local={local_md5}, remote={remote_md5}")
|
||||
# 大小匹配但 MD5 不匹配,可能是 PTY 换行符问题
|
||||
print(" (size matches, trying gzip test instead)")
|
||||
r2 = session.run_command(f"gzip -t {remote_path} 2>&1 && echo GZIP_OK || echo GZIP_FAIL", timeout=10)
|
||||
if r2["success"] and "GZIP_OK" in r2["output"]:
|
||||
print(" OK gzip integrity test passed")
|
||||
# 清理临时文件
|
||||
session.run_command(f"rm -f {remote_path}.b64", timeout=5)
|
||||
return True
|
||||
else:
|
||||
print(f" GZIP FAIL: {r2}")
|
||||
return False
|
||||
else:
|
||||
print(f" WARN cannot verify MD5")
|
||||
|
||||
# 6. 清理临时文件
|
||||
session.run_command(f"rm -f {remote_path}.b64", timeout=5)
|
||||
print("\nUpload complete!")
|
||||
return True
|
||||
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description="Fast base64 upload via JumpServer PTY")
|
||||
parser.add_argument("local", help="Local file path")
|
||||
parser.add_argument("remote", help="Remote file path")
|
||||
parser.add_argument("--chunk-size", type=int, default=8000, help="Chunk size in chars (default: 8000)")
|
||||
args = parser.parse_args()
|
||||
|
||||
success = fast_upload(args.local, args.remote, args.chunk_size)
|
||||
sys.exit(0 if success else 1)
|
||||
@@ -0,0 +1,181 @@
|
||||
"""在容器内运行,渲染 scan 端点的实际 HTML 输出"""
|
||||
import sys
|
||||
sys.path.insert(0, '/app')
|
||||
|
||||
# 模拟 f-string 中的变量
|
||||
user_name = "测试用户"
|
||||
jsapi_signature = "test_sig_abc123"
|
||||
jsapi_timestamp = 1752105600
|
||||
jsapi_nonce = "test_nonce_xyz"
|
||||
jsapi_appid = "ww_test_corp_id"
|
||||
current_url = "https://itsupport.servyou.com.cn/api/auth_qrcode/scan?code=test&state=test"
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>登录成功 - IT智能服务台</title>
|
||||
<style>
|
||||
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
||||
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: linear-gradient(135deg, #07C160 0%, #06AD56 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; }}
|
||||
.card {{ background: rgba(255,255,255,0.95); border-radius: 20px; padding: 48px 32px; max-width: 360px; width: 100%; text-align: center; box-shadow: 0 20px 60px rgba(0,0,0,0.3); }}
|
||||
.check {{ width: 64px; height: 64px; margin: 0 auto 16px; }}
|
||||
.title {{ color: #1f2937; font-size: 24px; font-weight: 600; margin-bottom: 8px; }}
|
||||
.subtitle {{ color: #6b7280; font-size: 14px; margin-bottom: 24px; }}
|
||||
.status {{ display: inline-flex; align-items: center; gap: 6px; background: #dcfce7; color: #166534; padding: 10px 20px; border-radius: 50px; font-size: 14px; font-weight: 500; }}
|
||||
.footer {{ margin-top: 20px; color: #9ca3af; font-size: 12px; }}
|
||||
.back-btn {{ display: none; margin-top: 20px; padding: 12px 32px; background: #07C160; color: white; border: none; border-radius: 50px; font-size: 16px; font-weight: 500; cursor: pointer; }}
|
||||
.debug {{ margin-top: 16px; color: #6b7280; font-size: 11px; line-height: 1.6; word-break: break-all; text-align: left; background: #f3f4f6; padding: 10px 12px; border-radius: 8px; }}
|
||||
.debug b {{ color: #07C160; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<svg class="check" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="32" cy="32" r="30" fill="#07C160" stroke="#06AD56" stroke-width="4"/>
|
||||
<path d="M20 32l8 8 16-16" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<h1 class="title">登录成功</h1>
|
||||
<div class="status">已自动确认登录</div>
|
||||
<p class="subtitle">你好,{user_name}<br>请返回电脑端查看</p>
|
||||
<button class="back-btn" id="backBtn" onclick="manualClose()">点击返回企微</button>
|
||||
<div class="footer">页面即将自动关闭 · 税友集团</div>
|
||||
<div class="debug" id="debugInfo">
|
||||
<b>签名:</b> {'成功' if jsapi_signature else '未生成'}<br>
|
||||
<b>URL:</b> {current_url}<br>
|
||||
<b>状态:</b> <span id="jsStatus">JS加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function() {{
|
||||
var statusEl = document.getElementById('jsStatus');
|
||||
var btnEl = document.getElementById('backBtn');
|
||||
var startTime = Date.now();
|
||||
var tried = {{}};
|
||||
|
||||
function setStatus(msg) {{
|
||||
if (statusEl) statusEl.textContent = msg + ' (' + (Date.now() - startTime) + 'ms)';
|
||||
}}
|
||||
|
||||
function showBtn() {{
|
||||
if (btnEl) btnEl.style.display = 'inline-block';
|
||||
}}
|
||||
|
||||
function tryClose(forceShowBtn) {{
|
||||
setStatus('尝试关闭');
|
||||
|
||||
if (!tried.wxClose && typeof wx !== 'undefined' && wx.closeWindow) {{
|
||||
tried.wxClose = true;
|
||||
try {{
|
||||
setStatus('wx.closeWindow');
|
||||
wx.closeWindow();
|
||||
return true;
|
||||
}} catch(e) {{ setStatus('wx.closeWindow失败:' + (e.message || e)); }}
|
||||
}}
|
||||
|
||||
if (!tried.wxInvoke && typeof wx !== 'undefined' && wx.invoke) {{
|
||||
tried.wxInvoke = true;
|
||||
try {{
|
||||
setStatus('wx.invoke closeWindow');
|
||||
wx.invoke('closeWindow', {{}}, function(){{}});
|
||||
return true;
|
||||
}} catch(e) {{ setStatus('wx.invoke失败:' + (e.message || e)); }}
|
||||
}}
|
||||
|
||||
if (!tried.jsBridge && typeof WeixinJSBridge !== 'undefined' && WeixinJSBridge.call) {{
|
||||
tried.jsBridge = true;
|
||||
try {{
|
||||
setStatus('WeixinJSBridge.closeWindow');
|
||||
WeixinJSBridge.call('closeWindow');
|
||||
return true;
|
||||
}} catch(e) {{ setStatus('JSBridge失败:' + (e.message || e)); }}
|
||||
}}
|
||||
|
||||
if (!tried.windowClose) {{
|
||||
tried.windowClose = true;
|
||||
try {{
|
||||
setStatus('window.close');
|
||||
window.close();
|
||||
return true;
|
||||
}} catch(e) {{}}
|
||||
}}
|
||||
|
||||
if (!tried.historyBack) {{
|
||||
tried.historyBack = true;
|
||||
try {{
|
||||
setStatus('history.back');
|
||||
history.back();
|
||||
return true;
|
||||
}} catch(e) {{}}
|
||||
}}
|
||||
|
||||
if (forceShowBtn) {{
|
||||
setStatus('无法自动关闭,请手动返回');
|
||||
showBtn();
|
||||
}}
|
||||
return false;
|
||||
}}
|
||||
|
||||
function manualClose() {{
|
||||
tryClose(true);
|
||||
}}
|
||||
|
||||
var checkCount = 0;
|
||||
var maxChecks = 50;
|
||||
var interval = setInterval(function() {{
|
||||
checkCount++;
|
||||
var hasWx = typeof wx !== 'undefined';
|
||||
var hasBridge = typeof WeixinJSBridge !== 'undefined';
|
||||
setStatus('检测中 wx=' + hasWx + ' bridge=' + hasBridge + ' count=' + checkCount);
|
||||
|
||||
if (hasWx || hasBridge) {{
|
||||
clearInterval(interval);
|
||||
setStatus('已检测到关闭API,1秒后尝试关闭');
|
||||
setTimeout(function() {{
|
||||
tryClose(true);
|
||||
}}, 1000);
|
||||
return;
|
||||
}}
|
||||
|
||||
if (checkCount >= maxChecks) {{
|
||||
clearInterval(interval);
|
||||
setStatus('未检测到API,直接尝试关闭');
|
||||
tryClose(true);
|
||||
}}
|
||||
}}, 100);
|
||||
|
||||
setTimeout(function() {{
|
||||
showBtn();
|
||||
}}, 3000);
|
||||
}})();
|
||||
</script>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
# 保存渲染后的 HTML
|
||||
with open('/tmp/test_scan.html', 'w', encoding='utf-8') as f:
|
||||
f.write(html)
|
||||
|
||||
print(f"HTML rendered: {len(html)} chars")
|
||||
print("Saved to /tmp/test_scan.html")
|
||||
|
||||
# 检查 script 部分
|
||||
import re
|
||||
script_match = re.search(r'<script>(.+?)</script>', html, re.DOTALL)
|
||||
if script_match:
|
||||
js = script_match.group(1)
|
||||
print(f"\nJS section: {len(js)} chars")
|
||||
# 检查是否有 {{ 残留(f-string 未正确渲染)
|
||||
if '{{' in js:
|
||||
print("ERROR: Found unrendered {{ in JS!")
|
||||
for i, line in enumerate(js.split('\n')):
|
||||
if '{{' in line:
|
||||
print(f" Line {i+1}: {line.strip()[:80]}")
|
||||
else:
|
||||
print("OK: No unrendered braces in JS")
|
||||
|
||||
# 打印前 20 行 JS
|
||||
print("\nFirst 20 lines of rendered JS:")
|
||||
for i, line in enumerate(js.split('\n')[:20]):
|
||||
print(f" {i+1}: {line}")
|
||||
@@ -0,0 +1,208 @@
|
||||
# 会议室预定-小鱼易联终端 部署指南
|
||||
|
||||
> **日期**: 2026-07-11
|
||||
> **版本**: v1.0
|
||||
> **代码状态**: 40/40 测试通过,待部署
|
||||
> **预估部署时间**: 30-45 分钟
|
||||
|
||||
---
|
||||
|
||||
## 一、部署前置条件
|
||||
|
||||
### 1.1 企微配置
|
||||
- [x] 企微会议室 Secret 已申请
|
||||
- [x] 企微会议室已在管理后台创建
|
||||
- [ ] 确认会议室 `meetingroom_id` 列表
|
||||
|
||||
### 1.2 服务器环境
|
||||
- [ ] PostgreSQL 可用(需执行 Alembic 迁移)
|
||||
- [ ] Redis 可用(会议室缓存依赖)
|
||||
- [ ] Nginx 可用(终端前端静态文件)
|
||||
- [ ] Docker Compose 可用
|
||||
|
||||
### 1.3 终端设备
|
||||
- [ ] 确认小鱼易联终端型号(当前按浏览器方案设计)
|
||||
- [ ] 终端浏览器支持 WebSocket + ES6
|
||||
|
||||
---
|
||||
|
||||
## 二、部署步骤
|
||||
|
||||
### Step 1: 数据库迁移
|
||||
|
||||
```bash
|
||||
# 进入后端容器
|
||||
docker compose exec backend alembic upgrade head
|
||||
|
||||
# 验证新表
|
||||
docker compose exec backend python -c "
|
||||
from app.database import engine
|
||||
from sqlalchemy import inspect
|
||||
insp = inspect(engine)
|
||||
tables = insp.get_table_names()
|
||||
print('meetingroom_booking_snapshots' in tables) # 应为 True
|
||||
print('terminal_room_bindings' in tables) # 应为 True
|
||||
"
|
||||
```
|
||||
|
||||
### Step 2: 环境变量配置
|
||||
|
||||
在 `docker-compose.yml` 中添加:
|
||||
```yaml
|
||||
backend:
|
||||
environment:
|
||||
- WECOM_MEETINGROOM_SECRET=<your_secret>
|
||||
- MEETINGROOM_CACHE_TTL_ROOMS=600
|
||||
- MEETINGROOM_CACHE_TTL_BOOKING=30
|
||||
- MEETINGROOM_CACHE_TTL_STATUS=10
|
||||
```
|
||||
|
||||
重启后端:
|
||||
```bash
|
||||
docker compose up -d backend
|
||||
```
|
||||
|
||||
### Step 3: 后端验证
|
||||
|
||||
```bash
|
||||
# 验证 API 端点注册
|
||||
curl -sk https://localhost/api/itportal/meetingroom/rooms \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
|
||||
# 验证 WS 端点
|
||||
wscat -c "wss://localhost/ws/terminal/TEST-SN-001"
|
||||
|
||||
# 验证管理端绑定 API
|
||||
curl -sk https://localhost/api/itportal/admin/terminal-bindings \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN"
|
||||
```
|
||||
|
||||
### Step 4: 终端前端部署
|
||||
|
||||
```bash
|
||||
# 1. 本地构建
|
||||
cd frontend-terminal
|
||||
npm run build
|
||||
|
||||
# 2. 打包
|
||||
tar -czf terminal-dist.tar.gz dist/
|
||||
|
||||
# 3. 上传到服务器(通过堡垒机)
|
||||
python C:\Users\simon\.workbuddy\skills\jumpserver-ops\scripts\jms_ops.py \
|
||||
pack-upload ./terminal-dist.tar.gz /opt/wecom-it-desk/frontend-terminal/
|
||||
|
||||
# 4. 服务器解压
|
||||
cd /opt/wecom-it-desk/frontend-terminal/
|
||||
tar -xzf /tmp/terminal-dist.tar.gz
|
||||
|
||||
# 5. Nginx 配置
|
||||
# 在 nginx.conf 中添加:
|
||||
location /itterminal/ {
|
||||
alias /usr/share/nginx/html/terminal/;
|
||||
try_files $uri $uri/ /itterminal/index.html;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Nginx 重启
|
||||
|
||||
```bash
|
||||
docker compose restart nginx
|
||||
```
|
||||
|
||||
### Step 6: H5 端入口验证
|
||||
|
||||
H5 端已内置会议室入口(`MeetingroomView.vue`),无需额外部署。
|
||||
验证:
|
||||
```bash
|
||||
curl -sk https://localhost/ith5/ | grep -o 'meetingroom'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、部署后验证
|
||||
|
||||
### 3.1 功能验证清单
|
||||
|
||||
| # | 验证项 | 验证方法 | 预期结果 |
|
||||
|---|--------|---------|---------|
|
||||
| 1 | 会议室列表 | `GET /api/itportal/meetingroom/rooms` | 返回会议室列表 |
|
||||
| 2 | 会议室状态 | `GET /api/itportal/meetingroom/rooms/{id}/status` | 返回当前状态 |
|
||||
| 3 | 预定会议室 | `POST /api/itportal/meetingroom/bookings` | 创建预定成功 |
|
||||
| 4 | 时间线 | `GET /api/itportal/meetingroom/rooms/{id}/timeline` | 返回当日时间线 |
|
||||
| 5 | 终端绑定 | `POST /api/itportal/admin/terminal-bindings` | 绑定终端-会议室 |
|
||||
| 6 | 终端WS | `WS /ws/terminal/{sn}` | 终端状态实时推送 |
|
||||
| 7 | 终端前端 | 浏览器打开 `/itterminal/` | 深色主题大屏页面 |
|
||||
| 8 | H5入口 | H5 端点击会议室入口 | 跳转 MeetingroomView |
|
||||
| 9 | 扫码登录 | 终端扫码 | 企微扫码登录成功 |
|
||||
| 10 | 状态同步 | 终端修改状态 → H5 实时更新 | WS 推送正常 |
|
||||
|
||||
### 3.2 Redis 缓存验证
|
||||
|
||||
```bash
|
||||
# 会议室 token
|
||||
redis-cli -a $REDIS_PASSWORD get wecom:meetingroom_access_token
|
||||
|
||||
# 会议室列表缓存
|
||||
redis-cli -a $REDIS_PASSWORD get meetingroom:room_list
|
||||
|
||||
# 预定缓存
|
||||
redis-cli -a $REDIS_PASSWORD get "meetingroom:booking:{room_id}:{date}"
|
||||
|
||||
# 状态缓存
|
||||
redis-cli -a $REDIS_PASSWORD get "meetingroom:status:{room_id}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、回滚方案
|
||||
|
||||
### 4.1 数据库回滚
|
||||
```bash
|
||||
# 回退迁移 050
|
||||
docker compose exec backend alembic downgrade -1
|
||||
```
|
||||
|
||||
### 4.2 后端回滚
|
||||
```bash
|
||||
# 恢复 .py 文件(bind mount 自动生效)
|
||||
git checkout HEAD~1 -- backend/app/api/meetingroom.py
|
||||
git checkout HEAD~1 -- backend/app/services/meetingroom_service.py
|
||||
# ... 其他文件
|
||||
docker compose restart backend
|
||||
```
|
||||
|
||||
### 4.3 前端回滚
|
||||
```bash
|
||||
# 恢复旧 dist
|
||||
mv /opt/wecom-it-desk/frontend-terminal/dist /opt/wecom-it-desk/frontend-terminal/dist.bak
|
||||
# 恢复上一版本
|
||||
docker compose restart nginx
|
||||
```
|
||||
|
||||
### 4.4 Nginx 配置回滚
|
||||
```bash
|
||||
# 移除 /itterminal/ location 块
|
||||
docker compose restart nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、已知限制
|
||||
|
||||
1. **企微API时间限制**: 会议室预定查询范围限制 31 天
|
||||
2. **终端身份**: 当前按访客可查看设计,管理操作需扫码登录
|
||||
3. **WS重连**: 终端断线后自动重连(指数退避),超过 5 次降级为轮询(10s间隔)
|
||||
4. **缓存TTL**: 会议室列表 600s / 预定 30s / 状态 10s,手动刷新可跳过缓存
|
||||
|
||||
---
|
||||
|
||||
## 六、配置参数速查
|
||||
|
||||
| 参数 | 默认值 | 环境变量 |
|
||||
|------|--------|---------|
|
||||
| 会议室Token TTL | 6900s | - |
|
||||
| 会议室列表缓存 | 600s | `MEETINGROOM_CACHE_TTL_ROOMS` |
|
||||
| 预定缓存 | 30s | `MEETINGROOM_CACHE_TTL_BOOKING` |
|
||||
| 状态缓存 | 10s | `MEETINGROOM_CACHE_TTL_STATUS` |
|
||||
| WS重连次数 | 5 | - |
|
||||
| WS降级轮询间隔 | 10s | - |
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user