```
refactor(Dockerfile): 使用requirements.txt替代硬编码依赖 将Dockerfile中的硬编码pip包列表替换为通过requirements.txt文件安装, 提高依赖管理的灵活性和可维护性。 feat(scheduling): 移除内置APScheduler,采用独立调度系统 移除app/core/background/scheduler.py中原来的APScheduler实现, 改为使用新的应用级调度系统app.application.scheduling。 refactor(task_queue): 调整任务队列模块结构和导入路径 将任务队列相关常量从app.core.background.task_queue.constants迁移至 app.tasks.constants,并更新所有相关导入路径和引用。 refactor(events): 将事件服务重构为独立的应用层组件 将事件分发逻辑从核心层迁移到应用层,使用app.application.events.EventDispatchService 替代原有的app.modules.events.services.EventService。 feat(ai_memory): 增强AI记忆自动写入的安全策略 新增ai_memory_blocked_content_terms配置项用于阻止敏感内容, 添加TTL过期机制控制自动写入条目的生命周期。 fix(security): 强化生产环境安全验证机制 增加model_validator确保生产环境中数据库连接、API密钥、CORS设置等 关键安全配置符合要求。 feat(risks): 优化风险事件操作动作的外键约束 为RiskEventAction模型的风险事件ID字段添加外键约束, 防止孤立记录并增强数据完整性。 refactor(audit): 优化审计服务方法命名和事务处理 将AuditService的log方法重命名为record以反映其阶段行为, 并调整事务提交时机以提高性能。 feat(events): 增强领域事件并发处理和响应模型 添加事件锁定机制防止重复处理,更新API响应模型以提供 更准确的数据类型定义。 ```
This commit is contained in:
192
app/application/feishu/handlers/rules.py
Normal file
192
app/application/feishu/handlers/rules.py
Normal file
@@ -0,0 +1,192 @@
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.application.feishu.delivery import send_text_if_configured
|
||||
from app.application.feishu.results import command_result
|
||||
from app.core.config import get_settings
|
||||
from app.modules.ai_memory.constants import AIMemoryStatus
|
||||
from app.modules.ai_memory.service import AIMemoryService
|
||||
from app.modules.feishu.constants import FeishuCommandName, FeishuReplyType
|
||||
from app.modules.feishu.service import FeishuService
|
||||
|
||||
RULE_TITLE = "AI 学习规则"
|
||||
RULE_CREATE_PATTERN = re.compile(r"^学习规则(?:\s+(\d{1,3}))?\s*[::]\s*(.*)$")
|
||||
MARKET_RULE_CREATE_PATTERN = re.compile(
|
||||
r"^学习市场规则(?:\s+(\d{1,3}))?\s*[::]\s*(.*)$"
|
||||
)
|
||||
RULE_DISABLE_PATTERN = re.compile(r"^停用规则\s+(MEM-[A-Za-z0-9-]+)$", re.IGNORECASE)
|
||||
RULE_ENABLE_PATTERN = re.compile(r"^启用规则\s+(MEM-[A-Za-z0-9-]+)$", re.IGNORECASE)
|
||||
RULE_LIST_COMMANDS = {"查看规则", "规则列表", "查看市场规则"}
|
||||
RULE_COMMAND_PREFIXES = (
|
||||
"学习市场规则",
|
||||
"学习规则",
|
||||
"查看市场规则",
|
||||
"查看规则",
|
||||
"规则列表",
|
||||
"停用规则",
|
||||
"启用规则",
|
||||
)
|
||||
RULE_COMMAND_HELP = (
|
||||
"规则指令格式:\n"
|
||||
"学习规则:<规则内容>\n"
|
||||
"学习规则 80:<规则内容>\n"
|
||||
"学习市场规则 80:<仅用于市场分析的规则内容>\n"
|
||||
"查看规则\n"
|
||||
"停用规则 <规则编号>\n"
|
||||
"启用规则 <规则编号>"
|
||||
)
|
||||
|
||||
|
||||
def handle_rule_command(
|
||||
db: Session,
|
||||
feishu: FeishuService,
|
||||
command_text: str,
|
||||
chat_id: str | None,
|
||||
actor: str,
|
||||
auto_reply: bool,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Handle persistent AI rule commands."""
|
||||
|
||||
if not command_text.startswith(RULE_COMMAND_PREFIXES):
|
||||
return None
|
||||
command = _command_name(command_text)
|
||||
if command in {
|
||||
FeishuCommandName.RULE_CREATE,
|
||||
FeishuCommandName.RULE_DISABLE,
|
||||
FeishuCommandName.RULE_ENABLE,
|
||||
} and get_settings().read_only_mode:
|
||||
return _result(
|
||||
feishu,
|
||||
command,
|
||||
"当前为只读模式,不能新增或修改学习规则。请由管理员启用操作后重试。",
|
||||
chat_id,
|
||||
actor,
|
||||
auto_reply,
|
||||
)
|
||||
|
||||
content = RULE_COMMAND_HELP
|
||||
try:
|
||||
command, content = _execute(db, command_text, command, actor)
|
||||
except HTTPException as exc:
|
||||
detail = str(exc.detail)
|
||||
if "secret-like" in detail:
|
||||
content = "规则疑似包含密码、令牌或其他密钥信息,已拒绝学习。"
|
||||
elif exc.status_code == 404:
|
||||
content = "没有找到该规则,请先发送“查看规则”确认规则编号。"
|
||||
elif "priority" in detail:
|
||||
content = "规则优先级必须在 1 到 100 之间。"
|
||||
else:
|
||||
content = "规则未保存,请检查指令内容后重试。"
|
||||
return _result(feishu, command, content, chat_id, actor, auto_reply)
|
||||
|
||||
|
||||
def _execute(
|
||||
db: Session,
|
||||
command_text: str,
|
||||
command: FeishuCommandName,
|
||||
actor: str,
|
||||
) -> tuple[FeishuCommandName, str]:
|
||||
market_create_match = MARKET_RULE_CREATE_PATTERN.fullmatch(command_text)
|
||||
create_match = market_create_match or RULE_CREATE_PATTERN.fullmatch(command_text)
|
||||
disable_match = RULE_DISABLE_PATTERN.fullmatch(command_text)
|
||||
enable_match = RULE_ENABLE_PATTERN.fullmatch(command_text)
|
||||
memory = AIMemoryService(db)
|
||||
if create_match:
|
||||
return command, _create_rule(memory, create_match, market_create_match is not None, actor)
|
||||
if command_text in RULE_LIST_COMMANDS:
|
||||
return FeishuCommandName.RULE_LIST, _list_rules(memory, command_text)
|
||||
if disable_match or enable_match:
|
||||
enabled = enable_match is not None
|
||||
match = enable_match or disable_match
|
||||
rule = memory.update_rule(
|
||||
code=match.group(1),
|
||||
content=None,
|
||||
priority=None,
|
||||
tags=None,
|
||||
enabled=enabled,
|
||||
actor=actor,
|
||||
)
|
||||
state = "已启用" if enabled else "已停用"
|
||||
return (
|
||||
FeishuCommandName.RULE_ENABLE if enabled else FeishuCommandName.RULE_DISABLE,
|
||||
f"规则{state}。\n"
|
||||
f"编号:{rule['code']}\n"
|
||||
f"优先级:{rule['importance']}\n"
|
||||
f"范围:{rule['scope']} / {rule['subject']}\n"
|
||||
f"状态:{state}",
|
||||
)
|
||||
return command, RULE_COMMAND_HELP
|
||||
|
||||
|
||||
def _create_rule(
|
||||
memory: AIMemoryService,
|
||||
match: re.Match[str],
|
||||
market_rule: bool,
|
||||
actor: str,
|
||||
) -> str:
|
||||
priority = int(match.group(1) or 50)
|
||||
content = match.group(2).strip()
|
||||
if not content:
|
||||
return f"规则内容不能为空。\n\n{RULE_COMMAND_HELP}"
|
||||
if not 1 <= priority <= 100:
|
||||
return "规则优先级必须在 1 到 100 之间。"
|
||||
rule = memory.create_rule(
|
||||
content=content,
|
||||
scope="market" if market_rule else "global",
|
||||
subject="market" if market_rule else "company",
|
||||
priority=priority,
|
||||
tags=["feishu", *(["market"] if market_rule else [])],
|
||||
actor=actor,
|
||||
)
|
||||
return (
|
||||
"规则已学习。\n"
|
||||
f"编号:{rule['code']}\n"
|
||||
f"优先级:{rule['importance']}\n"
|
||||
f"范围:{rule['scope']} / {rule['subject']}\n"
|
||||
"状态:已启用"
|
||||
)
|
||||
|
||||
|
||||
def _list_rules(memory: AIMemoryService, command_text: str) -> str:
|
||||
rules = memory.list_rules(
|
||||
scope="market" if command_text == "查看市场规则" else None,
|
||||
status_filter=AIMemoryStatus.ACTIVE,
|
||||
limit=20,
|
||||
)
|
||||
if not rules:
|
||||
return "当前没有已启用的学习规则。"
|
||||
lines = ["当前已启用的学习规则:"]
|
||||
for rule in rules:
|
||||
rule_text = str(rule["content"])
|
||||
if len(rule_text) > 80:
|
||||
rule_text = f"{rule_text[:80]}…"
|
||||
lines.append(
|
||||
f"{rule['code']}|优先级 {rule['importance']}|"
|
||||
f"{rule['scope']}/{rule['subject']}\n{rule_text}"
|
||||
)
|
||||
return "\n\n".join(lines)
|
||||
|
||||
|
||||
def _command_name(command_text: str) -> FeishuCommandName:
|
||||
if command_text.startswith("停用规则"):
|
||||
return FeishuCommandName.RULE_DISABLE
|
||||
if command_text.startswith("启用规则"):
|
||||
return FeishuCommandName.RULE_ENABLE
|
||||
if command_text.startswith(("查看市场规则", "查看规则", "规则列表")):
|
||||
return FeishuCommandName.RULE_LIST
|
||||
return FeishuCommandName.RULE_CREATE
|
||||
|
||||
|
||||
def _result(
|
||||
feishu: FeishuService,
|
||||
command: FeishuCommandName,
|
||||
content: str,
|
||||
chat_id: str | None,
|
||||
actor: str,
|
||||
auto_reply: bool,
|
||||
) -> dict[str, Any]:
|
||||
response = send_text_if_configured(feishu, chat_id, content, actor) if auto_reply else None
|
||||
return command_result(command, FeishuReplyType.TEXT, RULE_TITLE, content, response)
|
||||
Reference in New Issue
Block a user