chore: 整理项目结构,清理归档文件,更新部署配置
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
# Release Notes v0.7.1
|
||||
|
||||
> 📅 发布日期:2026-06-23 | 类型:🔐 安全 + ✨ 功能 | 紧急度:🟡 重要
|
||||
> 🔄 补充:2026-06-24 hotfix #116/#118/#119/#120 已部署(详见文末"补充 hotfix"小节)
|
||||
|
||||
## 🎯 本版本重点
|
||||
|
||||
1. **修复扫码登录 iOS NSURLErrorCannotFindHost** — 企微 App 用户扫码不再报错
|
||||
2. **完整 MFA + RBAC 权限体系** — 5 角色细粒度权限 + 高危操作二次认证
|
||||
3. **H5 员工端企微 OAuth 登录** — 员工不再需要输账号密码
|
||||
4. **🆕 扫码登录去掉手机 confirm 步骤** — 企微 App 无确认 UI,扫码成功 = 直接登录
|
||||
|
||||
## 🔐 安全修复
|
||||
|
||||
### #109 P0:扫码登录 NSURLError 修复
|
||||
|
||||
**问题**:用户企微 App 访问 `https://itsupport.servyou.com.cn/itportal/`,扫描二维码后跳转报 `NSURLErrorCannotFindHost`(iOS WebView DNS 解析失败)
|
||||
|
||||
**根因**:`backend/app/config.py` 新字段 `qrcode_oauth_callback` 用了 pydantic 默认行为,**不剥环境变量前缀**
|
||||
- 字段名:`qrcode_oauth_callback`
|
||||
- pydantic-settings 期望 env:`QRCODE_OAUTH_CALLBACK`
|
||||
- 实际 env:`WECOM_QRCODE_CALLBACK`(带前缀,匹配不上)
|
||||
- 结果:`settings.qrcode_oauth_callback = ""`,qrcode_service 走兜底返回**相对路径** `/api/auth_qrcode/scan`
|
||||
- iOS WebView 拼接相对路径失败 → DNS 解析错误
|
||||
|
||||
**修复**:
|
||||
```python
|
||||
from pydantic import Field
|
||||
|
||||
class Settings(BaseSettings):
|
||||
qrcode_oauth_callback: str = Field(
|
||||
default="",
|
||||
validation_alias="WECOM_QRCODE_CALLBACK",
|
||||
)
|
||||
```
|
||||
|
||||
**部署**:docker commit + restart + 验证,详见 [[phase1-progress#109-hotfix-二轮修复]]
|
||||
|
||||
### RBAC 5 角色 × 4 资源 × 4 操作 × 3 范围
|
||||
|
||||
- **5 角色**:super_admin / admin / agent / user / guest
|
||||
- **4 资源**:user / conversation / config / audit_log
|
||||
- **4 操作**:read / create / update / delete
|
||||
- **3 范围**:all / department / self
|
||||
|
||||
### 高危路由白名单 + 中间件
|
||||
|
||||
5 类高危操作需 OTP 二次认证:
|
||||
- `role_change`(角色变更)
|
||||
- `config_change`(配置变更)
|
||||
- `data_export`(数据导出)
|
||||
- `account_disable`(账户禁用)
|
||||
- `account_create_reset`(账户创建/重置)
|
||||
|
||||
## ✨ 新功能
|
||||
|
||||
### 后端扫码登录(端点 4 个)
|
||||
|
||||
| 端点 | 方法 | 说明 |
|
||||
|---|---|---|
|
||||
| `/api/auth_qrcode/create` | POST | 生成二维码(含 PNG base64) |
|
||||
| `/api/auth_qrcode/poll/{ticket}` | POST | 扫码端轮询 |
|
||||
| `/api/auth_qrcode/scan` | POST | 企微回调 |
|
||||
| `/api/auth_qrcode/confirm` | POST | 用户确认登录 |
|
||||
|
||||
### 后端 MFA + pyotp(端点 5 个)
|
||||
|
||||
| 端点 | 方法 | 说明 |
|
||||
|---|---|---|
|
||||
| `/api/mfa/status` | GET | 查询绑定状态 |
|
||||
| `/api/mfa/bind/start` | POST | 开始绑定(返回 secret + QR) |
|
||||
| `/api/mfa/bind/confirm` | POST | 确认绑定 |
|
||||
| `/api/mfa/verify` | POST | 验证 OTP |
|
||||
| `/api/mfa/disable` | POST | 禁用 MFA |
|
||||
|
||||
### 前端 MFA UI
|
||||
|
||||
- `MfaBind.vue` — 绑定弹窗
|
||||
- `useHighRiskOtp.ts` composable — 高危操作 OTP 弹窗
|
||||
- `Login.vue` / `TopBar.vue` — 集成 MFA 状态显示
|
||||
- 路由 + store 全链路集成
|
||||
|
||||
### H5 员工端 OAuth 登录
|
||||
|
||||
- 部署路径:`https://itsupport.servyou.com.cn/itdesk/`
|
||||
- 自动 OAuth 静默授权(`snsapi_base`)
|
||||
- 会话列表 + 消息收发
|
||||
- 已完成 E2E 验证(#104)
|
||||
|
||||
## 🛠️ 部署要点
|
||||
|
||||
### 必备环境变量
|
||||
|
||||
```bash
|
||||
# /opt/wecom-it-desk/.env
|
||||
WECOM_QRCODE_CALLBACK=https://itsupport.servyou.com.cn/api/auth_qrcode/scan
|
||||
WECOM_SSO_ENABLED=true
|
||||
WECOM_SSO_CALLBACK_BASE=https://itsupport.servyou.com.cn
|
||||
```
|
||||
|
||||
### 部署清单
|
||||
|
||||
- [x] Backend commit `78f60c6`(v0.7.1-dev)
|
||||
- [x] docker 镜像 `wecom-it-desk-backend:latest` 已 commit 新 config
|
||||
- [x] 容器已重启 + /api/ready 200
|
||||
- [x] H5 dist 已部署到 `/opt/wecom-it-desk/nginx/html/itdesk/`
|
||||
- [x] nginx 已 reload
|
||||
- [x] /api/auth_qrcode/create 返回绝对 URL
|
||||
- [ ] Gitea push(等用户重授权 token)
|
||||
- [ ] 企微 App 实际扫码验证
|
||||
|
||||
## ⚠️ 已知问题
|
||||
|
||||
1. **Gitea push 阻塞**:`workbuddy-claude` token 2026-06-15 被吊销,需去 Gitea Web 重授权
|
||||
2. **#108 still pending**:v0.7.1-dev 78f60c6 未推到 origin
|
||||
3. **/api/admin/ IP 白名单**仍是 0.0.0.0/0(临时方案),v1.0 前必须收窄
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [[H5-DEPLOY-RUNBOOK-v0.7.1]] — H5 部署 runbook
|
||||
- [[DEPLOY-LOGIN-MIGRATION-v0.7.0]] — 旧版扫码登录
|
||||
- [[E2E-CHECKLIST-v0.7.0]] — 端到端验收
|
||||
- [[NGINX-DOMAIN-ROUTING]] — nginx 域名分发
|
||||
- [[USER-GUIDE-QRCODE-MFA]] — 用户手册
|
||||
- [[phase1-progress]] — 进展跟踪
|
||||
|
||||
## 📊 改动统计
|
||||
|
||||
- **代码**:22 文件 / +4705 行
|
||||
- **测试**:78 passed + 4 xfail + 33 pre-existing failures(已分类)
|
||||
- **文档**:5 新增 / 3 更新
|
||||
- **Agent**:4 并行 worktree 合并
|
||||
|
||||
---
|
||||
|
||||
## 🔄 补充 hotfix(2026-06-24 部署)
|
||||
|
||||
### #116 P0 + #118 P0:扫码端点 405 + ticket≠state
|
||||
|
||||
**问题**:
|
||||
- 坐席扫码登录报 `405 Method Not Allowed`
|
||||
- 企微 OAuth 回调走 GET(`?code=xxx&state=<ticket>`),旧代码只支持 POST
|
||||
- 即使改双方法,`Optional[str] = None` 没 `Query()` 装饰器,FastAPI 当 body 参数处理
|
||||
- 函数参数叫 `ticket`,企微用 `state`,参数名不匹配
|
||||
|
||||
**修复**:`backend/app/api/auth_qrcode.py` scan 端点
|
||||
```python
|
||||
@router.api_route("/scan", methods=["GET", "POST"], response_model=None)
|
||||
async def scan_qrcode(
|
||||
body: Optional[QrcodeScanRequest] = None,
|
||||
ticket: Optional[str] = Query(None, description="兼容旧参数名"),
|
||||
state: Optional[str] = Query(None, description="企微 OAuth state 标准参数名"),
|
||||
code: Optional[str] = Query(None, description="企微 OAuth 授权码"),
|
||||
redis_client = Depends(dep_redis),
|
||||
):
|
||||
if body is not None:
|
||||
final_ticket, final_code = body.ticket, body.code
|
||||
else:
|
||||
final_ticket = state or ticket # 优先 state(企微标准),回退 ticket
|
||||
final_code = code
|
||||
```
|
||||
|
||||
**部署**:`deploy-staging/hotfix-116-qrcode-scan-get/` — webcli v7 一键,20/20 pytest 过
|
||||
|
||||
### #119:webcli v7 全自动部署链路
|
||||
|
||||
复用 jumpserver Playwright + Luna webcli + OTP 自动,实现 18-31KB 命令脚本单次输入,2-3 分钟跑完。
|
||||
|
||||
### #120 P0:扫码成功 = 自动登录(去掉 confirm)
|
||||
|
||||
**问题**:企微 App 端没有"确认登录" UI,旧 confirm 步骤在生产永远走不通(用户报告"扫码成功后,手机端没有出现确认登录按钮")
|
||||
|
||||
**用户决策**:方案 A — 扫码成功 = 自动登录(推荐),2026-06-24 拍板
|
||||
|
||||
**修复**:`backend/app/services/qrcode_service.py` `process_scan` 默认 `auto_confirm=True`
|
||||
- 扫码成功直接写 `qrcode:confirm:{ticket}`(含 token)
|
||||
- 前端 poll 立即拿到 status=confirmed + token
|
||||
- 跳过手机 confirm 步骤
|
||||
- 默认 roles=["user"],admin/agent 走 portal 端角色选择
|
||||
|
||||
**测试**:20/20 pytest 过(`test_scan_then_poll_returns_confirmed` + `test_scan_auto_confirm_writes_confirm_key` 等)
|
||||
|
||||
**部署**:`deploy-staging/hotfix-120-qrcode-auto-confirm/` — webcli v7 一键,容器内 `/api/ready` 200 OK
|
||||
|
||||
**残留**:外部域名 `https://itsupport.servyou.com.cn/api/ready` 不通(nginx `/api/` upstream 配错 + 8000 端口未暴露),**等 #48 修复**。容器内 backend 完全健康。
|
||||
@@ -0,0 +1,185 @@
|
||||
# 智能IT支持服务台 - 部署修复记录
|
||||
|
||||
**日期**:2026-06-13
|
||||
**负责人**:宋献
|
||||
**状态**:待部署验证
|
||||
|
||||
---
|
||||
|
||||
## 一、问题概述
|
||||
|
||||
### 1.1 部署后 H5 用户端报错
|
||||
|
||||
```
|
||||
POST /api/h5/conversations/current/messages 返回 500 错误:
|
||||
- 错误1:column conversations.impact_scope does not exist
|
||||
- 错误2:AIHandler.__init__() missing 1 required positional argument: 'ai_service'
|
||||
```
|
||||
|
||||
### 1.2 影响范围
|
||||
|
||||
| 系统 | 影响 | 说明 |
|
||||
|------|------|------|
|
||||
| H5 用户端 | 阻塞 | 无法发送消息触发 AI 回复 |
|
||||
| Dify AI | 无法测试 | 依赖 H5 消息发送 |
|
||||
| 管理后台 | 已修复 | admin001 已设为管理员 |
|
||||
|
||||
---
|
||||
|
||||
## 二、根因分析
|
||||
|
||||
### 2.1 数据库缺列
|
||||
|
||||
服务器上数据库 `conversations` 表缺少4个新增列:
|
||||
- `impact_scope` — 影响范围
|
||||
- `is_blocking` — 是否阻塞
|
||||
- `emotion_state` — 情绪状态
|
||||
- `dify_conversation_id` — Dify 会话ID
|
||||
|
||||
### 2.2 AIHandler 初始化错误
|
||||
|
||||
代码重构后 `AIHandler.__init__` 需要传入 `AIService` 实例,但 `dependencies.py` 中两处调用仍使用无参构造函数:
|
||||
|
||||
```python
|
||||
# 错误代码
|
||||
return AIHandler()
|
||||
|
||||
# 正确代码
|
||||
return AIHandler(ai_service=AIService())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、修复内容
|
||||
|
||||
### 3.1 数据库修复(已完成)
|
||||
|
||||
```sql
|
||||
ALTER TABLE conversations ADD COLUMN IF NOT EXISTS impact_scope VARCHAR(50);
|
||||
ALTER TABLE conversations ADD COLUMN IF NOT EXISTS is_blocking BOOLEAN DEFAULT false;
|
||||
ALTER TABLE conversations ADD COLUMN IF NOT EXISTS emotion_state VARCHAR(50);
|
||||
ALTER TABLE conversations ADD COLUMN IF NOT EXISTS dify_conversation_id VARCHAR(255);
|
||||
```
|
||||
|
||||
### 3.2 代码修复
|
||||
|
||||
**文件**:`backend/app/dependencies.py`
|
||||
|
||||
**修复内容**:2处 AIHandler 调用补上 ai_service 参数
|
||||
|
||||
| 位置 | 修复前 | 修复后 |
|
||||
|------|--------|--------|
|
||||
| get_shared_ai_handler() | `return AIHandler()` | `return AIHandler(ai_service=AIService())` |
|
||||
| dep_ai_handler() | `return AIHandler()` | `return AIHandler(ai_service=AIService())` |
|
||||
|
||||
---
|
||||
|
||||
## 四、部署步骤
|
||||
|
||||
### 4.1 本地打包
|
||||
|
||||
```powershell
|
||||
cd D:\资料\03-项目开发\wecom_it_smart_desk\deploy-server
|
||||
.\打包部署.bat
|
||||
```
|
||||
|
||||
生成文件:
|
||||
- `it-smart-desk-server-deploy.zip` — 前端+nginx+docker-compose
|
||||
- `deploy-backend.tar` — 后端 Docker 镜像(含修复)
|
||||
|
||||
### 4.2 上传服务器
|
||||
|
||||
通过堡垒机将文件上传到服务器 `/tmp/`:
|
||||
- `it-smart-desk-server-deploy.zip`
|
||||
- `deploy-backend.tar`
|
||||
|
||||
### 4.3 服务器部署
|
||||
|
||||
```bash
|
||||
# 1. 加载后端镜像
|
||||
docker load -i /tmp/deploy-backend.tar
|
||||
|
||||
# 2. 重启后端容器
|
||||
docker stop wecom_it_backend && docker rm wecom_it_backend
|
||||
docker run -d --name wecom_it_backend ... (原启动命令)
|
||||
|
||||
# 3. 验证后端健康
|
||||
curl https://itsupport.servyou.com.cn/health
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、验证检查项
|
||||
|
||||
### 5.1 后端健康检查
|
||||
|
||||
```bash
|
||||
curl https://itsupport.servyou.com.cn/health
|
||||
# 预期返回:{"status":"ok"}
|
||||
```
|
||||
|
||||
### 5.2 H5 消息发送测试
|
||||
|
||||
1. H5 Mock 登录:`POST /api/h5/mock-login`
|
||||
2. 发送消息:`POST /api/h5/conversations/current/messages`
|
||||
3. 预期:返回 AI 回复(调用 Dify 成功)
|
||||
|
||||
### 5.3 Dify AI 集成状态
|
||||
|
||||
管理后台 → 集成配置 → Dify AI 状态应为 `connected`
|
||||
|
||||
---
|
||||
|
||||
## 六、相关配置
|
||||
|
||||
### 6.1 服务器信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 服务器 IP | 10.90.5.110 |
|
||||
| 域名 | itsupport.servyou.com.cn |
|
||||
| WAF | 115.236.188.3 |
|
||||
|
||||
### 6.2 企微配置
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| CorpID | wwa8c87970b2011f41 |
|
||||
| AgentID | 1000133 |
|
||||
| Token | wAqMCP |
|
||||
| EncodingAESKey | KQY3cEsBc3rdi3xua9rPd5WxH8kYOhyASzWZQf75aJS |
|
||||
|
||||
### 6.3 Dify 配置
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| API URL | http://yw-dify.dc.servyou-it.com/dify2openai/v1/chat/completions |
|
||||
| API Key | http://yw-dify.dc.servyou-it.com/v1\|app-UaTWYdBSwN6VktKQlbh5YN5H\|Chat |
|
||||
|
||||
### 6.4 数据库配置
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 数据库 | PostgreSQL |
|
||||
| 库名 | wecom_it_desk |
|
||||
| 用户 | wecom |
|
||||
| 密码 | wecom_secret_2026 |
|
||||
|
||||
---
|
||||
|
||||
## 七、相关文件
|
||||
|
||||
| 文件路径 | 说明 |
|
||||
|---------|------|
|
||||
| `backend/app/dependencies.py` | 修复后的代码 |
|
||||
| `deploy-server/build-and-deploy.ps1` | 打包部署脚本 |
|
||||
| `deploy-server/打包部署.bat` | 一键执行入口 |
|
||||
| `docs/IT服务台PRDv1.0.md` | 产品需求文档 |
|
||||
|
||||
---
|
||||
|
||||
**更新历史**
|
||||
|
||||
| 日期 | 更新内容 |
|
||||
|------|---------|
|
||||
| 2026-06-13 | 初始记录,数据库修复 + 代码修复 + 打包脚本 |
|
||||
@@ -0,0 +1,186 @@
|
||||
# 智能IT支持服务台 - 版本更新说明
|
||||
|
||||
**版本**: v1.1.0
|
||||
**更新日期**: 2026-06-14
|
||||
**文档状态**: 待审核
|
||||
|
||||
---
|
||||
|
||||
## 一、本次更新内容
|
||||
|
||||
### 1.1 新增功能
|
||||
|
||||
| 功能 | 说明 | 优先级 |
|
||||
|------|------|--------|
|
||||
| 消息撤回 | 2分钟内可撤回自己的消息 | P0 |
|
||||
| 消息删除 | 删除自己的消息 | P0 |
|
||||
| 消息状态 | 支持 sending/sent/delivered/read/recalled 状态 | P0 |
|
||||
| 标记已读 | 一键标记会话已读 | P1 |
|
||||
| 图片上传 | 支持图片上传(≤10MB) | P1 |
|
||||
| 文件上传 | 支持文件上传(≤10MB) | P1 |
|
||||
|
||||
### 1.2 架构优化
|
||||
|
||||
| 优化项 | 说明 |
|
||||
|--------|------|
|
||||
| Health Check | 所有容器已配置健康检查 |
|
||||
| 自动重启 | 容器崩溃自动重启(restart: unless-stopped) |
|
||||
| AI Gateway 设计 | 预留多模型切换架构 |
|
||||
|
||||
### 1.3 安全增强
|
||||
|
||||
| 功能 | 说明 |
|
||||
|------|------|
|
||||
| OTP 双因素认证 | 访问管理后台时二次验证 |
|
||||
| 操作审计 | 关键操作日志记录(规划中) |
|
||||
|
||||
---
|
||||
|
||||
## 二、需要同步的代码
|
||||
|
||||
### 2.1 后端文件
|
||||
|
||||
| 文件 | 改动 | 说明 |
|
||||
|------|------|------|
|
||||
| `app/models/message.py` | 修改 | 添加 status、recallable_until 字段 |
|
||||
| `app/api/messages.py` | 修改 | 添加撤回/删除/标记已读/上传 API |
|
||||
| `app/api/ws_manager.py` | 修改 | 添加消息状态广播 |
|
||||
| `docker-compose.yml` | 修改 | healthcheck 已配置 |
|
||||
|
||||
### 2.2 数据库变更
|
||||
|
||||
```sql
|
||||
-- 需要执行的 SQL 迁移
|
||||
ALTER TABLE messages ADD COLUMN status VARCHAR(20) DEFAULT 'sent';
|
||||
ALTER TABLE messages ADD COLUMN recallable_until TIMESTAMP;
|
||||
```
|
||||
|
||||
### 2.3 前端文件(如果有)
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| 消息操作菜单 | 撤回/删除按钮 |
|
||||
| 消息状态显示 | 状态图标 |
|
||||
| 已读标记 | 一键已读 |
|
||||
|
||||
---
|
||||
|
||||
## 三、部署步骤
|
||||
|
||||
### 3.1 本地打包
|
||||
|
||||
```bash
|
||||
# 后端打包
|
||||
cd backend
|
||||
docker build -t wecom-it-desk-backend:latest .
|
||||
|
||||
# 导出镜像
|
||||
docker save wecom-it-desk-backend:latest -o wecom-it-desk-backend.tar
|
||||
```
|
||||
|
||||
### 3.2 服务器部署
|
||||
|
||||
```bash
|
||||
# 1. 上传镜像到堡垒机
|
||||
# 堡垒机: sxn@10.212.189.210:2222 (OTP)
|
||||
# 目标路径: /tmp/
|
||||
|
||||
# 2. SSH 到正式服务器
|
||||
ssh sxn@10.212.189.210 -p 2222
|
||||
ssh 10.90.5.110
|
||||
|
||||
# 3. 导入镜像
|
||||
docker load -i /tmp/wecom-it-desk-backend.tar
|
||||
|
||||
# 4. 解决容器冲突(重要!)
|
||||
docker rm -f wecom_it_redis wecom_it_backend wecom_it_postgres wecom_it_nginx 2>/dev/null
|
||||
|
||||
# 5. 重新启动
|
||||
docker compose -p root up -d
|
||||
|
||||
# 6. 执行数据库迁移
|
||||
docker compose exec backend python -c "from app.database import engine; engine.execute('ALTER TABLE messages ADD COLUMN status VARCHAR(20) DEFAULT 'sent'')"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、验证清单
|
||||
|
||||
### 4.1 健康检查
|
||||
|
||||
| 检查项 | 命令 | 预期结果 |
|
||||
|--------|------|----------|
|
||||
| 后端服务 | `curl -s http://localhost:8000/health` | `{"status": "ok"}` |
|
||||
| Nginx | `curl -s http://localhost:80/itdesk/health` | `{"status": "ok"}` |
|
||||
| 数据库 | `docker compose exec backend python -c "from app.database import engine; print('OK')"` | OK |
|
||||
| Redis | `docker compose exec redis redis-cli ping` | PONG |
|
||||
|
||||
### 4.2 API 测试
|
||||
|
||||
| API | 方法 | 测试命令 |
|
||||
|-----|------|----------|
|
||||
| 撤回消息 | POST | `curl -X POST http://localhost:8000/api/messages/{id}/recall` |
|
||||
| 删除消息 | DELETE | `curl -X DELETE http://localhost:8000/api/messages/{id}` |
|
||||
| 标记已读 | POST | `curl -X POST http://localhost:8000/api/conversations/{id}/mark-read` |
|
||||
| 图片上传 | POST | `curl -X POST -F "file=@test.jpg" http://localhost:8000/api/messages/image` |
|
||||
|
||||
### 4.3 功能测试
|
||||
|
||||
| 功能 | 测试场景 | 预期结果 |
|
||||
|------|----------|----------|
|
||||
| 消息发送 | 发送文本消息 | 消息正常显示 |
|
||||
| 消息撤回 | 2分钟内撤回 | 状态变为 recalled |
|
||||
| 消息撤回 | 超过2分钟 | 返回 403 错误 |
|
||||
| 标记已读 | 点击已读 | 所有消息标记为已读 |
|
||||
|
||||
---
|
||||
|
||||
## 五、已知问题与限制
|
||||
|
||||
### 5.1 待解决
|
||||
|
||||
| 问题 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| 容器冲突 | 进行中 | 需指定项目名 `-p root` |
|
||||
| 前端同步 | 待确认 | 可能需要更新前端代码 |
|
||||
|
||||
### 5.2 已知限制
|
||||
|
||||
| 限制 | 说明 |
|
||||
|------|------|
|
||||
| 单节点部署 | MVP 阶段保持单节点 |
|
||||
| 文件大小 | 单文件 ≤10MB |
|
||||
| 撤回时间 | 2分钟后不可撤回 |
|
||||
|
||||
---
|
||||
|
||||
## 六、回滚方案
|
||||
|
||||
如果部署失败,执行:
|
||||
|
||||
```bash
|
||||
# 停止服务
|
||||
docker compose -p root down
|
||||
|
||||
# 恢复旧镜像(如果有备份)
|
||||
docker load -i wecom-it-desk-backend-old.tar
|
||||
|
||||
# 使用备份的配置启动
|
||||
docker compose -p root up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、联系人
|
||||
|
||||
| 角色 | 联系人 | 说明 |
|
||||
|------|--------|------|
|
||||
| 产品负责人 | 许清楚 | PRD 确认 |
|
||||
| 技术负责人 | 寇豆码 | 代码审查 |
|
||||
| QA 负责人 | 严过关 | 测试验证 |
|
||||
| 运维负责人 | 宋献 | 部署执行 |
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: 1.0
|
||||
**审核状态**: 待审核
|
||||
@@ -0,0 +1,89 @@
|
||||
# OTP 二次验证实现文档
|
||||
|
||||
## 功能概述
|
||||
|
||||
为 IT 支持服务台坐席端增加 OTP 二次验证功能:
|
||||
- admin 角色登录时需要输入 Google Authenticator 动态码
|
||||
- 首次绑定需要验证一次码才启用
|
||||
- OTP 丢失后需管理员重置
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 1. 后端修改
|
||||
|
||||
#### 1.1 安装依赖
|
||||
```bash
|
||||
pip install pyotp qrcode[pil] pillow
|
||||
```
|
||||
|
||||
#### 1.2 数据库模型
|
||||
Agent 模型已有字段:
|
||||
- `otp_secret`: OTP 密钥(Base32编码)
|
||||
- `otp_enabled`: OTP 是否启用(0=否, 1=是)
|
||||
|
||||
#### 1.3 Schema 修改
|
||||
文件:`backend/app/schemas/agent.py`
|
||||
- `AgentLogin` 增加 `otp_code` 可选字段
|
||||
- `AgentResponse` 增加 `otp_enabled` 字段
|
||||
|
||||
#### 1.4 API 修改
|
||||
文件:`backend/app/api/agents.py`
|
||||
|
||||
新增接口:
|
||||
- `POST /api/agents/otp-bind` - 生成 OTP 密钥和二维码
|
||||
- `POST /api/agents/otp-verify` - 验证并启用 OTP
|
||||
- `POST /api/agents/otp-unbind` - 解绑 OTP
|
||||
|
||||
登录接口修改:
|
||||
- admin 角色且 otp_enabled=1 时,检查 otp_code
|
||||
- 未提供 otp_code 返回 `require_otp: True`
|
||||
- 验证 OTP 码正确后生成 token
|
||||
|
||||
### 2. 前端修改
|
||||
|
||||
#### 2.1 坐席端 API
|
||||
文件:`frontend-agent/src/api/agent.ts`
|
||||
- `login()` 增加 `otpCode` 参数
|
||||
|
||||
#### 2.2 坐席端 Store
|
||||
文件:`frontend-agent/src/stores/agent.ts`
|
||||
- `login()` 增加 `otpCode` 参数
|
||||
- 返回 `require_otp` 标记让页面处理
|
||||
|
||||
#### 2.3 坐席端登录页面
|
||||
文件:`frontend-agent/src/views/Login.vue`
|
||||
- 增加 OTP 输入框(v-if="requireOtp")
|
||||
- 首次登录返回 require_otp 时显示输入框
|
||||
- 用户输入 OTP 后再次登录
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 首次绑定 OTP
|
||||
1. 管理员登录坐席端
|
||||
2. 调用 `POST /api/agents/otp-bind`
|
||||
3. 获取二维码和密钥
|
||||
4. 使用 Google Authenticator 扫描二维码
|
||||
5. 调用 `POST /api/agents/otp-verify` 输入动态码验证
|
||||
6. 验证成功,otp_enabled 设为 1
|
||||
|
||||
### 登录流程
|
||||
1. 用户输入 user_id 和 name
|
||||
2. 后端检查 admin 角色且 otp_enabled=1
|
||||
3. 返回 `require_otp: True`
|
||||
4. 前端显示 OTP 输入框
|
||||
5. 用户输入 6 位动态码
|
||||
6. 后端验证通过,生成 token
|
||||
|
||||
### 解绑流程
|
||||
1. 管理员调用 `POST /api/agents/otp-unbind`
|
||||
2. otp_secret 和 otp_enabled 清空
|
||||
|
||||
## 错误码
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 1006 | OTP 验证码错误 |
|
||||
| 1007 | OTP 绑定失败 |
|
||||
| 1008 | 请先绑定 OTP |
|
||||
| 1009 | OTP 验证失败 |
|
||||
| 1010 | OTP 解绑失败 |
|
||||
@@ -0,0 +1,220 @@
|
||||
# 部署手册:扫码登录 + OTP 二次认证(Phase 1+2)
|
||||
|
||||
> 创建:2026-06-21
|
||||
> 适用版本:v0.7.0+ (Phase 1+2)
|
||||
> 部署顺序:后端 → 前端 4 端 → nginx → 数据库 migration → 验收
|
||||
|
||||
---
|
||||
|
||||
## 🎯 部署目标
|
||||
|
||||
从 v0.6.x 的"企微 OAuth + SMS 2FA"升级到 v0.7.0 的"扫码登录 + OTP TOTP + SMS 备用"。
|
||||
|
||||
涉及后端变更:
|
||||
- 新增 `/api/auth_qrcode/*` 4 个端点(扫码登录)
|
||||
- 新增 `/api/mfa/*` 6 个端点(OTP 二次认证)
|
||||
- 新增 `/api/admin/mfa/reset/{employee_id}`(管理员重置)
|
||||
- 新增 `/api/admin/high-risk/*` 演示端点 + require_high_risk_otp 守卫
|
||||
- 新增 2 个数据库字段: `users.mfa_secret`, `users.mfa_enabled`, `users.mfa_bound_at`, `users.mfa_last_verified_at`
|
||||
|
||||
涉及前端变更:
|
||||
- frontend-agent:Login.vue 重写(扫码 UI)+ 新增 MfaBind.vue + useHighRiskOtp
|
||||
- frontend-portal:新增 QrcodeLogin.vue + 默认路由
|
||||
- frontend-admin:新增 MfaManage.vue(管理员 MFA 重置 UI)
|
||||
- frontend-h5:**不变**(仍走企微 OAuth)
|
||||
|
||||
涉及 nginx 变更:
|
||||
- `/itportal/` 新增 location(扫码入口)
|
||||
- 其余 4 个 location 已有,配置按 docs/NGINX-DOMAIN-ROUTING.md
|
||||
|
||||
---
|
||||
|
||||
## 📋 部署前检查
|
||||
|
||||
### 1. 后端镜像依赖
|
||||
|
||||
`backend/requirements.txt` 必须包含:
|
||||
```
|
||||
pyotp==2.9.0 # TOTP 生成
|
||||
qrcode[pil]==7.4.2 # 二维码生成
|
||||
redis==5.0.7 # 已存在
|
||||
```
|
||||
|
||||
### 2. 数据库迁移文件
|
||||
|
||||
确认以下 migration 已存在:
|
||||
- `backend/alembic/versions/023_mfa_fields.py`(加 4 个 MFA 字段)
|
||||
- `backend/alembic/versions/024_*.py`(可选:其他变更)
|
||||
|
||||
### 3. 配置文件
|
||||
|
||||
`backend/.env` 确认:
|
||||
```bash
|
||||
# 新增(扫码登录)
|
||||
WECOM_OAUTH_REDIRECT_URI=https://itsupport.servyou.com.cn/itportal/qrcode-callback
|
||||
WECOM_CORP_ID=ww1234567890abcdef
|
||||
WECOM_AGENT_ID=1000002
|
||||
|
||||
# 已有(OTP)
|
||||
SMS_2FA_ENABLED=true # 蜂鸟 SMS 备用通道
|
||||
```
|
||||
|
||||
### 4. 域名 / DNS
|
||||
|
||||
- `itsupport.servyou.com.cn`(主域名,已有)
|
||||
- 子路径:`/itportal/` `/itagent/` `/itadmin/` `/itdesk/`(同一域名,nginx 分发)
|
||||
- 证书:`itsupport.servyou.com.cn.crt`(公司统一管理)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 部署步骤
|
||||
|
||||
### 步骤 1:部署后端(注意 RO bind mount)
|
||||
|
||||
```bash
|
||||
# 1. 上传新 backend 包到堡垒机
|
||||
scp backend-v070-p1.tar.gz user@bastion:/tmp/
|
||||
|
||||
# 2. 通过堡垒机 PuTTY(不用 ssh -J)登录生产服务器
|
||||
# 参考:feedback-putty-not-openssh.md
|
||||
|
||||
# 3. 解压并复制到 backend 目录(走宿主机路径,避开 RO bind mount 陷阱)
|
||||
cd /opt/wecom-it-desk/
|
||||
tar -xzf /tmp/backend-v070-p1.tar.gz
|
||||
# 注意:用 cp -r 不是 docker cp(避开 RO bind mount 假成功陷阱)
|
||||
sudo cp -r backend-v070-p1/* backend/
|
||||
|
||||
# 4. 数据库 migration
|
||||
cd /opt/wecom-it-desk/backend
|
||||
sudo docker exec wecom_it_backend alembic upgrade head
|
||||
# 验证:
|
||||
sudo docker exec wecom_it_backend alembic current
|
||||
# 期望:023_mfa_fields (head)
|
||||
|
||||
# 5. 重启 backend(注意:backend 不在 compose 里,直接 docker restart)
|
||||
sudo docker restart wecom_it_backend
|
||||
# 验证:等待 ~30s,看健康检查
|
||||
curl http://localhost:8000/health
|
||||
# 期望:{"status":"ok",...}
|
||||
```
|
||||
|
||||
### 步骤 2:部署前端 4 端
|
||||
|
||||
```bash
|
||||
# 1. 各前端 build(本地)
|
||||
cd frontend-agent && npm run build
|
||||
cd frontend-portal && npm run build
|
||||
cd frontend-admin && npm run build
|
||||
# frontend-h5 不变,不用 build
|
||||
|
||||
# 2. 上传 dist 到生产服务器
|
||||
scp -r frontend-agent/dist user@bastion:/tmp/agent-dist/
|
||||
scp -r frontend-portal/dist user@bastion:/tmp/portal-dist/
|
||||
scp -r frontend-admin/dist user@bastion:/tmp/admin-dist/
|
||||
|
||||
# 3. 通过堡垒机,复制到 nginx 容器挂载的目录
|
||||
# 路径可能是 /opt/wecom-it-desk/frontend-*/
|
||||
sudo cp -r /tmp/agent-dist/* /opt/wecom-it-desk/frontend-agent/dist/
|
||||
sudo cp -r /tmp/portal-dist/* /opt/wecom-it-desk/frontend-portal/dist/
|
||||
sudo cp -r /tmp/admin-dist/* /opt/wecom-it-desk/frontend-admin/dist/
|
||||
|
||||
# 4. 验证:curl HTML 文件
|
||||
curl -I https://itsupport.servyou.com.cn/itportal/
|
||||
# 期望:200 OK,content-type: text/html
|
||||
```
|
||||
|
||||
### 步骤 3:更新 nginx 配置
|
||||
|
||||
```bash
|
||||
# 1. 上传新 nginx 配置
|
||||
# 新增 /itportal/ location,更新其他 location
|
||||
# 参考:docs/NGINX-DOMAIN-ROUTING.md
|
||||
|
||||
# 2. 验证配置(在 nginx 容器里)
|
||||
sudo docker exec wecom_it_nginx nginx -t
|
||||
# 注意容器名是 wecom_it_nginx 不是 wecom-nginx
|
||||
# 期望:nginx: configuration file /etc/nginx/nginx.conf test is successful
|
||||
|
||||
# 3. reload(不重启容器)
|
||||
sudo docker exec wecom_it_nginx nginx -s reload
|
||||
```
|
||||
|
||||
### 步骤 4:验收测试
|
||||
|
||||
按 docs/NGINX-DOMAIN-ROUTING.md 末"验证清单"逐条测试。
|
||||
|
||||
---
|
||||
|
||||
## 🔄 回滚方案
|
||||
|
||||
### 后端回滚
|
||||
|
||||
```bash
|
||||
# 1. 用上次 patch1 备份
|
||||
sudo cp -r /opt/wecom-it-desk/backend-v070-patch1/* /opt/wecom-it-desk/backend/
|
||||
sudo docker restart wecom_it_backend
|
||||
|
||||
# 2. 数据库回滚(谨慎!)
|
||||
sudo docker exec wecom_it_backend alembic downgrade -1
|
||||
# 注意:只能降 1 个版本,如果已经升到 023,降到 022
|
||||
```
|
||||
|
||||
### 前端回滚
|
||||
|
||||
```bash
|
||||
# 直接覆盖 dist
|
||||
sudo cp -r /opt/wecom-it-desk/frontend-*-bak/* /opt/wecom-it-desk/frontend-*/dist/
|
||||
```
|
||||
|
||||
### nginx 回滚
|
||||
|
||||
```bash
|
||||
# 容器内 sed -i 改回旧配置(避开 RO bind mount 假成功陷阱)
|
||||
sudo docker exec wecom_it_nginx cp /etc/nginx/nginx.conf.bak /etc/nginx/nginx.conf
|
||||
sudo docker exec wecom_it_nginx nginx -s reload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 已知风险
|
||||
|
||||
| 风险 | 影响 | 缓解 |
|
||||
|---|---|---|
|
||||
| OTP 二维码渲染失败(后端 base64 生成出错) | 用户绑不上 OTP | 前端降级显示 qrcode_url 让用户手动复制 |
|
||||
| pyotp 库版本升级导致不兼容 | OTP 验证失败 | 锁版本 pyotp==2.9.0,生产前跑 pytest |
|
||||
| Admin MFA 重置端点被未授权访问 | 安全 | require_admin + 后续可加 IP 白名单 |
|
||||
| 蜂鸟 SMS API 未上线 | 备用通道不可用 | 不影响 OTP 主通道,先上线 OTP,后接 SMS |
|
||||
| nginx IP 白名单临时全开 | 安全 | v1.0 前必须收窄(task #48) |
|
||||
|
||||
---
|
||||
|
||||
## 📊 部署后验证
|
||||
|
||||
### 业务指标
|
||||
|
||||
- [ ] 扫码登录成功率 > 95%
|
||||
- [ ] OTP 验证成功率 > 99%
|
||||
- [ ] 高危操作 OTP 触发率 100%
|
||||
- [ ] 蜂鸟 SMS fallback 触发 < 5%(绝大多数人用 OTP)
|
||||
|
||||
### 技术指标
|
||||
|
||||
- [ ] 扫码登录端到端 < 5s(从扫码到进入工作台)
|
||||
- [ ] OTP 验证 < 500ms
|
||||
- [ ] 高危操作 OTP 弹窗响应 < 200ms
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [USER-GUIDE-QRCODE-MFA.md](./USER-GUIDE-QRCODE-MFA.md) — 用户手册
|
||||
- [NGINX-DOMAIN-ROUTING.md](./NGINX-DOMAIN-ROUTING.md) — nginx 域名分发
|
||||
- [v070-alpha-deploy-runbook.md](../memory/v070-alpha-deploy-runbook.md) — v0.7.0-alpha 总览
|
||||
- [docker-cp-readonly-bind-mount-fake-success.md](../memory/docker-cp-readonly-bind-mount-fake-success.md) — RO bind mount 陷阱
|
||||
- [nginx-container-name-wecom-it-nginx.md](../memory/nginx-container-name-wecom-it-nginx.md) — 容器名坑
|
||||
- [feedback-putty-not-openssh.md](../memory/feedback-putty-not-openssh.md) — 堡垒机 PuTTY
|
||||
|
||||
---
|
||||
|
||||
**变更历史**:
|
||||
- 2026-06-21 创建(Phase 1+2 部署手册)
|
||||
@@ -0,0 +1,227 @@
|
||||
# 企微智能IT支持服务台 — 远程服务器部署指南(预生产)
|
||||
|
||||
> **预生产环境**:本系统与 IT 数据查询平台部署在**不同主机**。正式环境将迁移到 K8s。
|
||||
|
||||
## 部署架构
|
||||
|
||||
```
|
||||
浏览器 ──→ it-dataquery.dc.servyou-it.com:80
|
||||
│
|
||||
▼
|
||||
┌─── nginx (本系统主机) ──────────────────────┐
|
||||
│ │
|
||||
│ /itdesk/* → H5 员工端 SPA │
|
||||
│ /itagent/* → 坐席工作台 SPA │
|
||||
│ /api/* → backend:8000 (FastAPI) │
|
||||
│ /ws/* → backend:8000 (WebSocket) │
|
||||
│ /* → 远程代理到数据平台主机 IP │ ← 跨主机
|
||||
│ │
|
||||
└──────────────┬─────────────────────────────┘
|
||||
│ 本机 Docker 网络
|
||||
┌─────────────┼─────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ backend │ │ postgres │ │ redis │
|
||||
│ :8000 │ │ :5432 │ │ :6379 │
|
||||
└──────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
## 网络互联
|
||||
|
||||
预生产环境中,数据平台在独立主机,**不需要 Docker 网络互联**。Nginx 通过远程 IP 直接反代数据平台:
|
||||
|
||||
```
|
||||
本系统主机 (Docker) 数据平台主机
|
||||
┌──────────────────┐ ┌─────────────────┐
|
||||
│ nginx ──────────┼── HTTP 反代 ──→ │ 数据平台 :80 │
|
||||
│ │ │ 远程 IP │ │
|
||||
│ ▼ │ └─────────────────┘
|
||||
│ backend:8000 │
|
||||
│ postgres:5432 │
|
||||
│ redis:6379 │
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
## 前置条件
|
||||
|
||||
- 服务器已安装 Docker + Docker Compose
|
||||
- IT 数据查询平台已部署运行
|
||||
- 有 SSH 登录权限
|
||||
|
||||
## 部署步骤
|
||||
|
||||
### 1. 配置数据平台反代地址
|
||||
|
||||
预生产环境中,数据平台在**独立主机**。部署前,必须将 nginx 配置中的数据平台上游地址改为实际 IP。
|
||||
|
||||
编辑 `nginx/nginx.conf`:
|
||||
|
||||
```nginx
|
||||
# 将 DATAQUERY_HOST 替换为数据平台主机的实际 IP:端口
|
||||
upstream dataquery {
|
||||
server 10.80.0.86:80; # ← 改为数据平台实际 IP
|
||||
}
|
||||
```
|
||||
|
||||
> 不再需要创建 `it-platform-net` —— Docker 网络无法跨主机互联。nginx 通过 HTTP 直接反代到远程 IP。
|
||||
|
||||
### 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/
|
||||
```
|
||||
|
||||
### 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` | 数据库密码 | 自定义强密码 |
|
||||
|
||||
### 5. 启动服务
|
||||
|
||||
```bash
|
||||
bash scripts/deploy.sh
|
||||
```
|
||||
|
||||
这会自动执行:检查前置条件 → 构建后端镜像 → 启动所有容器
|
||||
|
||||
### 6. 验证部署
|
||||
|
||||
```bash
|
||||
# 检查容器状态
|
||||
docker compose ps
|
||||
|
||||
# 健康检查
|
||||
curl http://localhost:18080/itdesk/health
|
||||
|
||||
# 查看 H5 员工端
|
||||
curl -I http://localhost:18080/itdesk/
|
||||
|
||||
# 查看坐席工作台
|
||||
curl -I http://localhost:18080/itagent/
|
||||
|
||||
# 查看后端 API
|
||||
curl http://localhost:18080/api/docs
|
||||
```
|
||||
|
||||
浏览器验证:
|
||||
|
||||
| 地址 | 预期 |
|
||||
|------|------|
|
||||
| `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 文档 |
|
||||
|
||||
## 两种网络接入方式
|
||||
|
||||
### 方式 A:数据平台 nginx 反代到本项目 nginx(推荐)
|
||||
|
||||
数据平台的 nginx 配置添加:
|
||||
|
||||
```nginx
|
||||
# IT 智能服务台路由
|
||||
location /itdesk/ {
|
||||
proxy_pass http://wecom_it_nginx:80/itdesk/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
location /itagent/ {
|
||||
proxy_pass http://wecom_it_nginx:80/itagent/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
location /api/ {
|
||||
proxy_pass http://wecom_it_nginx:80/api/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
location /ws/ {
|
||||
proxy_pass http://wecom_it_nginx:80/ws/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
```
|
||||
|
||||
此方式下本项目 nginx 不暴露 80 端口,docker-compose.yml 的 `ports` 可以删除。
|
||||
|
||||
### 方式 B:本项目 nginx 直接监听 80 端口
|
||||
|
||||
如果数据平台没有自己的 nginx,或者想用本项目的 nginx 统一管理:
|
||||
|
||||
1. 停止数据平台的端口映射
|
||||
2. 本项目 nginx 的 80 端口直接对外
|
||||
3. nginx.conf 中的 `location /` 反代到数据平台容器
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: nginx 启动失败,报 `host not found in upstream "dataquery"`
|
||||
A: `nginx/nginx.conf` 中的 `DATAQUERY_HOST` 未替换为数据平台实际 IP。编辑 nginx.conf,将占位符改为实际 IP:端口。
|
||||
|
||||
### Q: 访问 /itdesk/ 返回 404
|
||||
A: 检查前端 dist 是否正确挂载:
|
||||
```bash
|
||||
docker exec wecom_it_nginx ls -la /usr/share/nginx/html/itdesk/
|
||||
```
|
||||
|
||||
### Q: API 返回 CORS 错误
|
||||
A: 检查 `.env` 中的 `CORS_ORIGINS` 是否包含 `http://it-dataquery.dc.servyou-it.com`
|
||||
|
||||
### Q: 数据库迁移失败
|
||||
A: 查看 backend 日志:
|
||||
```bash
|
||||
docker compose logs backend
|
||||
```
|
||||
如果 PostgreSQL 未就绪,等 30 秒后重启 backend:
|
||||
```bash
|
||||
docker compose restart backend
|
||||
```
|
||||
|
||||
## 更新部署
|
||||
|
||||
只需更新变更的部分:
|
||||
|
||||
```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
|
||||
```
|
||||
@@ -0,0 +1,438 @@
|
||||
# 群晖 NAS + Cloudflare Tunnel + 未认证企微 部署指南
|
||||
|
||||
> **适用范围**:阶段一功能测试
|
||||
> **目标域名**:`itdesk.amanzac.com`
|
||||
> **最后更新**:2026-06-07
|
||||
|
||||
---
|
||||
|
||||
## 架构总览
|
||||
|
||||
```
|
||||
HTTPS HTTP
|
||||
员工手机 ──────────→ Cloudflare Edge ──────────→ cloudflared ──→ nginx:80
|
||||
(企微H5) (自动SSL+CDN) Tunnel 容器 │
|
||||
┌────┴────┐
|
||||
│ 路由分发 │
|
||||
└────┬────┘
|
||||
┌──────┼──────┐
|
||||
│ │ │
|
||||
/itdesk/ /itagent/ /api/
|
||||
H5员工端 坐席工作台 后端
|
||||
```
|
||||
|
||||
**关键特点**:
|
||||
- ✅ 无需公网 IP
|
||||
- ✅ 无需 SSL 证书(Cloudflare 自动处理)
|
||||
- ✅ 无需开放 NAS 端口
|
||||
- ✅ 未认证企微可正常使用 OAuth2 + 消息 API
|
||||
|
||||
---
|
||||
|
||||
## §1 前置条件检查清单
|
||||
|
||||
| # | 条件 | 你的状态 | 说明 |
|
||||
|---|------|---------|------|
|
||||
| 1 | 群晖 NAS(DS220+ 及以上) | ✅ 已确认 | 需支持 Docker(ARM 机型需确认镜像兼容) |
|
||||
| 2 | Container Manager 已安装 | ✅ 已确认 | 套件中心安装 |
|
||||
| 3 | Cloudflare 账号 | ✅ 已确认 | 免费版即可 |
|
||||
| 4 | 域名 `amanzac.com` 已托管 Cloudflare | ✅ 已确认 | DNS 管理 → Cloudflare |
|
||||
| 5 | 企微管理后台权限 | ✅ 已确认 | 需配置自建应用 |
|
||||
| 6 | SSH 访问 NAS | ⬜ 待确认 | 需开启 SSH 以执行 docker compose 命令 |
|
||||
|
||||
---
|
||||
|
||||
## §2 Cloudflare Tunnel 配置
|
||||
|
||||
### 2.1 创建 Tunnel
|
||||
|
||||
1. 登录 [Cloudflare Zero Trust](https://one.dash.cloudflare.com/)
|
||||
2. 左侧菜单 → **Networks** → **Tunnels**
|
||||
3. 点击 **Create a tunnel**
|
||||
4. 选择 **Cloudflared** 类型
|
||||
5. 输入 Tunnel 名称,如 `itdesk-nas`
|
||||
6. 点击 **Save tunnel**
|
||||
|
||||
### 2.2 获取 Tunnel Token
|
||||
|
||||
创建完成后,页面会显示安装命令,其中包含 Token:
|
||||
|
||||
```bash
|
||||
# 示例安装命令
|
||||
cloudflared service install eyJhIjoiNjM1...
|
||||
# ^^^^^^^^^^^^
|
||||
# 这就是 Token
|
||||
```
|
||||
|
||||
**复制这个 Token**,后面要填到 `.env` 文件中。
|
||||
|
||||
### 2.3 配置 Tunnel 路由(Public Hostname)
|
||||
|
||||
在 Tunnel 创建页面,配置 **Public Hostname**:
|
||||
|
||||
| 字段 | 填写 | 说明 |
|
||||
|------|------|------|
|
||||
| Subdomain | `itdesk` | 前缀 |
|
||||
| Domain | `amanzac.com` | 你的域名 |
|
||||
| Type | `HTTP` | 容器内是 HTTP |
|
||||
| URL | `nginx` | Docker 容器名(同一网络内) |
|
||||
|
||||
> ⚠️ 注意:Type 选 **HTTP**(不是 HTTPS),因为 cloudflared 和 nginx 之间走的是容器内网 HTTP。SSL 由 Cloudflare Edge 终止。
|
||||
|
||||
点击 **Save tunnel**。
|
||||
|
||||
### 2.4 验证 DNS 记录
|
||||
|
||||
Cloudflare 会自动创建一条 CNAME 记录:
|
||||
- `itdesk.amanzac.com` → `cfargotunnel.com`
|
||||
|
||||
可在 Cloudflare Dashboard → DNS → Records 中确认。
|
||||
|
||||
---
|
||||
|
||||
## §3 项目文件部署到 NAS
|
||||
|
||||
### 3.1 上传项目文件
|
||||
|
||||
**方式一:Git Clone(推荐)**
|
||||
|
||||
如果 NAS 上有 Git:
|
||||
```bash
|
||||
# SSH 登录 NAS
|
||||
ssh admin@NAS_IP
|
||||
|
||||
# 创建项目目录
|
||||
mkdir -p /volume1/docker/wecom-it-desk
|
||||
cd /volume1/docker/wecom-it-desk
|
||||
|
||||
# 克隆项目
|
||||
git clone <你的仓库地址> .
|
||||
```
|
||||
|
||||
**方式二:SCP 上传**
|
||||
|
||||
从开发机上传构建好的文件:
|
||||
```powershell
|
||||
# 在 Windows PowerShell 中执行
|
||||
# 上传核心文件(不含 node_modules 和 .git)
|
||||
scp -r "D:\资料\03-项目开发\wecom_it_smart_desk\docker-compose.nas.yml" admin@NAS_IP:/volume1/docker/wecom-it-desk/
|
||||
scp -r "D:\资料\03-项目开发\wecom_it_smart_desk\.env.nas" admin@NAS_IP:/volume1/docker/wecom-it-desk/
|
||||
scp -r "D:\资料\03-项目开发\wecom_it_smart_desk\nginx" admin@NAS_IP:/volume1/docker/wecom-it-desk/
|
||||
scp -r "D:\资料\03-项目开发\wecom_it_smart_desk\backend" admin@NAS_IP:/volume1/docker/wecom-it-desk/
|
||||
scp -r "D:\资料\03-项目开发\wecom_it_smart_desk\frontend-h5\dist" admin@NAS_IP:/volume1/docker/wecom-it-desk/frontend-h5/dist/
|
||||
scp -r "D:\资料\03-项目开发\wecom_it_smart_desk\frontend-agent\dist" admin@NAS_IP:/volume1/docker/wecom-it-desk/frontend-agent/dist/
|
||||
```
|
||||
|
||||
**方式三:群晖 File Station**
|
||||
|
||||
把构建产物打包成 zip,通过 File Station 上传到 `/docker/wecom-it-desk/` 然后解压。
|
||||
|
||||
### 3.2 配置环境变量
|
||||
|
||||
```bash
|
||||
cd /volume1/docker/wecom-it-desk
|
||||
|
||||
# 复制模板
|
||||
cp .env.nas .env
|
||||
|
||||
# 编辑 .env 文件
|
||||
vi .env
|
||||
```
|
||||
|
||||
**必须修改的项**:
|
||||
|
||||
```bash
|
||||
# 1. 填入 Cloudflare Tunnel Token(从 §2.2 获取)
|
||||
CF_TUNNEL_TOKEN=eyJhIjoiNjM1... # ← 替换为你的实际 Token
|
||||
|
||||
# 2. 修改数据库密码
|
||||
POSTGRES_PASSWORD=YourStrongPassword123! # ← 替换为强密码
|
||||
|
||||
# 3. 如果 NAS 能访问公司内网 Dify,填入 Dify 配置
|
||||
# 如果不能访问,留空即可(AI 功能暂不可用,不影响阶段一)
|
||||
DIFY_API_URL=
|
||||
DIFY_API_KEY=
|
||||
```
|
||||
|
||||
### 3.3 构建前端(如果还没构建)
|
||||
|
||||
前端需要先在开发机(Windows)上构建,再上传 dist/ 目录:
|
||||
|
||||
```powershell
|
||||
# 在 Windows 开发机上
|
||||
cd "D:\资料\03-项目开发\wecom_it_smart_desk"
|
||||
|
||||
# 构建坐席端
|
||||
cd frontend-agent
|
||||
npm install
|
||||
npx vite build
|
||||
|
||||
# 构建 H5 员工端
|
||||
cd ..\frontend-h5
|
||||
npm install
|
||||
npx vite build
|
||||
```
|
||||
|
||||
构建产物在 `frontend-agent/dist/` 和 `frontend-h5/dist/` 中。
|
||||
|
||||
---
|
||||
|
||||
## §4 启动服务
|
||||
|
||||
### 4.1 SSH 登录 NAS 启动
|
||||
|
||||
```bash
|
||||
# SSH 登录 NAS
|
||||
ssh admin@NAS_IP
|
||||
|
||||
# 进入项目目录
|
||||
cd /volume1/docker/wecom-it-desk
|
||||
|
||||
# 启动所有容器(5 个容器)
|
||||
docker compose -f docker-compose.nas.yml up -d
|
||||
|
||||
# 等待约 30 秒,检查状态
|
||||
docker compose -f docker-compose.nas.yml ps
|
||||
```
|
||||
|
||||
**预期输出**:
|
||||
|
||||
| 容器名 | 状态 | 说明 |
|
||||
|--------|------|------|
|
||||
| wecom_it_cloudflared | Running | Cloudflare Tunnel |
|
||||
| wecom_it_nginx | Up (healthy) | 反向代理 |
|
||||
| wecom_it_backend | Up | FastAPI 后端 |
|
||||
| wecom_it_postgres | Up (healthy) | PostgreSQL |
|
||||
| wecom_it_redis | Up (healthy) | Redis |
|
||||
|
||||
### 4.2 验证服务
|
||||
|
||||
```bash
|
||||
# 1. 内网验证(在 NAS 上执行)
|
||||
curl http://localhost:18080/api/health
|
||||
# 预期输出: {"status":"ok","service":"wecom-it-smart-desk"}
|
||||
|
||||
# 2. 内网验证前端
|
||||
curl http://localhost:18080/itdesk/health
|
||||
# 预期输出: healthy
|
||||
|
||||
# 3. 公网验证(从任意有网络的设备)
|
||||
curl https://itdesk.amanzac.com/api/health
|
||||
# 预期输出: {"status":"ok","service":"wecom-it-smart-desk"}
|
||||
|
||||
# 4. 浏览器访问
|
||||
# H5 员工端: https://itdesk.amanzac.com/itdesk/
|
||||
# 坐席工作台: https://itdesk.amanzac.com/itagent/
|
||||
```
|
||||
|
||||
### 4.3 常用运维命令
|
||||
|
||||
```bash
|
||||
# 查看日志
|
||||
docker compose -f docker-compose.nas.yml logs -f backend # 后端日志
|
||||
docker compose -f docker-compose.nas.yml logs -f cloudflared # Tunnel 日志
|
||||
docker compose -f docker-compose.nas.yml logs -f nginx # Nginx 日志
|
||||
|
||||
# 重启某个服务
|
||||
docker compose -f docker-compose.nas.yml restart backend
|
||||
|
||||
# 停止所有服务
|
||||
docker compose -f docker-compose.nas.yml down
|
||||
|
||||
# 更新并重启(代码更新后)
|
||||
docker compose -f docker-compose.nas.yml up -d --build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §5 企微自建应用配置
|
||||
|
||||
### 5.1 创建自建应用
|
||||
|
||||
1. 登录 [企微管理后台](https://work.weixin.qq.com/wework_admin/frame)
|
||||
2. **应用管理** → **自建** → **创建应用**
|
||||
3. 填写:
|
||||
- 应用名称:`智能IT支持服务台`
|
||||
- 应用logo:上传一个图标
|
||||
- 可见范围:选择测试部门/人员
|
||||
|
||||
### 5.2 配置网页授权(OAuth2)
|
||||
|
||||
在应用详情页 → **网页授权及JS-SDK**:
|
||||
|
||||
| 配置项 | 填写 | 说明 |
|
||||
|--------|------|------|
|
||||
| 可信域名 | `itdesk.amanzac.com` | OAuth2 回调域名 |
|
||||
|
||||
> **验证方式**:Cloudflare Tunnel 已提供 HTTPS,下载企微提供的验证文件,放到 `frontend-h5/dist/` 根目录后重新构建。
|
||||
|
||||
### 5.3 配置应用主页
|
||||
|
||||
在应用详情页 → **应用主页**:
|
||||
|
||||
```
|
||||
https://itdesk.amanzac.com/itdesk/
|
||||
```
|
||||
|
||||
员工点击企微中的应用入口,直接打开 H5 页面。
|
||||
|
||||
### 5.4 配置接收消息(回调 URL)
|
||||
|
||||
在应用详情页 → **接收消息** → **设置API接收**:
|
||||
|
||||
| 配置项 | 填写 | 说明 |
|
||||
|--------|------|------|
|
||||
| URL | `https://itdesk.amanzac.com/api/wecom/callback` | 企微消息推送地址 |
|
||||
| Token | `wAqMCP` | 与 .env 中 WECOM_TOKEN 一致 |
|
||||
| EncodingAESKey | `KQY3cEsBc3rdi3xua9rPd5WxH8kYOhyASzWZQf75aJS` | 与 .env 中一致 |
|
||||
|
||||
> 点击保存时,企微会向 URL 发送验证请求,后端必须正常响应才能保存成功。
|
||||
|
||||
### 5.5 修改 AI 机器人转人工链接
|
||||
|
||||
在现有 AI 机器人的 Dify 工作流中,将转人工关键字触发的链接从:
|
||||
|
||||
```
|
||||
旧链接:https://work.weixin.qq.com/XXXX(员工服务入口)
|
||||
```
|
||||
|
||||
改为:
|
||||
|
||||
```
|
||||
新链接:https://itdesk.amanzac.com/itdesk/
|
||||
```
|
||||
|
||||
> 这样员工点击转人工链接后,会跳转到 H5 自建应用页面(而非企微员工服务窗口)。
|
||||
|
||||
---
|
||||
|
||||
## §6 阶段一功能测试清单
|
||||
|
||||
### 6.1 基础连通性测试
|
||||
|
||||
| # | 测试项 | 方法 | 预期结果 | 状态 |
|
||||
|---|--------|------|---------|------|
|
||||
| 1 | Cloudflare Tunnel 连通 | 浏览器访问 `https://itdesk.amanzac.com/` | 页面正常加载 | ⬜ |
|
||||
| 2 | 后端 API 健康 | 浏览器访问 `https://itdesk.amanzac.com/api/health` | 返回 `{"status":"ok"}` | ⬜ |
|
||||
| 3 | H5 员工端页面 | 浏览器访问 `https://itdesk.amanzac.com/itdesk/` | H5 页面渲染 | ⬜ |
|
||||
| 4 | 坐席工作台页面 | 浏览器访问 `https://itdesk.amanzac.com/itagent/` | 工作台页面渲染 | ⬜ |
|
||||
|
||||
### 6.2 OAuth2 登录测试
|
||||
|
||||
| # | 测试项 | 方法 | 预期结果 | 状态 |
|
||||
|---|--------|------|---------|------|
|
||||
| 5 | OAuth2 静默授权 | 在企微内点击应用入口 | H5 页面自动登录,显示员工身份 | ⬜ |
|
||||
| 6 | 身份识别 | 授权后查看 H5 页面 | 显示当前用户姓名/工号 | ⬜ |
|
||||
|
||||
### 6.3 坐席工作台测试
|
||||
|
||||
| # | 测试项 | 方法 | 预期结果 | 状态 |
|
||||
|---|--------|------|---------|------|
|
||||
| 7 | 会话列表 | 坐席登录工作台 | 显示进行中的会话 | ⬜ |
|
||||
| 8 | 聊天窗口 | 点击某个会话 | 显示完整对话记录 | ⬜ |
|
||||
| 9 | 发送消息 | 坐席输入文本发送 | 消息发送成功 | ⬜ |
|
||||
| 10 | 快速回复 | 点击快速回复面板 | 三级导航正常,模板可填入 | ⬜ |
|
||||
|
||||
### 6.4 端到端流程测试
|
||||
|
||||
| # | 测试项 | 方法 | 预期结果 | 状态 |
|
||||
|---|--------|------|---------|------|
|
||||
| 11 | AI 对话 → 转人工 | 员工与 AI 对话,触发转人工关键字 | 推送 H5 链接 | ⬜ |
|
||||
| 12 | 员工点击 H5 链接 | 点击推送的链接 | 跳转到 H5 页面,自动登录 | ⬜ |
|
||||
| 13 | 坐席收到会话 | 员工进入 H5 后 | 坐席工作台出现新会话 | ⬜ |
|
||||
| 14 | 坐席回复 | 坐席使用快速回复 | 员工 H5 页面显示回复 | ⬜ |
|
||||
| 15 | 企微通知 | 坐席回复后 | 员工收到企微应用消息通知 | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## §7 故障排查
|
||||
|
||||
### 7.1 Cloudflare Tunnel 连不上
|
||||
|
||||
```bash
|
||||
# 检查 cloudflared 容器日志
|
||||
docker compose -f docker-compose.nas.yml logs cloudflared
|
||||
|
||||
# 常见错误:
|
||||
# ERR error="failed to connect to Cloudflare edge"
|
||||
# → 检查 Token 是否正确
|
||||
# → 检查 NAS 是否能访问外网
|
||||
```
|
||||
|
||||
### 7.2 企微回调验证失败
|
||||
|
||||
```bash
|
||||
# 检查后端是否收到回调请求
|
||||
docker compose -f docker-compose.nas.yml logs backend | grep callback
|
||||
|
||||
# 常见原因:
|
||||
# 1. Token / EncodingAESKey 与 .env 不一致
|
||||
# 2. 后端回调路由路径不对(应为 /api/wecom/callback)
|
||||
# 3. Nginx 反代配置未正确转发
|
||||
```
|
||||
|
||||
### 7.3 OAuth2 授权失败
|
||||
|
||||
```
|
||||
常见原因:
|
||||
1. 可信域名未配置或未验证 → 企微管理后台检查
|
||||
2. redirect_uri 与可信域名不匹配 → 检查回调 URL
|
||||
3. CorpID 不正确 → 检查 .env 中的 WECOM_CORP_ID
|
||||
```
|
||||
|
||||
### 7.4 容器状态异常
|
||||
|
||||
```bash
|
||||
# 查看所有容器状态
|
||||
docker compose -f docker-compose.nas.yml ps
|
||||
|
||||
# 查看特定容器详细日志
|
||||
docker compose -f docker-compose.nas.yml logs --tail 100 backend
|
||||
|
||||
# 重启所有容器
|
||||
docker compose -f docker-compose.nas.yml restart
|
||||
|
||||
# 完全重建(代码更新后)
|
||||
docker compose -f docker-compose.nas.yml down
|
||||
docker compose -f docker-compose.nas.yml up -d --build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §8 与正式部署的区别
|
||||
|
||||
| 维度 | NAS 部署(测试) | 正式部署 |
|
||||
|------|----------------|---------|
|
||||
| 域名 | `itdesk.amanzac.com` | `it-dataquery.dc.servyou-it.com` |
|
||||
| 内网穿透 | Cloudflare Tunnel | 公司内网直连 |
|
||||
| HTTPS | Cloudflare 自动 | Nginx + 公司 CA 证书 |
|
||||
| 数据库密码 | 测试密码 | 强密码 + 审计 |
|
||||
| AI 引擎 | 可能不可用(Dify 在内网) | 可用 |
|
||||
| 员工数 | 测试人员(<10人) | 全公司 |
|
||||
| 企业微信认证 | 未认证(200人上限) | 已认证 |
|
||||
| 数据持久化 | Docker Volume | K8s PVC / 独立 PG 集群 |
|
||||
|
||||
---
|
||||
|
||||
## 附录 A:Cloudflare Tunnel 原理简述
|
||||
|
||||
```
|
||||
传统方式 Cloudflare Tunnel
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
互联网 ────→ │ 开放端口 + 公网IP │ 互联网 ────→ │ Cloudflare Edge │
|
||||
│ + SSL 证书 │ │ (自动HTTPS) │
|
||||
│ + DDNS/域名解析 │ └────────┬────────┘
|
||||
└─────────────────┘ │
|
||||
↑ │ Tunnel(长连接)
|
||||
│ │
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ NAS/服务器 │ cloudflared │ NAS/服务器 │
|
||||
│ (必须可达) │ ←──主动连接──→│ (无需开放端口) │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
|
||||
优势:
|
||||
1. 无需公网 IP — cloudflared 主动外连,不需要入站端口
|
||||
2. 无需 SSL 证书 — Cloudflare Edge 自动处理 HTTPS
|
||||
3. 无需 DDNS — 域名始终指向 Cloudflare
|
||||
4. 更安全 — 不暴露 NAS 任何端口到公网
|
||||
```
|
||||
@@ -0,0 +1,252 @@
|
||||
# v0.7.0 一键部署操作包(给生产运维)
|
||||
|
||||
> **目的**:把所有部署命令按顺序排好,生产运维复制粘贴即可完成 v0.7.0 部署。
|
||||
> **预计时间**:15-20 分钟(含等 docker pull)
|
||||
> **回滚**:每步都有 rollback 命令,任意一步失败立即回滚。
|
||||
|
||||
---
|
||||
|
||||
## 🔴 部署前 必做(用户自己操作)
|
||||
|
||||
### 1. 撤销并重签 Gitea token
|
||||
|
||||
```
|
||||
1. 浏览器打开 http://100.85.152.112:8418
|
||||
2. 右上角头像 → Settings → Applications → Manage Access Tokens
|
||||
3. 找到旧 token(workbuddy-claude),点 Revoke
|
||||
4. 点 Generate New Token,scope 选 "All",点 Generate
|
||||
5. 复制新 token(只显示一次),临时存到 ~/Downloads/gitea-new-token.txt
|
||||
```
|
||||
|
||||
### 2. 推送代码到 Gitea(用新 token)
|
||||
|
||||
```bash
|
||||
# 在本地工作目录(D:\资料\03-项目开发\wecom_it_smart_desk-claude\backend)
|
||||
cd /d/资料/03-项目开发/wecom_it_smart_desk-claude
|
||||
|
||||
# 临时把新 token 加进 remote URL(push 后立刻删除)
|
||||
git remote set-url origin "http://workbuddy-claude:新TOKEN@100.85.152.112:8418/simon/wecom_it_smart_desk.git"
|
||||
|
||||
# 推送 main + tag
|
||||
git push origin main
|
||||
git push origin v0.7.0
|
||||
|
||||
# push 成功后,立刻从 URL 移除 token
|
||||
git remote set-url origin "http://workbuddy-claude@100.85.152.112:8418/simon/wecom_it_smart_desk.git"
|
||||
|
||||
# 验证 token 已移除
|
||||
git remote -v
|
||||
# 期望:没有 token 字样
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🟢 部署操作(在生产服务器,SSH/PuTTY)
|
||||
|
||||
> 服务器 IP: **10.90.5.110** (内网),**115.236.188.3** (公网入口)
|
||||
> SSH 用户:堡垒机登录后跳转
|
||||
|
||||
### 步骤 1/6:备份当前生产状态
|
||||
|
||||
```bash
|
||||
# 1.1 备份 backend 当前镜像
|
||||
sudo docker tag wecom-it-desk-backend:latest wecom-it-desk-backend:v0.6.0-backup
|
||||
|
||||
# 1.2 备份 4 端 dist
|
||||
sudo mkdir -p /opt/wecom-it-desk/dist-backup-2026-06-21
|
||||
sudo cp -r /opt/wecom-it-desk/frontend-admin/dist /opt/wecom-it-desk/dist-backup-2026-06-21/admin
|
||||
sudo cp -r /opt/wecom-it-desk/frontend-agent/dist /opt/wecom-it-desk/dist-backup-2026-06-21/agent
|
||||
sudo cp -r /opt/wecom-it-desk/frontend-portal/dist /opt/wecom-it-desk/dist-backup-2026-06-21/portal
|
||||
sudo cp -r /opt/wecom-it-desk/frontend-h5/dist /opt/wecom-it-desk/dist-backup-2026-06-21/h5
|
||||
echo "备份完成"
|
||||
|
||||
# 1.3 备份 alembic 版本号(用于回滚确认)
|
||||
sudo docker exec wecom_it_postgres psql -U postgres -d wecom_it -c "SELECT version_num FROM alembic_version;"
|
||||
```
|
||||
|
||||
### 步骤 2/6:拉新 backend 镜像并跑 migration
|
||||
|
||||
```bash
|
||||
# 2.1 拉新镜像
|
||||
sudo docker pull wecom-it-desk-backend:v0.7.0
|
||||
|
||||
# 2.2 跑 migration(只 PG,SQLite 跳过)
|
||||
sudo docker exec wecom_it_backend alembic upgrade head
|
||||
# 期望输出:
|
||||
# Running upgrade 024 -> 025, messages.id UUID
|
||||
# Running upgrade <old> -> 022, qrcode_login
|
||||
# Running upgrade <old> -> 023, mfa_fields
|
||||
|
||||
# 2.3 验证 migration head
|
||||
sudo docker exec wecom_it_postgres psql -U postgres -d wecom_it -c "SELECT version_num FROM alembic_version;"
|
||||
# 期望:025_messages_id_uuid
|
||||
|
||||
# 2.4 验证 messages.id 已改为 UUID
|
||||
sudo docker exec wecom_it_postgres psql -U postgres -d wecom_it -c "\d messages" | grep "^ id"
|
||||
# 期望:类型为 uuid
|
||||
```
|
||||
|
||||
**🚨 若 migration 失败**:
|
||||
```bash
|
||||
sudo docker exec wecom_it_backend alembic downgrade -1
|
||||
# 联系 Claude 排查
|
||||
```
|
||||
|
||||
### 步骤 3/6:重启 backend 容器
|
||||
|
||||
```bash
|
||||
# 3.1 重启(用 v0.7.0 镜像)
|
||||
sudo docker restart wecom_it_backend
|
||||
|
||||
# 3.2 等 10 秒,检查启动日志
|
||||
sudo docker logs wecom_it_backend --tail 50
|
||||
|
||||
# 期望看到:
|
||||
# Application startup complete
|
||||
# Uvicorn running on http://0.0.0.0:8000
|
||||
# 没有 "ModuleNotFoundError" / "relation already exists" / "Restarting" 循环
|
||||
|
||||
# 3.3 健康检查
|
||||
sudo docker ps | grep wecom_it_backend
|
||||
# 期望:STATUS = Up X minutes (healthy)
|
||||
```
|
||||
|
||||
**🚨 若 backend 启动失败,回滚**:
|
||||
```bash
|
||||
sudo docker tag wecom-it-desk-backend:v0.6.0-backup wecom-it-desk-backend:latest
|
||||
sudo docker restart wecom_it_backend
|
||||
```
|
||||
|
||||
### 步骤 4/6:上传 4 端 dist 到宿主机
|
||||
|
||||
```bash
|
||||
# 4.1 在本地(Windows)打包 4 端 dist
|
||||
cd /d/资料/03-项目开发/wecom_it_smart_desk-claude
|
||||
tar -czf /tmp/frontend-v0.7.0.tar.gz \
|
||||
frontend-admin/dist frontend-agent/dist frontend-portal/dist frontend-h5/dist
|
||||
ls -la /tmp/frontend-v0.7.0.tar.gz
|
||||
|
||||
# 4.2 上传到生产服务器(走堡垒机)
|
||||
scp /tmp/frontend-v0.7.0.tar.gz <堡垒机用户>@<堡垒机>:/tmp/
|
||||
|
||||
# 4.3 在生产服务器解压
|
||||
ssh <堡垒机> # 跳到生产
|
||||
cd /opt/wecom-it-desk
|
||||
sudo tar -xzf /tmp/frontend-v0.7.0.tar.gz
|
||||
ls -la frontend-*/dist | head -20
|
||||
# 期望:每个 dist 都有 index.html + assets/
|
||||
|
||||
# 4.4 清理压缩包
|
||||
sudo rm /tmp/frontend-v0.7.0.tar.gz
|
||||
```
|
||||
|
||||
**🚨 若上传失败,回滚**:
|
||||
```bash
|
||||
# 4 端用备份恢复
|
||||
sudo cp -r /opt/wecom-it-desk/dist-backup-2026-06-21/admin/* /opt/wecom-it-desk/frontend-admin/dist/
|
||||
sudo cp -r /opt/wecom-it-desk/dist-backup-2026-06-21/agent/* /opt/wecom-it-desk/frontend-agent/dist/
|
||||
sudo cp -r /opt/wecom-it-desk/dist-backup-2026-06-21/portal/* /opt/wecom-it-desk/frontend-portal/dist/
|
||||
sudo cp -r /opt/wecom-it-desk/dist-backup-2026-06-21/h5/* /opt/wecom-it-desk/frontend-h5/dist/
|
||||
```
|
||||
|
||||
### 步骤 5/6:应用 nginx access_log 脱敏 + reload
|
||||
|
||||
```bash
|
||||
# 5.1 验证当前 nginx 容器名(下划线不是横杠!)
|
||||
sudo docker ps | grep wecom_it_nginx
|
||||
# 期望:0.0.0.0:80->80/tcp wecom_it_nginx
|
||||
|
||||
# 5.2 进入容器加 log_format 脱敏配置
|
||||
sudo docker exec wecom_it_nginx bash -c '
|
||||
cat > /etc/nginx/conf.d/log-format.conf << "EOF"
|
||||
log_format secure $remote_addr - $remote_user [$time_local] "$request_method $uri $server_protocol" $status $body_bytes_sent "$http_referer" "$http_user_agent";
|
||||
access_log /var/log/nginx/access.log secure;
|
||||
EOF
|
||||
'
|
||||
# 验证写入
|
||||
sudo docker exec wecom_it_nginx cat /etc/nginx/conf.d/log-format.conf
|
||||
|
||||
# 5.3 验证配置
|
||||
sudo docker exec wecom_it_nginx nginx -t
|
||||
# 期望:nginx: configuration file /etc/nginx/nginx.conf test is successful
|
||||
|
||||
# 5.4 reload(不重启容器)
|
||||
sudo docker exec wecom_it_nginx nginx -s reload
|
||||
|
||||
# 5.5 验证 reload 生效
|
||||
sudo docker exec wecom_it_nginx tail -3 /var/log/nginx/access.log
|
||||
# 期望:没有 Authorization: Bearer xxx 字样
|
||||
```
|
||||
|
||||
**🚨 若 nginx reload 失败**:
|
||||
```bash
|
||||
# 恢复默认 access_log
|
||||
sudo docker exec wecom_it_nginx bash -c 'echo "access_log /var/log/nginx/access.log;" > /etc/nginx/conf.d/log-format.conf'
|
||||
sudo docker exec wecom_it_nginx nginx -t
|
||||
sudo docker exec wecom_it_nginx nginx -s reload
|
||||
```
|
||||
|
||||
### 步骤 6/6:验证域名路由
|
||||
|
||||
```bash
|
||||
# 6.1 验证 4 个 location 都返回 200
|
||||
curl -I https://<生产域名>/itportal/ # 应 200
|
||||
curl -I https://<生产域名>/itagent/ # 应 200
|
||||
curl -I https://<生产域名>/itadmin/ # 应 200
|
||||
curl -I https://<生产域名>/itdesk/ # 应 200
|
||||
|
||||
# 6.2 验证 API 端点
|
||||
curl https://<生产域名>/api/health
|
||||
# 期望:{"code":0,"data":{"status":"ok"}}
|
||||
|
||||
# 6.3 验证扫码登录端点
|
||||
curl -X POST https://<生产域名>/api/auth_qrcode/create -H "Content-Type: application/json" -d '{}'
|
||||
# 期望:{"code":0,"data":{"ticket":"...","qrcode_url":"...","expires_in":120}}
|
||||
|
||||
# 6.4 验证 MFA 端点(无 token 应 401)
|
||||
curl https://<生产域名>/api/mfa/status
|
||||
# 期望:401 Unauthorized
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🟡 部署后 必做(用户/QA 验收)
|
||||
|
||||
按 `docs/E2E-CHECKLIST-v0.7.0.md` 35 项,逐项打勾。
|
||||
|
||||
**关键项**:
|
||||
- [ ] 浏览器扫码登录全流程(5 子项)
|
||||
- [ ] MFA 绑定 + 30 分钟有效期
|
||||
- [ ] 高危操作守卫(5 类端点)
|
||||
- [ ] WS 推送无 missing argument 错误
|
||||
- [ ] 消息 ID 改为 UUID,无 500
|
||||
- [ ] nginx access_log 无 Authorization/Cookie
|
||||
|
||||
---
|
||||
|
||||
## 🔴 部署后 1 周观察(用户拍板)
|
||||
|
||||
- 一切正常 → 清理 `/opt/wecom-it-desk/dist-backup-2026-06-21/` 和 `~/Downloads/patch1/`
|
||||
- 任何 regression → 用 `DEPLOY-LOGIN-MIGRATION-v0.7.0.md` 末尾的"回滚预案"恢复
|
||||
|
||||
---
|
||||
|
||||
## 📊 部署时间预估
|
||||
|
||||
| 步骤 | 预计时间 | 风险 |
|
||||
|---|---|---|
|
||||
| 1. 备份 | 1 min | 低 |
|
||||
| 2. migration | 1 min | 中(若冲突需手动) |
|
||||
| 3. 重启 backend | 2 min(含等健康) | 中(若镜像问题需回滚) |
|
||||
| 4. 上传 4 端 | 5 min(含上传) | 低 |
|
||||
| 5. nginx reload | 1 min | 低 |
|
||||
| 6. 验证 | 5 min | 低 |
|
||||
| **总计** | **15 min** | |
|
||||
|
||||
---
|
||||
|
||||
## 🆘 紧急联系人
|
||||
|
||||
- 部署问题:本会话 + Claude
|
||||
- backend 代码:Claude session
|
||||
- 生产服务器:IT 基础设施组
|
||||
@@ -0,0 +1,87 @@
|
||||
# 堡垒机运维工具 (jumpserver-ops)
|
||||
|
||||
## 概述
|
||||
|
||||
通过 JumpServer 堡垒机自动化执行远程命令、文件上传下载。统一入口为 `jms_ops.py`,支持 4 种操作模式。
|
||||
|
||||
## 连接方式决策
|
||||
|
||||
| 场景 | 连接方式 |
|
||||
|------|----------|
|
||||
| **默认** | 通过堡垒机跳转(大多数内网服务器) |
|
||||
| **例外** | 直连(NAS、开发机等)需单独配置 |
|
||||
|
||||
> **规则**:默认都需要通过堡垒机,除非明确告知某台服务器是直连。
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 远程命令执行(推荐)
|
||||
|
||||
```bash
|
||||
# 单命令(纯文本输出)
|
||||
python scripts/jms_ops.py exec -c "docker ps"
|
||||
|
||||
# 多命令串行(一次登录,5x 提速)
|
||||
python scripts/jms_ops.py exec -c "hostname" -c "uptime" -c "docker ps"
|
||||
|
||||
# 并行模式(不冲突的长命令)
|
||||
python scripts/jms_ops.py exec -c "docker logs nginx --tail 100" -c "df -h" --parallel
|
||||
```
|
||||
|
||||
### 2. 批量命令
|
||||
|
||||
```bash
|
||||
python scripts/jms_ops.py batch -f commands.txt
|
||||
```
|
||||
|
||||
### 3. 文件上传
|
||||
|
||||
```bash
|
||||
# 本地 → 堡垒机 → 目标服务器
|
||||
python scripts/jms_ops.py upload 本地文件.conf /tmp/远程路径.conf
|
||||
```
|
||||
|
||||
### 4. 文件下载
|
||||
|
||||
```bash
|
||||
# 目标服务器 → 堡垒机 → 本地
|
||||
python scripts/jms_ops.py download /远程路径.conf ./本地文件.conf
|
||||
```
|
||||
|
||||
## 方案选择依据
|
||||
|
||||
| 需求 | 推荐方案 | 速度 |
|
||||
|------|----------|------|
|
||||
| 执行命令获取文本结果 | v16 REST API + plink PTY | ~15s |
|
||||
| 执行命令看界面效果 | v10 Web CLI(截图) | ~60s |
|
||||
| 小文件传输 (<100KB) | upload/download | - |
|
||||
| 大文件传输 | elFinder Web UI | - |
|
||||
|
||||
## 目标服务器配置
|
||||
|
||||
当前预设目标:`hz-oa-ai-g-dataquery-90-5-110` (10.90.5.110)
|
||||
|
||||
新增直连服务器时,需提供:
|
||||
- IP 地址
|
||||
- 端口(默认 22)
|
||||
- 用户名
|
||||
- 认证方式(密码或 SSH 密钥)
|
||||
|
||||
## 故障排除
|
||||
|
||||
| 问题 | 解决方法 |
|
||||
|------|----------|
|
||||
| 登录失败 | 检查 `config/jumpserver_config.json` 密码是否正确(Base64 编码) |
|
||||
| MFA 失败 | 确认 `scripts/otp_secret.key` 存在且系统时间准确 |
|
||||
| 资产未找到 | 确认目标名称与 JumpServer 中显示一致 |
|
||||
|
||||
## 脚本位置
|
||||
|
||||
```
|
||||
C:\Users\simon\.workbuddy\skills\jumpserver-ops\scripts\jms_ops.py
|
||||
```
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [服务器部署手册](./服务器部署手册.md)
|
||||
- [版本更新说明](./05-版本更新说明-v1.1.0-20260614.md)
|
||||
@@ -0,0 +1,286 @@
|
||||
# 企微智能IT支持服务台 — 服务器部署指南
|
||||
|
||||
> 目标服务器:`10.90.5.110`(Linux)
|
||||
> 域名:`itsupport.servyou.com.cn`
|
||||
> 更新日期:2026-06-12
|
||||
|
||||
---
|
||||
|
||||
## 一、前置条件
|
||||
|
||||
- [x] 服务器可访问内网(火绒 `huorong.oa.servyou-it.com`、Dify `yw-dify.dc.servyou-it.com`)
|
||||
- [ ] 服务器已安装 Docker + Docker Compose
|
||||
- [ ] 域名 `itsupport.servyou.com.cn` DNS 已解析到 `10.90.5.110`(或先用 IP 访问)
|
||||
|
||||
---
|
||||
|
||||
## 二、安装 Docker(如已安装跳过)
|
||||
|
||||
### 2.1 检查是否已安装
|
||||
|
||||
```bash
|
||||
docker --version # 应显示 Docker version 24.x+
|
||||
docker compose version # 应显示 Docker Compose version v2.x+
|
||||
```
|
||||
|
||||
如果已安装,跳到第三步。
|
||||
|
||||
### 2.2 安装 Docker(CentOS/RHEL)
|
||||
|
||||
```bash
|
||||
# 1. 卸载旧版本(如有)
|
||||
sudo yum remove -y docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine
|
||||
|
||||
# 2. 安装 yum 工具
|
||||
sudo yum install -y yum-utils
|
||||
|
||||
# 3. 添加 Docker 官方仓库(国内用阿里云镜像加速)
|
||||
sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
|
||||
|
||||
# 4. 安装 Docker Engine + Compose 插件
|
||||
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
|
||||
# 5. 启动 Docker 并设置开机自启
|
||||
sudo systemctl start docker
|
||||
sudo systemctl enable docker
|
||||
|
||||
# 6. 验证安装
|
||||
docker --version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
### 2.3 安装 Docker(Ubuntu/Debian)
|
||||
|
||||
```bash
|
||||
# 1. 卸载旧版本
|
||||
sudo apt-get remove -y docker docker-engine docker.io containerd runc
|
||||
|
||||
# 2. 安装依赖
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ca-certificates curl gnupg
|
||||
|
||||
# 3. 添加 Docker GPG 密钥
|
||||
sudo install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
sudo chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
|
||||
# 4. 添加仓库
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
# 5. 安装
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
|
||||
# 6. 启动
|
||||
sudo systemctl start docker
|
||||
sudo systemctl enable docker
|
||||
|
||||
# 7. 验证
|
||||
docker --version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
### 2.4(可选)非 root 用户使用 Docker
|
||||
|
||||
```bash
|
||||
# 将当前用户加入 docker 组,避免每次 sudo
|
||||
sudo usermod -aG docker $USER
|
||||
# 重新登录生效
|
||||
newgrp docker
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、上传部署包
|
||||
|
||||
### 3.1 在服务器创建目录
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /opt/wecom-it-desk
|
||||
sudo chown $USER:$USER /opt/wecom-it-desk
|
||||
```
|
||||
|
||||
### 3.2 上传文件
|
||||
|
||||
在本地 Windows 用 SCP/SFTP 上传部署包:
|
||||
|
||||
```powershell
|
||||
# 方法1:用 scp 命令(Git Bash 或 PowerShell)
|
||||
scp it-smart-desk-server-deploy.zip user@10.90.5.110:/opt/wecom-it-desk/
|
||||
|
||||
# 方法2:用 WinSCP / FileZilla 图形化工具上传
|
||||
```
|
||||
|
||||
### 3.3 解压
|
||||
|
||||
```bash
|
||||
cd /opt/wecom-it-desk
|
||||
unzip it-smart-desk-server-deploy.zip
|
||||
# 解压后目录结构:
|
||||
# /opt/wecom-it-desk/
|
||||
# ├── docker-compose.yml
|
||||
# ├── .env
|
||||
# ├── nginx/
|
||||
# │ └── nginx.conf
|
||||
# ├── backend/
|
||||
# │ ├── Dockerfile
|
||||
# │ ├── app/
|
||||
# │ ├── alembic/
|
||||
# │ ├── alembic.ini
|
||||
# │ └── requirements.txt
|
||||
# ├── frontend-h5/dist/
|
||||
# ├── frontend-agent/dist/
|
||||
# └── frontend-admin/dist/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、修改配置
|
||||
|
||||
### 4.1 编辑环境变量
|
||||
|
||||
```bash
|
||||
cd /opt/wecom-it-desk
|
||||
vim .env
|
||||
```
|
||||
|
||||
**必须确认的配置项:**
|
||||
|
||||
| 配置项 | 当前值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `WECOM_CORP_ID` | `wwa8c87970b2011f41` | 企微企业ID |
|
||||
| `WECOM_AGENT_ID` | `1000133` | 企微应用AgentId |
|
||||
| `WECOM_SECRET` | `EOtQsl...` | 企微应用Secret |
|
||||
| `MOCK_LOGIN_ENABLED` | `true` | 测试阶段用 true,正式上线改为 false |
|
||||
| `DIFY_API_KEY` | `http://...` | Dify AI 服务 Key |
|
||||
| `POSTGRES_PASSWORD` | `wecom_secret_2026` | 数据库密码(首次初始化后不可改) |
|
||||
|
||||
### 4.2 确认域名解析
|
||||
|
||||
```bash
|
||||
# 测试域名是否指向本机
|
||||
ping itsupport.servyou.com.cn
|
||||
# 如果还没配 DNS,可以先在 .env 中把 CORS_ORIGINS 改为:
|
||||
# CORS_ORIGINS=http://10.90.5.110
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、启动服务
|
||||
|
||||
### 5.1 首次启动
|
||||
|
||||
```bash
|
||||
cd /opt/wecom-it-desk
|
||||
|
||||
# 构建后端镜像 + 启动所有容器
|
||||
docker compose up -d --build
|
||||
|
||||
# 首次启动需要 2-3 分钟(下载镜像 + 构建后端 + 数据库迁移)
|
||||
```
|
||||
|
||||
### 5.2 查看启动状态
|
||||
|
||||
```bash
|
||||
# 查看所有容器状态(应全部 healthy/running)
|
||||
docker compose ps
|
||||
|
||||
# 查看实时日志(Ctrl+C 退出)
|
||||
docker compose logs -f
|
||||
|
||||
# 只看后端日志
|
||||
docker compose logs -f backend
|
||||
```
|
||||
|
||||
**预期输出(`docker compose ps`):**
|
||||
|
||||
```
|
||||
NAME STATUS PORTS
|
||||
wecom_it_postgres Up (healthy) 5432/tcp
|
||||
wecom_it_redis Up (healthy) 6379/tcp
|
||||
wecom_it_backend Up (healthy) 8000/tcp
|
||||
wecom_it_nginx Up (healthy) 0.0.0.0:80->80/tcp
|
||||
```
|
||||
|
||||
### 5.3 验证服务
|
||||
|
||||
```bash
|
||||
# 1. 健康检查
|
||||
curl http://localhost/itdesk/health
|
||||
# 预期:healthy
|
||||
|
||||
# 2. 后端 API
|
||||
curl http://localhost/api/health
|
||||
# 预期:{"status":"ok"}
|
||||
|
||||
# 3. 浏览器访问
|
||||
# H5 员工端:http://itsupport.servyou.com.cn/itdesk/
|
||||
# 坐席工作台:http://itsupport.servyou.com.cn/itagent/
|
||||
# 管理后台:http://itsupport.servyou.com.cn/itadmin/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、常用运维命令
|
||||
|
||||
```bash
|
||||
cd /opt/wecom-it-desk
|
||||
|
||||
# 重启所有服务
|
||||
docker compose restart
|
||||
|
||||
# 只重启后端(代码更新后)
|
||||
docker compose restart backend
|
||||
|
||||
# 查看某个容器的日志
|
||||
docker compose logs -f --tail=100 backend
|
||||
|
||||
# 进入后端容器调试
|
||||
docker compose exec backend /bin/sh
|
||||
|
||||
# 停止所有服务
|
||||
docker compose down
|
||||
|
||||
# 停止并删除数据卷(⚠️ 会清空数据库!)
|
||||
docker compose down -v
|
||||
|
||||
# 查看磁盘使用
|
||||
docker system df
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、代码更新流程
|
||||
|
||||
当有新代码需要部署时:
|
||||
|
||||
```bash
|
||||
# 1. 上传新的部署包,覆盖旧文件
|
||||
# 2. 重新构建并启动
|
||||
cd /opt/wecom-it-desk
|
||||
docker compose up -d --build
|
||||
|
||||
# 如果只有前端更新,不需要重建后端镜像:
|
||||
docker compose up -d --no-deps --build nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、故障排查
|
||||
|
||||
| 问题 | 排查命令 | 常见原因 |
|
||||
|------|----------|----------|
|
||||
| 容器反复重启 | `docker compose logs backend` | 数据库连接失败、环境变量缺失 |
|
||||
| 页面空白 | `docker compose logs nginx` | 前端 dist 目录为空或路径错误 |
|
||||
| API 404 | `curl http://localhost:8000/health` | 后端未启动或 nginx proxy 配置错误 |
|
||||
| 数据库连接失败 | `docker compose logs postgres` | POSTGRES_PASSWORD 与 DATABASE_URL 不一致 |
|
||||
| 端口被占用 | `sudo lsof -i :80` | 其他服务占用 80 端口 |
|
||||
|
||||
---
|
||||
|
||||
## 九、安全建议(后续)
|
||||
|
||||
- [ ] 配置 HTTPS(Nginx 反代 + 证书,或使用反向代理)
|
||||
- [ ] 修改默认数据库密码
|
||||
- [ ] 关闭 Mock 登录(`MOCK_LOGIN_ENABLED=false`)
|
||||
- [ ] 限制 80 端口访问来源(防火墙规则)
|
||||
@@ -0,0 +1,595 @@
|
||||
# v0.7.0 Hotfix #63 回滚方案
|
||||
|
||||
> **场景**: 生产 backend 容器 `wecom_it_backend` 已回滚到 `v0.7.0-backup-pre-qrfix` 镜像(因 `v0.7.0.1-hotfix1` 失败)。现在通过 jumpserver 终端用 base64 分段 echo 上传 `auth_qrcode.py` + `qrcode_service.py` 到 `/tmp/`,然后 `docker cp` 到容器,`pip install qrcode[pil]`,`restart`。本文件给出**失败时的回滚方案**。
|
||||
>
|
||||
> **目标读者**: 运维小白(用户)。每步带中文注释,失败兜底齐全。
|
||||
>
|
||||
> **生效条件**: 当且仅当 `curl /api/auth_qrcode/create` 行为异常时触发。
|
||||
>
|
||||
> **回滚总目标**: 1 分钟内把 backend 拉回到 `v0.7.0-backup-pre-qrfix` 镜像,业务不中断。
|
||||
|
||||
---
|
||||
|
||||
## 0. 当前状态快照(回滚前必看)
|
||||
|
||||
回滚前先确认现在到底在跑哪个镜像、哪 2 个文件、pip 装了什么。**3 条命令 30 秒**:
|
||||
|
||||
```bash
|
||||
# 1) 看当前容器用的镜像 ID
|
||||
docker inspect wecom_it_backend --format '{{.Image}}' | head -c 12
|
||||
# 期望: 现在(回滚后)应该是 v0.7.0-backup-pre-qrfix 镜像 ID
|
||||
# 如果 hotfix 装好,可能是 wecom-it-desk-backend:patched 或 latest
|
||||
|
||||
# 2) 看容器内 2 个文件的修改时间(确认 hotfix 是否真生效)
|
||||
docker exec wecom_it_backend stat -c '%Y %n' \
|
||||
/app/app/api/auth_qrcode.py \
|
||||
/app/app/services/qrcode_service.py
|
||||
# 期望 hotfix 装好后: 数字是最近的(今天/刚刚);否则是 6/15 左右的旧时间
|
||||
|
||||
# 3) 看 qrcode 是否真装上
|
||||
docker exec wecom_it_backend pip show qrcode 2>&1 | head -5
|
||||
# 期望装好: Name: qrcode Version: 7.4.2
|
||||
# 没装: WARNING: Package(s) not found: qrcode
|
||||
```
|
||||
|
||||
把这 3 个输出截图给 Claude,后续诊断直接定位问题。
|
||||
|
||||
---
|
||||
|
||||
## 1. 失败可能性清单(7 种 + 回滚命令)
|
||||
|
||||
| # | 失败模式 | 现象 | 检测命令 | 回滚命令 |
|
||||
|---|---------|------|---------|---------|
|
||||
| F1 | `qrcode` pip 安装失败 | `restart` 后容器立刻 exit | `docker ps -a \| grep wecom_it_backend` 看到 `Restarting` 或 `Exited` | 见 §1.1 |
|
||||
| F2 | 容器启动失败(模块导入报错) | backend 启动循环重启 | `docker logs wecom_it_backend --tail 30` 看到 `ModuleNotFoundError` / `ImportError` / `SyntaxError` | 见 §1.2 |
|
||||
| F3 | `curl` `/api/auth_qrcode/create` 返回 500 | 容器 healthy 但端点挂 | `curl -k -X POST https://itsupport.servyou.com.cn/api/auth_qrcode/create` | 见 §1.3 |
|
||||
| F4 | `curl` 返回 200 但**没** `qrcode_png_base64` 字段 | 代码覆盖不彻底(还是旧文件) | `curl ... \| python -m json.tool \| grep qrcode_png_base64` | 见 §1.4 |
|
||||
| F5 | `curl` 返回 502/504 | nginx 找不到 backend 容器 | `docker ps \| grep backend` | 见 §1.5 |
|
||||
| F6 | 端口冲突(8000 被占) | 容器一直 restarting | `docker logs wecom_it_backend --tail 50 \| grep -i "address already"` | 见 §1.6 |
|
||||
| F7 | 镜像 ID 错乱/标签漂移 | `restart` 后跑的镜像不是预期的 | `docker images \| grep wecom-it-desk-backend` | 见 §1.7 |
|
||||
|
||||
### 1.1 F1: qrcode pip 安装失败回滚
|
||||
|
||||
**原因**: `pip install qrcode[pil]` 网络抽风 / 镜像精简版没 gcc / 版本冲突。
|
||||
|
||||
**回滚命令**(jumpserver 终端执行,root 用户):
|
||||
|
||||
```bash
|
||||
# 1) 停容器
|
||||
docker stop wecom_it_backend
|
||||
|
||||
# 2) 删容器(保留数据卷 / 网络)
|
||||
docker rm wecom_it_backend
|
||||
|
||||
# 3) 用回滚镜像起新容器(关键: 命令行要跟当前生产容器完全一致)
|
||||
# 抄一下当前容器的完整 run 命令,免得环境变量 / 挂载丢了
|
||||
docker run -d \
|
||||
--name wecom_it_backend \
|
||||
--restart=always \
|
||||
--network wecom_it_network \
|
||||
-e DATABASE_URL='...' \
|
||||
-e REDIS_URL='...' \
|
||||
-e WECOM_CORP_ID='...' \
|
||||
-v /opt/wecom-it-desk/backend:/app:rw \
|
||||
wecom-it-desk-backend:v0.7.0-backup-pre-qrfix
|
||||
|
||||
# 4) 验证
|
||||
docker ps | grep wecom_it_backend
|
||||
# 期望: STATUS = Up X seconds (healthy)
|
||||
```
|
||||
|
||||
> **简化方案**(如果你之前记录了完整 run 命令):
|
||||
>
|
||||
> ```bash
|
||||
> # 直接用 docker commit 出来的镜像
|
||||
> docker run -d --name wecom_it_backend <完整原参数> \
|
||||
> wecom-it-desk-backend:v0.7.0-backup-pre-qrfix
|
||||
> ```
|
||||
|
||||
### 1.2 F2: 模块导入报错回滚
|
||||
|
||||
**原因**: `auth_qrcode.py` 或 `qrcode_service.py` 上传时 base64 解码坏掉 / Python 缩进错。
|
||||
|
||||
**检测**:
|
||||
|
||||
```bash
|
||||
docker logs wecom_it_backend --tail 30 2>&1 | grep -E "(ModuleNotFoundError|ImportError|SyntaxError|IndentationError)"
|
||||
```
|
||||
|
||||
**回滚命令**(比 F1 简单,只用覆盖文件 + 重启,不用换镜像):
|
||||
|
||||
```bash
|
||||
# 1) 从 backup 镜像里把原版文件拷出来
|
||||
docker create --name tmp_rollback wecom-it-desk-backend:v0.7.0-backup-pre-qrfix
|
||||
docker cp tmp_rollback:/app/app/api/auth_qrcode.py /tmp/auth_qrcode.py.bak
|
||||
docker cp tmp_rollback:/app/app/services/qrcode_service.py /tmp/qrcode_service.py.bak
|
||||
docker rm tmp_rollback
|
||||
|
||||
# 2) 覆盖回滚(注意: bind mount 模式下必须改宿主机路径)
|
||||
docker cp /tmp/auth_qrcode.py.bak wecom_it_backend:/app/app/api/auth_qrcode.py
|
||||
docker cp /tmp/qrcode_service.py.bak wecom_it_backend:/app/app/services/qrcode_service.py
|
||||
|
||||
# 3) 重启
|
||||
docker restart wecom_it_backend
|
||||
|
||||
# 4) 验证
|
||||
sleep 5
|
||||
docker ps | grep wecom_it_backend
|
||||
curl -k -X POST https://itsupport.servyou.com.cn/api/auth_qrcode/create | python -m json.tool
|
||||
```
|
||||
|
||||
### 1.3 F3: create 端点 500 回滚
|
||||
|
||||
**原因**: `qrcode_service.py` 内的 `_render_qrcode_png` 抛异常(`qrcode` 没装好 / PIL 缺包)。
|
||||
|
||||
**检测**:
|
||||
|
||||
```bash
|
||||
# 拿返回内容
|
||||
curl -k -X POST https://itsupport.servyou.com.cn/api/auth_qrcode/create -v 2>&1 | tail -20
|
||||
|
||||
# 看后端日志,找 traceback
|
||||
docker logs wecom_it_backend --tail 50 2>&1 | grep -A 20 "Traceback"
|
||||
```
|
||||
|
||||
**回滚命令**: 同 §1.2(覆盖文件 + restart)。如果还 500,升级到 §1.1(换镜像)。
|
||||
|
||||
### 1.4 F4: 没 qrcode_png_base64 字段回滚
|
||||
|
||||
**原因**: `docker cp` 后容器内文件**没真覆盖**(典型 bind mount / overlay fs 坑)。
|
||||
|
||||
**检测**:
|
||||
|
||||
```bash
|
||||
curl -k -X POST https://itsupport.servyou.com.cn/api/auth_qrcode/create | python -m json.tool
|
||||
# 看 data 字段里有没有 "qrcode_png_base64"
|
||||
# 没有 → 文件没真覆盖
|
||||
```
|
||||
|
||||
**回滚命令**(强制覆盖):
|
||||
|
||||
```bash
|
||||
# 1) 确认宿主机上 bind mount 的文件位置
|
||||
docker inspect wecom_it_backend --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}' | grep app
|
||||
# 输出: /opt/wecom-it-desk/backend -> /app
|
||||
|
||||
# 2) 直接改宿主机路径(这是 bind mount 唯一能稳定生效的方式)
|
||||
ls -la /opt/wecom-it-desk/backend/app/api/auth_qrcode.py /opt/wecom-it-desk/backend/app/services/qrcode_service.py
|
||||
|
||||
# 3) 如果是新文件没生效,先 rm 再 cp
|
||||
rm -f /opt/wecom-it-desk/backend/app/api/auth_qrcode.py
|
||||
rm -f /opt/wecom-it-desk/backend/app/services/qrcode_service.py
|
||||
cp /tmp/auth_qrcode.py /opt/wecom-it-desk/backend/app/api/
|
||||
cp /tmp/qrcode_service.py /opt/wecom-it-desk/backend/app/services/
|
||||
|
||||
# 4) 必须 restart 容器(overlay 不会自动 sync bind mount)
|
||||
docker restart wecom_it_backend
|
||||
|
||||
# 5) 验证
|
||||
sleep 5
|
||||
curl -k -X POST https://itsupport.servyou.com.cn/api/auth_qrcode/create | python -m json.tool | grep qrcode_png_base64
|
||||
```
|
||||
|
||||
### 1.5 F5: 502/504 回滚
|
||||
|
||||
**原因**: nginx 解析到旧 backend 容器,或容器网络断了。
|
||||
|
||||
**检测**:
|
||||
|
||||
```bash
|
||||
# 1) 看 backend 容器在不在
|
||||
docker ps | grep wecom_it_backend
|
||||
|
||||
# 2) nginx 容器内直接测 backend
|
||||
docker exec wecom_it_nginx wget -qO- --timeout=3 http://wecom_it_backend:8000/api/ready
|
||||
# 期望: {"status":"ready",...}
|
||||
# 502 → 网络通但 backend 内部挂
|
||||
# timeout → 网络都不通
|
||||
```
|
||||
|
||||
**回滚命令**(全链路重拉):
|
||||
|
||||
```bash
|
||||
# 1) 停 backend
|
||||
docker stop wecom_it_backend
|
||||
|
||||
# 2) 删容器
|
||||
docker rm wecom_it_backend
|
||||
|
||||
# 3) 用回滚镜像起(完整参数)
|
||||
docker run -d --name wecom_it_backend \
|
||||
--restart=always --network wecom_it_network \
|
||||
<完整原参数> \
|
||||
wecom-it-desk-backend:v0.7.0-backup-pre-qrfix
|
||||
|
||||
# 4) 重新加载 nginx(让 upstream 刷新)
|
||||
docker exec wecom_it_nginx nginx -s reload
|
||||
|
||||
# 5) 验证
|
||||
sleep 10
|
||||
curl -k https://itsupport.servyou.com.cn/api/ready
|
||||
```
|
||||
|
||||
### 1.6 F6: 端口冲突回滚
|
||||
|
||||
**原因**: 旧容器没删干净 / 8000 被别的进程占。
|
||||
|
||||
**检测**:
|
||||
|
||||
```bash
|
||||
docker logs wecom_it_backend --tail 50 2>&1 | grep -i "address already in use"
|
||||
# 或
|
||||
ss -tlnp | grep 8000
|
||||
```
|
||||
|
||||
**回滚命令**:
|
||||
|
||||
```bash
|
||||
# 1) 看谁占 8000
|
||||
ss -tlnp | grep ':8000'
|
||||
|
||||
# 2) 通常是僵尸容器,删它
|
||||
docker ps -a | grep ":8000" # 不一定能直接看到
|
||||
docker rm -f wecom_it_backend # 强制删当前容器
|
||||
|
||||
# 3) 再起
|
||||
docker run -d --name wecom_it_backend <完整原参数> wecom-it-desk-backend:v0.7.0-backup-pre-qrfix
|
||||
```
|
||||
|
||||
### 1.7 F7: 镜像 ID 错乱回滚
|
||||
|
||||
**原因**: `docker run` 时没指定 tag,默认拉 `latest`,可能不是预期的。
|
||||
|
||||
**检测**:
|
||||
|
||||
```bash
|
||||
docker images --format '{{.Repository}}:{{.Tag}} {{.ID}} {{.CreatedSince}}' | grep wecom-it-desk-backend
|
||||
# 应该看到 3 个:
|
||||
# wecom-it-desk-backend:v0.7.0-backup-pre-qrfix (回滚用的)
|
||||
# wecom-it-desk-backend:latest (可能等于上面那个,也可能等于 patched)
|
||||
# wecom-it-desk-backend:patched (hotfix 试装版,如果有)
|
||||
```
|
||||
|
||||
**回滚命令**(显式指定 tag):
|
||||
|
||||
```bash
|
||||
# 拿到回滚镜像的精确 ID
|
||||
ROLLBACK_IMAGE=$(docker images -q wecom-it-desk-backend:v0.7.0-backup-pre-qrfix)
|
||||
echo "回滚镜像 ID: $ROLLBACK_IMAGE"
|
||||
|
||||
# 删旧容器
|
||||
docker stop wecom_it_backend && docker rm wecom_it_backend
|
||||
|
||||
# 用**精确 ID** 起(避免 tag 被覆盖)
|
||||
docker run -d --name wecom_it_backend <完整原参数> $ROLLBACK_IMAGE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 健康检查命令速查
|
||||
|
||||
### 2.1 容器层
|
||||
|
||||
```bash
|
||||
# 状态(看是不是 healthy)
|
||||
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}' | grep wecom_it_backend
|
||||
# 期望: wecom_it_backend Up X minutes (healthy) wecom-it-desk-backend:v0.7.0-backup-pre-qrfix
|
||||
|
||||
# 看 healthcheck 详细日志
|
||||
docker inspect wecom_it_backend --format '{{json .State.Health}}' | python -m json.tool
|
||||
```
|
||||
|
||||
### 2.2 进程层
|
||||
|
||||
```bash
|
||||
# Python 进程在不在
|
||||
docker exec wecom_it_backend ps aux | grep -E "uvicorn|gunicorn" | grep -v grep
|
||||
# 期望: 1 行 uvicorn 进程
|
||||
|
||||
# 端口监听
|
||||
docker exec wecom_it_backend ss -tlnp | grep 8000
|
||||
# 期望: LISTEN 0 128 0.0.0.0:8000 ...
|
||||
```
|
||||
|
||||
### 2.3 端点层
|
||||
|
||||
```bash
|
||||
# readiness 端点(由 /api/ready 提供)
|
||||
curl -k https://itsupport.servyou.com.cn/api/ready
|
||||
# 期望: {"code":200,"data":{"status":"ready","checks":{...}}}
|
||||
|
||||
# health 端点
|
||||
curl -k https://itsupport.servyou.com.cn/api/health
|
||||
# 期望: {"status":"ok"}
|
||||
```
|
||||
|
||||
### 2.4 业务层(create 端点)
|
||||
|
||||
```bash
|
||||
# 标准 create 调用
|
||||
curl -k -X POST https://itsupport.servyou.com.cn/api/auth_qrcode/create \
|
||||
-H 'Content-Type: application/json' | python -m json.tool
|
||||
```
|
||||
|
||||
期望返回(200 + data 里**有** `qrcode_png_base64`):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "ok",
|
||||
"data": {
|
||||
"ticket": "AbCdEf123456...",
|
||||
"qrcode_url": "https://open.weixin.qq.com/connect/oauth2/authorize?...",
|
||||
"qrcode_png_base64": "iVBORw0KGgoAAAANSUhEUgAA...(超长 base64 字符串)...",
|
||||
"expires_in": 120,
|
||||
"expires_at": "2026-06-22T10:30:45.123456"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关键判定**:
|
||||
- HTTP 200 + `qrcode_png_base64` 长度 > 100 字符 = hotfix 生效 ✅
|
||||
- HTTP 200 + 字段缺失 = §1.4 文件没覆盖
|
||||
- HTTP 500 = §1.3
|
||||
- HTTP 502/504 = §1.5
|
||||
|
||||
---
|
||||
|
||||
## 3. 验证 hotfix 真正生效(5 步)
|
||||
|
||||
```bash
|
||||
# Step 1: 文件 md5 对比(确认是 hotfix 版)
|
||||
docker exec wecom_it_backend md5sum /app/app/api/auth_qrcode.py /app/app/services/qrcode_service.py
|
||||
# 跟宿主机 /tmp/ 里那 2 个文件的 md5 对比,必须一致
|
||||
md5sum /tmp/auth_qrcode.py /tmp/qrcode_service.py
|
||||
|
||||
# Step 2: 关键代码片段存在性
|
||||
docker exec wecom_it_backend grep -n "_render_qrcode_png\|qrcode_png_base64" \
|
||||
/app/app/api/auth_qrcode.py /app/app/services/qrcode_service.py
|
||||
# 期望: 至少 3 行匹配(import / def / return)
|
||||
|
||||
# Step 3: qrcode 装上了
|
||||
docker exec wecom_it_backend python -c "import qrcode; print(qrcode.__version__)"
|
||||
# 期望: 7.4.2
|
||||
|
||||
# Step 4: create 端点返回 qrcode_png_base64
|
||||
RESP=$(curl -k -s -X POST https://itsupport.servyou.com.cn/api/auth_qrcode/create)
|
||||
echo "$RESP" | python -c "import json,sys; d=json.load(sys.stdin); print('has_field:', 'qrcode_png_base64' in d.get('data',{})); print('len:', len(d.get('data',{}).get('qrcode_png_base64','')))"
|
||||
# 期望: has_field: True len: 500~2000
|
||||
|
||||
# Step 5: 浏览器实测(用户手工)
|
||||
# 打开 https://itsupport.servyou.com.cn/itportal/
|
||||
# 应该看到二维码图片(不是空白)
|
||||
```
|
||||
|
||||
**5 步全过 = hotfix 真生效**。任何一步失败,跳到 §1 对应章节回滚。
|
||||
|
||||
---
|
||||
|
||||
## 4. 决策树:何时回滚 vs 何时修复
|
||||
|
||||
```
|
||||
┌──────────────────────────┐
|
||||
│ hotfix 装好,开始验证 │
|
||||
│ (curl /api/auth_qrcode/ │
|
||||
│ create) │
|
||||
└────────────┬─────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────┐
|
||||
│ HTTP 200 + 有 base64 字段? │
|
||||
└────┬──────────────┬──────┘
|
||||
│ │
|
||||
Yes No
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌──────────────────┐
|
||||
│ ✅ hotfix 生效 │ │ 看 HTTP 状态码 │
|
||||
│ 跑 §3 后 5 步 │ └────┬───────┬─────┘
|
||||
│ 浏览器实测 │ │ │
|
||||
└─────────────────┘ 500 502/504
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────┐ ┌──────────────┐
|
||||
│ 看 trace │ │ 看容器在不在 │
|
||||
│ 见 §1.3 │ │ 见 §1.5 │
|
||||
└────┬─────┘ └──────┬───────┘
|
||||
│ │
|
||||
┌────────┴────┐ │
|
||||
▼ ▼ │
|
||||
修不好(< 5 分钟) 修得好 │
|
||||
│ │ │
|
||||
▼ ▼ │
|
||||
§1.2 覆盖文件 继续验证 │
|
||||
完整走完 §3 ┌────┴────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ 走完 §3 五步验证 │
|
||||
└────┬─────────┬───┘
|
||||
│ │
|
||||
全过(5/5) 有失败
|
||||
│ │
|
||||
▼ ▼
|
||||
浏览器实测 §1.4 文件覆盖
|
||||
/itportal/ (bind mount)
|
||||
看到二维码
|
||||
│
|
||||
┌────┴────┐
|
||||
▼ ▼
|
||||
看到二维码 还是空白
|
||||
│ │
|
||||
▼ ▼
|
||||
✅ 成功 截图给 Claude
|
||||
走 §1.1 换镜像
|
||||
```
|
||||
|
||||
**何时回滚的硬性触发条件**(任一即回滚):
|
||||
|
||||
1. ❌ **容器健康检查连续 3 次失败**(每 30s 一次,> 90s 不 healthy)
|
||||
2. ❌ **其他业务端点挂掉**(扫一下 /api/ready / /api/health / 别的 create 端点)
|
||||
3. ❌ **修复尝试超过 5 分钟无进展**
|
||||
4. ❌ **用户报告前端页面打不开 / 报 500**
|
||||
|
||||
**何时继续修复的判断**:
|
||||
|
||||
- 容器 healthy + 仅 `create` 端点 500 → 尝试 §1.2 覆盖文件,5 分钟内没好就走 §1.1
|
||||
- 容器 healthy + `create` 端点正常 + 没 base64 字段 → §1.4 强制覆盖(这是文件问题,不是代码问题)
|
||||
- 容器 not healthy + 启动报错 → 直接 §1.1 换镜像(别浪费时间)
|
||||
|
||||
---
|
||||
|
||||
## 5. 回滚后清理步骤(2 步)
|
||||
|
||||
回滚成功 + 业务恢复后,把现场收拾干净。
|
||||
|
||||
### 5.1 恢复 image tag
|
||||
|
||||
```bash
|
||||
# 1) 看现在有哪些镜像
|
||||
docker images | grep wecom-it-desk-backend
|
||||
# 期望看到:
|
||||
# REPOSITORY TAG IMAGE ID CREATED
|
||||
# wecom-it-desk-backend v0.7.0-backup-pre-qrfix abc123... 3 days ago
|
||||
# wecom-it-desk-backend patched def456... 10 minutes ago (hotfix 试装版)
|
||||
# wecom-it-desk-backend latest abc123... 3 days ago (跟 backup 同 ID)
|
||||
|
||||
# 2) 把 latest 重新指向回滚镜像
|
||||
docker tag wecom-it-desk-backend:v0.7.0-backup-pre-qrfix wecom-it_desk-backend:latest
|
||||
# 防止下次 pull latest 时拉到错版本
|
||||
|
||||
# 3) 给 hotfix 试装镜像打孤 tag(留底,后面排查用)
|
||||
docker tag wecom-it-desk-backend:patched wecom-it-desk-backend:hotfix-63-failed
|
||||
# 避免被下次构建覆盖
|
||||
```
|
||||
|
||||
### 5.2 清理多余镜像(谨慎)
|
||||
|
||||
```bash
|
||||
# 1) 先看磁盘占用
|
||||
docker system df
|
||||
|
||||
# 2) 看哪些镜像没人用
|
||||
docker images --filter "dangling=true" # 悬空镜像(<none>:<none>)
|
||||
# 期望: 如果有 hotfix 中间层,会列出来
|
||||
|
||||
# 3) 删悬空镜像(安全)
|
||||
docker image prune -f
|
||||
|
||||
# 4) 看 patched 镜像是否还有容器引用
|
||||
docker ps -a --filter "ancestor=wecom-it-desk-backend:patched" --format '{{.ID}} {{.Names}} {{.Status}}'
|
||||
# 期望: 0 行(回滚后应该没容器在用 patched)
|
||||
|
||||
# 5) 删 patched 镜像
|
||||
docker rmi wecom-it-desk-backend:patched
|
||||
|
||||
# 6) 删 failed 留底(可选,建议先保留 7 天)
|
||||
# docker rmi wecom-it-desk-backend:hotfix-63-failed
|
||||
|
||||
# 7) 再看一次
|
||||
docker images | grep wecom-it-desk-backend
|
||||
# 期望只剩 v0.7.0-backup-pre-qrfix + latest(同 ID)
|
||||
```
|
||||
|
||||
### 5.3 清理宿主机临时文件
|
||||
|
||||
```bash
|
||||
# 删 /tmp/ 里那 2 个 base64 上传用的文件
|
||||
rm -f /tmp/auth_qrcode.py /tmp/qrcode_service.py
|
||||
rm -f /tmp/auth_qrcode.py.bak /tmp/qrcode_service.py.bak # 回滚时产生的
|
||||
ls -la /tmp/ | grep -E "(qrcode|auth_qrcode)"
|
||||
# 期望: 无输出
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 一键回滚脚本(把 §1.1 打包)
|
||||
|
||||
如果手动操作太烦,把回滚流程封装成一个脚本(jumpserver 上直接跑):
|
||||
|
||||
**文件**: `/opt/wecom-it-desk/rollback-hotfix63.sh`
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# v0.7.0 hotfix #63 一键回滚
|
||||
# 用法: bash /opt/wecom-it-desk/rollback-hotfix63.sh
|
||||
|
||||
set -e # 任一命令失败立即退出
|
||||
|
||||
echo "===== hotfix #63 一键回滚 ====="
|
||||
|
||||
# 1) 停 + 删当前容器
|
||||
docker stop wecom_it_backend
|
||||
docker rm wecom_it_backend
|
||||
|
||||
# 2) 用 backup 镜像起
|
||||
docker run -d \
|
||||
--name wecom_it_backend \
|
||||
--restart=always \
|
||||
--network wecom_it_network \
|
||||
$(cat /opt/wecom-it-desk/backend-run.env) \
|
||||
wecom-it-desk-backend:v0.7.0-backup-pre-qrfix
|
||||
|
||||
# 3) 等 5 秒让容器启动
|
||||
sleep 5
|
||||
|
||||
# 4) 健康检查
|
||||
echo "===== 验证 ====="
|
||||
docker ps | grep wecom_it_backend
|
||||
curl -kf https://itsupport.servyou.com.cn/api/ready && echo "READY OK" || echo "READY FAIL"
|
||||
|
||||
echo "===== 回滚完成 ====="
|
||||
```
|
||||
|
||||
**部署方式**(在 jumpserver 终端):
|
||||
|
||||
```bash
|
||||
# 1) 创建文件
|
||||
cat > /opt/wecom-it-desk/rollback-hotfix63.sh << 'EOF'
|
||||
# (上面那段内容)
|
||||
EOF
|
||||
|
||||
# 2) 加执行权限
|
||||
chmod +x /opt/wecom-it-desk/rollback-hotfix63.sh
|
||||
|
||||
# 3) 提取当前 backend 容器的 run 参数(给脚本里的 $(cat ...) 用)
|
||||
docker inspect wecom_it_backend --format '{{range .Config.Env}}export {{.}}{{"\n"}}{{end}}' \
|
||||
> /opt/wecom-it-desk/backend-run.env 2>/dev/null || true
|
||||
|
||||
# 4) 跑回滚
|
||||
bash /opt/wecom-it-desk/rollback-hotfix63.sh
|
||||
```
|
||||
|
||||
> **注意**: `--env-file` / `-e` 在 `docker run` 里比脚本里 export 更稳。**生产建议把完整 `docker run` 命令存到 `/opt/wecom-it-desk/backend-run.sh`,回滚脚本里直接 `bash backend-run.sh`**。这个留给后续优化。
|
||||
|
||||
---
|
||||
|
||||
## 7. 回滚后通知清单
|
||||
|
||||
回滚完 = 业务恢复,但**还要做 3 件事**:
|
||||
|
||||
1. **更新 `CURRENT-FOCUS.md`**: 在「最近搞定」加一行 `❌ v0.7.0 hotfix #63 失败已回滚到 v0.7.0-backup-pre-qrfix,前端 /itportal/ 二维码仍不显示,等下一轮修复`
|
||||
2. **记入 memory**: 在 `memory/` 加 `hotfix-63-rollback-2026-06-22.md`,写清楚: 失败在哪一步 / 用了哪个回滚命令 / 跟 Claude 复盘结论
|
||||
3. **贴 logs 给 Claude**: 把 `docker logs wecom_it_backend --tail 200` 输出贴回来,分析根因,准备下一轮 hotfix 方案(v0.7.0.2-hotfix2)
|
||||
|
||||
---
|
||||
|
||||
## 8. 速查表(贴在屏幕边上)
|
||||
|
||||
| 我看到 | 跑这个 |
|
||||
|--------|--------|
|
||||
| 容器 restarting | `docker logs wecom_it_backend --tail 30` 看启动错误 → §1.1 |
|
||||
| 容器 healthy 但 create 500 | §1.3 拿 traceback → §1.2 覆盖文件 |
|
||||
| 容器 healthy + create 200 + 无 base64 | §1.4 强制 bind mount 覆盖 |
|
||||
| 502/504 | §1.5 看网络 + 容器 |
|
||||
| 8000 占用 | §1.6 |
|
||||
| 完全不知道啥情况 | §1.1 一键换镜像(最稳) |
|
||||
| 不知道回滚到哪个镜像 | `docker images \| grep backup` |
|
||||
| 不知道完整 run 命令 | `docker inspect wecom_it_backend --format '{{.Config.Cmd}} {{json .Config.Env}}' \| head -c 500` |
|
||||
| 想一键回滚 | `bash /opt/wecom-it-desk/rollback-hotfix63.sh` |
|
||||
| 验证 hotfix 生效 | §3 五步全过 = ✅ |
|
||||
| 回滚后清理 | §5 三步 |
|
||||
|
||||
---
|
||||
|
||||
**文档结束**。所有命令都在 jumpserver 终端以 root 跑,`docker exec` 都假设容器名叫 `wecom_it_backend`(生产实际名,见 `memory/container-names-wecom-it-backend.md`)。如果容器名变了,先跑 `docker ps --format '{{.Names}}' \| grep backend` 确认。
|
||||
@@ -0,0 +1,256 @@
|
||||
# Nginx 域名路由分发配置(Phase 1.3 task #16)
|
||||
|
||||
> 创建:2026-06-21
|
||||
> 适用版本:v0.7.0+ (Phase 1.3 扫码登录上线后)
|
||||
|
||||
## 🎯 目标
|
||||
|
||||
不同入口域名/子路径 → 不同前端应用,但所有请求共用同一个后端 API。
|
||||
|
||||
| 入口 | URL | 前端应用 | 用途 |
|
||||
|---|---|---|---|
|
||||
| **坐席端** | `https://itsupport.servyou.com.cn/itagent/` | `frontend-agent/dist` | 坐席工作台 |
|
||||
| **管理端** | `https://itsupport.servyou.com.cn/itadmin/` | `frontend-admin/dist` | 管理后台 |
|
||||
| **Portal 统一入口** | `https://itsupport.servyou.com.cn/itportal/` | `frontend-portal/dist` | 扫码登录 + 多角色选择 |
|
||||
| **H5 员工端** | `https://itsupport.servyou.com.cn/itdesk/` | `frontend-h5/dist` | 员工端(企微内) |
|
||||
|
||||
> **两种方案**:单域名多路径(本项目当前)+ 多子域名(可选升级)
|
||||
|
||||
---
|
||||
|
||||
## 🅰️ 方案 A:单域名 + 多子路径(推荐,运维简单)
|
||||
|
||||
### nginx server block
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name itsupport.servyou.com.cn;
|
||||
|
||||
# SSL 证书(由公司统一管理)
|
||||
ssl_certificate /etc/nginx/certs/itsupport.servyou.com.cn.crt;
|
||||
ssl_certificate_key /etc/nginx/certs/itsupport.servyou.com.cn.key;
|
||||
|
||||
# 通用安全头
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
# ========================================================================
|
||||
# 1. Portal 统一入口(扫码登录)
|
||||
# ========================================================================
|
||||
location /itportal/ {
|
||||
alias /opt/wecom-it-desk/frontend-portal/dist/;
|
||||
try_files $uri $uri/ /itportal/index.html;
|
||||
|
||||
# 允许企业微信 OAuth 回调(测试期)
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||
}
|
||||
|
||||
# ========================================================================
|
||||
# 2. 坐席工作台
|
||||
# ========================================================================
|
||||
location /itagent/ {
|
||||
alias /opt/wecom-it-desk/frontend-agent/dist/;
|
||||
try_files $uri $uri/ /itagent/index.html;
|
||||
}
|
||||
|
||||
# ========================================================================
|
||||
# 3. 管理后台
|
||||
# ========================================================================
|
||||
# IP 白名单(临时方案,v1.0 前收窄 — 见 ip-whitelist-trust-proxies-todo.md)
|
||||
location /itadmin/ {
|
||||
allow 0.0.0.0/0; # ⚠️ 临时全开
|
||||
# allow 10.90.0.0/16; # TODO 收窄到内网
|
||||
# allow 115.236.188.3; # 公网入口 IP
|
||||
|
||||
alias /opt/wecom-it-desk/frontend-admin/dist/;
|
||||
try_files $uri $uri/ /itadmin/index.html;
|
||||
}
|
||||
|
||||
# ========================================================================
|
||||
# 4. H5 员工端
|
||||
# ========================================================================
|
||||
location /itdesk/ {
|
||||
alias /opt/wecom-it-desk/frontend-h5/dist/;
|
||||
try_files $uri $uri/ /itdesk/index.html;
|
||||
|
||||
# 允许嵌入到企微 WebView
|
||||
add_header X-Frame-Options "ALLOW-FROM https://work.weixin.qq.com" always;
|
||||
}
|
||||
|
||||
# ========================================================================
|
||||
# 5. 后端 API(4 个端共用)
|
||||
# ========================================================================
|
||||
location /api/ {
|
||||
# 管理端 API 严格白名单
|
||||
location /api/admin/ {
|
||||
allow 0.0.0.0/0; # ⚠️ 临时全开
|
||||
# allow 10.90.0.0/16; # TODO 收窄
|
||||
# allow 115.236.188.3;
|
||||
|
||||
proxy_pass http://wecom_it_backend;
|
||||
}
|
||||
|
||||
# 其他 API 放行
|
||||
proxy_pass http://wecom_it_backend;
|
||||
}
|
||||
|
||||
# ========================================================================
|
||||
# 6. WebSocket(坐席端 WS-01 鉴权)
|
||||
# ========================================================================
|
||||
location /ws/ {
|
||||
proxy_pass http://wecom_it_backend;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# WS 心跳
|
||||
proxy_read_timeout 600s;
|
||||
}
|
||||
|
||||
# ========================================================================
|
||||
# 7. 静态资源(图片/上传文件)
|
||||
# ========================================================================
|
||||
location /api/media/ {
|
||||
proxy_pass http://wecom_it_backend;
|
||||
proxy_set_header Host $host;
|
||||
# 上传文件 30 天缓存
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# ========================================================================
|
||||
# 8. 根路径 → Portal 统一入口
|
||||
# ========================================================================
|
||||
location = / {
|
||||
return 302 /itportal/;
|
||||
}
|
||||
}
|
||||
|
||||
# upstream 后端(内网容器)
|
||||
upstream wecom_it_backend {
|
||||
server 127.0.0.1:8000; # 容器映射到宿主机的端口
|
||||
}
|
||||
```
|
||||
|
||||
### 部署步骤
|
||||
|
||||
```bash
|
||||
# 1. 上传 dist 文件(各前端 build 产物)
|
||||
scp -r frontend-portal/dist root@10.90.5.110:/opt/wecom-it-desk/frontend-portal/
|
||||
scp -r frontend-agent/dist root@10.90.5.110:/opt/wecom-it-desk/frontend-agent/
|
||||
scp -r frontend-admin/dist root@10.90.5.110:/opt/wecom-it-desk/frontend-admin/
|
||||
scp -r frontend-h5/dist root@10.90.5.110:/opt/wecom-it-desk/frontend-h5/
|
||||
|
||||
# 2. 上传 nginx 配置(本地 + 堡垒机 PuTTY)
|
||||
# 参考:feedback-putty-not-openssh.md(用 PuTTY 操作)
|
||||
|
||||
# 3. 验证配置
|
||||
sudo nginx -t
|
||||
|
||||
# 4. reload
|
||||
sudo nginx -s reload
|
||||
|
||||
# 5. 验证(本地或企微)
|
||||
curl -I https://itsupport.servyou.com.cn/itportal/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🅱️ 方案 B:多子域名(可选升级,需要 DNS 解析)
|
||||
|
||||
| 子域名 | 解析到 | 用途 |
|
||||
|---|---|---|
|
||||
| `portal.itsupport.servyou.com.cn` | nginx:443 | 统一入口 |
|
||||
| `agent.itsupport.servyou.com.cn` | nginx:443 | 坐席工作台 |
|
||||
| `admin.itsupport.servyou.com.cn` | nginx:443 | 管理后台(内网白名单) |
|
||||
| `h5.itsupport.servyou.com.cn` | nginx:443 | H5 员工端 |
|
||||
|
||||
### 优点
|
||||
- 跨域 cookie 隔离更清晰
|
||||
- 每个子域可独立上 HTTPS 证书
|
||||
- 内网白名单更容易配置(直接 deny all 到 admin.*)
|
||||
|
||||
### 缺点
|
||||
- 需要运维额外加 4 个 A 记录
|
||||
- 前端跨域 API 调用要 CORS 配全
|
||||
- 坐席/管理员跨域切换要 CORS preflight
|
||||
|
||||
**当前 v0.7.0 推荐方案 A**,v1.0 再考虑方案 B。
|
||||
|
||||
---
|
||||
|
||||
## 🔄 扫码登录流程(方案 A 下)
|
||||
|
||||
```
|
||||
[1] 用户访问 https://itsupport.servyou.com.cn/itagent/
|
||||
→ nginx 命中 location /itagent/ → 返回 frontend-agent/dist/index.html
|
||||
→ 前端路由守卫检查 localStorage.agent_token,没有 → 跳 /itportal/
|
||||
|
||||
[2] 用户访问 https://itsupport.servyou.com.cn/itportal/
|
||||
→ nginx 命中 location /itportal/ → 返回 frontend-portal/dist/index.html
|
||||
→ QrcodeLogin.vue 显示二维码
|
||||
|
||||
[3] 员工用企微扫码
|
||||
→ 企微 OAuth 回调到后端 → 后端写 Redis qrcode:scan:{ticket}
|
||||
→ Portal 轮询 /api/auth_qrcode/poll/{ticket} → 拿到 status=scanned
|
||||
→ UI 显示"请在手机上确认登录"
|
||||
|
||||
[4] 员工在手机上点"确认登录"
|
||||
→ 后端 /api/auth_qrcode/confirm → 创建 token → 写 Redis qrcode:confirm:{ticket}
|
||||
→ Portal 轮询拿到 status=confirmed + token + roles
|
||||
|
||||
[5] Portal 按角色分发(见 QrcodeLogin.vue dispatchToRole)
|
||||
- 只有 agent → window.location.href = /itagent/?token=xxx
|
||||
- 只有 admin → window.location.href = /itadmin/?token=xxx
|
||||
- admin + agent → window.location.href = /itportal/select(让用户选)
|
||||
- 默认 user → window.location.href = /itdesk/?token=xxx
|
||||
|
||||
[6] 目标端 Login.vue 读 ?token=xxx 写入 localStorage + 跳 /workspace
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 已知问题 & TODO
|
||||
|
||||
| 问题 | 状态 | 备注 |
|
||||
|---|---|---|
|
||||
| `/itadmin/` IP 白名单临时全开 | 🟡 临时 | v1.0 前必须收窄(见 `ip-whitelist-trust-proxies-todo.md`) |
|
||||
| `/api/admin/` IP 白名单临时全开 | 🟡 临时 | 同上 |
|
||||
| H5 端需要企微内访问 | 🟢 保持 | 用户决策,H5 仍在企微内是主场景 |
|
||||
| 跨子路径刷新 404 | 🟢 已处理 | `try_files $uri $uri/ /itagent/index.html` |
|
||||
| 静态资源 cache | 🟡 待优化 | 可加 version hash 强制刷新 |
|
||||
| admin Login.vue 仍用表单 | 🟡 待改 | 后续 task:重写 admin Login 为扫码 UI |
|
||||
|
||||
---
|
||||
|
||||
## 🧪 验证清单
|
||||
|
||||
部署完成后,在以下场景测试:
|
||||
|
||||
- [ ] 浏览器直接访问 `/itportal/` → 显示扫码二维码
|
||||
- [ ] 用企微扫码 + 确认 → Portal 自动跳到对应端
|
||||
- [ ] 坐席(只有 agent 角色)扫码 → 自动跳 `/itagent/?token=xxx` → 自动登录进 /workspace
|
||||
- [ ] 管理员(只有 admin 角色)扫码 → 自动跳 `/itadmin/?token=xxx` → 进 admin dashboard
|
||||
- [ ] 多角色用户(admin + agent)扫码 → 跳 `/itportal/select` → 看到选择页
|
||||
- [ ] H5(企微内) → 仍走企微 OAuth,扫码二维码区域正常
|
||||
- [ ] 浏览器直接访问 `/itagent/workspace`(没 token)→ 跳 `/itportal/`
|
||||
- [ ] 扫码登录 120s 过期 → UI 显示"已过期,点击刷新"
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [project-knowledge-base.md](../memory/project-knowledge-base.md) — 项目知识库
|
||||
- [feedback-wecom-only-external-urls.md](../memory/feedback-wecom-only-external-urls.md) — 企微入口约束(部分解除)
|
||||
- [phase1-progress.md](../memory/phase1-progress.md) — Phase 1+2 进度
|
||||
- [deployment.md](../memory/deployment.md) — 部署经验
|
||||
- [nginx-container-name-wecom-it-nginx.md](../memory/nginx-container-name-wecom-it-nginx.md) — 容器名坑
|
||||
|
||||
---
|
||||
|
||||
**变更历史**:
|
||||
- 2026-06-21 创建(Phase 1.3 task #16)
|
||||
@@ -0,0 +1,43 @@
|
||||
# H5用户端原型图 → Vue3代码实现概览
|
||||
|
||||
## 完成时间
|
||||
2026-06-09
|
||||
|
||||
## 变更摘要
|
||||
根据已锁定的原型图 v1.1 修复版,将 H5 用户端设计实现为 Vue3 代码。
|
||||
|
||||
## 修改文件清单
|
||||
|
||||
### 1. `frontend-h5/src/components/chat/ChatPanel.vue`
|
||||
- **标题栏重构**:左侧(标题 + 坐席在线/离线状态胶囊) + 右侧(🔔呼叫按钮 + 主题切换)
|
||||
- **🔔摇铃按钮**:从输入栏移至标题栏(桌面端+手机端统一)
|
||||
- **排查步骤固定顶部**:从消息列表内移出,固定在标题栏下方、所有消息之上,不随滚动消失
|
||||
- **移除 InputBar 事件**:不再需要 @call-agent 事件(摇铃直接在 ChatPanel 内控制)
|
||||
|
||||
### 2. `frontend-h5/src/components/chat/InputBar.vue`
|
||||
- **移除摇铃按钮**:删除 🔔 摇铃按钮及相关 CSS(bell-btn/bell-icon/bell-idle/bell-ring 动画)
|
||||
- **新增工具栏**:😊表情 / 🖼️图片 / 📎文件 / 📸拍照(4个圆形按钮)
|
||||
- **布局改为两行**:工具栏(上) + 输入行(输入框+发送按钮)(下)
|
||||
- **新增方法**:handleEmoji/handleImage/handleFile/handleCamera(阶段二实现具体功能)
|
||||
- **引导条文案更新**:"点击标题栏铃铛呼叫 IT 坐席"
|
||||
|
||||
### 3. `frontend-h5/src/components/assistant/RightPanel.vue`(新建)
|
||||
- **三段式面板**:AI推送区 / 常用资源标签页 / 趣味问答
|
||||
- **AI推送区**:3种卡片类型(guide/process/download) + 动态图标+颜色
|
||||
- **常用资源**:2个Tab(申请流程/必装软件) + 资源列表
|
||||
- **趣味问答**:题目+4选项+积分+答题结果反馈
|
||||
- **阶段一静态数据**,阶段二接入 Dify 动态推送
|
||||
|
||||
### 4. `frontend-h5/src/views/ChatView.vue`
|
||||
- **替换右侧面板**:AiHelperPanel → RightPanel(三段式面板)
|
||||
- **响应式断点**:从768px改为500px(与原型图对齐)
|
||||
- **移动端**:<500px 不显示右侧面板
|
||||
- **拖拽逻辑修复**:只固定左侧宽度,右侧 flex:1 自动填满(消除拖拽后空白)
|
||||
- **移除浮动按钮**:不再需要移动端AI助手浮动按钮
|
||||
|
||||
### 5. `frontend-h5/src/stores/conversation.ts`
|
||||
- **新增 agentOnline 状态**:默认true,阶段一简化处理
|
||||
- **暴露到 return 语句**:使组件可以访问
|
||||
|
||||
## 构建验证
|
||||
✅ `npx vite build` 构建成功,无编译错误
|
||||
@@ -0,0 +1,362 @@
|
||||
# nginx 真实 IP 还原 — 生产部署(小白友好版)
|
||||
|
||||
> 术语速查:**nginx** = 你这台服务器的"门卫",负责把用户请求分发给后端 / 把静态文件返回给浏览器
|
||||
> **配置** = nginx 的工作规则,改配置 = 改门卫的工作方式
|
||||
|
||||
---
|
||||
|
||||
## 我们要做啥(整体目标)
|
||||
|
||||
**一句话目标**:`https://itsupport.servyou.com.cn/itadmin/` 之前返回 403(被门卫拦了),原因是门卫把"代理服务器 IP"当成了"用户 IP",而代理 IP 不在白名单里。这次我们改门卫的规则,让它从请求头里读"真实用户 IP"。
|
||||
|
||||
**一共 7 个动作**:
|
||||
|
||||
| # | 动作 | 大概多久 | 风险 |
|
||||
|---|---|---|---|
|
||||
| 1 | PuTTY 连上服务器 | 1 分钟 | ⚪ 无风险 |
|
||||
| 2 | 备份当前配置 | 几秒 | 🟢 备份原文件,可还原 |
|
||||
| 3 | 写入 13 行新规则 | 几秒 | 🟡 改配置,但有备份 |
|
||||
| 4 | 确认写入正确 | 几秒 | ⚪ 只读不写 |
|
||||
| 5 | 检查配置语法 | 几秒 | ⚪ 只读不写 |
|
||||
| 6 | 让 nginx 重新读规则 | 1 秒 | 🟡 短暂重载,服务不中断 |
|
||||
| 7 | 浏览器看效果 | 几秒 | ⚪ 只读 |
|
||||
|
||||
**总耗时**:第一次大概 5-10 分钟;熟练了 2 分钟
|
||||
|
||||
**整体风险**:🟢 **低** — 每一步都给了"回滚"按钮,改坏了随时能恢复
|
||||
|
||||
---
|
||||
|
||||
## PuTTY 是啥?在哪儿打开?
|
||||
|
||||
**PuTTY** = 一个 SSH 客户端软件,作用是让你从你的 Windows 电脑远程连到公司的 Linux 服务器
|
||||
|
||||
**打开方式**:
|
||||
- 按 `Win 键` → 输入 `putty` → 回车
|
||||
- 或者开始菜单 → 找到 PuTTY 图标
|
||||
|
||||
打开后会看到一个灰底配置界面,我们要填 4 项:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────┐
|
||||
│ Host Name (or IP address) │ ← 填: 10.212.189.210
|
||||
│ Port │ ← 填: 2222
|
||||
│ Connection type │ ← 选: SSH(默认就是)
|
||||
│ Saved Sessions │ ← 填: wecom-bastion(起个名)
|
||||
└──────────────────────────────────────┘
|
||||
|
||||
点 Save 保存 → 点 Open 开始连接
|
||||
```
|
||||
|
||||
连接后会黑底白字,提示 `login as:` → 输入 `sxn` 回车 → 提示 `password:` → 输入你的堡垒机密码(输入时屏幕不显示,正常,输完回车就行)
|
||||
|
||||
**注意**:输错密码不会锁账号,直接重新输
|
||||
|
||||
---
|
||||
|
||||
## 动作 1:PuTTY 连服务器(⚪ 无风险)
|
||||
|
||||
> **为啥要连服务器?**:改配置必须在服务器上操作,你 Windows 这边只是"遥控器"
|
||||
|
||||
连上堡垒机后,黑底白字会显示一个类似 `sxn@jump-host:~$` 的提示符,说明你已经到堡垒机了。
|
||||
|
||||
**决策树**:
|
||||
```
|
||||
你现在看到了堡垒机提示符(类似 sxn@jump-host:~$)
|
||||
├─ 是 → 在 PuTTY 里继续输入下面命令
|
||||
└─ 否 → 截图发给我,卡哪儿了
|
||||
```
|
||||
|
||||
贴下面的命令(右键 = 粘贴,Enter = 执行):
|
||||
|
||||
```bash
|
||||
# 从堡垒机跳到真正的生产服务器
|
||||
ssh sxn@10.90.5.110
|
||||
```
|
||||
|
||||
回车后可能要输密码(堡垒机和目标机密码可能不同,试一下你之前用过的那个)
|
||||
|
||||
**✅ 成功长这样**:
|
||||
```text
|
||||
sxn@prod-server:~$
|
||||
```
|
||||
|
||||
**❌ 失败常见**:
|
||||
- `Permission denied` → 密码错了,重输
|
||||
- `Connection timed out` → 网络问题,可能 VPN 没连
|
||||
- 卡住不动 → 可能需要输 `yes` 确认服务器指纹,看到 `(yes/no/[fingerprint])?` 就输 `yes` 回车
|
||||
|
||||
---
|
||||
|
||||
## 动作 2:备份当前配置(🟢 低风险,改坏了能还原)
|
||||
|
||||
> **为啥要备份?**:运维铁律 — **改任何东西之前先备份**,这样改坏了能用备份还原,不会把生产搞挂
|
||||
|
||||
```bash
|
||||
# 进入 nginx 配置所在目录
|
||||
cd /opt/wecom-it-desk/nginx
|
||||
|
||||
# 复制一份当前配置,文件名带当前时间(分),方便区分
|
||||
sudo cp nginx.conf nginx.conf.bak-$(date +%H%M)
|
||||
|
||||
# 列出所有备份文件,确认刚才那行成功
|
||||
ls -la nginx.conf.bak-*
|
||||
```
|
||||
|
||||
**为啥用 `$(date +%H%M)`?**:这个写法会自动拼上当前时间(比如 1430 表示 14:30),每次备份文件名都不一样,不会覆盖之前的备份
|
||||
|
||||
**✅ 成功长这样**:
|
||||
```text
|
||||
-rw-r--r-- 1 root root 4821 Jun 15 14:30 nginx.conf.bak-1430
|
||||
```
|
||||
|
||||
**❌ 失败常见**:
|
||||
- `cp: cannot stat 'nginx.conf'` → 当前不在 nginx 目录,先 `cd /opt/wecom-it-desk/nginx` 进去
|
||||
- `Permission denied` → 缺 `sudo`,命令前面加 `sudo` 重试
|
||||
|
||||
---
|
||||
|
||||
## 动作 3:写入 13 行新规则(🟡 中风险,但有备份兜底)
|
||||
|
||||
> **写入啥?**:13 行 nginx 配置,告诉 nginx"从请求头 X-Forwarded-For 里读真实用户 IP"
|
||||
>
|
||||
> **为啥要这样做?**:用户通过公司 WAF/堡垒机访问,WAF 会把真实 IP 放在 `X-Forwarded-For` 请求头里,但 nginx 默认只看直连 IP,所以才误判 403
|
||||
|
||||
**重要**:把下面**从 `cat > /tmp/patch.py` 到 `PYEOF`** 的**整段**一次性粘贴进 PuTTY(右键 = 粘贴)。整段会作为一条命令执行。
|
||||
|
||||
```bash
|
||||
# 创建一个 python 脚本到 /tmp/patch.py
|
||||
cat > /tmp/patch.py << 'PYEOF'
|
||||
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)))
|
||||
PYEOF
|
||||
|
||||
# 运行这个 python 脚本,它会自动把上面那 13 行插入到 nginx.conf
|
||||
sudo python3 /tmp/patch.py
|
||||
```
|
||||
|
||||
**术语解释**:
|
||||
- `cat > /tmp/patch.py` → 创建一个文件,内容是后面所有内容
|
||||
- `<< 'PYEOF' ... PYEOF` → 这种写法叫 **heredoc**(直译"这里是文档"),作用是把多行文字原样写入文件
|
||||
- `sudo` → 以管理员身份运行(改系统文件需要权限)
|
||||
|
||||
**✅ 成功长这样**:
|
||||
```text
|
||||
patched, +492 bytes
|
||||
```
|
||||
|
||||
**❌ 失败常见**:
|
||||
- `Permission denied` → 缺 `sudo`,或者 nginx.conf 不存在
|
||||
- `NameError: name 'fp' is not defined` → heredoc 没贴完整,最末尾的 `PYEOF` 没贴上
|
||||
- 没任何输出 → python 没运行,看光标有没有新行,可能没回车
|
||||
|
||||
---
|
||||
|
||||
## 动作 4:确认写入正确(⚪ 无风险,只读)
|
||||
|
||||
> **为啥要确认?**:虽然脚本说写入了,但**人眼看到才真的算**。这步只读不写,放心跑
|
||||
|
||||
```bash
|
||||
# 在 nginx.conf 里搜索"真实 IP 还原"关键字,并显示后面 13 行
|
||||
sudo grep -A 13 "真实 IP 还原" /opt/wecom-it-desk/nginx/nginx.conf
|
||||
```
|
||||
|
||||
**✅ 成功长这样**(应该看到完整 13 行):
|
||||
```nginx
|
||||
# 真实 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;
|
||||
```
|
||||
|
||||
**❌ 失败**:
|
||||
- 啥也没输出 → 写入失败,回到动作 3 重做
|
||||
- 只输出一两行 → heredoc 没贴全,需要回滚后重来
|
||||
|
||||
---
|
||||
|
||||
## 动作 5:检查配置语法(⚪ 无风险,只读不执行)
|
||||
|
||||
> **为啥要检查?**:这个命令 nginx 会"假装"按新配置启动,只检查语法,不会真的重启。**通过 = 配置写得对,放心用;不通过 = 写得有问题,继续走会出问题**
|
||||
|
||||
```bash
|
||||
# 在 nginx 容器(就是跑 nginx 服务的那个小 Linux)内,做配置语法检查
|
||||
docker compose exec nginx nginx -t
|
||||
```
|
||||
|
||||
**术语解释**:
|
||||
- `docker compose` → 管理这台服务器上所有"容器"的命令
|
||||
- `exec` → "钻进"某个容器里执行命令
|
||||
- `nginx -t` → nginx 自带的"语法检查"工具(全称 `--test`)
|
||||
|
||||
**✅ 成功长这样**:
|
||||
```text
|
||||
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
|
||||
nginx: configuration file /etc/nginx/nginx.conf test is successful
|
||||
```
|
||||
|
||||
**❌ 失败**(`test is successful` 没出现):
|
||||
- `unexpected "}"` / `unknown directive` → 写错字了,回去动作 4 看看哪里对不上
|
||||
- **直接停下,不要继续** → 复制错误信息贴回给我
|
||||
|
||||
---
|
||||
|
||||
## 动作 6:让 nginx 重新读规则(🟡 中风险,但服务不中断)
|
||||
|
||||
> **"重新读"是啥意思?**:nginx 现在用的还是旧配置,我们让 nginx 不用重启(不会断服务)就把新配置加载进来。这个动作叫"热加载"或 "reload"
|
||||
>
|
||||
> **会断网吗?**:不会,reload 是无缝的,用户那边无感知
|
||||
|
||||
```bash
|
||||
# 通知 nginx 容器内的 master 进程重新读配置
|
||||
docker compose exec nginx nginx -s reload
|
||||
```
|
||||
|
||||
**术语解释**:`-s reload` = 发信号(英文 signal)给 nginx,告诉它"重读配置"
|
||||
|
||||
**✅ 成功长这样**(没报错即成功):
|
||||
```text
|
||||
2026/06/15 14:35:12 [notice] 1#1: signal process started
|
||||
```
|
||||
|
||||
**❌ 失败**:
|
||||
- `nginx: [error]` 开头 → 配置没通过,回去动作 5 看哪里没对
|
||||
- 啥也没输出 → 命令没执行,看光标位置
|
||||
|
||||
---
|
||||
|
||||
## 动作 7:浏览器看效果(⚪ 无风险)
|
||||
|
||||
**为啥这步是浏览器而不是 curl?**:curl 看响应头,浏览器看真实页面。**人眼看到才作数**
|
||||
|
||||
**操作步骤**:
|
||||
1. 打开浏览器
|
||||
2. **开隐身模式**(`Ctrl + Shift + N`,Chrome / Edge 都是这个快捷键)
|
||||
- **为啥要隐身?**:隐身模式不读本地缓存,看到的就是 nginx **当下**返回的
|
||||
3. 地址栏输入 `https://itsupport.servyou.com.cn/itadmin/`
|
||||
4. 按回车
|
||||
|
||||
**✅ 成功长这样**:
|
||||
- 页面正常显示
|
||||
- 按 `F12` 打开开发者工具 → `Network` 选项卡 → 顶部那一行状态码是 **200**(不是 403)
|
||||
|
||||
**❌ 失败**:
|
||||
- 仍然是 403 → 见下面"如果还是 403"段
|
||||
- 502 / 504 → nginx 后面那个服务挂了,贴错误给我
|
||||
- 页面打不开(连接被拒) → DNS 没配,联系 IT 运维
|
||||
|
||||
---
|
||||
|
||||
## 如果还是 403 — 看 WAF 出口 IP(诊断)
|
||||
|
||||
> **啥是 WAF?**:公司部署在 nginx 前面的"统一入口",所有用户请求先经过 WAF 再到 nginx。WAF 自己的 IP 不一定在你写的 4 段内网里,所以还得加
|
||||
|
||||
```bash
|
||||
# 看 nginx 最后 20 条访问日志,找 $remote_addr 是不是 WAF 的 IP
|
||||
docker compose exec nginx tail -20 /var/log/nginx/access.log
|
||||
```
|
||||
|
||||
**日志长这样**:
|
||||
```text
|
||||
10.80.5.123 - - [15/Jun/2026:14:35:45 +0800] "GET /itadmin/ HTTP/1.1" 403 ...
|
||||
^^^^^^^
|
||||
这就是 $remote_addr
|
||||
```
|
||||
|
||||
把那个 IP 数字(比如 `10.80.5.123`)贴回给我,我会:
|
||||
1. 给你追加一行 `set_real_ip_from 10.80.5.123;`
|
||||
2. 让你重跑动作 5 + 动作 6
|
||||
|
||||
---
|
||||
|
||||
## 如果改坏了 — 回滚(啥时候都能用)
|
||||
|
||||
> **啥时候用?**:任何一个动作出问题,你都可以直接回滚到动作 2 备份的版本
|
||||
|
||||
```bash
|
||||
# 列出所有备份,挑最近的一个
|
||||
ls -la /opt/wecom-it-desk/nginx/nginx.conf.bak-*
|
||||
```
|
||||
|
||||
```bash
|
||||
# 用最近那个备份覆盖当前配置(把 1430 换成上面列出的真实时间)
|
||||
sudo cp /opt/wecom-it-desk/nginx/nginx.conf.bak-1430 /opt/wecom-it-desk/nginx/nginx.conf
|
||||
```
|
||||
|
||||
```bash
|
||||
# 重新加载回滚后的配置
|
||||
docker compose exec nginx nginx -s reload
|
||||
```
|
||||
|
||||
回滚后页面应该回到改之前的状态(403 回来),说明回滚成功
|
||||
|
||||
---
|
||||
|
||||
## 一张图看懂流程
|
||||
|
||||
```
|
||||
PuTTY 连服务器
|
||||
│
|
||||
▼
|
||||
备份原配置
|
||||
│
|
||||
▼
|
||||
写入 13 行新规则
|
||||
│
|
||||
▼
|
||||
确认写入正确 ──→ ❌ 不对 ──→ 重做写入 / 回滚
|
||||
│ ✅
|
||||
▼
|
||||
检查配置语法 ──→ ❌ 语法错 ──→ 复制错误贴回给我,不要继续
|
||||
│ ✅
|
||||
▼
|
||||
重载 nginx ─────→ ❌ 报错 ──→ 检查容器状态 / 找 Claude
|
||||
│ ✅
|
||||
▼
|
||||
浏览器看效果 ──→ ❌ 还是 403 ──→ 看 WAF 出口 IP,贴给 Claude
|
||||
│ ✅
|
||||
▼
|
||||
🎉 完成
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 我建议你第一次做
|
||||
|
||||
**第一次建议**:动作 1 → 动作 2 → **停一下,截图发给我** → 我确认备份成功 → 你再继续动作 3 之后
|
||||
|
||||
**熟练了以后**:一口气跑完动作 1-7,中间不打断
|
||||
|
||||
---
|
||||
|
||||
## 关联
|
||||
|
||||
- 评审报告:`review-p0-security-2026-06-14.md` P0-3
|
||||
- 待办:`ip-whitelist-trust-proxies-todo.md` — v1.0 前必须收窄 4 段 → 4 个 IP
|
||||
- 本地配置:`deploy-server/nginx/nginx.conf`(已包含 patch,下次重打包自动带)
|
||||
- 服务器 IP 变更:`project-production-server-ip-2026-06-15.md` — 10.80.0.136 已下线,用 10.90.5.110
|
||||
- 客户端约束:`feedback-putty-not-openssh.md` — 用 PuTTY,不用 `ssh -J`
|
||||
- 命令行规范:`feedback-cmd-step-by-step.md` — 每行一条 + 中文注释
|
||||
- 小白引导规范:`feedback-beginner-friendly-guide.md` — 讲清目标+风险、术语解释、出错兜底
|
||||
@@ -0,0 +1,81 @@
|
||||
# 快速诊断 /itdesk/ 500 错误
|
||||
|
||||
**Claude 无法直接 SSH(Windows known_hosts 权限 + 堡垒机交互登录限制),需你跑下面命令并把输出贴回。**
|
||||
|
||||
---
|
||||
|
||||
## 🚀 一键跑法(推荐)
|
||||
|
||||
**完整脚本已写到** `D:\资料\03-项目开发\wecom_it_smart_desk-claude\diagnose-500.sh`(3484 字节)
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. **上传脚本到服务器**(`/tmp/`):
|
||||
```powershell
|
||||
# 你在 PowerShell(堡垒机后的 Windows)跑:
|
||||
scp "D:\资料\03-项目开发\wecom_it_smart_desk-claude\diagnose-500.sh" user@10.90.5.110:/tmp/
|
||||
# (用你自己的文件传输方式,因为堡垒机禁 scp ProxyJump)
|
||||
```
|
||||
|
||||
2. **PuTTY 登录**:
|
||||
- Host:`10.212.189.210`,Port:`2222`,SSH → Open
|
||||
- 用户 `sxn` + 密码
|
||||
- 堡垒机内 `ssh sxn@10.90.5.110` 跳目标机
|
||||
|
||||
3. **在服务器上跑**:
|
||||
```bash
|
||||
sudo cp /tmp/diagnose-500.sh /opt/wecom-it-desk/
|
||||
cd /opt/wecom-it-desk
|
||||
bash diagnose-500.sh > /tmp/diag.log 2>&1
|
||||
cat /tmp/diag.log
|
||||
```
|
||||
|
||||
4. **把 /tmp/diag.log 的内容贴回 Claude**
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 或者手敲(精简版)
|
||||
|
||||
```bash
|
||||
# 1. 容器状态
|
||||
docker compose ps
|
||||
|
||||
# 2. dist 目录在不在
|
||||
ls /opt/wecom-it-desk/frontend-h5/dist/
|
||||
ls /opt/wecom-it-desk/frontend-h5/dist/assets/
|
||||
|
||||
# 3. nginx 容器内能看到 dist 吗
|
||||
docker compose exec nginx ls /usr/share/nginx/html/itdesk/
|
||||
docker compose exec nginx ls /usr/share/nginx/html/itdesk/assets/
|
||||
|
||||
# 4. SSL 证书
|
||||
docker compose exec nginx ls /etc/nginx/ssl/
|
||||
|
||||
# 5. 直接 curl 测试
|
||||
curl -ksI https://itsupport.servyou.com.cn/itdesk/ | head -10
|
||||
curl -ksI https://itsupport.servyou.com.cn/itportal/ | head -10
|
||||
curl -ksI https://itsupport.servyou.com.cn/itagent/ | head -10
|
||||
curl -ksI https://itsupport.servyou.com.cn/itadmin/ | head -10
|
||||
|
||||
# 6. nginx 日志
|
||||
docker compose logs --tail=20 nginx
|
||||
docker compose logs --tail=20 backend
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 我会关注
|
||||
|
||||
| 现象 | 诊断 |
|
||||
|---|---|
|
||||
| `ls /opt/wecom-it-desk/frontend-h5/dist/` 显示 **No such file** | 部署包没含 H5 dist(nginx 会 404 → 但一般不会 500) |
|
||||
| `docker compose exec nginx ls /usr/share/nginx/html/itdesk/` 失败 | nginx 容器挂载路径错了,或 dist 没拷贝进去 |
|
||||
| `curl -ksI https://itsupport.servyou.com.cn/itdesk/` 返回 **HTTP/1.1 500** | 后端代理或 SPA 内部错误 |
|
||||
| `curl -ksI https://itsupport.servyou.com.cn/itportal/` 也 500 | **全站问题**,看 nginx 日志 |
|
||||
| `curl -ksI https://itsupport.servyou.com.cn/itportal/` 200 但 /itdesk/ 500 | **H5 端特定问题**,看 nginx 容器内的文件 |
|
||||
| nginx 错误日志有 **proxy_pass 错误** | 后端没启动或端口不通 |
|
||||
| nginx 错误日志有 **"rewrite ... cycle"** | try_files 死循环,需修 nginx 配置 |
|
||||
|
||||
---
|
||||
|
||||
> 把输出贴回 Claude 后,我会精确定位 500 根因并给出最小修复。
|
||||
@@ -0,0 +1,54 @@
|
||||
# 手敲 6 段命令(脚本上传失败时用)
|
||||
|
||||
**PuTTY 登录**:
|
||||
- Host:`10.212.189.210`,Port:`2222`,SSH → Open
|
||||
- 用户 `sxn` + 密码
|
||||
- 堡垒机内再 `ssh sxn@10.90.5.110` 跳目标机
|
||||
|
||||
**逐段跑(每段贴回输出)**:
|
||||
|
||||
```bash
|
||||
# === 段 1: 容器 + dist 目录 ===
|
||||
docker compose ps
|
||||
echo "--- H5 dist ---"
|
||||
ls -la /opt/wecom-it-desk/frontend-h5/dist/ 2>&1
|
||||
echo "--- H5 dist/assets ---"
|
||||
ls -la /opt/wecom-it-desk/frontend-h5/dist/assets/ 2>&1
|
||||
|
||||
# === 段 2: nginx 容器内挂载 ===
|
||||
docker compose exec nginx ls -la /usr/share/nginx/html/ 2>&1
|
||||
echo "--- nginx 容器内 itdesk ---"
|
||||
docker compose exec nginx ls -la /usr/share/nginx/html/itdesk/ 2>&1
|
||||
echo "--- nginx 容器内 SSL ---"
|
||||
docker compose exec nginx ls -la /etc/nginx/ssl/ 2>&1
|
||||
|
||||
# === 段 3: 各路径 curl 头(用主机端口绕开 nginx 容器内)===
|
||||
echo "--- /itdesk/ ---"
|
||||
curl -ksI https://itsupport.servyou.com.cn/itdesk/ 2>&1 | head -8
|
||||
echo "--- /itportal/ ---"
|
||||
curl -ksI https://itsupport.servyou.com.cn/itportal/ 2>&1 | head -8
|
||||
echo "--- /itagent/ ---"
|
||||
curl -ksI https://itsupport.servyou.com.cn/itagent/ 2>&1 | head -8
|
||||
echo "--- /itadmin/ ---"
|
||||
curl -ksI https://itsupport.servyou.com.cn/itadmin/ 2>&1 | head -8
|
||||
echo "--- /itdesk/index.html(直接抓 index)---"
|
||||
curl -ks https://itsupport.servyou.com.cn/itdesk/ 2>&1 | head -20
|
||||
|
||||
# === 段 4: 容器内 curl 443 测 ===
|
||||
docker compose exec nginx curl -ksI https://localhost/itdesk/ 2>&1 | head -8
|
||||
echo "---"
|
||||
docker compose exec nginx curl -ksI https://localhost/itportal/ 2>&1 | head -8
|
||||
|
||||
# === 段 5: nginx + backend 日志 ===
|
||||
echo "--- nginx 日志 ---"
|
||||
docker compose logs --tail=30 nginx 2>&1
|
||||
echo "--- backend 日志 ---"
|
||||
docker compose logs --tail=30 backend 2>&1
|
||||
|
||||
# === 段 6: 容器内 nginx 错误日志 ===
|
||||
docker compose exec nginx tail -30 /var/log/nginx/error.log 2>&1
|
||||
echo "--- access.log ---"
|
||||
docker compose exec nginx tail -30 /var/log/nginx/access.log 2>&1
|
||||
```
|
||||
|
||||
**把全部输出贴回 Claude。**
|
||||
@@ -0,0 +1,101 @@
|
||||
# 3 种方法在服务器上跑诊断脚本
|
||||
|
||||
**目标**:在 10.90.5.110 服务器上跑 diagnose-500.sh,把输出粘回给我
|
||||
|
||||
---
|
||||
|
||||
## 方法 1(推荐):PuTTY 连进去,一行命令恢复 + 跑
|
||||
|
||||
**步骤 1**:PuTTY 客户端
|
||||
- Host:`10.212.189.210`,Port:`2222`,SSH → Open
|
||||
- 用户 `sxn` + 密码
|
||||
- 堡垒机内再 `ssh sxn@10.90.5.110` 跳目标机
|
||||
|
||||
**步骤 2**:服务器内贴这一行(整段一次性):
|
||||
```bash
|
||||
cat > /tmp/diag.sh << 'ENDOFSCRIPT'
|
||||
#!/bin/bash
|
||||
docker compose ps
|
||||
echo "---"
|
||||
ls -la /opt/wecom-it-desk/frontend-h5/dist/ 2>&1 | head -10
|
||||
echo "--- assets ---"
|
||||
ls -la /opt/wecom-it-desk/frontend-h5/dist/assets/ 2>&1 | head -10
|
||||
echo "--- nginx 容器内 ---"
|
||||
docker compose exec nginx ls -la /usr/share/nginx/html/itdesk/ 2>&1 | head -10
|
||||
echo "--- nginx 容器内 assets ---"
|
||||
docker compose exec nginx ls -la /usr/share/nginx/html/itdesk/assets/ 2>&1 | head -10
|
||||
echo "--- SSL ---"
|
||||
docker compose exec nginx ls -la /etc/nginx/ssl/ 2>&1 | head -10
|
||||
echo "--- /itdesk/ 头 ---"
|
||||
curl -ksI https://itsupport.servyou.com.cn/itdesk/ 2>&1 | head -8
|
||||
echo "--- /itportal/ 头 ---"
|
||||
curl -ksI https://itsupport.servyou.com.cn/itportal/ 2>&1 | head -8
|
||||
echo "--- /itagent/ 头 ---"
|
||||
curl -ksI https://itsupport.servyou.com.cn/itagent/ 2>&1 | head -8
|
||||
echo "--- /itadmin/ 头 ---"
|
||||
curl -ksI https://itsupport.servyou.com.cn/itadmin/ 2>&1 | head -8
|
||||
echo "--- /itdesk/ 完整 body 前 20 行 ---"
|
||||
curl -ks https://itsupport.servyou.com.cn/itdesk/ 2>&1 | head -20
|
||||
echo "--- nginx 错误日志 ---"
|
||||
docker compose exec nginx tail -30 /var/log/nginx/error.log 2>&1
|
||||
echo "--- nginx 访问日志 ---"
|
||||
docker compose exec nginx tail -20 /var/log/nginx/access.log 2>&1
|
||||
echo "--- backend 日志 ---"
|
||||
docker compose logs --tail=20 backend 2>&1
|
||||
ENDOFSCRIPT
|
||||
bash /tmp/diag.sh 2>&1
|
||||
```
|
||||
|
||||
**步骤 3**:把输出整段粘回给我
|
||||
|
||||
---
|
||||
|
||||
## 方法 2:用 scp 上传本地脚本
|
||||
|
||||
**前提**:你能 scp 到 10.90.5.110(堡垒机后的方式)
|
||||
|
||||
```bash
|
||||
scp "C:\Users\simon\Downloads\diagnose-500 (1).sh" sxn@10.90.5.110:/tmp/
|
||||
# (如果直连 scp 不通,可能要用堡垒机的文件传输功能)
|
||||
```
|
||||
|
||||
然后 PuTTY 连进去跑:
|
||||
- Host:`10.212.189.210`,Port:`2222`,SSH → Open
|
||||
- 堡垒机内 `ssh sxn@10.90.5.110` 跳目标机
|
||||
```bash
|
||||
sudo cp /tmp/diagnose-500.sh /opt/wecom-it-desk/
|
||||
cd /opt/wecom-it-desk
|
||||
bash diagnose-500.sh > /tmp/diag.log 2>&1
|
||||
cat /tmp/diag.log
|
||||
```
|
||||
|
||||
把 `cat /tmp/diag.log` 的输出粘回
|
||||
|
||||
---
|
||||
|
||||
## 方法 3:服务器直接下载(若服务器能上外网)
|
||||
|
||||
```bash
|
||||
# PuTTY 连:Host 10.212.189.210 Port 2222 → 堡垒机内 ssh sxn@10.90.5.110
|
||||
cd /tmp
|
||||
# 如果服务器能访问 GitHub raw / Gitea
|
||||
curl -O https://你的存放点/diagnose-500.sh
|
||||
bash diagnose-500.sh > /tmp/diag.log 2>&1
|
||||
cat /tmp/diag.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最简版(只要 5 行输出)
|
||||
|
||||
如果方法 1 太长,**只要这 5 行**就够我定位:
|
||||
|
||||
```bash
|
||||
docker compose ps 2>&1
|
||||
ls -la /opt/wecom-it-desk/frontend-h5/dist/assets/ 2>&1
|
||||
docker compose exec nginx ls -la /usr/share/nginx/html/itdesk/ 2>&1
|
||||
docker compose exec nginx tail -10 /var/log/nginx/error.log 2>&1
|
||||
curl -ksI https://itsupport.servyou.com.cn/itdesk/ 2>&1 | head -8
|
||||
```
|
||||
|
||||
**把这 5 段输出粘回,我能立刻定位 500 原因。**
|
||||
@@ -0,0 +1,470 @@
|
||||
# 智能IT支持服务台 — 新服务器部署手册
|
||||
|
||||
> **目标服务器**:`10.90.5.110`(公司内网,**2026-06-15 起替代 10.80.0.136**)
|
||||
> **域名**:`itsupport.servyou.com.cn`
|
||||
> **访问方式**:通过堡垒机 `10.212.189.210:2222`(用户 `sxn`,OTP 动态口令认证)
|
||||
> **Docker**:已安装
|
||||
> **部署方式**:Docker Compose(4容器:nginx + backend + postgres + redis)
|
||||
|
||||
---
|
||||
|
||||
## 一、前置条件检查清单
|
||||
|
||||
| 条件 | 状态 | 验证命令 |
|
||||
|------|------|---------|
|
||||
| Linux 服务器 10.90.5.110(替代旧 10.80.0.136) | ✅ 已确认 | 2026-06-15 起使用 |
|
||||
| Docker 已安装 | ✅ 已确认 | `docker --version` |
|
||||
| Docker Compose V2 | 待确认 | `docker compose version` |
|
||||
| 端口 80 未被占用 | 待确认 | `ss -tlnp \| grep :80` |
|
||||
| DNS 解析 | 待配置 | `nslookup itsupport.servyou.com.cn` |
|
||||
| 堡垒机可访问 | 待确认 | `ssh -p 2222 user@10.212.189.210` |
|
||||
|
||||
---
|
||||
|
||||
## 二、SSH 通过堡垒机连接
|
||||
|
||||
### 2.1 什么是堡垒机?
|
||||
|
||||
堡垒机(跳板机)是公司内网的安全访问入口。你不能直接 SSH 到目标服务器,必须先登录堡垒机,再从堡垒机跳转到目标服务器。OTP(One-Time Password)是指每次登录需要输入动态验证码(通常来自手机令牌 App)。
|
||||
|
||||
### 2.2 连接方式
|
||||
|
||||
**PuTTY 客户端(用户实际使用)**:
|
||||
- 打开 PuTTY
|
||||
- Host Name(IP 地址):`10.212.189.210`
|
||||
- Port:`2222`
|
||||
- Connection type:SSH
|
||||
- Saved Sessions:起名(如 `wecom-bastion`)→ Save
|
||||
- 点 Open
|
||||
- 用户 `sxn` + 密码
|
||||
- **堡垒机内再跳目标机**:
|
||||
```bash
|
||||
ssh sxn@10.90.5.110
|
||||
```
|
||||
|
||||
> **OpenSSH `ssh -J` 方式不再使用**(用户已确认用 PuTTY,2026-06-15)
|
||||
# 登录成功后:
|
||||
ssh sxn@10.90.5.110
|
||||
```
|
||||
|
||||
### 2.3 配置 SSH 快捷方式(推荐)
|
||||
|
||||
在开发机上编辑 `~/.ssh/config`,添加以下内容,以后只需要 `ssh itdesk` 即可:
|
||||
|
||||
```
|
||||
# 堡垒机
|
||||
Host bastion
|
||||
HostName 10.212.189.210
|
||||
Port 2222
|
||||
User sxn
|
||||
|
||||
# 智能IT支持服务台服务器
|
||||
Host itdesk
|
||||
HostName 10.90.5.110
|
||||
User sxn
|
||||
ProxyJump bastion
|
||||
```
|
||||
|
||||
> **堡垒机用户名为 `sxn`,已填入下方命令中**
|
||||
|
||||
之后只需:
|
||||
```bash
|
||||
ssh itdesk # 自动通过堡垒机跳转
|
||||
scp file itdesk:/opt/ # 文件传输也会自动走堡垒机
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、文件传输(通过堡垒机)
|
||||
|
||||
### 3.1 SCP 传输(推荐小文件/单次传输)
|
||||
|
||||
```bash
|
||||
# 上传单个文件
|
||||
scp -o "ProxyJump=sxn@10.212.189.210:2222" \
|
||||
it-smart-desk-server-deploy.zip \
|
||||
sxn@10.90.5.110:/opt/
|
||||
|
||||
# 如果已配置 ~/.ssh/config:
|
||||
scp it-smart-desk-server-deploy.zip itdesk:/opt/
|
||||
```
|
||||
|
||||
### 3.2 大文件传输优化
|
||||
|
||||
部署包可能较大(含后端源码 + 前端产物),如果 SCP 速度慢,可以先传到堡垒机再转:
|
||||
|
||||
```bash
|
||||
# 步骤1:传到堡垒机
|
||||
scp -P 2222 it-smart-desk-server-deploy.zip sxn@10.212.189.210:/tmp/
|
||||
|
||||
# 步骤2:SSH 到堡垒机
|
||||
ssh -p 2222 sxn@10.212.189.210
|
||||
|
||||
# 步骤3:从堡垒机传到目标服务器
|
||||
scp /tmp/it-smart-desk-server-deploy.zip sxn@10.90.5.110:/opt/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、部署步骤(完整流程)
|
||||
|
||||
### 步骤 1:在开发机上构建前端并打包
|
||||
|
||||
```bash
|
||||
# 在开发机(Windows)上,进入项目根目录
|
||||
cd D:\资料\03-项目开发\wecom_it_smart_desk
|
||||
|
||||
# 方法A:使用打包脚本(自动构建前端 + 组装 + 打包)
|
||||
bash deploy-server/package.sh
|
||||
|
||||
# 方法B:手动构建
|
||||
# H5 员工端
|
||||
cd frontend-h5
|
||||
npm install && npm run build
|
||||
|
||||
# 坐席工作台
|
||||
cd ../frontend-agent
|
||||
npm install && npm run build
|
||||
|
||||
# 手动打包(如果不用 package.sh)
|
||||
# 需要把 frontend-h5/dist/、frontend-agent/dist/、backend/、deploy-server/ 下的配置文件一起打包
|
||||
```
|
||||
|
||||
打包完成后,项目根目录下会生成 `it-smart-desk-server-deploy.zip`。
|
||||
|
||||
### 步骤 2:上传部署包到服务器
|
||||
|
||||
```bash
|
||||
# 在开发机上执行
|
||||
scp -o "ProxyJump=sxn@10.212.189.210:2222" \
|
||||
it-smart-desk-server-deploy.zip \
|
||||
sxn@10.90.5.110:/tmp/
|
||||
```
|
||||
|
||||
> 上传到 `/tmp/` 而非 `/opt/`,因为普通用户对 `/opt/` 没有写权限
|
||||
|
||||
### 步骤 3:登录服务器并解压
|
||||
|
||||
**PuTTY 登录**(见 §2.2):
|
||||
- Host:`10.212.189.210`,Port:`2222`,SSH
|
||||
- 堡垒机内再 `ssh sxn@10.90.5.110`
|
||||
|
||||
```bash
|
||||
# 切换 root(普通用户对 /opt 无写权限)
|
||||
sudo -i
|
||||
|
||||
# 移动并解压部署包
|
||||
mv /tmp/it-smart-desk-server-deploy.zip /opt/
|
||||
cd /opt
|
||||
unzip it-smart-desk-server-deploy.zip
|
||||
|
||||
# 重命名目录为更简短的名称
|
||||
mv it-smart-desk-server-deploy wecom-it-desk
|
||||
cd wecom-it-desk
|
||||
```
|
||||
|
||||
### 步骤 4:配置环境变量
|
||||
|
||||
```bash
|
||||
cd /opt/wecom-it-desk
|
||||
|
||||
# 从模板创建 .env
|
||||
cp .env.example .env
|
||||
|
||||
# 编辑 .env
|
||||
vi .env
|
||||
```
|
||||
|
||||
**阶段一(Mock 模式)最小配置** — 只需确认以下默认值:
|
||||
|
||||
```ini
|
||||
# 数据库密码(默认即可,首次初始化后不可更改)
|
||||
POSTGRES_PASSWORD=wecom_secret_2024
|
||||
|
||||
# 企微配置(阶段一 Mock 模式可以留空)
|
||||
WECOM_CORP_ID=
|
||||
WECOM_AGENT_ID=1000002
|
||||
WECOM_SECRET=
|
||||
WECOM_TOKEN=
|
||||
WECOM_ENCODING_AES_KEY=
|
||||
|
||||
# Mock 登录(阶段一设为 true)
|
||||
MOCK_LOGIN_ENABLED=true
|
||||
|
||||
# Dify AI(暂时可以留空)
|
||||
DIFY_API_URL=http://yw-dify.dc.servyou-it.com/dify2openai/v1/chat/completions
|
||||
DIFY_API_KEY=
|
||||
```
|
||||
|
||||
> **重要**:`POSTGRES_PASSWORD` 首次启动时写入数据库,之后修改 `.env` 不会生效。如需修改密码,必须删除数据卷重建。
|
||||
|
||||
### 步骤 5:部署
|
||||
|
||||
```bash
|
||||
cd /opt/wecom-it-desk
|
||||
|
||||
# 添加执行权限
|
||||
chmod +x deploy.sh
|
||||
|
||||
# 执行部署
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
脚本会自动:
|
||||
1. ✅ 检查 Docker 环境
|
||||
2. ✅ 检查 .env 配置
|
||||
3. ✅ 检查前端文件
|
||||
4. ✅ 构建后端 Docker 镜像
|
||||
5. ✅ 启动 4 个容器
|
||||
6. ✅ 等待服务就绪
|
||||
7. ✅ 验证部署
|
||||
|
||||
### 步骤 6:验证部署
|
||||
|
||||
```bash
|
||||
# 在服务器上验证
|
||||
curl http://localhost/api/health
|
||||
# 应返回 {"status":"healthy"}
|
||||
|
||||
curl http://localhost/itdesk/
|
||||
# 应返回 H5 前端 HTML
|
||||
|
||||
# 查看所有容器状态
|
||||
docker compose ps
|
||||
# 应显示 4 个容器都是 Up 状态
|
||||
|
||||
# 如果有容器未启动,查看日志
|
||||
docker compose logs --tail 50 backend
|
||||
docker compose logs --tail 50 postgres
|
||||
```
|
||||
|
||||
### 步骤 7:配置 DNS
|
||||
|
||||
需要联系公司 IT 运维,在公司 DNS 上添加 A 记录:
|
||||
|
||||
```
|
||||
itsupport.servyou.com.cn A 10.90.5.110
|
||||
```
|
||||
|
||||
**DNS 未生效前**,可以通过本地 hosts 文件测试:
|
||||
|
||||
```
|
||||
# Windows: C:\Windows\System32\drivers\etc\hosts
|
||||
# macOS/Linux: /etc/hosts
|
||||
# 添加一行:
|
||||
10.90.5.110 itsupport.servyou.com.cn
|
||||
```
|
||||
|
||||
> 注意:修改 hosts 文件后,浏览器可能有 DNS 缓存。Chrome 可访问 `chrome://net-internals/#dns` 清除缓存,或用无痕窗口测试。
|
||||
|
||||
### 步骤 8:浏览器验证
|
||||
|
||||
DNS 生效后(或配置了本地 hosts),在浏览器中访问:
|
||||
|
||||
| 页面 | URL | 预期结果 |
|
||||
|------|-----|---------|
|
||||
| H5 员工端 | `http://itsupport.servyou.com.cn/itdesk/` | 看到登录页面 |
|
||||
| 坐席工作台 | `http://itsupport.servyou.com.cn/itagent/` | 看到坐席工作台 |
|
||||
| API 健康检查 | `http://itsupport.servyou.com.cn/api/health` | `{"status":"healthy"}` |
|
||||
|
||||
**Mock 登录测试**:
|
||||
1. 访问 `http://itsupport.servyou.com.cn/itdesk/login`
|
||||
2. 输入任意工号和姓名(如 `test001` / `测试用户`)
|
||||
3. 应成功登录并进入聊天页面
|
||||
|
||||
---
|
||||
|
||||
## 五、部署文件结构
|
||||
|
||||
```
|
||||
/opt/wecom-it-desk/
|
||||
├── docker-compose.yml # Docker Compose 配置(4容器)
|
||||
├── .env # 环境变量(已配置)
|
||||
├── .env.example # 环境变量模板
|
||||
├── deploy.sh # 一键部署脚本
|
||||
├── README.md # 本手册
|
||||
├── nginx/
|
||||
│ └── nginx.conf # Nginx 配置(反代 + 静态文件)
|
||||
├── backend/
|
||||
│ ├── Dockerfile # 后端镜像构建文件
|
||||
│ ├── requirements.txt # Python 依赖
|
||||
│ └── app/ # 后端源代码
|
||||
├── frontend-h5/
|
||||
│ └── dist/ # H5 员工端构建产物
|
||||
└── frontend-agent/
|
||||
└── dist/ # 坐席工作台构建产物
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、常用运维命令
|
||||
|
||||
在服务器上 `/opt/wecom-it-desk` 目录下执行:
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 查看服务状态 | `./deploy.sh status` |
|
||||
| 查看后端日志 | `./deploy.sh logs` |
|
||||
| 停止所有服务 | `./deploy.sh stop` |
|
||||
| 重新构建后端 | `./deploy.sh rebuild` |
|
||||
| 重置数据库 | `./deploy.sh reset-db` |
|
||||
| 手动启动 | `docker compose up -d` |
|
||||
| 手动停止 | `docker compose down` |
|
||||
| 只重启后端 | `docker compose restart backend` |
|
||||
| 查看数据库 | `docker exec -it wecom_it_postgres psql -U wecom -d wecom_it_desk` |
|
||||
| 查看 Redis | `docker exec -it wecom_it_redis redis-cli` |
|
||||
| 重载 Nginx | `docker exec wecom_it_nginx nginx -s reload` |
|
||||
| 查看容器日志 | `docker compose logs --tail 50 <容器名>` |
|
||||
|
||||
---
|
||||
|
||||
## 七、升级前端
|
||||
|
||||
当有新的前端版本需要部署时:
|
||||
|
||||
```bash
|
||||
# 1. 在开发机上构建新版本
|
||||
cd frontend-h5 && npm run build
|
||||
cd frontend-agent && npm run build
|
||||
|
||||
# 2. 上传到服务器(通过堡垒机)
|
||||
scp -o "ProxyJump=sxn@10.212.189.210:2222" \
|
||||
-r frontend-h5/dist/ \
|
||||
sxn@10.90.5.110:/opt/wecom-it-desk/frontend-h5/dist/
|
||||
|
||||
scp -o "ProxyJump=sxn@10.212.189.210:2222" \
|
||||
-r frontend-agent/dist/ \
|
||||
sxn@10.90.5.110:/opt/wecom-it-desk/frontend-agent/dist/
|
||||
|
||||
# 3. 重载 Nginx(不需要重启整个服务)
|
||||
ssh itdesk # 如果已配置 SSH 快捷方式
|
||||
cd /opt/wecom-it-desk
|
||||
docker exec wecom_it_nginx nginx -s reload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、升级后端
|
||||
|
||||
```bash
|
||||
# 1. 上传新代码到服务器
|
||||
scp -o "ProxyJump=sxn@10.212.189.210:2222" \
|
||||
-r backend/ \
|
||||
sxn@10.90.5.110:/opt/wecom-it-desk/backend/
|
||||
|
||||
# 2. 重新构建并启动
|
||||
ssh itdesk
|
||||
cd /opt/wecom-it-desk
|
||||
./deploy.sh rebuild
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、故障排查
|
||||
|
||||
### 后端容器一直重启
|
||||
|
||||
```bash
|
||||
# 1. 查看容器状态
|
||||
docker compose ps
|
||||
|
||||
# 2. 查看后端日志(最常见原因:数据库连接失败)
|
||||
docker compose logs --tail 100 backend
|
||||
|
||||
# 3. 检查 PostgreSQL 是否健康
|
||||
docker exec wecom_it_postgres pg_isready -U wecom -d wecom_it_desk
|
||||
|
||||
# 4. 检查 Redis 是否健康
|
||||
docker exec wecom_it_redis redis-cli ping
|
||||
```
|
||||
|
||||
### PostgreSQL 密码错误
|
||||
|
||||
```bash
|
||||
# ⚠️ 这会清空所有数据!只有首次部署密码错误时才需要
|
||||
docker compose down
|
||||
docker volume rm wecom-it-desk_postgres_data
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### H5/坐席端白屏
|
||||
|
||||
```bash
|
||||
# 检查前端文件是否存在
|
||||
docker exec wecom_it_nginx ls /usr/share/nginx/html/itdesk/
|
||||
docker exec wecom_it_nginx ls /usr/share/nginx/html/itagent/
|
||||
|
||||
# 检查 index.html 中的 base 路径是否正确
|
||||
docker exec wecom_it_nginx cat /usr/share/nginx/html/itdesk/index.html | grep /itdesk/
|
||||
docker exec wecom_it_nginx cat /usr/share/nginx/html/itagent/index.html | grep /itagent/
|
||||
```
|
||||
|
||||
### DNS 未生效
|
||||
|
||||
```bash
|
||||
# 在服务器上验证
|
||||
nslookup itsupport.servyou.com.cn
|
||||
|
||||
# 如果 DNS 未配置,临时用 IP 直接访问
|
||||
curl http://10.90.5.110/itdesk/
|
||||
curl http://10.90.5.110/api/health
|
||||
```
|
||||
|
||||
### Mock 登录返回 401
|
||||
|
||||
```bash
|
||||
# 1. 确认 .env 中 MOCK_LOGIN_ENABLED=true
|
||||
cat /opt/wecom-it-desk/.env | grep MOCK
|
||||
|
||||
# 2. 检查后端日志
|
||||
docker compose logs --tail 50 backend | grep mock
|
||||
|
||||
# 3. 直接测试 mock-login 接口
|
||||
curl -X POST http://localhost/api/h5/mock-login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"employee_id":"test001","employee_name":"测试用户"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、HTTPS 配置(可选)
|
||||
|
||||
如果公司要求 HTTPS,有两种方式:
|
||||
|
||||
### 方式一:公司统一 SSL 终端(推荐)
|
||||
|
||||
```
|
||||
客户端 → HTTPS → 公司SSL终端(F5/网关,公网 115.236.188.3) → HTTP → 10.90.5.110:80
|
||||
```
|
||||
|
||||
不需要在本服务器上配置证书。联系运维配置 SSL 终端即可。
|
||||
|
||||
### 方式二:本机 SSL
|
||||
|
||||
编辑 `nginx/nginx.conf`,取消 HTTPS server 块注释,配置证书路径。
|
||||
|
||||
---
|
||||
|
||||
## 十一、部署说明
|
||||
|
||||
> ⚠️ NAS部署方案(itdesk.amanzac.com)已于2026年6月15日下线,现统一使用公司内网服务器部署。
|
||||
|
||||
| 维度 | NAS 部署(已下线) | 当前服务器部署(10.90.5.110) |
|
||||
|------|---------------------------|-------------------------------|
|
||||
| 容器数量 | 5个(含 cloudflared) | 4个(无 cloudflared) |
|
||||
| 外网访问 | Cloudflare Tunnel | 公司 DNS 直连 |
|
||||
| 域名 | itdesk.amanzac.com | itsupport.servyou.com.cn |
|
||||
| SSL | Cloudflare 自动 | 无(内网 HTTP)或公司统一 SSL |
|
||||
| 数据平台反代 | 需要(共用域名) | 不需要(独立域名) |
|
||||
| 部署目录 | `/volume1/docker/wecom-it-desk` | `/opt/wecom-it-desk` |
|
||||
| 文件传输 | File Station / 7z | SCP 通过堡垒机 |
|
||||
|
||||
---
|
||||
|
||||
## 十二、相关文档
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [堡垒机运维工具](./11-堡垒机运维工具.md) | 通过 JumpServer 自动化执行远程命令、文件上传下载 |
|
||||
| [版本更新说明](./05-版本更新说明-v1.1.0-20260614.md) | 各版本功能变更记录 |
|
||||
| [NAS 部署指南](./08-NAS部署指南-预生产.md) | 群晖 NAS 测试环境部署 |
|
||||
@@ -0,0 +1,165 @@
|
||||
# 用户手册:扫码登录 + OTP 二次认证(Phase 1+2)
|
||||
|
||||
> 创建:2026-06-21
|
||||
> 适用版本:v0.7.0+ (Phase 1+2 上线后)
|
||||
> 读者:全体员工、坐席、管理员
|
||||
|
||||
---
|
||||
|
||||
## 📖 这是什么?
|
||||
|
||||
从 v0.7.0 开始,登录方式升级为**扫码登录 + OTP 二次认证**:
|
||||
- ✅ 不再依赖企业微信应用入口,任意浏览器都能打开
|
||||
- ✅ 多角色用户(坐席+管理员)可在 Portal 选角色自动跳转
|
||||
- ✅ 管理员每次登录强制 OTP,高危操作也强制 OTP
|
||||
- ✅ 备用通道:蜂鸟短信(手机丢/没装 Authenticator 时用)
|
||||
|
||||
---
|
||||
|
||||
## 🧑💼 员工端(H5)
|
||||
|
||||
### 入口
|
||||
- 仍在企微内打开"IT智能服务台"应用
|
||||
- 不需要扫码登录,沿用企微 OAuth
|
||||
|
||||
### 使用场景
|
||||
- 提工单
|
||||
- 看历史会话
|
||||
- 查知识库
|
||||
|
||||
---
|
||||
|
||||
## 🧑🔧 坐席端(Agent)
|
||||
|
||||
### 首次登录(扫码)
|
||||
|
||||
```
|
||||
步骤 1:浏览器访问 https://itsupport.servyou.com.cn/itportal/
|
||||
步骤 2:页面显示二维码(120 秒有效)
|
||||
步骤 3:用企业微信扫 → 确认登录
|
||||
步骤 4:自动跳到 /itagent/workspace
|
||||
```
|
||||
|
||||
### 日常登录
|
||||
|
||||
- 浏览器直接打开 `https://itsupport.servyou.com.cn/itagent/`
|
||||
- 没登录 → 自动跳到 Portal 扫码
|
||||
- 第二次扫码可免重复(浏览器记住 localStorage)
|
||||
|
||||
### 高危操作时 OTP
|
||||
|
||||
如果你是**坐席+管理员**(双角色),触发以下操作前会弹 OTP 输入框:
|
||||
- 改权限
|
||||
- 改系统配置
|
||||
- 导出数据
|
||||
- 封号
|
||||
- 新增账号/MFA 重置
|
||||
|
||||
弹框出现 → 输入 Authenticator 6 位码 → 验证通过(30 分钟内免重输)
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ 管理员端(Admin)
|
||||
|
||||
### 入口
|
||||
|
||||
- 浏览器直接打开 `https://itsupport.servyou.com.cn/itadmin/`
|
||||
- 没登录 → 自动跳到 Portal 扫码
|
||||
|
||||
### 强制 OTP
|
||||
|
||||
管理员**每次登录都需要 OTP**:
|
||||
- 扫码登录成功后,会跳到 MFA 绑定页(首次)或 OTP 验证页
|
||||
- 输入 Authenticator 6 位码 → 进入管理后台
|
||||
- 高危操作前还要再验一次(30 分钟内免重输)
|
||||
|
||||
### 首次绑定 OTP(强制)
|
||||
|
||||
```
|
||||
步骤 1:登录后 → 自动跳 /mfa-bind
|
||||
步骤 2:用 Google Authenticator / 微软 Authenticator / Authy 扫描二维码
|
||||
步骤 3:输入 Authenticator 显示的 6 位码 → 点"启用 OTP"
|
||||
步骤 4:绑定成功,后续登录用 OTP 验证
|
||||
```
|
||||
|
||||
### 丢手机兜底(管理员后台重置)
|
||||
|
||||
如果你手机丢了/坏了,**找其他管理员重置**:
|
||||
|
||||
```
|
||||
步骤 1:其他管理员登录 /itadmin/
|
||||
步骤 2:进入"用户管理" → "MFA 管理"
|
||||
步骤 3:搜索你的姓名 → 点"重置 MFA"
|
||||
步骤 4:你下次登录时重新绑定 OTP
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🆘 备用通道:蜂鸟 SMS
|
||||
|
||||
什么情况下用:
|
||||
- 📱 手机丢了/坏了
|
||||
- 🆕 刚入职,还没装 Authenticator
|
||||
- 🔧 Authenticator 客户端不兼容
|
||||
|
||||
怎么用:
|
||||
- 在 OTP 输入页点"收不到验证码?短信验证"
|
||||
- 输入手机号(企微已绑定)→ 收短信码 → 验证
|
||||
|
||||
---
|
||||
|
||||
## 📱 推荐 OTP 客户端
|
||||
|
||||
| 客户端 | 平台 | 推荐度 |
|
||||
|---|---|---|
|
||||
| Google Authenticator | iOS / Android | ⭐⭐⭐⭐⭐ |
|
||||
| 微软 Authenticator | iOS / Android | ⭐⭐⭐⭐ |
|
||||
| Authy | iOS / Android / 桌面 | ⭐⭐⭐⭐⭐ |
|
||||
| 1Password | 全平台 | ⭐⭐⭐ |
|
||||
|
||||
公司偏好:**Google Authenticator**(零依赖,离线可用)
|
||||
|
||||
---
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
### Q1:扫码登录过期了怎么办?
|
||||
A:二维码有效期 120 秒,过期后点"刷新二维码"按钮。
|
||||
|
||||
### Q2:扫码登录失败?
|
||||
A:
|
||||
- 确认用的是企业微信(不是普通微信)
|
||||
- 确认企微里能看到"IT智能服务台"应用
|
||||
- 刷新页面重新生成二维码
|
||||
|
||||
### Q3:OTP 输入错误?
|
||||
A:连续 5 次错误会被锁定 5 分钟,等 5 分钟后再试。
|
||||
|
||||
### Q4:换手机了怎么办?
|
||||
A:登录前在旧手机上导出 OTP(Google Authenticator 支持),或者找管理员后台重置。
|
||||
|
||||
### Q5:多角色用户(admin + agent)怎么登录?
|
||||
A:扫码登录成功后,Portal 自动跳到角色选择页,选你要进入的工作台。
|
||||
|
||||
### Q6:H5 员工端也需要扫码吗?
|
||||
A:不需要。H5 员工端仍在企微内,沿用企微 OAuth。
|
||||
|
||||
### Q7:扫码登录安全吗?
|
||||
A:扫码登录比企微 OAuth 还安全:
|
||||
- 员工必须用企微扫(企微已经做了员工身份认证)
|
||||
- 二维码 120 秒过期
|
||||
- Token 8 小时过期
|
||||
- 高危操作还要再 OTP 一次
|
||||
|
||||
---
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
- 内部: 信息技术部 服务台
|
||||
- 紧急: 群里 @ IT 主管
|
||||
- 反馈: https://itsupport.servyou.com.cn/itdesk/feedback
|
||||
|
||||
---
|
||||
|
||||
**变更历史**:
|
||||
- 2026-06-21 创建(Phase 1+2 培训)
|
||||
@@ -0,0 +1,114 @@
|
||||
# WAF 转发配置申请
|
||||
|
||||
## 问题描述
|
||||
|
||||
`itsupport.servyou.com.cn` 域名无法访问,浏览器超时。需 WAF 配置转发规则。
|
||||
|
||||
---
|
||||
|
||||
## 证据链
|
||||
|
||||
### 1. 服务器本地 — 服务正常 ✅
|
||||
|
||||
```
|
||||
# HTTP 已强制跳转 HTTPS(nginx 配置 301 重定向)
|
||||
[root@hz-oa-ai-g-dataquery-90-5-110 ~]# curl http://localhost/itdesk/health
|
||||
<html><head><title>301 Moved Permanently</title></head>...nginx/1.27.5</html>
|
||||
|
||||
# HTTPS 正常响应
|
||||
[root@hz-oa-ai-g-dataquery-90-5-110 ~]# curl -k https://127.0.0.1/itdesk/health -H "Host: itsupport.servyou.com.cn"
|
||||
healthy
|
||||
```
|
||||
|
||||
### 2. SSL 证书 — 有效 ✅
|
||||
|
||||
```
|
||||
[root@hz-oa-ai-g-dataquery-90-5-110 ~]# echo | openssl s_client -connect 127.0.0.1:443 -servername itsupport.servyou.com.cn
|
||||
CONNECTED(00000003)
|
||||
depth=2 C=US, O=DigiCert Inc, CN=DigiCert Global Root G2
|
||||
depth=1 C=US, O=DigiCert, Inc., CN=GeoTrust G2 TLS CN RSA4096 SHA256 2022 CA1
|
||||
depth=0 C=CN, ST=浙江省, L=杭州市, O=税友软件集团股份有限公司, CN=*.servyou.com.cn
|
||||
Verification: OK
|
||||
Protocol: TLSv1.3, Cipher: TLS_AES_256_GCM_SHA384
|
||||
Verify return code: 0 (ok)
|
||||
```
|
||||
|
||||
证书信息:
|
||||
- 主体:`CN=*.servyou.com.cn`(通配符证书)
|
||||
- 颁发者:`GeoTrust G2 TLS CN RSA4096 SHA256 2022 CA1`
|
||||
- 有效期:2025-12-23 ~ 2027-01-12
|
||||
|
||||
### 3. DNS 解析 — 指向 WAF ✅
|
||||
|
||||
```
|
||||
# 服务器 DNS 解析到 WAF 公网 IP
|
||||
[root@hz-oa-ai-g-dataquery-90-5-110 ~]# ping -c 1 itsupport.servyou.com.cn
|
||||
PING itsupport.servyou.com.cn (115.236.188.3): 56(84) bytes of data.
|
||||
--- itsupport.servyou.com.cn ping statistics ---
|
||||
1 packets transmitted, 0 received, 100% packet loss
|
||||
```
|
||||
|
||||
- 解析结果:`115.236.188.3`(WAF 公网 IP)
|
||||
- ping 100% 丢失(WAF 禁 ICMP,正常)
|
||||
|
||||
### 4. WAF 转发 — 不通 ❌
|
||||
|
||||
```
|
||||
# 从服务器通过域名访问 HTTP(超时)
|
||||
[root@hz-oa-ai-g-dataquery-90-5-110 ~]# curl -v http://itsupport.servyou.com.cn/itdesk/health
|
||||
* Trying 115.236.188.3:80...
|
||||
^C(超时无响应)
|
||||
|
||||
# 从服务器通过域名访问 HTTPS(超时)
|
||||
[root@hz-oa-ai-g-dataquery-90-5-110 ~]# curl -v https://itsupport.servyou.com.cn/itdesk/health
|
||||
* Trying 115.236.188.3:443...
|
||||
^C(超时无响应)
|
||||
```
|
||||
|
||||
### 5. 服务器外网连通性 — 正常 ✅
|
||||
|
||||
```
|
||||
# 企微 API 可达
|
||||
[root@hz-oa-ai-g-dataquery-90-5-110 ~]# curl -s https://qyapi.weixin.qq.com/cgi-bin/gettoken
|
||||
{"errcode":41004,"errmsg":"corpsecret missing", "from ip": "218.75.34.87"}
|
||||
|
||||
# PyPI 镜像可达
|
||||
[root@hz-oa-ai-g-dataquery-90-5-110 ~]# curl -s https://pypi.tuna.tsinghua.edu.cn/
|
||||
<html><head><title>302 Found</title></head>...nginx/1.22.1</html>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 结论
|
||||
|
||||
| 环节 | 状态 |
|
||||
|------|------|
|
||||
| 服务器(10.90.5.110) | ✅ HTTP/HTTPS 服务正常 |
|
||||
| SSL 证书(*.servyou.com.cn) | ✅ 有效,TLSv1.3 |
|
||||
| DNS 解析 | ✅ 指向 WAF(115.236.188.3) |
|
||||
| 服务器外网连通性 | ✅ 企微 API / PyPI 均可达 |
|
||||
| **WAF 转发到后端** | **❌ 未配置 — 流量未到达 10.90.5.110** |
|
||||
|
||||
---
|
||||
|
||||
## 需要配置
|
||||
|
||||
请 WAF/网络团队配置转发规则:
|
||||
|
||||
```
|
||||
域名:itsupport.servyou.com.cn
|
||||
源端口:80(HTTP)/ 443(HTTPS)
|
||||
转发目标:10.90.5.110:80
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 服务器信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| 服务器 IP | 10.90.5.110 |
|
||||
| 服务端口 | 80(HTTP→HTTPS 重定向)+ 443(HTTPS) |
|
||||
| 域名 | itsupport.servyou.com.cn |
|
||||
| SSL 证书 | *.servyou.com.cn(DigiCert,有效期至 2027-01-12) |
|
||||
| 系统 | Linux(Docker 部署,nginx 反向代理) |
|
||||
@@ -0,0 +1,138 @@
|
||||
# 通讯链路诊断方案
|
||||
|
||||
> 日期:2026-07-03
|
||||
> 目标:诊断当前系统通讯问题,无论结果启动重构方案
|
||||
|
||||
---
|
||||
|
||||
## 一、通讯链路架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ 完整通讯链路 │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 【用户 → 坐席】 │
|
||||
│ ┌──────────┐ 企微回调 ┌──────────┐ 路由 ┌─────────┐ │
|
||||
│ │ 用户发送 │ ──────────────→ │ 后端API │ ──────────→ │ Message │ │
|
||||
│ │ 消息 │ /wecom/ │ 回调入口 │ │ Router │ │
|
||||
│ └──────────┘ callback └──────────┘ └────┬────┘ │
|
||||
│ │ │ │
|
||||
│ │ ▼ │
|
||||
│ │ ┌───────────┐ │
|
||||
│ │ │ 消息入库 │ │
|
||||
│ │ │ (DB存储) │ │
|
||||
│ │ └───────────┘ │
|
||||
│ │ │ │
|
||||
│ │ ┌────────────────┘ │
|
||||
│ │ ▼ │
|
||||
│ │ ┌──────────┐ │
|
||||
│ │ │ 坐席收到 │ │
|
||||
│ │ │(WS/轮询) │ │
|
||||
│ │ └──────────┘ │
|
||||
│ │ │
|
||||
│ 【坐席 → 用户】 │
|
||||
│ ┌──────────┐ API调用 ┌──────────┐ 企微API ┌────────┐ │
|
||||
│ │ 坐席发送 │ ──────────────→ │ 后端API │ ──────────→ │企微 │ │
|
||||
│ │ 消息 │ POST │ 发送消息 │ /message │服务器 │ │
|
||||
│ └──────────┘ /conversations└──────────┘ /send └────┬───┘ │
|
||||
│ │ /{id}/messages │ │ │
|
||||
│ │ ▼ ▼ │
|
||||
│ │ ┌──────────┐ ┌────────┐ │
|
||||
│ │ │ 消息入库 │ │用户收到 │ │
|
||||
│ │ │(DB存储) │ │消息 │ │
|
||||
│ │ └──────────┘ └────────┘ │
|
||||
│ │ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、诊断检查点
|
||||
|
||||
### 2.1 企微回调链路(用户 → 系统)
|
||||
|
||||
| 检查点 | 文件位置 | 检查内容 | 预期结果 |
|
||||
|--------|---------|---------|---------|
|
||||
| C-01 | `wecom_callback.py` GET `/wecom/callback` | 企微URL验证 | 返回解密后的echostr |
|
||||
| C-02 | `wecom_callback.py` POST `/wecom/callback` | 消息解密 | 正确解析XML并解密 |
|
||||
| C-03 | `message_router.py` | 消息路由 | 正确分配会话/坐席 |
|
||||
| C-04 | 数据库 `messages` 表 | 消息存储 | 消息正确写入 |
|
||||
|
||||
### 2.2 坐席发送链路(系统 → 用户)
|
||||
|
||||
| 检查点 | 文件位置 | 检查内容 | 预期结果 |
|
||||
|--------|---------|---------|---------|
|
||||
| C-05 | `messages.py` POST `/conversations/{id}/messages` | API入口 | 正确接收坐席消息 |
|
||||
| C-06 | `wecom_service.py` `send_text_message()` | 企微API调用 | errcode=0 |
|
||||
| C-07 | 企微客户端 | 用户收到消息 | 正常展示 |
|
||||
|
||||
### 2.3 H5 实时推送
|
||||
|
||||
| 检查点 | 文件位置 | 检查内容 | 预期结果 |
|
||||
|--------|---------|---------|---------|
|
||||
| C-08 | `ws_manager.py` | WS连接管理 | 坐席WS连接 |
|
||||
| C-09 | `frontend-agent` | WS接收 | 消息实时展示 |
|
||||
| C-10 | `frontend-h5` | 轮询/WebSocket | 新消息实时更新 |
|
||||
|
||||
---
|
||||
|
||||
## 三、已发现的问题
|
||||
|
||||
### 问题1:非文本消息不推送(messages.py:210-233)
|
||||
|
||||
```python
|
||||
# 只有 text 类型消息才调用企微 API 推送给员工
|
||||
if body.msg_type == "text":
|
||||
# 调用企微API
|
||||
```
|
||||
|
||||
**影响**:图片、文件等消息无法推送到用户微信端
|
||||
|
||||
### 问题2:dev_mode 短路(messages.py:215-216)
|
||||
|
||||
```python
|
||||
if getattr(settings, 'dev_mode', False):
|
||||
logger.debug(f"[DEV] 跳过企微推送: msg_id={message.id}")
|
||||
```
|
||||
|
||||
**影响**:测试环境下消息不会推送到用户
|
||||
|
||||
### 问题3:企微API错误处理(messages.py:231-233)
|
||||
|
||||
```python
|
||||
except Exception as e:
|
||||
# 企微 API 调用失败不阻塞消息存储
|
||||
logger.warning(f"企微消息发送失败(消息已存储): {e}")
|
||||
```
|
||||
|
||||
**影响**:企微API失败时仅记录日志,用户实际未收到消息
|
||||
|
||||
---
|
||||
|
||||
## 四、诊断执行记录
|
||||
|
||||
| 时间 | 检查项 | 结果 | 说明 |
|
||||
|------|--------|------|------|
|
||||
| 2026-07-03 | 代码审查 | ✅ | 完成链路分析 |
|
||||
| - | C-01 企微回调 | ⏳ | 待部署环境验证 |
|
||||
| - | C-05 坐席发送 | ⏳ | 待部署环境验证 |
|
||||
| - | C-07 用户收到 | ⏳ | 待实际测试 |
|
||||
|
||||
---
|
||||
|
||||
## 五、结论
|
||||
|
||||
**当前系统通讯链路代码完整**,但存在以下已知风险:
|
||||
|
||||
1. 非文本消息(图片/文件)无法推送
|
||||
2. dev_mode 会跳过企微推送
|
||||
3. 企微API失败时静默失败
|
||||
|
||||
这些问题可通过系统重构进一步优化消息通讯能力。
|
||||
|
||||
---
|
||||
|
||||
## 六、下一步
|
||||
|
||||
**下一步**:根据诊断结果优化现有通讯链路
|
||||
Reference in New Issue
Block a user