Files
wecom_it_smart_desk/backend/app/config.py
T
Simon 449c6d4875 feat: 2026-07-12~13 全量更新 - AI对话链路改造+H5 v4/v5+坐席端v5+上下文感知诊断+知识库迭代3
## H5 员工端 v4 (2026-07-13 00:48 已部署)
- 人工按钮三态文案统一为"人工坐席"
- 按钮位置移至发送键和语音按钮上方(垂直堆叠)
- 点按钮直接调 store.shakeAgent(),删除 CallAgentModal 弹窗动画
- 截图快捷键提示改为"截图->粘贴:Alt+Shift+A-Ctrl+V ---> Ctrl+V"
- 移动端隐藏截图提示(CSS 媒体查询)
- AI转人工提示改为"已为您呼叫人工坐席,请稍等!"
- 坐席接入提示改为"坐席正在查看您的信息,请等待处理回复!"
- 删除"摇铃呼叫坐席"入口和文案
- 删除孤儿组件 MessageList.vue + shake 动画 CSS

## H5 员工端 v5 (2026-07-13 02:08 已部署)
- RightPanel v2.1:删除"软件安装"和"资源权限"标签页
- 移除标签栏,智能推荐(DynamicRecommend)直接展示
- 删除 SoftwareDownloads/ApprovalLinks 引用和相关 CSS

## AI 对话链路全栈改造 Phase 1-6 (已部署)
- Phase 1: Dify JSON输出 + 后端blocking解析 + 双WS推送 + 错误降级
- Phase 2: 关键词收窄(~25强意图词) + 两级分类Prompt + 删除前端checkApprovalIntent
- Phase 3: WS扩展(ai_thinking+dynamic_recommend) + ai_structured气泡 + RightPanel v2 + 选项回传
- Phase 4: VisionService接入 + 图片消息融合(5秒窗口) + 降级策略
- Phase 5: 坐席端ai_thinking指示器 + ai_structured/byod_card渲染 + handleNewMessage修复
- Phase 6: diagnosis_stage(6值) + response_time_ms计时 + 慢响应告警(>10s)

## 坐席端 v5 (2026-07-13 01:38 已部署)
- ai_structured/byod_card 只读渲染
- AI思考指示器 UI
- handleNewMessage 透传 msg_type/extra_data 修复
- 布局优化v2.0: QuickReplyBar L1+L2悬浮 + ReplyBox左右分区 + 右栏260/560px切换
- 键盘快捷键v2.3: 纯数字路由 + ESC分层撤销 + Shift+Space用event.code

## 上下文感知智能诊断闭环 (2026-07-12 已部署)
- 三层诊断(API→Script→AI) + 三段排队(VIP→info_locked→not locked)
- 答题插队 + 五场景关闭
- 迁移052(6表+6列) + queue_service + quiz_service + closing_service
- H5前端: QueueWaiting + RightPanel双Tab + InputBar三态 + ResolveConfirmCard
- 坐席前端: pending_close结单流程 + 信息锁定(Dify步骤完成+有效回答率≥70%)

## 知识库迭代3 (2026-07-12 已部署)
- 分诊交互(H5+坐席+Dify独立应用)
- 拓扑预览(ECharts只读)
- 代答排除(4种匹配器: keyword/regex/intent/category)
- 迁移051 + 44文件43测试通过

## 后端变更
- 6个Python文件改造(h5_ai_task.py/h5.py/ai_service.py/closing_service.py等)
- funny_phrase_service.py: shake/connected/keyword 默认文案更新
- session_service.py: 企微消息文案同步
- 新增: queue.py/quiz.py/triage.py/exclusion_rules.py 等API端点
- 新增: diagnostic.py/quiz.py/triage_session.py 等模型
- 新增: closing_service/queue_service/quiz_service/triage_service 等服务

## 文档更新
- CHANGELOG.md: 新增 [未发布] 区全部变更记录
- 项目管理主文档 v2.5: 新增v0.7.3版本 + 已完成看板 + 最近搞定
- 版本记录: 新增v0.7.3条目
- AI对话链路实施计划: Phase 1-6 全部标记已实施
- 新增架构图/时序图/类图(mermaid)

## 部署路径修正
- 服务器项目根路径: /opt/wecom-it-desk/
- 所有前端dist均为ro bind mount,只能在宿主机源路径操作
- 服务器nginx /h5/ 是静态文件服务(非proxy_pass)
- elFinder上传二进制不可靠(MD5不匹配),改用base64分块上传
2026-07-13 02:17:03 +08:00

510 lines
25 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# =============================================================================
# 企微IT智能服务台 — 配置管理模块
# =============================================================================
# 说明:使用 pydantic-settings 从环境变量读取所有配置项
# 优先级:环境变量 > .env 文件 > 默认值
# 所有配置项集中管理,避免散落在代码各处
# =============================================================================
import os
from typing import List
import redis.asyncio as aioredis
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""应用配置类。
使用 pydantic-settings 自动从环境变量读取配置值。
支持 .env 文件自动加载(开发环境便利)。
Attributes:
wecom_corp_id: 企业微信企业ID
wecom_agent_id: 企业微信应用AgentId
wecom_secret: 企业微信应用Secret
wecom_token: 企业微信回调Token
wecom_encoding_aes_key: 企业微信回调EncodingAESKey43位)
database_url: PostgreSQL 数据库连接地址
redis_url: Redis 连接地址
backend_host: 后端监听地址
backend_port: 后端监听端口
cors_origins: CORS 允许的源地址(逗号分隔)
"""
# ----------------------------------------------------------------------
# 企微自建应用配置
# ----------------------------------------------------------------------
# 企业ID(在企微管理后台 > 我的企业 > 企业信息 中查看)
wecom_corp_id: str = "ww1234567890abcdef"
# 应用AgentId(在企微管理后台 > 应用管理 > 自建应用 中查看)
wecom_agent_id: str = "1000002"
# 应用Secret(在企微管理后台 > 应用管理 > 自建应用 中查看)
wecom_secret: str = "your-agent-secret"
# 企微通讯录同步 Secret(管理工具 → 通讯录同步 → 开启接口同步)
# 与应用 Secret 不同,此 Secret 有完整的通讯录读取权限
# 如果配置了,get_department_list / get_department_members 将使用此 Secret 获取 access_token
wecom_contact_secret: str = ""
# 审批应用Secret(在企微管理后台 > 应用管理 > 审批 > 查看Secret
wecom_approval_secret: str = ""
# 会议室API专用Secret(在企微管理后台 > 应用管理 > 会议室 > 可调用接口的应用 中配置)
# 与应用Secret和通讯录Secret不同,用于获取会议室API的独立access_token
wecom_meetingroom_secret: str = ""
# 回调Token(在企微管理后台 > 应用管理 > 接收消息 中设置)
wecom_token: str = "your-callback-token"
# ----------------------------------------------------------------------
# 小鱼易联终端管理配置
# ----------------------------------------------------------------------
# 小鱼易联企业ID
xylink_enterprise_id: str = ""
# SDK客户端ID
xylink_client_id: str = ""
# SDK客户端密钥
xylink_client_secret: str = ""
# 企业ID(扩展ID
xylink_ext_id: str = ""
# API基础URL(小鱼易联开放平台)
xylink_api_base: str = "https://sdk.xylink.com/api/rest/external/v1/"
# 回调EncodingAESKey43位字符串,用于消息加解密)
wecom_encoding_aes_key: str = "your-aes-key-43-characters-long-encoding-key"
# ----------------------------------------------------------------------
# 数据库配置
# ----------------------------------------------------------------------
# PostgreSQL 连接地址
# Docker 环境使用容器名 postgres,本地开发使用 localhost
database_url: str = "postgresql://wecom:wecom_secret@localhost:5432/wecom_it_desk"
# ----------------------------------------------------------------------
# Redis 配置
# ----------------------------------------------------------------------
# Redis 连接地址
# Docker 环境使用容器名 redis,本地开发使用 localhost
# 从环境变量 REDIS_URL 读取,格式: redis://:password@host:port/db
redis_url: str = "" # 默认为空,由环境变量 REDIS_URL 提供
# ----------------------------------------------------------------------
# 服务配置
# ----------------------------------------------------------------------
# 后端监听地址(0.0.0.0 表示监听所有网卡)
backend_host: str = "0.0.0.0"
# 后端监听端口
backend_port: int = 8000
# CORS 允许的源地址(逗号分隔的字符串)
cors_origins: str = "http://localhost:5173,http://localhost:5174,http://localhost:5175"
# ----------------------------------------------------------------------
# 运行期日志目录(供"运行期日志"管理页面读取)
# ----------------------------------------------------------------------
# 后端运行期日志(JSON 格式,由 utils.logging_config.setup_logging 写入)
# 所在目录。容器内默认 /app/logs,可通过环境变量 RUNTIME_LOG_DIR 覆盖;
# 宿主机需将真实日志目录挂载到该路径(见 docker-compose.yml backend 卷)。
RUNTIME_LOG_DIR: str = os.getenv("RUNTIME_LOG_DIR", "/app/logs")
# ----------------------------------------------------------------------
# AI 服务配置(Dify
# ----------------------------------------------------------------------
# Dify API 端点(兼容 OpenAI Chat Completions 格式)
# 必须通过环境变量 DIFY_API_URL 配置,不设置默认值(防止凭据泄露)
dify_api_url: str = ""
# Dify API Key(格式:base_url|app_id|app_name
# 必须通过环境变量 DIFY_API_KEY 配置,不设置默认值(防止凭据泄露)
dify_api_key: str = ""
# Dify API 请求超时(秒),在网络慢时可调大
dify_timeout: int = 30
# Dify 原生 API 配置(绕过 dify2openai 代理,直连 Dify /v1/chat-messages
# 为什么:dify2openai 代理存在 [object Object] 序列化 bug
# 直连 Dify 原生 API 可绕过此问题,且响应格式更简单(answer 字段直接返回内容)
# 用法:配置后 get_structured_reply() 优先使用原生 API,未配置则回退到代理
dify_native_base_url: str = "" # 如 http://yw-dify.dc.servyou-it.com
dify_native_api_key: str = "" # 如 app-7jkRkAzvX4QM9v9SM3P8mMEO(仅 app key,不含管道分隔格式)
# ----------------------------------------------------------------------
# AI Wingman 服务配置(Dify Agent 2 — 坐席端辅助)
# ----------------------------------------------------------------------
# 坐席端 Wingman 专用 Dify API 端点(与员工端 Agent 分开)
# 留空则禁用 Wingman 功能(不影响主流程)
dify_wingman_api_url: str = ""
# 坐席端 Wingman Dify API Key(需要新建 Agent 后填入,留空则禁用)
# 格式:base_url|app_id|app_name(与 dify_api_key 相同格式)
dify_wingman_api_key: str = ""
# Wingman API 请求超时(秒)
dify_wingman_timeout: int = 30
# ----------------------------------------------------------------------
# AI 分诊服务配置(Dify Agent 3 — 独立分诊应用)
# ----------------------------------------------------------------------
# Dify 分诊应用 OpenAI 兼容接口地址(共用 dify2openai 代理,通过不同 API Key 区分)
dify_triage_api_url: str = ""
# Dify 分诊应用 API Key(格式:base_url|app_id|app_name
dify_triage_api_key: str = ""
# Dify 分诊请求超时(秒),超时自动转人工
dify_triage_timeout: int = 5
# ----------------------------------------------------------------------
# Mock 登录配置(测试阶段使用,跳过企微 OAuth2)
# ----------------------------------------------------------------------
# 是否启用 Mock 登录(默认 false,生产环境必须关闭)
mock_login_enabled: bool = False
# ----------------------------------------------------------------------
# 终端页面配置(小鱼易联会议室终端)
# ----------------------------------------------------------------------
# 终端页面基础URL(用于生成NE2005等不支持H5的终端的二维码)
# 格式:https://域名/itterminal/
terminal_base_url: str = "https://itsupport.servyou.com.cn/itterminal/"
# ----------------------------------------------------------------------
# 开发模式配置(本地 docker-compose.dev.yml 用)
# ----------------------------------------------------------------------
# 是否启用开发模式(本地开发环境,启用后挂载 /api/dev/* Mock OAuth 路由)
# ⚠️ 生产环境必须为 false / 不设置
# 启用的副作用:
# 1. 后端启动时挂载 /api/dev/login /users /health 三个 Mock 端点
# 2. /api/dev/login 跳过企微 OAuth 直接生成 token
# 3. 启动日志会大声警告 "🧪 DEV_MODE enabled"
dev_mode: bool = False
# 开发模式默认 userid(本地前端兜底用,实际由前端 /api/dev/login 传入)
dev_default_userid: str = "dev-user-001"
# 开发模式默认姓名
dev_default_name: str = "开发测试用户"
# 开发模式默认部门
dev_default_dept: str = "信息技术部"
# ----------------------------------------------------------------------
# 运行环境 & 管理后台 IP 白名单(三端认证重构 AUTH-01)
# ----------------------------------------------------------------------
# 应用运行环境:dev / test / production
# 控制 UA 校验 / IP 白名单 / 真实企微 OAuth 的启用(仅 production 启用)
# 通过环境变量 APP_ENV 控制(默认 dev,避免本地误触发强校验)
app_env: str = "dev"
# 管理后台登录 IP 白名单(逗号分隔,支持 CIDR,如 10.240.0.0/16
# 仅允许白名单内的 IP 访问管理后台登录;其余 IP 返回 4004(无权限)
# 通过环境变量 ADMIN_ALLOWED_IPS 覆盖
admin_allowed_ips: str = "117.147.35.138,218.75.34.87,10.240.0.0/16"
# ----------------------------------------------------------------------
# 审批模板配置(企微审批应用)
# ----------------------------------------------------------------------
# 资源申请审批模板ID(在企微审批应用设置中获取)
approval_template_resource: str = ""
# 设备申请审批模板ID(在企微审批应用设置中获取)
approval_template_device: str = ""
# ----------------------------------------------------------------------
# ITSM 运维平台配置(一站式运维平台 OpenAPI)
# ----------------------------------------------------------------------
# ITSM OpenAPI app_id(向ITSM平台方申请,用于签名认证)
itsm_app_id: str = ""
# ITSM OpenAPI app_secret(向ITSM平台方申请,用于签名认证)
itsm_app_secret: str = ""
# ITSM 生产环境基址
itsm_base_url: str = "https://devops.dc.servyou-it.com/itsm"
# ITSM 测试环境基址
itsm_test_base_url: str = "https://test-devops.dc.servyou-it.com/itsm"
# ----------------------------------------------------------------------
# 审批意图识别 Dify 应用配置(独立于主 AI 对话和自动化引擎)
# ----------------------------------------------------------------------
# Dify 审批意图识别应用基址(OpenAI 兼容代理,如 http://yw-dify.dc.servyou-it.com/dify2openai
approval_dify_base_url: str = ""
# Dify 审批意图识别应用 API Key
approval_dify_api_key: str = ""
# Dify 审批意图识别请求超时(秒)
approval_dify_timeout: int = 15
# 审批意图置信度阈值(≥ 此值才触发审批卡片)
approval_confidence_threshold: float = 0.7
# ----------------------------------------------------------------------
# IT 资产升级审批推送配置
# ----------------------------------------------------------------------
# Excel 资产清单文件路径(12个月度 sheet,含固定资产编码、开始使用日期等)
# 通过环境变量 ASSET_EXCEL_PATH 覆盖
asset_excel_path: str = "D:/资料/00-工作文件/03-资产管理/固定资产清单/资产记录/2025资产/2025资产1~12.xlsx"
# 资产更换年限阈值(≥ 此年限视为符合更换条件)
# 通过环境变量 ASSET_REPLACEMENT_THRESHOLD_YEARS 覆盖
asset_replacement_threshold_years: int = 5
# ----------------------------------------------------------------------
# 业务路由推荐配置(路由推荐功能)
# ----------------------------------------------------------------------
# 路由置信度阈值:Dify 返回的 routing_confidence ≥ 此值才触发名片推荐
# 低于此值走正常 AI 回复流程,避免误路由
routing_confidence_threshold: float = 0.7
# ----------------------------------------------------------------------
# v0.7.1 企微 SSO 入口配置 (task #85)
# ----------------------------------------------------------------------
# 是否启用企微 SSOtrue = 优先用企微 OAuth2 静默授权,失败时降级扫码)
# 通过环境变量 WECOM_SSO_ENABLED 控制(默认 false,避免老用户被打扰)
wecom_sso_enabled: bool = False
# SSO OAuth 回调 base URL(企微要求 redirect_uri 必须用可信域名)
# 生产: https://itsupport.servyou.com.cn 开发: http://localhost:5176
wecom_sso_callback_base: str = ""
# ----------------------------------------------------------------------
# v0.5.4 应急页身份检测配置
# ----------------------------------------------------------------------
# IT支持-咨询坐席 通讯录标签 ID(在企微管理后台 > 通讯录管理 > 标签管理 中查看)
# 配置后,应急页会通过此标签判断当前用户是否为坐席
# 留空则降级到下面的硬编码名单
wecom_agent_tag_id: str = ""
# 硬编码坐席 userid 列表(逗号分隔),作为标签检测的降级方案
# 例:"zhangsan,lisi,wangwu"(生产环境建议用标签方案)
wecom_agent_userids: str = ""
# ----------------------------------------------------------------------
# v0.6.0 内容审核报警配置(占位,后续完善)
# ----------------------------------------------------------------------
# 合规通知企微群机器人 webhook
content_audit_webhook: str = ""
# 主管接收报警的 userid(多个用逗号分隔)
content_audit_supervisor_userids: str = ""
# ----------------------------------------------------------------------
# 阶段5 自动化闭环配置(环境变量前缀 AUTOMATION_*
# ----------------------------------------------------------------------
# 说明:自动化引擎连接的外部系统基址与密钥占位。
# 优先级:环境变量 AUTOMATION_* > 阶段1-4 既有的 system_configs 集成配置
# huorong/lianruan/ragflow 在 app/integrations/*/config.py 中已有 getter
# 注意:密钥均为占位,生产环境必须通过环境变量注入,切勿硬编码真实密钥。
# ----------------------------------------------------------------------
# Dify(意图识别 / AI 编排)
automation_dify_base_url: str = ""
automation_dify_api_key: str = ""
# RAGFlow(知识库检索,默认内网 :9380)
automation_ragflow_base_url: str = "http://10.80.0.85:9380"
automation_ragflow_api_key: str = ""
# 火绒终端安全(HRESS HMAC-SHA1 签名)
automation_huorong_base_url: str = ""
automation_huorong_access_key_id: str = ""
automation_huorong_access_key_secret: str = ""
# 联软 LV7000(三层认证:IP白名单 + 账号密码 + Token
automation_lianruan_base_url: str = ""
automation_lianruan_api_account: str = ""
automation_lianruan_api_password: str = ""
automation_lianruan_validate_key: str = ""
# 北森 EHR(静态映射兜底)
automation_ehr_base_url: str = ""
automation_ehr_api_key: str = ""
# 自动化阈值(JSON 字符串):置信度下限 / 超时秒 / 连续未解决次数 / 高危必转
# 管理后台可配(见 ScenarioConfig + 全局阈值),此处为默认值。
automation_thresholds: str = '{"confidence_min":0.6,"timeout_seconds":60,"unresolved_threshold":2,"high_risk_force_handoff":true}'
# ----------------------------------------------------------------------
# Neo4j 图数据库配置(知识图谱存储 — Tier0 / T01
# ----------------------------------------------------------------------
# Neo4j bolt 协议连接地址(默认本地开发容器)
neo4j_uri: str = "bolt://localhost:7687"
# Neo4j 用户名
neo4j_user: str = "neo4j"
# Neo4j 密码(⚠️ 仅从环境变量注入,不设默认值)
neo4j_password: str = ""
# Neo4j 默认数据库名
neo4j_database: str = "neo4j"
# Neo4j 连接最大存活时间(秒)
neo4j_max_connection_lifetime: int = 3600
# Neo4j 连接池上限
neo4j_max_connection_pool_size: int = 50
# Neo4j 连接获取超时(秒)
neo4j_connection_acquisition_timeout: int = 30
# ----------------------------------------------------------------------
# 置信门控配置(D3 — 全局置信阈值)
# ----------------------------------------------------------------------
# AI 回复置信度低于此阈值时,前端渲染"转人工"入口
# 可通过环境变量 CONFIDENCE_GATE_THRESHOLD 覆盖
confidence_gate_threshold: float = 0.7
# ----------------------------------------------------------------------
# RAGFlow Ingestion 开关(通道 C — 文档→KB)
# ----------------------------------------------------------------------
# 是否启用 RAGFlow 文档 ingestion 功能(默认关闭,需部署 RAGFlow 服务后开启)
ragflow_ingestion_enabled: bool = False
# ----------------------------------------------------------------------
# 百度语音识别(ASR)配置
# ----------------------------------------------------------------------
# 百度智能云语音识别 AppID(在百度智能云控制台 > 语音技术 中查看)
baidu_asr_app_id: str = ""
# 百度 ASR API Key
baidu_asr_api_key: str = ""
# 百度 ASR Secret Key
baidu_asr_secret_key: str = ""
# ----------------------------------------------------------------------
# Qwen-VL 视觉理解配置(D5 — 截图理解)
# ----------------------------------------------------------------------
# 本地 Qwen-VL 模型名称(Dify vision workflow 中配置的模型标识)
qwen_vl_model: str = "Qwen3-VL-8B-Instruct"
# Dify Vision Workflow API 端点(独立于 Wingman Agent
dify_vision_api_url: str = ""
# Dify Vision Workflow API Key
dify_vision_api_key: str = ""
# ----------------------------------------------------------------------
# 阶段5 自动化闭环外部系统配置(简化命名,供管理后台展示)
# ----------------------------------------------------------------------
# Dify(意图识别 / AI 编排)
dify_base_url: str = ""
dify_key: str = ""
# RAGFlow(知识库检索)
ragflow_base_url: str = ""
# 火绒终端安全(HRESS HMAC-SHA1 签名)
huorong_base_url: str = ""
huorong_key: str = ""
huorong_secret: str = ""
# 联软 LV7000(三层认证:IP白名单 + 账号密码 + Token
lianruan_base_url: str = ""
lianruan_username: str = ""
lianruan_password: str = ""
lianruan_api_key: str = ""
# 北森 EHR(静态映射兜底)
ehr_base_url: str = ""
# ----------------------------------------------------------------------
# P2 上下文压缩配置
# ----------------------------------------------------------------------
context_compress_threshold: int = 6000 # 压缩触发阈值(token数)
context_compress_timeout: int = 30 # LLM摘要超时(秒)
context_max_compress_level: int = 3 # 渐进式压缩最大级别
context_keep_recent_turns: int = 4 # 压缩后保留最近对话轮数
context_compress_ratio_warn: float = 0.3 # 压缩率警告阈值
# ----------------------------------------------------------------------
# P3 多轮纠错配置
# ----------------------------------------------------------------------
max_undo_count: int = 5 # 最大撤销更正次数
def get_automation_thresholds(self) -> dict:
"""解析自动化阈值配置,返回带默认值的字典。
为什么单独成方法:阈值是 JSON 字符串(便于通过环境变量整体注入),
解析失败时回退到代码内默认值,避免单点配置错误导致引擎不可用。
"""
default = {
"confidence_min": 0.6,
"timeout_seconds": 60,
"unresolved_threshold": 2,
"high_risk_force_handoff": True,
}
try:
import json as _json
if self.automation_thresholds:
parsed = _json.loads(self.automation_thresholds)
if isinstance(parsed, dict):
default.update(parsed)
except Exception as e: # 解析失败仅记日志,不中断启动
logger.warning(f"自动化阈值解析失败,使用默认值: {e}")
return default
# ----------------------------------------------------------------------
# Pydantic-settings 配置
# ----------------------------------------------------------------------
model_config = SettingsConfigDict(
# 自动从 .env 文件加载环境变量
env_file=".env",
# .env 文件编码
env_file_encoding="utf-8",
# 环境变量名大小写不敏感
case_sensitive=False,
# 额外字段不允许(防止拼写错误的配置被忽略)
extra="ignore",
)
@property
def cors_origins_list(self) -> List[str]:
"""将 CORS 源地址字符串解析为列表。
将逗号分隔的字符串(如 "http://a,http://b"
转换为列表(如 ["http://a", "http://b"]),
方便 FastAPI 的 CORSMiddleware 使用。
Returns:
List[str]: CORS 允许的源地址列表
"""
# 去除每项的前后空格,过滤空字符串
return [origin.strip() for origin in self.cors_origins.split(",") if origin.strip()]
def create_redis_client(self) -> aioredis.Redis:
"""创建 Redis 异步客户端实例。
使用单独的 host/port/password 参数,避免 URL 解析问题
(特别是密码中包含特殊字符 ! @ # 时)。
自动附加 protocol=2 参数,强制使用 RESP2 协议。
原因:Windows 版 Redis 3.x 不支持 RESP3 协议(HELLO 命令),
而 redis-py 8.0+ 默认使用 RESP3,会导致连接失败。
全项目统一使用此方法创建 Redis 客户端,避免协议不匹配。
Returns:
aioredis.Redis: 配置好的 Redis 异步客户端
"""
# 连接超时保护:防止 Redis 不可达时请求无限挂起
# (历史事故:REDIS_URL 密码含 @ # 导致 urlparse 解析到错误 host
# 连接一直挂起,最终表现为登录接口超时 / 502 / 浏览器"网络连接失败")
socket_connect_timeout = 5
socket_timeout = 5
# 如果 redis_url 为空,使用默认值
if not self.redis_url:
# 默认值:本地 Redis
return aioredis.Redis(
host="localhost",
port=6379,
protocol=2,
decode_responses=True,
socket_connect_timeout=socket_connect_timeout,
socket_timeout=socket_timeout,
)
# 解析 REDIS_URL 提取连接参数
# 格式: redis://:password@host:port/db
# ⚠️ 密码可能含 URL 保留字符(@ # ! 等),部署时必须用 URL-encode:
# @ → %40, # → %23, ! → %21
# 例: R3d!s@2026#Secure → R3d%21s%402026%23Secure
# urlparse 不会自动解码百分号编码,这里用 unquote 还原真实密码/主机
from urllib.parse import urlparse, unquote
parsed = urlparse(self.redis_url)
# 提取密码(先尝试标准 urlparse 字段,失败则从 netloc 兜底)
password = parsed.password
if not password:
# 尝试从 netloc 中提取(格式 :password@host
netloc = parsed.netloc
if "@" in netloc:
password = netloc.split("@")[0].split(":")[-1]
if password:
password = unquote(password)
hostname = unquote(parsed.hostname) if parsed.hostname else "localhost"
port = parsed.port or 6379
db = parsed.path and int(parsed.path.lstrip("/")) or 0
return aioredis.Redis(
host=hostname,
port=port,
password=password,
db=db,
protocol=2,
decode_responses=True,
socket_connect_timeout=socket_connect_timeout,
socket_timeout=socket_timeout,
)
# 创建全局配置实例
# 整个应用通过 from app.config import settings 使用同一个实例
settings = Settings()