Initial certificate system

This commit is contained in:
nelso
2026-06-06 19:32:24 +08:00
commit ce4ed6213b
105 changed files with 9133 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
from fastapi import APIRouter, Depends, Query
from fastapi.responses import StreamingResponse
from openpyxl import Workbook
from sqlalchemy.orm import Session
from app.api.deps import require_roles
from app.core.paths import data_path
from app.db.session import get_db
from app.models import AdminUser, OperationLog
from app.schemas.log import OperationLogOut
from app.services.logs import HIGH_RISK_LOG_RETENTION_DAYS, LOG_RETENTION_DAYS, cleanup_expired_logs, format_log
router = APIRouter()
@router.get("", response_model=list[OperationLogOut])
def list_logs(
action: str | None = Query(default=None),
object_type: str | None = Query(default=None),
risk: str | None = Query(default=None),
keyword: str | None = Query(default=None),
page: int = Query(default=1, ge=1),
page_size: int = Query(default=20, ge=1, le=100),
db: Session = Depends(get_db),
_: AdminUser = Depends(require_roles("system_admin", "certificate_admin", "readonly")),
) -> dict[str, object]:
items = _query_logs(db, action, object_type, risk, keyword)
total = len(items)
total_pages = max(1, (total + page_size - 1) // page_size)
page = min(page, total_pages)
start = (page - 1) * page_size
return {
"items": items[start:start + page_size],
"total": total,
"page": page,
"page_size": page_size,
"total_pages": total_pages,
}
@router.get("/export")
def export_logs(
action: str | None = Query(default=None),
object_type: str | None = Query(default=None),
risk: str | None = Query(default=None),
keyword: str | None = Query(default=None),
db: Session = Depends(get_db),
_: AdminUser = Depends(require_roles("system_admin", "certificate_admin", "readonly")),
) -> StreamingResponse:
items = _query_logs(db, action, object_type, risk, keyword)
workbook = Workbook()
sheet = workbook.active
sheet.title = "\u64cd\u4f5c\u65e5\u5fd7"
sheet.append(["\u65f6\u95f4", "\u64cd\u4f5c\u4ebaID", "\u64cd\u4f5c", "\u5bf9\u8c61", "\u5bf9\u8c61ID", "\u98ce\u9669\u7b49\u7ea7", "\u6458\u8981", "\u8be6\u60c5"])
for item in items:
sheet.append([
item.get("created_at"),
item.get("admin_user_id"),
item.get("action_label"),
item.get("object_label"),
item.get("object_id"),
item.get("risk_label"),
item.get("summary"),
item.get("detail_json"),
])
for column in "ABCDEFGH":
sheet.column_dimensions[column].width = 22 if column != "H" else 60
export_path = data_path("exports") / "operation-logs.xlsx"
workbook.save(export_path)
file_handle = export_path.open("rb")
return StreamingResponse(
file_handle,
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={"Content-Disposition": 'attachment; filename="operation-logs.xlsx"'},
)
@router.post("/cleanup")
def cleanup_logs(
db: Session = Depends(get_db),
_: AdminUser = Depends(require_roles("system_admin", "certificate_admin")),
) -> dict[str, int]:
removed = cleanup_expired_logs(db)
db.commit()
return {
"removed": removed,
"normal_retention_days": LOG_RETENTION_DAYS,
"high_risk_retention_days": HIGH_RISK_LOG_RETENTION_DAYS,
}
def _query_logs(
db: Session,
action: str | None,
object_type: str | None,
risk: str | None,
keyword: str | None,
) -> list[dict[str, object]]:
query = db.query(OperationLog).order_by(OperationLog.id.desc())
cleanup_expired_logs(db)
db.commit()
if action:
query = query.filter(OperationLog.action == action)
if object_type:
query = query.filter(OperationLog.object_type == object_type)
items = [format_log(item) for item in query.limit(500).all()]
if risk:
items = [item for item in items if item["risk"] == risk]
if keyword:
word = keyword.strip().lower()
items = [
item for item in items
if word in " ".join(str(item.get(key, "")) for key in ["object_id", "summary", "detail_json", "action_label", "object_label"]).lower()
]
return items[:200]