智教助手平台:完整初始化
- 前端:Vue3 + TS + Element Plus,24 个页面路由(课件/组题/教案/动画/思维导图/作文批改/命题/课堂/资源/社区等) - 后端:FastAPI + SQLAlchemy + SQLite,17 个路由模块,AI 服务层含降级模板 - AI:glm-5.x 推理模型已禁用思维链,确保输出真实内容 - 修复:ai_service 两处请求体注入 enable_thinking/thinking disabled - 测试账号:13900999999 / test1234
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
"""安全审计日志服务。
|
||||
|
||||
提供 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),
|
||||
)
|
||||
Reference in New Issue
Block a user