docs: 移动蓝绿部署指南到 troubleshooting 目录

This commit is contained in:
Simon
2026-07-05 17:03:36 +08:00
parent ab90db3d3d
commit ca7c6d937a
91 changed files with 4841 additions and 406 deletions
+6 -2
View File
@@ -167,7 +167,7 @@ class QrcodeService:
"""拼接企微 OAuth2 授权 URL(供前端生成二维码)。
URL 格式:
https://open.weixin.qq.com/connect/oauth2/authorize
https://open.work.weixin.qq.com/connect/oauth2/authorize
?appid={corp_id}
&redirect_uri={callback}
&response_type=code
@@ -175,6 +175,9 @@ class QrcodeService:
&state={ticket}
#wechat_redirect
注意:必须使用 open.work.weixin.qq.com(企业微信开放平台),
而不是 open.weixin.qq.com(微信开放平台)。
Args:
ticket: 扫码登录票据
@@ -194,7 +197,8 @@ class QrcodeService:
"state": ticket,
}
query = urlencode(params)
return f"https://open.weixin.qq.com/connect/oauth2/authorize?{query}#wechat_redirect"
# 企业微信 OAuth2 地址(注意是 open.work.weixin.qq.com,不是 open.weixin.qq.com
return f"https://open.work.weixin.qq.com/connect/oauth2/authorize?{query}#wechat_redirect"
def _get_scan_callback_url(self) -> str:
"""获取 OAuth 回调地址。
+59
View File
@@ -0,0 +1,59 @@
# =============================================================================
# 企微IT智能服务台 — 超时提醒服务
# =============================================================================
# 说明:提供发送超时提醒企微消息的功能
# 在坐席回复后员工长时间未回复时,发送企微提醒消息
# =============================================================================
import logging
from app.config import settings
from app.services.wecom_service import WecomService
logger = logging.getLogger(__name__)
# 超时提醒消息内容
REMINDER_MESSAGE = (
"IT服务提醒:您有新的消息未查看,"
"咨询将在10分钟后标记为待关闭,请尽快点击处理 👉 "
"https://itsupport.servyou.com.cn/itdesk/"
)
async def send_reminder_message(employee_id: str) -> bool:
"""发送超时提醒企微消息。
当坐席回复后员工超过3分钟未回复时,发送企微消息提醒员工查看。
Args:
employee_id: 员工的企微 UserID
Returns:
bool: 发送是否成功
Raises:
Exception: 发送失败时抛出异常
"""
try:
redis_client = settings.create_redis_client()
wecom_service = WecomService(redis_client)
try:
result = await wecom_service.send_text_message(
employee_id, REMINDER_MESSAGE
)
if result.get("errcode") == 0:
logger.info(f"超时提醒发送成功: employee_id={employee_id}")
return True
else:
logger.warning(
f"超时提醒发送失败: employee_id={employee_id}, "
f"errcode={result.get('errcode')}, errmsg={result.get('errmsg')}"
)
return False
finally:
await wecom_service.close()
await redis_client.close()
except Exception as e:
logger.error(f"发送超时提醒异常: employee_id={employee_id}, error={e}")
raise
+50 -11
View File
@@ -12,7 +12,7 @@
import logging
from datetime import datetime
from typing import List, Optional
from typing import Any, List, Optional
from uuid import UUID
from sqlalchemy import and_, case, desc, func, select
@@ -43,15 +43,18 @@ class SessionService:
self,
db: AsyncSession,
wecom_service: Optional[WecomService] = None,
redis_client: Optional[Any] = None,
):
"""初始化会话状态管理服务。
Args:
db: 异步数据库会话
wecom_service: 企微 API 服务(用于坐席接入时发送通知,可选)
redis_client: Redis客户端(用于头像缓存,可选)
"""
self.db = db
self.wecom_service = wecom_service
self.redis_client = redis_client
# --------------------------------------------------------------------------
# 创建会话
@@ -819,12 +822,16 @@ class SessionService:
# 邀请功能(P0-09~P0-11):坐席邀请员工/部门加入会话
# ======================================================================
async def _get_employee_avatar(self, employee_id: str) -> str:
"""获取员工头像URL。
# 头像缓存 TTL7天
AVATAR_CACHE_TTL = 7 * 24 * 60 * 60
做什么:从 employees 表或企微通讯录API获取头像
为什么:邀请参与者时需要展示头像,前端无法单独获取被邀请人头像
优先级:employees 表(本地缓存) > 企微API(实时获取)
async def _get_employee_avatar(self, employee_id: str) -> str:
"""获取员工头像URL(带Redis缓存)。
优先级:
1. Redis 缓存(最快)
2. employees 表
3. 企微API(获取后存入Redis缓存)
Args:
employee_id: 企微员工UserID
@@ -832,25 +839,57 @@ class SessionService:
Returns:
str: 头像URL,获取不到返回空字符串
"""
# 1. 优先从 employees 表查(本地缓存,速度快)
cache_key = f"employee:avatar:{employee_id}"
# 1. 优先从 Redis 缓存获取
if self.redis_client:
try:
cached_avatar = await self.redis_client.get(cache_key)
if cached_avatar:
logger.debug(f"从Redis缓存获取头像: employee_id={employee_id}")
return cached_avatar.decode("utf-8") if isinstance(cached_avatar, bytes) else cached_avatar
except Exception as e:
logger.warning(f"从Redis获取头像缓存失败: employee_id={employee_id}, error={e}")
# 2. 从 employees 表获取(需要匹配 corp_id
from app.models.employee import Employee
from app.core.config import settings
result = await self.db.execute(
select(Employee.avatar).where(Employee.employee_id == employee_id)
select(Employee.avatar).where(
Employee.employee_id == employee_id,
Employee.corp_id == settings.wecom_corp_id
)
)
row = result.first()
if row and row[0]:
logger.info(f"从employees表获取头像: employee_id={employee_id}, avatar={row[0][:50]}...")
# 存入 Redis 缓存
if self.redis_client:
try:
await self.redis_client.setex(cache_key, self.AVATAR_CACHE_TTL, row[0])
except Exception as e:
logger.warning(f"存入Redis头像缓存失败: employee_id={employee_id}, error={e}")
return row[0]
else:
logger.info(f"employees表无头像记录: employee_id={employee_id}")
# 2. employees 表没有,尝试从企微通讯录API获取
# 3. employees 表没有,尝试从企微通讯录API获取
avatar = ""
if self.wecom_service:
try:
user_info = await self.wecom_service.get_user_info(employee_id)
avatar = user_info.get("avatar", "")
return avatar
logger.info(f"企微API返回头像: employee_id={employee_id}, avatar={'有值(' + str(len(avatar)) + '字符)' if avatar else ''}")
# 存入 Redis 缓存(即使为空也缓存,避免频繁请求API)
if self.redis_client and avatar:
try:
await self.redis_client.setex(cache_key, self.AVATAR_CACHE_TTL, avatar)
except Exception as e:
logger.warning(f"存入Redis头像缓存失败: employee_id={employee_id}, error={e}")
except Exception as e:
logger.warning(f"从企微API获取头像失败: employee_id={employee_id}, error={e}")
return ""
return avatar
async def invite_participants(
self,