feat: restore source parity and harden agent runtime
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
电子签章模块
|
||||
"""
|
||||
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
电子签章API接口
|
||||
"""
|
||||
from typing import Optional, List
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
from app.config import settings
|
||||
from app.base_schema import PaginatedResponse, ResponseModel
|
||||
|
||||
from online_dev.electronic_seal.schema import (
|
||||
ElectronicSealCreate, ElectronicSealUpdate, ElectronicSealOut, SealUsageLogOut
|
||||
)
|
||||
from online_dev.electronic_seal.service import ElectronicSealService, SealUsageLogService
|
||||
|
||||
router = APIRouter(prefix="/electronic-seal", tags=["电子签章"])
|
||||
|
||||
|
||||
@router.get("", response_model=PaginatedResponse[ElectronicSealOut], summary="获取签章列表")
|
||||
async def get_seal_list(
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=settings.PAGE_SIZE, ge=1, le=settings.PAGE_MAX_SIZE, alias="pageSize"),
|
||||
seal_type: Optional[str] = Query(default=None, alias="sealType"),
|
||||
status: Optional[str] = Query(default=None),
|
||||
keyword: Optional[str] = Query(default=None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取签章列表"""
|
||||
items, total = await ElectronicSealService.get_list(
|
||||
db,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
seal_type=seal_type,
|
||||
status=status,
|
||||
keyword=keyword,
|
||||
)
|
||||
return PaginatedResponse(items=items, total=total)
|
||||
|
||||
|
||||
@router.get("/available", response_model=List[ElectronicSealOut], summary="获取可用签章")
|
||||
async def get_available_seals(
|
||||
template_id: Optional[str] = Query(default=None, alias="templateId"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
request: Request = None,
|
||||
):
|
||||
"""获取当前用户可用的签章"""
|
||||
# 从请求中获取用户信息
|
||||
user_id = getattr(request.state, "user_id", "") if request else ""
|
||||
dept_id = getattr(request.state, "dept_id", None) if request else None
|
||||
role_ids = getattr(request.state, "role_ids", None) if request else None
|
||||
|
||||
seals = await ElectronicSealService.get_available_seals(
|
||||
db,
|
||||
user_id=user_id,
|
||||
dept_id=dept_id,
|
||||
role_ids=role_ids,
|
||||
template_id=template_id,
|
||||
)
|
||||
return seals
|
||||
|
||||
|
||||
@router.post("", response_model=ElectronicSealOut, summary="创建签章")
|
||||
async def create_seal(
|
||||
data: ElectronicSealCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""创建电子签章"""
|
||||
seal = await ElectronicSealService.create(db, data)
|
||||
return seal
|
||||
|
||||
|
||||
@router.get("/{seal_id}", response_model=ElectronicSealOut, summary="获取签章详情")
|
||||
async def get_seal(
|
||||
seal_id: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取签章详情"""
|
||||
seal = await ElectronicSealService.get_by_id(db, seal_id)
|
||||
if not seal:
|
||||
raise HTTPException(status_code=404, detail="签章不存在")
|
||||
return seal
|
||||
|
||||
|
||||
@router.put("/{seal_id}", response_model=ElectronicSealOut, summary="更新签章")
|
||||
async def update_seal(
|
||||
seal_id: str,
|
||||
data: ElectronicSealUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""更新签章"""
|
||||
seal = await ElectronicSealService.update(db, seal_id, data)
|
||||
if not seal:
|
||||
raise HTTPException(status_code=404, detail="签章不存在")
|
||||
return seal
|
||||
|
||||
|
||||
@router.delete("/{seal_id}", response_model=ResponseModel, summary="删除签章")
|
||||
async def delete_seal(
|
||||
seal_id: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""删除签章"""
|
||||
seal = await ElectronicSealService.get_by_id(db, seal_id)
|
||||
if not seal:
|
||||
raise HTTPException(status_code=404, detail="签章不存在")
|
||||
|
||||
await ElectronicSealService.delete(db, seal_id)
|
||||
return ResponseModel(message="删除成功")
|
||||
|
||||
|
||||
@router.post("/{seal_id}/enable", response_model=ElectronicSealOut, summary="启用签章")
|
||||
async def enable_seal(
|
||||
seal_id: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""启用签章"""
|
||||
seal = await ElectronicSealService.get_by_id(db, seal_id)
|
||||
if not seal:
|
||||
raise HTTPException(status_code=404, detail="签章不存在")
|
||||
|
||||
seal = await ElectronicSealService.update(
|
||||
db, seal_id, ElectronicSealUpdate(status="active")
|
||||
)
|
||||
return seal
|
||||
|
||||
|
||||
@router.post("/{seal_id}/disable", response_model=ElectronicSealOut, summary="禁用签章")
|
||||
async def disable_seal(
|
||||
seal_id: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""禁用签章"""
|
||||
seal = await ElectronicSealService.get_by_id(db, seal_id)
|
||||
if not seal:
|
||||
raise HTTPException(status_code=404, detail="签章不存在")
|
||||
|
||||
seal = await ElectronicSealService.update(
|
||||
db, seal_id, ElectronicSealUpdate(status="disabled")
|
||||
)
|
||||
return seal
|
||||
|
||||
|
||||
@router.get("/{seal_id}/logs", response_model=PaginatedResponse[SealUsageLogOut], summary="获取签章使用记录")
|
||||
async def get_seal_usage_logs(
|
||||
seal_id: str,
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=settings.PAGE_SIZE, ge=1, le=settings.PAGE_MAX_SIZE, alias="pageSize"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取签章的使用记录"""
|
||||
seal = await ElectronicSealService.get_by_id(db, seal_id)
|
||||
if not seal:
|
||||
raise HTTPException(status_code=404, detail="签章不存在")
|
||||
|
||||
items, total = await SealUsageLogService.get_logs_by_seal(
|
||||
db, seal_id, page=page, page_size=page_size
|
||||
)
|
||||
return PaginatedResponse(items=items, total=total)
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
电子签章数据模型
|
||||
"""
|
||||
from sqlalchemy import Column, String, Text, Integer, Boolean, Index, JSON
|
||||
|
||||
from app.base_model import BaseModel
|
||||
|
||||
|
||||
class ElectronicSeal(BaseModel):
|
||||
"""电子签章配置"""
|
||||
__tablename__ = "electronic_seal"
|
||||
|
||||
# 基础信息
|
||||
name = Column(String(100), nullable=False, comment="签章名称")
|
||||
seal_type = Column(String(20), nullable=False, comment="类型: company/department/personal/contract/finance")
|
||||
description = Column(Text, default="", comment="描述")
|
||||
|
||||
# 签章图片
|
||||
seal_image_id = Column(String(36), nullable=False, comment="签章图片文件ID")
|
||||
|
||||
# 使用权限
|
||||
owner_type = Column(String(20), default="all", comment="所有者类型: all/dept/role/user")
|
||||
owner_ids = Column(JSON, nullable=True, comment="所有者ID列表")
|
||||
|
||||
# 使用范围
|
||||
scope = Column(String(20), default="all", comment="使用范围: all/specific")
|
||||
allowed_template_ids = Column(JSON, nullable=True, comment="允许使用的模板ID列表")
|
||||
|
||||
# 签章属性
|
||||
width = Column(Integer, default=120, comment="宽度(像素)")
|
||||
height = Column(Integer, default=120, comment="高度(像素)")
|
||||
|
||||
# 状态
|
||||
status = Column(String(20), default="active", comment="状态: active/disabled")
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_electronic_seal_seal_type", "seal_type"),
|
||||
Index("ix_electronic_seal_status", "status"),
|
||||
)
|
||||
|
||||
|
||||
class SealUsageLog(BaseModel):
|
||||
"""签章使用记录"""
|
||||
__tablename__ = "seal_usage_log"
|
||||
|
||||
# 关联签章
|
||||
seal_id = Column(String(36), nullable=False, index=True, comment="签章ID")
|
||||
seal_name = Column(String(100), nullable=True, comment="签章名称")
|
||||
|
||||
# 关联文档
|
||||
document_id = Column(String(36), nullable=False, index=True, comment="文档ID")
|
||||
document_name = Column(String(200), nullable=True, comment="文档名称")
|
||||
|
||||
# 使用信息
|
||||
user_id = Column(String(36), nullable=False, comment="使用人ID")
|
||||
user_name = Column(String(100), nullable=True, comment="使用人姓名")
|
||||
|
||||
# 位置信息
|
||||
position_x = Column(Integer, default=0, comment="X坐标")
|
||||
position_y = Column(Integer, default=0, comment="Y坐标")
|
||||
page_number = Column(Integer, default=1, comment="页码")
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_seal_usage_log_seal_id", "seal_id"),
|
||||
Index("ix_seal_usage_log_document_id", "document_id"),
|
||||
)
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
电子签章Schema定义
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
class ElectronicSealBase(BaseModel):
|
||||
"""电子签章基础Schema"""
|
||||
name: str = Field(..., min_length=1, max_length=100, description="签章名称")
|
||||
seal_type: str = Field(..., description="类型: company/department/personal/contract/finance")
|
||||
description: Optional[str] = Field(default="", description="描述")
|
||||
|
||||
seal_image_id: str = Field(..., description="签章图片文件ID")
|
||||
|
||||
owner_type: str = Field(default="all", description="所有者类型: all/dept/role/user")
|
||||
owner_ids: Optional[List[str]] = Field(default=None, description="所有者ID列表")
|
||||
|
||||
scope: str = Field(default="all", description="使用范围: all/specific")
|
||||
allowed_template_ids: Optional[List[str]] = Field(default=None, description="允许使用的模板ID列表")
|
||||
|
||||
width: int = Field(default=120, ge=20, le=500, description="宽度(像素)")
|
||||
height: int = Field(default=120, ge=20, le=500, description="高度(像素)")
|
||||
|
||||
|
||||
class ElectronicSealCreate(ElectronicSealBase):
|
||||
"""创建电子签章"""
|
||||
pass
|
||||
|
||||
|
||||
class ElectronicSealUpdate(BaseModel):
|
||||
"""更新电子签章"""
|
||||
name: Optional[str] = Field(default=None, max_length=100, description="签章名称")
|
||||
description: Optional[str] = Field(default=None, description="描述")
|
||||
|
||||
seal_image_id: Optional[str] = Field(default=None, description="签章图片文件ID")
|
||||
|
||||
owner_type: Optional[str] = Field(default=None, description="所有者类型")
|
||||
owner_ids: Optional[List[str]] = Field(default=None, description="所有者ID列表")
|
||||
|
||||
scope: Optional[str] = Field(default=None, description="使用范围")
|
||||
allowed_template_ids: Optional[List[str]] = Field(default=None, description="允许使用的模板ID列表")
|
||||
|
||||
width: Optional[int] = Field(default=None, description="宽度")
|
||||
height: Optional[int] = Field(default=None, description="高度")
|
||||
|
||||
status: Optional[str] = Field(default=None, description="状态")
|
||||
|
||||
|
||||
class ElectronicSealOut(ElectronicSealBase):
|
||||
"""电子签章输出"""
|
||||
id: str
|
||||
status: str = "active"
|
||||
seal_image_url: Optional[str] = None
|
||||
sys_create_datetime: Optional[datetime] = None
|
||||
sys_update_datetime: Optional[datetime] = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class SealUsageLogOut(BaseModel):
|
||||
"""签章使用记录输出"""
|
||||
id: str
|
||||
seal_id: str
|
||||
seal_name: Optional[str] = None
|
||||
document_id: str
|
||||
document_name: Optional[str] = None
|
||||
user_id: str
|
||||
user_name: Optional[str] = None
|
||||
position_x: int = 0
|
||||
position_y: int = 0
|
||||
page_number: int = 1
|
||||
sys_create_datetime: Optional[datetime] = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
@@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
电子签章服务层
|
||||
"""
|
||||
from typing import Optional, List, Tuple
|
||||
|
||||
from sqlalchemy import select, func, and_, or_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.base_service import BaseService
|
||||
from online_dev.electronic_seal.model import ElectronicSeal, SealUsageLog
|
||||
from online_dev.electronic_seal.schema import ElectronicSealCreate, ElectronicSealUpdate
|
||||
|
||||
|
||||
class ElectronicSealService(BaseService[ElectronicSeal, ElectronicSealCreate, ElectronicSealUpdate]):
|
||||
"""电子签章服务"""
|
||||
|
||||
model = ElectronicSeal
|
||||
|
||||
@classmethod
|
||||
async def get_list(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
seal_type: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
keyword: Optional[str] = None,
|
||||
) -> Tuple[List[ElectronicSeal], int]:
|
||||
"""获取签章列表"""
|
||||
conditions = [cls.model.is_deleted == False]
|
||||
|
||||
if seal_type:
|
||||
conditions.append(cls.model.seal_type == seal_type)
|
||||
if status:
|
||||
conditions.append(cls.model.status == status)
|
||||
if keyword:
|
||||
conditions.append(
|
||||
or_(
|
||||
cls.model.name.ilike(f"%{keyword}%"),
|
||||
cls.model.description.ilike(f"%{keyword}%"),
|
||||
)
|
||||
)
|
||||
|
||||
# 查询总数
|
||||
count_stmt = select(func.count()).select_from(cls.model).where(and_(*conditions))
|
||||
total = (await db.execute(count_stmt)).scalar() or 0
|
||||
|
||||
# 查询列表
|
||||
stmt = (
|
||||
select(cls.model)
|
||||
.where(and_(*conditions))
|
||||
.order_by(cls.model.sys_create_datetime.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
items = list(result.scalars().all())
|
||||
|
||||
return items, total
|
||||
|
||||
@classmethod
|
||||
async def get_available_seals(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
user_id: str,
|
||||
dept_id: Optional[str] = None,
|
||||
role_ids: Optional[List[str]] = None,
|
||||
template_id: Optional[str] = None,
|
||||
) -> List[ElectronicSeal]:
|
||||
"""获取用户可用的签章"""
|
||||
conditions = [
|
||||
cls.model.is_deleted == False,
|
||||
cls.model.status == "active",
|
||||
]
|
||||
|
||||
stmt = select(cls.model).where(and_(*conditions))
|
||||
result = await db.execute(stmt)
|
||||
all_seals = list(result.scalars().all())
|
||||
|
||||
available_seals = []
|
||||
for seal in all_seals:
|
||||
# 检查所有者权限
|
||||
if seal.owner_type == "all":
|
||||
pass
|
||||
elif seal.owner_type == "user":
|
||||
if not seal.owner_ids or user_id not in seal.owner_ids:
|
||||
continue
|
||||
elif seal.owner_type == "dept":
|
||||
if not seal.owner_ids or not dept_id or dept_id not in seal.owner_ids:
|
||||
continue
|
||||
elif seal.owner_type == "role":
|
||||
if not seal.owner_ids or not role_ids:
|
||||
continue
|
||||
if not any(r in seal.owner_ids for r in role_ids):
|
||||
continue
|
||||
|
||||
# 检查模板范围
|
||||
if seal.scope == "specific" and template_id:
|
||||
if not seal.allowed_template_ids or template_id not in seal.allowed_template_ids:
|
||||
continue
|
||||
|
||||
available_seals.append(seal)
|
||||
|
||||
return available_seals
|
||||
|
||||
|
||||
class SealUsageLogService(BaseService[SealUsageLog, None, None]):
|
||||
"""签章使用记录服务"""
|
||||
|
||||
model = SealUsageLog
|
||||
|
||||
@classmethod
|
||||
async def log_usage(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
seal_id: str,
|
||||
seal_name: str,
|
||||
document_id: str,
|
||||
document_name: str,
|
||||
user_id: str,
|
||||
user_name: str,
|
||||
position_x: int = 0,
|
||||
position_y: int = 0,
|
||||
page_number: int = 1,
|
||||
) -> SealUsageLog:
|
||||
"""记录签章使用"""
|
||||
log = SealUsageLog(
|
||||
seal_id=seal_id,
|
||||
seal_name=seal_name,
|
||||
document_id=document_id,
|
||||
document_name=document_name,
|
||||
user_id=user_id,
|
||||
user_name=user_name,
|
||||
position_x=position_x,
|
||||
position_y=position_y,
|
||||
page_number=page_number,
|
||||
)
|
||||
|
||||
db.add(log)
|
||||
await db.commit()
|
||||
await db.refresh(log)
|
||||
return log
|
||||
|
||||
@classmethod
|
||||
async def get_logs_by_seal(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
seal_id: str,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> Tuple[List[SealUsageLog], int]:
|
||||
"""获取签章的使用记录"""
|
||||
conditions = [
|
||||
cls.model.is_deleted == False,
|
||||
cls.model.seal_id == seal_id,
|
||||
]
|
||||
|
||||
# 查询总数
|
||||
count_stmt = select(func.count()).select_from(cls.model).where(and_(*conditions))
|
||||
total = (await db.execute(count_stmt)).scalar() or 0
|
||||
|
||||
# 查询列表
|
||||
stmt = (
|
||||
select(cls.model)
|
||||
.where(and_(*conditions))
|
||||
.order_by(cls.model.sys_create_datetime.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
items = list(result.scalars().all())
|
||||
|
||||
return items, total
|
||||
|
||||
@classmethod
|
||||
async def get_logs_by_document(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
document_id: str,
|
||||
) -> List[SealUsageLog]:
|
||||
"""获取文档的签章记录"""
|
||||
stmt = (
|
||||
select(cls.model)
|
||||
.where(
|
||||
and_(
|
||||
cls.model.is_deleted == False,
|
||||
cls.model.document_id == document_id,
|
||||
)
|
||||
)
|
||||
.order_by(cls.model.sys_create_datetime.desc())
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
return list(result.scalars().all())
|
||||
Reference in New Issue
Block a user