Files
Zhang Jing Xuan 0841b1a103 智教助手平台:完整初始化
- 前端:Vue3 + TS + Element Plus,24 个页面路由(课件/组题/教案/动画/思维导图/作文批改/命题/课堂/资源/社区等)
- 后端:FastAPI + SQLAlchemy + SQLite,17 个路由模块,AI 服务层含降级模板
- AI:glm-5.x 推理模型已禁用思维链,确保输出真实内容
- 修复:ai_service 两处请求体注入 enable_thinking/thinking disabled
- 测试账号:13900999999 / test1234
2026-07-29 16:29:10 +08:00

97 lines
2.6 KiB
Python

"""安全审计日志服务。
提供 log_action() 用于在任意路由中记录安全相关事件。日志写入数据库 audit_logs 表,
同时输出到 logger,便于后续接入文件/集中式日志收集。
"""
from __future__ import annotations
import logging
from typing import Any, Optional
from fastapi import Request
from sqlalchemy.orm import Session
from models.audit import AuditLog
from models.user import User
logger = logging.getLogger(__name__)
def _user_label(user: Optional[User]) -> str:
"""返回用户可读标识:优先 username,其次 phone,最后 id。"""
if user is None:
return ""
for attr in ("username", "phone", "name"):
val = getattr(user, attr, None)
if val:
return str(val)
return str(getattr(user, "id", ""))
def _client_ip(request: Optional[Request]) -> str:
if request is None:
return ""
try:
fwd = request.headers.get("x-forwarded-for")
if fwd:
return fwd.split(",")[0].strip()[:64]
client = getattr(request, "client", None)
if client and client.host:
return client.host[:64]
except Exception:
pass
return ""
def _user_agent(request: Optional[Request]) -> str:
if request is None:
return ""
try:
return (request.headers.get("user-agent") or "")[:255]
except Exception:
return ""
def log_action(
db: Session,
*,
action: str,
user: Optional[User] = None,
request: Optional[Request] = None,
target_type: str = "",
target_id: Any = "",
detail: str = "",
status: str = "success",
) -> None:
"""记录一条审计日志。失败不影响主流程。"""
try:
log = AuditLog(
user_id=user.id if user else None,
username=_user_label(user),
action=action[:64],
target_type=target_type[:32] if target_type else "",
target_id=str(target_id)[:64] if target_id else "",
detail=detail[:2000] if detail else "",
ip=_client_ip(request),
user_agent=_user_agent(request),
status=status[:16] if status else "success",
)
db.add(log)
db.commit()
except Exception as exc: # pragma: no cover - 日志失败不应中断业务
logger.warning("audit log failed: %s", exc)
try:
db.rollback()
except Exception:
pass
# 同时输出到日志通道
logger.info(
"audit action=%s user=%s target=%s/%s status=%s ip=%s",
action,
_user_label(user),
target_type,
target_id,
status,
_client_ip(request),
)