feat(backend): RBAC admin roles + runtime logs service

This commit is contained in:
Simon
2026-07-09 13:46:15 +08:00
parent 7ffc6c8e23
commit d480aa4c1d
12 changed files with 726 additions and 25 deletions
+67 -9
View File
@@ -22,6 +22,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.models.agent import Agent
from app.models.config_change_log import ConfigChangeLog
from app.models.audit_log import AuditLog
from app.models.conversation import Conversation
from app.models.message import Message
from app.models.quick_reply_template import QuickReplyTemplate
@@ -451,7 +452,10 @@ async def update_config(
old_value = config.config_value
# 写入变更日志
# ===== A+B 双写(决策2:配置变更同时进入 config_change_logs 与 audit_logs=====
# 表 Aconfig_change_logs(既有,供"配置变更历史"页)
# 表 Baudit_logs 的 config_change 事件(供"安全审计日志"页,复用 record_audit_log 统一构造)
# 两表在同一 DB session 内 add,由调用方统一 commit(与 record_audit_log 约定一致:本函数不 commit)。
change_log = ConfigChangeLog(
config_key=key,
old_value=old_value,
@@ -460,6 +464,26 @@ async def update_config(
)
db.add(change_log)
# 表 Baudit_logs —— 严格复用 app.services.audit_log_service.record_audit_log 的构造方式
# (字段命名 / 默认值保持一致)。update_config 为服务层、无 FastAPI Request 上下文,
# 故 ip_address / user_agent 置 Nonerecord_audit_log 在无 request 时同样取 None)。
audit_log = AuditLog(
employee_id=agent_id,
action="config_change",
resource="system_config",
resource_id=key,
details={
"old": old_value,
"new": value,
"operator": agent_id,
},
result="success",
ip_address=None,
user_agent=None,
created_at=datetime.now(),
)
db.add(audit_log)
# 更新配置值
config.config_value = value
config.updated_at = datetime.now()
@@ -1690,19 +1714,53 @@ async def get_system_logs(
db: AsyncSession,
page: int = 1,
page_size: int = 50,
config_key: Optional[str] = None,
changed_by: Optional[str] = None,
from_time: Optional[datetime] = None,
to_time: Optional[datetime] = None,
) -> Dict[str, Any]:
"""获取系统日志(配置变更日志)"""
count_result = await db.execute(select(func.count(ConfigChangeLog.id)))
total = count_result.scalar() or 0
"""获取系统日志(配置变更日志),支持配置键/操作人/时间范围筛选。
offset = (page - 1) * page_size
result = await db.execute(
select(ConfigChangeLog)
Args:
db: 数据库会话
page: 页码,从 1 开始
page_size: 每页条数
config_key: 按配置键精确筛选(可选,空则不追加条件)
changed_by: 按操作人 agent_id 精确筛选(可选)
from_time: 变更时间起始(闭区间,可选)
to_time: 变更时间截止(闭区间,可选)
Returns:
Dict: {items, total, page, page_size}
"""
# 动态构建筛选条件(空参数不追加条件,保持旧行为)
conditions = []
if config_key:
conditions.append(ConfigChangeLog.config_key == config_key)
if changed_by:
conditions.append(ConfigChangeLog.changed_by == changed_by)
if from_time:
conditions.append(ConfigChangeLog.changed_at >= from_time)
if to_time:
conditions.append(ConfigChangeLog.changed_at <= to_time)
# 总数(带筛选)
count_stmt = select(func.count(ConfigChangeLog.id))
if conditions:
count_stmt = count_stmt.where(and_(*conditions))
total = (await db.execute(count_stmt)).scalar() or 0
# 分页查询(带筛选,按变更时间倒序)
query = select(ConfigChangeLog)
if conditions:
query = query.where(and_(*conditions))
query = (
query
.order_by(ConfigChangeLog.changed_at.desc())
.offset(offset)
.offset((page - 1) * page_size)
.limit(page_size)
)
logs = list(result.scalars().all())
logs = list((await db.execute(query)).scalars().all())
agent_ids = list({log.changed_by for log in logs if log.changed_by})
agent_names: Dict[str, str] = {}