refactor(api): 使用常量替代硬编码字符串

- 在health_check接口中使用ApiResponseKey.STATUS和ApiStatus.OK常量
- 替换硬编码的状态返回值为枚举常量

refactor(core): 配置模块错误信息统一使用常量

- 从constants模块导入ConfigErrorDetail并替换CORS_ORIGINS和LEGACY_ALLOWED_QUERIES的验证错误信息
- 配置类中的默认值使用constants中定义的常量

feat(constants): 添加API响应、安全错误和配置错误常量类

- 新增ApiResponseKey用于API状态键名
- 新增ApiStatus用于API状态值
- 新增SecurityErrorDetail用于安全认证错误详情
- 新增ConfigErrorDetail用于配置验证错误详情
- 添加DEFAULT_MODEL_PROVIDER和DEFAULT_OPENCLAW_ACTION_JSON常量

refactor(security): 安全认证模块使用错误常量

- 将硬编码的安全错误信息替换为SecurityErrorDetail常量
- 包括API密钥、审批密钥和审计密钥的相关错误信息

refactor(ai-agent): AI代理适配器改进错误处理

- 将HTTP状态码替换为FastAPI状态常量
- 添加OpenClaw工具和操作的错误常量
- 修复健康检查和工具调用中的状态码比较逻辑
- 添加AIToolAuditKey用于工具审计键名

feat(ai-agent): 扩展AI代理常量定义

- 新增AIToolAuditKey用于工具审计字段
- 添加OpenClaw相关的错误常量如OPENCLAW_CHAT_PROVIDER_REQUIRED等
- 添加UNSUPPORTED_AI_SKILL_TEMPLATE模板字符串

refactor(approvals): 审批模块常量化重构

- 新增ApprovalPayloadKey用于审批载荷字段
- 添加approval_action函数和APPROVAL_ACTION_SEPARATOR分隔符
- 使用常量替换字面量值

feat(audit): 审计模块新增飞书事件动作类型

- 添加FEISHU_WEBHOOK_EVENT和FEISHU_LONG_CONNECTION_EVENT审计动作

refactor(business): 业务模块全面常量化

- 新增BusinessDomain枚举包含所有业务域
- 添加BusinessResponseKey、BusinessPayloadKey等常量类
- 重构DOMAIN_MODELS为frozenset以提高性能
- 添加normalize_domain等辅助函数用于域标准化
- 使用常量替换路由和业务服务中的硬编码字符串
- 添加业务错误常量和字段验证模板

refactor(feishu): 飞书客户端错误处理优化

- 将HTTP状态码替换为FastAPI标准状态常量
- 改进错误处理的一致性

refactor(approvals): 审批服务使用新常量结构

- 使用ApprovalPayloadKey常量重构载荷字段
- 使用approval_action函数统一动作命名格式
- 优化高风险域判断逻辑
```
This commit is contained in:
2026-07-06 15:56:43 +08:00
parent 514b14d390
commit fbd0aaa9e4
34 changed files with 1153 additions and 525 deletions

View File

@@ -9,9 +9,20 @@ from app.core.config import get_settings
from app.modules.ai_agent.service import AIService
from app.modules.ai_agent.constants import AIResponseKey
from app.modules.audit.constants import AuditSource
from app.modules.feishu.constants import FeishuCommandKey
from app.modules.feishu.constants import (
FEISHU_AI_REPLY_TITLE,
FEISHU_MENTION_PATTERN,
FEISHU_ZERO_WIDTH_SPACE,
FeishuCommandKey,
FeishuCommandName,
FeishuCommandResultKey,
FeishuPayloadKey,
FeishuReplyType,
)
from app.modules.reports.constants import ReportResponseKey
from app.modules.feishu.service import FeishuService
from app.modules.reports.service import ReportService
from app.modules.risk.constants import RiskSummaryKey
from app.modules.risk.service import RiskService
DAILY_REPORT_KEYWORDS = ("日报", "晨报", "经营日报", "经营晨报")
@@ -27,7 +38,11 @@ def _parse_content_text(content: Any) -> str:
"""Extract plain command text from a Feishu message content payload."""
if isinstance(content, dict):
return str(content.get("text") or content.get("content") or "")
return str(
content.get(FeishuPayloadKey.TEXT)
or content.get(FeishuPayloadKey.CONTENT)
or ""
)
if not isinstance(content, str):
return ""
try:
@@ -35,18 +50,42 @@ def _parse_content_text(content: Any) -> str:
except json.JSONDecodeError:
return content
if isinstance(data, dict):
return str(data.get("text") or data.get("content") or "")
return str(
data.get(FeishuPayloadKey.TEXT)
or data.get(FeishuPayloadKey.CONTENT)
or ""
)
return content
def _clean_command_text(text: str) -> str:
"""Remove mentions and invisible characters from Feishu command text."""
text = re.sub(r"@\S+", "", text or "")
text = text.replace("\u200b", "")
text = re.sub(FEISHU_MENTION_PATTERN, "", text or "")
text = text.replace(FEISHU_ZERO_WIDTH_SPACE, "")
return text.strip()
def _command_result(
command: FeishuCommandName,
reply_type: FeishuReplyType,
title: str,
content: str,
provider_response: dict[str, Any] | None = None,
lines: list[str] | None = None,
) -> dict[str, Any]:
result: dict[str, Any] = {
FeishuCommandResultKey.COMMAND: command,
FeishuCommandResultKey.REPLY_TYPE: reply_type,
FeishuCommandResultKey.TITLE: title,
FeishuCommandResultKey.CONTENT: content,
FeishuCommandResultKey.PROVIDER_RESPONSE: provider_response,
}
if lines is not None:
result[FeishuCommandResultKey.LINES] = lines
return result
class FeishuCommandService:
"""Route Feishu text commands to reports, risk summaries, or AI replies."""
@@ -55,16 +94,20 @@ class FeishuCommandService:
self.feishu = FeishuService(db)
def extract_event_command(self, payload: dict[str, Any]) -> dict[str, Any] | None:
event = payload.get("event") or {}
message = event.get("message") or {}
event = payload.get(FeishuPayloadKey.EVENT) or {}
message = event.get(FeishuPayloadKey.MESSAGE) or {}
if not message:
return None
text = _clean_command_text(_parse_content_text(message.get("content")))
text = _clean_command_text(_parse_content_text(message.get(FeishuPayloadKey.CONTENT)))
if not text:
return None
sender = event.get("sender") or {}
sender_id = sender.get("sender_id") or {}
actor = sender_id.get("open_id") or sender_id.get("user_id") or ActorValue.FEISHU
sender = event.get(FeishuPayloadKey.SENDER) or {}
sender_id = sender.get(FeishuPayloadKey.SENDER_ID) or {}
actor = (
sender_id.get(FeishuPayloadKey.OPEN_ID)
or sender_id.get(FeishuPayloadKey.USER_ID)
or ActorValue.FEISHU
)
return {
FeishuCommandKey.TEXT: text,
FeishuCommandKey.CHAT_ID: message.get(FeishuCommandKey.CHAT_ID),
@@ -84,80 +127,70 @@ class FeishuCommandService:
if any(keyword in command_text for keyword in DAILY_REPORT_KEYWORDS):
report = ReportService(self.db).daily_brief()
result = {
"command": "daily_brief",
"reply_type": "card",
"title": report["title"],
"content": report["content"],
"lines": report["lines"],
}
if auto_reply:
provider_response = self._send_card_if_configured(
chat_id,
report["title"],
report["lines"],
report[ReportResponseKey.TITLE],
report[ReportResponseKey.LINES],
actor,
)
result["provider_response"] = provider_response
return result
return _command_result(
FeishuCommandName.DAILY_BRIEF,
FeishuReplyType.CARD,
report[ReportResponseKey.TITLE],
report[ReportResponseKey.CONTENT],
provider_response,
report[ReportResponseKey.LINES],
)
if any(keyword in command_text for keyword in PROJECT_WEEKLY_KEYWORDS):
report = ReportService(self.db).project_weekly()
result = {
"command": "project_weekly",
"reply_type": "card",
"title": report["title"],
"content": report["content"],
"lines": report["lines"],
}
if auto_reply:
provider_response = self._send_card_if_configured(
chat_id,
report["title"],
report["lines"],
report[ReportResponseKey.TITLE],
report[ReportResponseKey.LINES],
actor,
)
result["provider_response"] = provider_response
return result
return _command_result(
FeishuCommandName.PROJECT_WEEKLY,
FeishuReplyType.CARD,
report[ReportResponseKey.TITLE],
report[ReportResponseKey.CONTENT],
provider_response,
report[ReportResponseKey.LINES],
)
if any(keyword in command_text for keyword in ATTENDANCE_KEYWORDS):
report = ReportService(self.db).attendance_summary()
result = {
"command": "attendance_summary",
"reply_type": "card",
"title": report["title"],
"content": report["content"],
"lines": report["lines"],
}
if auto_reply:
provider_response = self._send_card_if_configured(
chat_id,
report["title"],
report["lines"],
report[ReportResponseKey.TITLE],
report[ReportResponseKey.LINES],
actor,
)
result["provider_response"] = provider_response
return result
return _command_result(
FeishuCommandName.ATTENDANCE_SUMMARY,
FeishuReplyType.CARD,
report[ReportResponseKey.TITLE],
report[ReportResponseKey.CONTENT],
provider_response,
report[ReportResponseKey.LINES],
)
if any(keyword in command_text for keyword in RISK_KEYWORDS):
summary = RiskService(self.db).summary()
lines = [
f"- 综合风险等级:{summary['risk_level']}",
f"- 风险分:{summary['risk_score']}",
f"- 逾期任务:{len(summary['overdue_tasks'])}",
f"- 延期项目:{len(summary['delayed_projects'])}",
f"- 超预算项目:{len(summary['over_budget_projects'])}",
f"- 资金风险账户:{len(summary['fund_risks'])}",
f"- 供应商风险:{len(summary['supplier_risks'])}",
f"- 打开风险事件:{len(summary['open_events'])}",
f"- 综合风险等级:{summary[RiskSummaryKey.RISK_LEVEL]}",
f"- 风险分:{summary[RiskSummaryKey.RISK_SCORE]}",
f"- 逾期任务:{len(summary[RiskSummaryKey.OVERDUE_TASKS])}",
f"- 延期项目:{len(summary[RiskSummaryKey.DELAYED_PROJECTS])}",
f"- 超预算项目:{len(summary[RiskSummaryKey.OVER_BUDGET_PROJECTS])}",
f"- 资金风险账户:{len(summary[RiskSummaryKey.FUND_RISKS])}",
f"- 供应商风险:{len(summary[RiskSummaryKey.SUPPLIER_RISKS])}",
f"- 打开风险事件:{len(summary[RiskSummaryKey.OPEN_EVENTS])}",
]
result = {
"command": "risk_summary",
"reply_type": "card",
"title": RISK_TITLE,
"content": "\n".join(lines),
"lines": lines,
}
if auto_reply:
provider_response = self._send_card_if_configured(
chat_id,
@@ -165,8 +198,14 @@ class FeishuCommandService:
lines,
actor,
)
result["provider_response"] = provider_response
return result
return _command_result(
FeishuCommandName.RISK_SUMMARY,
FeishuReplyType.CARD,
RISK_TITLE,
"\n".join(lines),
provider_response,
lines,
)
prompt = command_text
for prefix in AI_COMMAND_PREFIXES:
@@ -186,16 +225,15 @@ class FeishuCommandService:
command_text.startswith(prefix) or lowered.startswith(prefix)
for prefix in AI_COMMAND_PREFIXES
)
result = {
"command": "ai_ask" if is_explicit_ai else "fallback_ai",
"reply_type": "text",
"title": "AI 回复",
"content": content,
}
if auto_reply:
provider_response = self._send_text_if_configured(chat_id, content, actor)
result["provider_response"] = provider_response
return result
return _command_result(
FeishuCommandName.AI_ASK if is_explicit_ai else FeishuCommandName.FALLBACK_AI,
FeishuReplyType.TEXT,
FEISHU_AI_REPLY_TITLE,
content,
provider_response,
)
def _send_card_if_configured(
self,