feat(backend): RBAC admin roles + runtime logs service
This commit is contained in:
@@ -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)=====
|
||||
# 表 A:config_change_logs(既有,供"配置变更历史"页)
|
||||
# 表 B:audit_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)
|
||||
|
||||
# 表 B:audit_logs —— 严格复用 app.services.audit_log_service.record_audit_log 的构造方式
|
||||
# (字段命名 / 默认值保持一致)。update_config 为服务层、无 FastAPI Request 上下文,
|
||||
# 故 ip_address / user_agent 置 None(record_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] = {}
|
||||
|
||||
Reference in New Issue
Block a user