Files
JiuContinent d7db84571d ```
feat: 添加飞书用户模块和订阅功能支持

- 新增feishu_users模块用于处理飞书用户身份验证和权限管理
- 新增subscriptions模块用于处理订阅相关功能
- 新增personalization模块用于个性化服务
- 在alembic迁移配置中注册新的模型模块
- 在API路由器中添加feishu_users和subscriptions路由
- 实现事件调度服务的改进,包括错误处理和状态更新优化
- 添加飞书命令处理的权限检查机制
- 实现飞书应用票据事件处理
- 改进审计日志记录功能
```
2026-07-27 08:02:17 +08:00

84 lines
2.7 KiB
Python

import json
from typing import Any
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.core.http.masking import is_sensitive_key
from app.core.http.pagination import bounded_limit
from app.core.http.request_context import get_request_id
from app.modules.audit.constants import AUDIT_REDACTED_VALUE
from app.modules.audit.models import AuditLog
from app.modules.audit.schemas import AuditLogCreate
def _redact(value: Any) -> Any:
if isinstance(value, dict):
safe: dict[str, Any] = {}
for key, item in value.items():
key_text = str(key)
if is_sensitive_key(key_text):
safe[key_text] = AUDIT_REDACTED_VALUE
else:
safe[key_text] = _redact(item)
return safe
if isinstance(value, list):
return [_redact(item) for item in value]
if isinstance(value, tuple):
return [_redact(item) for item in value]
return value
def _dump(value: Any | None) -> str | None:
"""Serialize audit payloads while preserving existing strings."""
if value is None:
return None
if isinstance(value, str):
try:
parsed = json.loads(value)
except (json.JSONDecodeError, TypeError):
return value
if isinstance(parsed, (dict, list)):
return json.dumps(_redact(parsed), ensure_ascii=False, default=str)
return value
return json.dumps(_redact(value), ensure_ascii=False, default=str)
class AuditService:
"""Persist and query audit log entries."""
def __init__(self, db: Session):
self.db = db
def record(self, payload: AuditLogCreate) -> AuditLog:
"""Stage an audit record in the caller's transaction."""
record = AuditLog(
actor=payload.actor,
source=payload.source,
action=payload.action,
target_type=payload.target_type,
target_id=payload.target_id,
risk_level=payload.risk_level,
request_payload=_dump(payload.request_payload),
response_payload=_dump(payload.response_payload),
status=payload.status,
request_id=payload.request_id or get_request_id(),
)
self.db.add(record)
self.db.flush()
return record
def log(self, payload: AuditLogCreate) -> AuditLog:
"""Persist an audit record as a standalone transaction."""
record = self.record(payload)
self.db.commit()
self.db.refresh(record)
return record
def list_logs(self, limit: int = 100) -> list[AuditLog]:
stmt = select(AuditLog).order_by(AuditLog.id.desc()).limit(bounded_limit(limit))
return list(self.db.execute(stmt).scalars())