feat: 新增飞书长连接模式,无需公网域名

## 🚀 重大更新

### 飞书集成升级
-  迁移到飞书官方 SDK 的事件订阅 2.0(长连接模式)
-  无需公网域名和 webhook 配置
-  支持内网部署
-  自动重连机制

### 核心功能优化
-  优化群聊隔离机制(每个用户在每个群独立会话)
-  增强日志输出(emoji 标记便于快速识别)
-  完善错误处理和异常恢复
-  添加 SSL 证书问题解决方案

### 新增文件
- `src/integrations/feishu_longconn_service.py` - 飞书长连接服务
- `start_feishu_bot.py` - 启动脚本
- `test_feishu_connection.py` - 连接诊断工具
- `docs/FEISHU_LONGCONN.md` - 详细使用文档
- `README.md` - 项目说明文档

### 技术改进
- 添加 lark-oapi==1.3.5 官方 SDK
- 升级 certifi 包以支持 SSL 验证
- 优化配置加载逻辑
- 改进会话管理机制

### 文档更新
- 新增飞书长连接模式完整文档
- 更新快速开始指南
- 添加常见问题解答(SSL、权限、部署等)
- 完善架构说明和技术栈介绍

## 📝 使用方式

启动飞书长连接服务(无需公网域名):
```bash
python3 start_feishu_bot.py
```

详见:docs/FEISHU_LONGCONN.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
zhaojie
2026-02-11 14:10:18 +08:00
parent f5acb05e61
commit e3a0396567
18 changed files with 1501 additions and 112 deletions

View File

@@ -38,6 +38,21 @@ class DatabaseInitializer:
# 迁移历史记录
self.migration_history = []
def _mask_db_url(self, url: str) -> str:
"""隐藏数据库连接字符串中的敏感信息"""
try:
# 隐藏密码部分
if '@' in url and ':' in url:
parts = url.split('@')
if len(parts) == 2:
auth_part = parts[0]
if ':' in auth_part:
protocol_user = auth_part.rsplit(':', 1)[0]
return f"{protocol_user}:***@{parts[1]}"
return url
except:
return url
def _get_database_version(self) -> str:
"""获取数据库版本信息"""
try:
@@ -61,14 +76,14 @@ class DatabaseInitializer:
print("TSP智能助手数据库初始化系统")
print("=" * 80)
print(f"数据库类型: {self.db_version}")
print(f"连接地址: {self.db_url}")
print(f"连接地址: {self._mask_db_url(self.db_url)}")
print(f"初始化时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 80)
try:
# 设置日志
config = get_config()
setup_logging(config.server.log_level, "logs/tsp_assistant.log")
setup_logging(config.server.log_level, "logs/tsp_assistant.log")
# 测试数据库连接
if not self._test_connection():
@@ -95,6 +110,9 @@ class DatabaseInitializer:
if not self._verify_database_integrity():
return False
# 初始化 Redis 缓存(如果启用)
self._initialize_redis_cache()
# 生成初始化报告
self._generate_init_report()
@@ -521,7 +539,16 @@ class DatabaseInitializer:
# 插入初始知识库数据
initial_data = self._get_initial_knowledge_data()
for data in initial_data:
entry = KnowledgeEntry(**data)
# 只使用模型定义中存在的字段
entry = KnowledgeEntry(
question=data['question'],
answer=data['answer'],
category=data['category'],
confidence_score=data.get('confidence_score', 0.7),
is_verified=data.get('is_verified', True),
verified_by=data.get('verified_by', 'system'),
verified_at=data.get('verified_at', datetime.now())
)
session.add(entry)
session.commit()
@@ -548,9 +575,7 @@ class DatabaseInitializer:
"confidence_score": 0.9,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.9
"verified_at": datetime.now()
},
{
"question": "账户被锁定了怎么办?",
@@ -559,9 +584,7 @@ class DatabaseInitializer:
"confidence_score": 0.8,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.8
"verified_at": datetime.now()
},
{
"question": "如何修改个人信息?",
@@ -570,9 +593,7 @@ class DatabaseInitializer:
"confidence_score": 0.7,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.7
"verified_at": datetime.now()
},
{
"question": "支付失败怎么办?",
@@ -581,9 +602,7 @@ class DatabaseInitializer:
"confidence_score": 0.8,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.8
"verified_at": datetime.now()
},
{
"question": "如何申请退款?",
@@ -592,9 +611,7 @@ class DatabaseInitializer:
"confidence_score": 0.7,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.7
"verified_at": datetime.now()
},
{
"question": "系统无法访问怎么办?",
@@ -603,9 +620,7 @@ class DatabaseInitializer:
"confidence_score": 0.8,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.8
"verified_at": datetime.now()
},
{
"question": "如何联系客服?",
@@ -614,9 +629,7 @@ class DatabaseInitializer:
"confidence_score": 0.9,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.9
"verified_at": datetime.now()
},
{
"question": "如何远程启动车辆?",
@@ -625,9 +638,7 @@ class DatabaseInitializer:
"confidence_score": 0.9,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.9
"verified_at": datetime.now()
},
{
"question": "APP显示车辆信息错误怎么办",
@@ -636,9 +647,7 @@ class DatabaseInitializer:
"confidence_score": 0.8,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.8
"verified_at": datetime.now()
},
{
"question": "车辆无法远程启动的原因?",
@@ -647,9 +656,7 @@ class DatabaseInitializer:
"confidence_score": 0.9,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.9
"verified_at": datetime.now()
}
]
@@ -688,10 +695,14 @@ class DatabaseInitializer:
entry.is_verified = True
entry.verified_by = "system_init"
entry.verified_at = datetime.now()
if not hasattr(entry, 'search_frequency'):
entry.search_frequency = 0
if not hasattr(entry, 'relevance_score'):
entry.relevance_score = 0.7
# 尝试设置优化字段(如果存在)
try:
if hasattr(entry, 'search_frequency') and entry.search_frequency is None:
entry.search_frequency = 0
if hasattr(entry, 'relevance_score') and entry.relevance_score is None:
entry.relevance_score = 0.7
except:
pass # 如果字段不存在,忽略
session.commit()
print(f" 成功验证 {len(unverified_entries)} 条知识库条目")
@@ -795,6 +806,34 @@ class DatabaseInitializer:
except Exception:
return 0
def _initialize_redis_cache(self):
"""初始化 Redis 缓存"""
print("\n检查 Redis 缓存配置...")
try:
from src.core.cache_manager import cache_manager
health = cache_manager.health_check()
if health["status"] == "healthy":
print(" ✓ Redis 缓存连接成功")
stats = cache_manager.get_stats()
print(f" - 当前连接数: {stats.get('connected_clients', 0)}")
print(f" - 内存使用: {stats.get('used_memory_human', '0B')}")
print(f" - 总键数: {stats.get('total_keys', 0)}")
elif health["status"] == "disabled":
print(" ⚠ Redis 缓存未启用(可选功能)")
else:
print(f" ⚠ Redis 缓存连接失败: {health.get('message', '未知错误')}")
print(" 提示: Redis 是可选功能,不影响系统正常运行")
except ImportError:
print(" ⚠ Redis 库未安装,缓存功能将被禁用")
print(" 提示: 运行 'pip install redis hiredis' 安装 Redis 支持")
except Exception as e:
print(f" ⚠ Redis 初始化失败: {e}")
print(" 提示: Redis 是可选功能,不影响系统正常运行")
def check_database_status(self) -> Dict[str, Any]:
"""检查数据库状态"""
print("\n" + "=" * 80)