feat: 飞书机器人按租户路由 群组绑定租户 + 独立凭证 + 知识库隔离

1. 新增 resolve_tenant_by_chat_id() 根据飞书群 chat_id 查找绑定的租户
2. 新增 get_tenant_feishu_config() 获取租户级飞书凭证
3. FeishuService 支持传入自定义 app_id/app_secret(租户级别)
4. feishu_bot.py 收到消息时自动解析租户,使用租户凭证回复
5. feishu_longconn_service.py 同样按 chat_id 解析租户并传递 tenant_id
6. 租户管理 UI 新增飞书配置字段:App ID、App Secret、绑定群 Chat ID
7. 租户列表展示飞书绑定状态和群数量
8. 保存租户时同步更新飞书配置到 config JSON
This commit is contained in:
2026-04-02 09:58:04 +08:00
parent edb0616f7f
commit 7950cd8237
18 changed files with 1347 additions and 16 deletions

View File

@@ -0,0 +1,85 @@
---
Name: http-error-analyzer
Description: 当日志中出现 4xx/5xx HTTP 错误(如 404、500自动提取相关日志上下文并分析可能的原因与影响范围给出排查建议。
---
你是一个「HTTP 报错分析助手」,技能名为 **http-error-analyzer**
你的职责:当系统日志中出现 404、500 等 HTTP 错误时,调用配套脚本,提取这些错误日志的上下文,分析可能的根因(路由/权限/参数/后端异常等),并给出清晰可执行的排查建议。
---
## 一、触发条件(什么时候使用 http-error-analyzer
在以下场景中,应考虑激活本 Skill
- 用户直接给出日志片段,包含 `404` / `500` / `502` / `503` 等状态码,并询问「为什么」;
- 用户提到「某个页面/接口 404/500」并附带或引用了日志文件
- 你通过其他 Skill例如 `log-summary`)发现最近 4xx/5xx 错误明显增多,需要进一步排查。
---
## 二、总体流程
1. 从项目根目录执行脚本 `scripts/analyze_http_errors.py`
2. 读取脚本输出的错误汇总与示例上下文;
3. 结合项目结构与路由、后端模块划分,推断每类错误的可能成因;
4. 向用户输出简明的「错误分类 → 可能原因 → 建议排查路径」列表。
---
## 三、脚本调用规范
从项目根目录执行命令:
```bash
python .claude/skills/http-error-analyzer/scripts/analyze_http_errors.py
```
脚本行为约定:
- 扫描 `logs/` 目录下最近若干个 `dashboard.log`(与 `log-summary` 类似,只读);
- 匹配常见 HTTP 错误状态码:`404``500``502``503` 等;
- 对每种状态码统计出现次数,并从每类中抽取若干代表性日志行(包含 URL/方法/错误信息等);
- 打印类似如下结构的文本到 stdout
- 每个状态码一段:
- 状态码 + 次数
- 若干示例行(截断到合理长度,避免过长)
你需要:
1. 运行脚本并捕获输出;
2. 判断 4xx 与 5xx 错误主要集中在哪些 URL / 接口;
3. 根据日志中的路径、报错栈信息等,推理可能的成因。
---
## 四、对用户的输出规范
当成功执行 `http-error-analyzer` 时,你应该向用户返回包括以下内容的简要报告:
1. **错误分布概览**
- 例如:「最近 5 个日志文件中404 共 32 次500 共 5 次。」
2. **按错误类型的分析**
- 对每种状态码(如 404、500用 13 句话说明:
- 主要集中在哪些 URL 或模块(如 `/knowledge/...``/api/workorders/...`
- 可能原因(如路由未配置、静态资源路径错误、模板缺失、后端异常、数据库错误等)。
3. **具体排查建议**
- 比如:
- 404检查对应 Blueprint/路由是否注册、前端跳转 URL 是否正确、静态资源路径是否匹配 Nginx 配置;
- 500查看对应视图函数/接口的 Python 代码与 Traceback重点排查数据库访问/配置读取/第三方服务调用。
避免:
- 一股脑贴出整段日志,只需引用少量代表性行做说明;
- 在没有足够信息的情况下,过度肯定某个具体根因;应以「可能是」「优先排查方向」来表述。
---
## 五、反模式与边界
- 本 Skill 只做分析与建议,不修改任何代码或配置;如需改动,应由用户确认后再进行;
- 若日志中没有任何 4xx/5xx 错误,应明确告知用户「未发现相关 HTTP 错误」,而不是强行分析;
- 如脚本运行失败(路径不对/权限问题等),应提示用户修复后再重试。

View File

@@ -0,0 +1,129 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
HTTP 错误日志分析脚本
扫描 logs/*/dashboard.log统计常见 4xx/5xx HTTP 状态码(如 404/500
并输出按状态码分组的出现次数与若干示例行,供 http-error-analyzer Skill 使用。
"""
import os
import re
from pathlib import Path
from typing import Dict, List, Tuple
LOG_ROOT = Path("logs")
LOG_FILENAME = "dashboard.log"
MAX_FILES = 5 # 最多分析最近 N 个日志文件
# 简单匹配常见 HTTP 错误码;根据你实际日志格式可以再调整
ERROR_CODES = [404, 500, 502, 503]
CODE_PATTERNS: Dict[int, re.Pattern] = {
code: re.compile(rf"\b{code}\b") for code in ERROR_CODES
}
def find_log_files() -> List[Path]:
if not LOG_ROOT.exists():
return []
candidates: List[Tuple[float, Path]] = []
for root, dirs, files in os.walk(LOG_ROOT):
if LOG_FILENAME in files:
p = Path(root) / LOG_FILENAME
try:
mtime = p.stat().st_mtime
except OSError:
continue
candidates.append((mtime, p))
candidates.sort(key=lambda x: x[0], reverse=True)
return [p for _, p in candidates[:MAX_FILES]]
def analyze_file(path: Path):
"""
返回:
counts: {status_code: 次数}
samples: {status_code: [示例行1, 示例行2, ...]}
"""
counts: Dict[int, int] = {code: 0 for code in ERROR_CODES}
samples: Dict[int, List[str]] = {code: [] for code in ERROR_CODES}
try:
with path.open("r", encoding="utf-8", errors="ignore") as f:
for line in f:
for code, pattern in CODE_PATTERNS.items():
if pattern.search(line):
counts[code] += 1
# 只收集每个状态码前若干条示例
if len(samples[code]) < 5:
samples[code].append(line.strip()[:300])
break
except OSError as e:
print(f"[!] 读取日志失败 {path}: {e}")
return None
return counts, samples
def main():
log_files = find_log_files()
if not log_files:
print("未找到任何日志文件logs/*/dashboard.log")
return
overall_counts: Dict[int, int] = {code: 0 for code in ERROR_CODES}
overall_samples: Dict[int, List[str]] = {code: [] for code in ERROR_CODES}
print(f"=== HTTP 错误日志分析(最近最多 {MAX_FILES} 个日志文件)===\n")
for idx, path in enumerate(log_files, start=1):
print(f"[{idx}] 日志文件: {path}")
result = analyze_file(path)
if result is None:
print(" 无法读取该日志文件。\n")
continue
counts, samples = result
any_error = any(counts[code] > 0 for code in ERROR_CODES)
if not any_error:
print(" 未发现 404/500/502/503 等 HTTP 错误。")
print()
continue
for code in ERROR_CODES:
c = counts[code]
if c == 0:
continue
overall_counts[code] += c
overall_samples[code].extend(samples[code])
print(f" 状态码 {code}: {c}")
if samples[code]:
print(" 示例:")
for line in samples[code]:
print(f" {line}")
print()
print("=== 汇总统计 ===")
any_overall = any(overall_counts[code] > 0 for code in ERROR_CODES)
if not any_overall:
print(" 未在最近的日志文件中发现任何配置的 HTTP 错误状态码。")
return
for code in ERROR_CODES:
c = overall_counts[code]
if c == 0:
continue
print(f" 状态码 {code}: {c}")
print("\n提示:以上为按状态码汇总的次数与部分示例行,")
print("你可以结合 URL 路径、接口名称、堆栈片段来推断可能的路由配置问题、权限问题或后端异常。")
if __name__ == "__main__":
main()