Files
freedakgmail 33b4c734aa feat: 完善前后端核心功能模块
后端:
- 新增认证(auth)、任务(tasks)、映射(mappings)、对账(reconciliation)、异常(exceptions)、导出(exports) API
- 新增核心模块: database, security, permissions, tenant, exceptions, error_handlers
- 新增数据模型: user, company, reconciliation_task, field_mapping, uploaded_file 等
- 新增服务层: ai_recognizer, file_parser, file_storage, mapping, reconciliation 等
- 添加数据库迁移脚本

前端:
- 新增登录页面和仪表盘页面
- 新增任务列表、任务详情、字段映射页面
- 新增异常处理页面和规则设置页面
- 新增 API 代理路由 /api/[...path]
- 新增 UI 组件库 (button, card, dialog, input, table 等)
- 新增 auth 组件 (ProtectedRoute, PermissionGate)
- 新增 layout 组件 (Header, Sidebar)
- 新增 mapping 组件 (FieldMappingTable, AISuggestionPanel)
- 新增 API 客户端和 hooks (useAsync, useToast, usePermission 等)
- 新增状态管理 (auth-store, company-store, ui-store)
- 集成 Tailwind CSS 和 shadcn/ui 组件库

其他:
- 添加 Alembic 数据库迁移配置
- 添加初始化示例数据脚本
- 更新项目文档
2026-07-07 09:04:47 +08:00

180 lines
5.2 KiB
Python

from datetime import datetime
from functools import wraps
from typing import Any, Callable, Optional
from fastapi import Request
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.audit_log import AuditAction, AuditLog
from app.schemas.audit_log import AuditLogCreate, AuditLogQuery
class AuditService:
"""审计日志服务"""
@staticmethod
async def log_action(
db: AsyncSession,
company_id: int,
action: str,
resource_type: str,
user_id: Optional[int] = None,
resource_id: Optional[str] = None,
details: Optional[dict[str, Any]] = None,
ip_address: Optional[str] = None,
user_agent: Optional[str] = None,
) -> AuditLog:
"""
记录审计日志
Args:
db: 数据库会话
company_id: 企业 ID
action: 操作类型
resource_type: 资源类型
user_id: 用户 ID
resource_id: 资源 ID
details: 操作详情
ip_address: IP 地址
user_agent: User-Agent
Returns:
创建的审计日志对象
"""
log_data = AuditLogCreate(
company_id=company_id,
user_id=user_id,
action=action,
resource_type=resource_type,
resource_id=resource_id,
details=details,
ip_address=ip_address,
user_agent=user_agent,
)
audit_log = AuditLog(**log_data.model_dump())
db.add(audit_log)
await db.commit()
await db.refresh(audit_log)
return audit_log
@staticmethod
async def get_logs(
db: AsyncSession,
query_params: AuditLogQuery,
) -> list[AuditLog]:
"""
查询审计日志
Args:
db: 数据库会话
query_params: 查询参数
Returns:
审计日志列表
"""
stmt = select(AuditLog)
# 添加过滤条件
if query_params.company_id:
stmt = stmt.where(AuditLog.company_id == query_params.company_id)
if query_params.user_id:
stmt = stmt.where(AuditLog.user_id == query_params.user_id)
if query_params.action:
stmt = stmt.where(AuditLog.action == query_params.action)
if query_params.resource_type:
stmt = stmt.where(AuditLog.resource_type == query_params.resource_type)
if query_params.resource_id:
stmt = stmt.where(AuditLog.resource_id == query_params.resource_id)
if query_params.start_date:
stmt = stmt.where(AuditLog.created_at >= query_params.start_date)
if query_params.end_date:
stmt = stmt.where(AuditLog.created_at <= query_params.end_date)
# 排序和分页
stmt = stmt.order_by(AuditLog.created_at.desc())
stmt = stmt.offset(query_params.skip).limit(query_params.limit)
result = await db.execute(stmt)
return list(result.scalars().all())
@staticmethod
async def log_from_request(
db: AsyncSession,
request: Request,
company_id: int,
action: str,
resource_type: str,
user_id: Optional[int] = None,
resource_id: Optional[str] = None,
details: Optional[dict[str, Any]] = None,
) -> AuditLog:
"""
从 Request 对象中提取信息并记录审计日志
Args:
db: 数据库会话
request: FastAPI 请求对象
company_id: 企业 ID
action: 操作类型
resource_type: 资源类型
user_id: 用户 ID
resource_id: 资源 ID
details: 操作详情
Returns:
创建的审计日志对象
"""
# 提取 IP 地址
ip_address = request.client.host if request.client else None
# 提取 User-Agent
user_agent = request.headers.get("user-agent")
return await AuditService.log_action(
db=db,
company_id=company_id,
action=action,
resource_type=resource_type,
user_id=user_id,
resource_id=resource_id,
details=details,
ip_address=ip_address,
user_agent=user_agent,
)
# 单例实例
audit_service = AuditService()
def audit_log(
action: str,
resource_type: str,
get_resource_id: Optional[Callable] = None,
):
"""
审计日志装饰器
Args:
action: 操作类型
resource_type: 资源类型
get_resource_id: 从函数返回值中获取 resource_id 的函数
Example:
@audit_log(action=AuditAction.CREATE, resource_type="company")
async def create_company(...):
...
"""
def decorator(func: Callable):
@wraps(func)
async def wrapper(*args, **kwargs):
result = await func(*args, **kwargs)
# TODO: 在实现认证后,从上下文获取 company_id 和 user_id
# 目前暂时跳过实际记录
return result
return wrapper
return decorator