| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713 |
- import sys
- import os
- import logging
- import httpx
- import urllib.parse
- import asyncio
- from datetime import datetime, timezone
- from typing import Optional, List, Any, Union
- from fastapi import APIRouter, Depends, HTTPException, Request, Response, BackgroundTasks
- from fastapi.responses import HTMLResponse
- from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
- from app.sample.schemas.sample_schemas import BatchEnterRequest, BatchDeleteRequest, ConvertRequest, DocumentAdd, UploadUrlRequest
- from app.services.sample_service import SampleService
- from app.services.jwt_token import verify_token
- from app.schemas.base import ApiResponse
- from app.base import get_mineru_manager
- from app.services.task_service import task_service
- # 获取logger
- logger = logging.getLogger(__name__)
- router = APIRouter(prefix="/sample", tags=["样本中心"])
- security = HTTPBearer()
- security_optional = HTTPBearer(auto_error=False)
- @router.get("/tasks")
- async def get_tasks(type: str, credentials: HTTPAuthorizationCredentials = Depends(security)):
- """获取任务列表"""
- try:
- payload = verify_token(credentials.credentials)
- if not payload:
- return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- tasks = await task_service.get_tasks(type)
- return ApiResponse(code=0, message="成功", data=tasks, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- except Exception as e:
- logger.exception("获取任务列表失败")
- return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- # --- 文档管理中心 API ---
- @router.post("/documents/upload-url")
- async def get_upload_url(req: UploadUrlRequest, credentials: HTTPAuthorizationCredentials = Depends(security)):
- """获取 MinIO 预签名上传 URL"""
- try:
- payload = verify_token(credentials.credentials)
- if not payload:
- return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- sample_service = SampleService()
- success, message, data = await sample_service.get_upload_url(req.filename, req.content_type)
-
- if success:
- return ApiResponse(code=0, message=message, data=data, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- else:
- return ApiResponse(code=500, message=message, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- except Exception as e:
- logger.exception("获取上传链接失败")
- return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- @router.get("/documents/proxy-view")
- async def proxy_view(url: str, token: Optional[str] = None, credentials: Optional[HTTPAuthorizationCredentials] = Depends(security_optional)):
- """抓取外部文档内容并返回,支持 HTML 和 PDF 等二进制文件。支持从 Header 或 Query 参数获取 Token。"""
- try:
- # 确保 URL 已解码
- url = urllib.parse.unquote(url)
-
- # 优先从 Header 获取,如果没有则从参数获取
- actual_token = None
- if credentials:
- actual_token = credentials.credentials
- elif token:
- actual_token = token
-
- if not actual_token:
- return ApiResponse(code=401, message="未提供认证令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- payload = verify_token(actual_token)
- if not payload or not payload.get("is_superuser"):
- return ApiResponse(code=403, message="权限不足", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- # 增加超时时间,支持大文件下载
- async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
- headers = {
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
- }
- response = await client.get(url, headers=headers)
- response.raise_for_status()
-
- content_type = response.headers.get("content-type", "").lower()
-
- # 如果是 PDF 或其他二进制文件
- binary_extensions = {
- ".pdf": "application/pdf",
- ".png": "image/png",
- ".jpg": "image/jpeg",
- ".jpeg": "image/jpeg",
- ".gif": "image/gif",
- ".doc": "application/msword",
- ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
- ".xls": "application/vnd.ms-excel",
- ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
- ".ppt": "application/vnd.ms-powerpoint",
- ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
- ".zip": "application/zip",
- ".rar": "application/x-rar-compressed",
- ".7z": "application/x-7z-compressed"
- }
-
- is_binary = "application/pdf" in content_type or \
- "application/vnd." in content_type or \
- "application/msword" in content_type or \
- "application/octet-stream" in content_type or \
- any(ext in url.lower() for ext in binary_extensions.keys())
-
- if is_binary:
- # 尝试根据扩展名修正 media_type
- for ext, m_type in binary_extensions.items():
- if ext in url.lower():
- content_type = m_type
- break
-
- return Response(
- content=response.content,
- media_type=content_type,
- headers={"Content-Disposition": "inline"}
- )
-
- # 默认处理为 HTML
- try:
- # 尝试多种编码解码 content
- data = response.content
- content = None
- encodings = ['utf-8', 'gbk', 'utf-8-sig', 'gb18030']
-
- for enc in encodings:
- try:
- content = data.decode(enc)
- break
- except UnicodeDecodeError:
- continue
-
- if content is None:
- content = data.decode('utf-8', errors='ignore')
-
- # 简单的注入一些基础样式,确保内容在 iframe 中显示良好
- base_style = """
- <style>
- body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; padding: 20px; line-height: 1.6; color: #333; }
- img { max-width: 100%; height: auto; }
- </style>
- """
- if "</head>" in content:
- content = content.replace("</head>", f"{base_style}</head>")
- else:
- content = f"{base_style}{content}"
-
- return HTMLResponse(content=content)
- except Exception:
- # 如果文本解析失败,返回原始字节
- return Response(content=response.content, media_type=content_type)
-
- except Exception as e:
- error_msg = f"<html><body><h3>无法加载内容</h3><p>错误原因: {str(e)}</p><p>URL: {url}</p></body></html>"
- return HTMLResponse(content=error_msg, status_code=500)
- @router.get("/documents/download")
- async def download_document(url: str, filename: Optional[str] = None, token: Optional[str] = None, credentials: Optional[HTTPAuthorizationCredentials] = Depends(security_optional)):
- """代理下载云端文件,支持从 MinIO 等外部地址下载"""
- try:
- if not url:
- return ApiResponse(code=400, message="缺少URL参数", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- # 确保 URL 已解码
- url = urllib.parse.unquote(url)
-
- # 优先从 Header 获取,如果没有则从参数获取
- actual_token = None
- if credentials:
- actual_token = credentials.credentials
- elif token:
- actual_token = token
-
- if not actual_token:
- return ApiResponse(code=401, message="未提供认证令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- payload = verify_token(actual_token)
- if not payload or not payload.get("is_superuser"):
- return ApiResponse(code=403, message="权限不足", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- # 增加超时时间,支持大文件下载
- async with httpx.AsyncClient(timeout=60.0, follow_redirects=True) as client:
- headers = {
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
- }
- response = await client.get(url, headers=headers)
- response.raise_for_status()
-
- content_type = response.headers.get("content-type", "application/octet-stream")
-
- # 设置下载文件名
- headers = {
- "Content-Disposition": f"attachment; filename*=UTF-8''{urllib.parse.quote(filename or 'downloaded_file')}" if filename else "attachment",
- "Content-Type": content_type
- }
-
- return Response(
- content=response.content,
- media_type=content_type,
- headers=headers
- )
-
- except Exception as e:
- logger.exception(f"文件下载失败, url={url}")
- return ApiResponse(code=500, message=f"下载失败: {str(e)} (URL: {url})", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- @router.post("/documents/batch-enter")
- async def batch_enter_knowledge_base(req: BatchEnterRequest, credentials: HTTPAuthorizationCredentials = Depends(security)):
- """批量将文档加入知识库"""
- try:
- payload = verify_token(credentials.credentials)
- if not payload or not payload.get("is_superuser"):
- return ApiResponse(code=403, message="权限不足", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- username = payload.get("username", "admin")
- sample_service = SampleService()
-
- affected_rows, message = await sample_service.batch_enter_knowledge_base(req.ids, username)
-
- return ApiResponse(code=0, message=message, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- except Exception as e:
- logger.exception("批量操作失败")
- return ApiResponse(code=500, message=f"批量操作失败: {str(e)}", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- @router.post("/documents/batch-delete")
- async def batch_delete_documents(req: BatchDeleteRequest, credentials: HTTPAuthorizationCredentials = Depends(security)):
- """批量删除文档"""
- try:
- payload = verify_token(credentials.credentials)
- if not payload or not payload.get("is_superuser"):
- return ApiResponse(code=403, message="权限不足", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- sample_service = SampleService()
- affected_rows, message = await sample_service.batch_delete_documents(req.ids)
-
- return ApiResponse(
- code=0,
- message=message,
- timestamp=datetime.now(timezone.utc).isoformat()
- ).model_dump()
- except Exception as e:
- logger.exception("批量删除失败")
- return ApiResponse(code=500, message=f"批量删除失败: {str(e)}", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- async def simulate_conversion(doc_id: str):
- """模拟文档转换过程 (仅保留状态切换)"""
- sample_service = SampleService()
-
- try:
- # 1. 模拟开始
- await sample_service.update_conversion_status(doc_id, status=1)
- await asyncio.sleep(2)
-
- # 2. 模拟完成
- converted_file_name = f"http://192.168.91.15:19000/aidata/sampledata/converted/simulated/{doc_id}.pdf"
- json_url = f"http://192.168.91.15:19000/aidata/sampledata/converted/simulated/{doc_id}.json"
- await sample_service.update_conversion_status(doc_id, status=2,
- md_url=converted_file_name,
- json_url=json_url)
-
- except Exception as e:
- logger.exception("模拟转换出错")
- await sample_service.update_conversion_status(doc_id, status=3,
- error_message=str(e))
- @router.post("/documents/convert")
- async def convert_document(req: ConvertRequest, background_tasks: BackgroundTasks, credentials: HTTPAuthorizationCredentials = Depends(security)):
- """启动文档转换 (使用 MinerUManager 在后台执行)"""
- try:
- payload = verify_token(credentials.credentials)
- if not payload or not payload.get("is_superuser"):
- return ApiResponse(code=403, message="权限不足", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- doc_id = str(req.id)
- sample_service = SampleService()
-
- # 1. 获取文档详情以取得 title 和 file_url
- doc = await sample_service.get_document_detail(doc_id)
- if not doc:
- return ApiResponse(code=404, message="文档不存在", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- title = doc.get("title")
- file_url = doc.get("file_url")
-
- # 如果主表没有 file_url,尝试从子表获取的逻辑已在 MinerUManager 或 service 中处理?
- # 其实 MinerUManager.process_document 需要 file_url。
- # 这里的 doc 是 detail,已经包含了子表关联。
-
- if not file_url:
- return ApiResponse(code=400, message="文档缺少文件链接,无法转换", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- # 2. 立即将状态更新为“转换中”,避免前端轮询延迟
- await sample_service.update_conversion_status(doc_id, status=1)
- # 3. 启动后台任务
- manager = get_mineru_manager()
- background_tasks.add_task(manager.process_document, doc_id, title, file_url)
-
- return ApiResponse(
- code=0,
- message="转换任务已在后台启动",
- timestamp=datetime.now(timezone.utc).isoformat()
- ).model_dump()
- except Exception as e:
- logger.exception("启动转换失败")
- return ApiResponse(code=500, message=f"启动转换失败: {str(e)}", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- @router.post("/documents/add")
- async def add_document(doc: DocumentAdd, credentials: HTTPAuthorizationCredentials = Depends(security)):
- """添加新文档 (同步主表和子表)"""
- try:
- payload = verify_token(credentials.credentials)
- if not payload:
- return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- user_id = payload.get("username", "admin")
- sample_service = SampleService()
-
- # 将 DocumentAdd 对象转换为字典
- doc_data = {
- 'title': doc.title,
- 'note': doc.note,
- 'table_type': doc.table_type,
- 'primary_category_id': doc.primary_category_id,
- 'secondary_category_id': doc.secondary_category_id,
- 'year': doc.year,
- 'file_url': doc.file_url,
- 'json_url': doc.json_url,
- 'file_extension': doc.file_extension,
- 'standard_no': doc.standard_no,
- 'issuing_authority': doc.issuing_authority,
- 'release_date': doc.release_date,
- 'document_type': doc.document_type,
- 'professional_field': doc.professional_field,
- 'validity': doc.validity,
- 'project_name': doc.project_name,
- 'project_section': doc.project_section,
- 'compilation_basis': doc.compilation_basis,
- 'plan_summary': doc.plan_summary,
- 'plan_category': doc.plan_category,
- 'level_1_classification': doc.level_1_classification,
- 'level_2_classification': doc.level_2_classification,
- 'level_3_classification': doc.level_3_classification,
- 'level_4_classification': doc.level_4_classification
- }
-
- success, message, doc_id = await sample_service.add_document(doc_data, user_id)
-
- if success:
- return ApiResponse(code=0, message=message, data={"id": doc_id}, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- else:
- return ApiResponse(code=500, message=message, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- except Exception as e:
- logger.exception("添加文档失败")
- return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- @router.get("/documents/detail/{doc_id}")
- async def get_document_detail(doc_id: str, credentials: HTTPAuthorizationCredentials = Depends(security)):
- """获取文档详情 (关联查询子表)"""
- logger.info(f"正在获取文档详情: {doc_id}")
- try:
- payload = verify_token(credentials.credentials)
- if not payload:
- return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- sample_service = SampleService()
- doc = await sample_service.get_document_detail(doc_id)
-
- if not doc:
- logger.warning(f"文档不存在: {doc_id}")
- return ApiResponse(code=404, message="文档不存在", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- logger.info(f"找到文档数据: {doc.get('title')}")
- return ApiResponse(code=0, message="获取详情成功", data=doc, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- except Exception as e:
- logger.exception("获取文档详情失败")
- return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- @router.get("/documents/list")
- async def get_document_list(
- whether_to_enter: Optional[int] = None,
- keyword: Optional[str] = None,
- table_type: Optional[str] = None,
- plan_category: Optional[str] = None,
- level_2_classification: Optional[str] = None,
- level_3_classification: Optional[str] = None,
- level_4_classification: Optional[str] = None,
- page: int = 1,
- size: int = 50,
- credentials: HTTPAuthorizationCredentials = Depends(security)
- ):
- """获取文档列表 (从主表查询)"""
- try:
- payload = verify_token(credentials.credentials)
- if not payload:
- return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- sample_service = SampleService()
- items, total, all_total, total_entered = await sample_service.get_document_list(
- whether_to_enter=whether_to_enter,
- keyword=keyword,
- table_type=table_type,
- plan_category=plan_category,
- level_2_classification=level_2_classification,
- level_3_classification=level_3_classification,
- level_4_classification=level_4_classification,
- page=page,
- size=size
- )
-
- return ApiResponse(
- code=0,
- message="查询成功",
- data={
- "items": items,
- "total": total,
- "page": page,
- "size": size,
- "all_total": all_total,
- "total_entered": total_entered
- },
- timestamp=datetime.now(timezone.utc).isoformat()
- ).model_dump()
- except Exception as e:
- logger.exception("获取文档列表失败")
- return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- @router.post("/documents/edit")
- async def edit_document(doc: DocumentAdd, credentials: HTTPAuthorizationCredentials = Depends(security)):
- """编辑文档 (同步主表和子表)"""
- try:
- payload = verify_token(credentials.credentials)
- if not payload:
- return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- if not doc.id:
- return ApiResponse(code=400, message="缺少ID参数", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- # 调用 service 层
- sample_service = SampleService()
-
- # 获取更新人ID
- updater_id = payload.get("username", "admin")
-
- # 将 DocumentAdd 对象转换为字典
- doc_data = {
- 'id': doc.id,
- 'title': doc.title,
- 'note': doc.note,
- 'table_type': doc.table_type,
- 'primary_category_id': doc.primary_category_id,
- 'secondary_category_id': doc.secondary_category_id,
- 'year': doc.year,
- 'file_url': doc.file_url,
- 'json_url': doc.json_url,
- 'file_extension': doc.file_extension,
- 'standard_no': doc.standard_no if hasattr(doc, 'standard_no') else None,
- 'issuing_authority': doc.issuing_authority if hasattr(doc, 'issuing_authority') else None,
- 'release_date': doc.release_date if hasattr(doc, 'release_date') else None,
- 'document_type': doc.document_type if hasattr(doc, 'document_type') else None,
- 'professional_field': doc.professional_field if hasattr(doc, 'professional_field') else None,
- 'validity': doc.validity if hasattr(doc, 'validity') else None,
- 'project_name': doc.project_name if hasattr(doc, 'project_name') else None,
- 'project_section': doc.project_section if hasattr(doc, 'project_section') else None,
- 'compilation_basis': doc.compilation_basis if hasattr(doc, 'compilation_basis') else None,
- 'plan_summary': doc.plan_summary if hasattr(doc, 'plan_summary') else None,
- 'plan_category': doc.plan_category if hasattr(doc, 'plan_category') else None,
- 'level_1_classification': doc.level_1_classification if hasattr(doc, 'level_1_classification') else None,
- 'level_2_classification': doc.level_2_classification if hasattr(doc, 'level_2_classification') else None,
- 'level_3_classification': doc.level_3_classification if hasattr(doc, 'level_3_classification') else None,
- 'level_4_classification': doc.level_4_classification if hasattr(doc, 'level_4_classification') else None
- }
-
- success, message = await sample_service.edit_document(doc_data, updater_id)
-
- if success:
- return ApiResponse(code=0, message=message, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- else:
- return ApiResponse(code=500, message=message, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- except Exception as e:
- logger.exception("编辑文档失败")
- return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- @router.post("/documents/enter")
- async def enter_document(data: dict, credentials: HTTPAuthorizationCredentials = Depends(security)):
- """文档入库"""
- try:
- doc_id = data.get("id")
- if not doc_id:
- return ApiResponse(code=400, message="缺少ID", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- payload = verify_token(credentials.credentials)
- username = payload.get("username", "admin") if payload else "admin"
-
- # 调用 service 层
- sample_service = SampleService()
- success, message = await sample_service.enter_document(doc_id, username)
-
- if success:
- return ApiResponse(code=0, message=message, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- else:
- return ApiResponse(code=500, message=message, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- except Exception as e:
- logger.exception("入库失败")
- return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- @router.get("/basic-info/list")
- async def get_basic_info_list(
- type: str,
- page: int = 1,
- size: int = 50,
- keyword: Optional[str] = None,
- title: Optional[str] = None,
- standard_no: Optional[str] = None,
- document_type: Optional[str] = None,
- professional_field: Optional[str] = None,
- validity: Optional[str] = None,
- issuing_authority: Optional[str] = None,
- release_date_start: Optional[str] = None,
- release_date_end: Optional[str] = None,
- plan_category: Optional[str] = None,
- level_1_classification: Optional[str] = None,
- level_2_classification: Optional[str] = None,
- level_3_classification: Optional[str] = None,
- level_4_classification: Optional[str] = None,
- credentials: HTTPAuthorizationCredentials = Depends(security)
- ):
- """获取基本信息列表 (支持多条件检索)"""
- try:
- payload = verify_token(credentials.credentials)
- if not payload:
- return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- sample_service = SampleService()
-
- # 构建过滤条件
- filters = {}
- if title:
- filters['title'] = title
- if standard_no:
- filters['standard_no'] = standard_no
- if document_type:
- filters['document_type'] = document_type
- if professional_field:
- filters['professional_field'] = professional_field
- if validity:
- filters['validity'] = validity
- if issuing_authority:
- filters['issuing_authority'] = issuing_authority
- if release_date_start:
- filters['release_date_start'] = release_date_start
- if release_date_end:
- filters['release_date_end'] = release_date_end
- if plan_category:
- filters['plan_category'] = plan_category
- if level_1_classification:
- filters['level_1_classification'] = level_1_classification
- if level_2_classification:
- filters['level_2_classification'] = level_2_classification
- if level_3_classification:
- filters['level_3_classification'] = level_3_classification
- if level_4_classification:
- filters['level_4_classification'] = level_4_classification
-
- items, total = await sample_service.get_basic_info_list(
- type=type,
- page=page,
- size=size,
- keyword=keyword,
- **filters
- )
-
- return ApiResponse(
- code=0,
- message="查询成功",
- data={"items": items, "total": total, "page": page, "size": size},
- timestamp=datetime.now(timezone.utc).isoformat()
- ).model_dump()
-
- except Exception as e:
- logger.exception("查询基本信息失败")
- return ApiResponse(code=500, message=f"服务器内部错误: {str(e)}", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- @router.post("/basic-info/add")
- async def add_basic_info(type: str, data: dict, credentials: HTTPAuthorizationCredentials = Depends(security)):
- """新增基本信息"""
- try:
- payload = verify_token(credentials.credentials)
- if not payload:
- return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- user_id = payload.get("username", "admin")
- sample_service = SampleService()
- success, message, doc_id = await sample_service.add_basic_info(type, data, user_id)
-
- if success:
- return ApiResponse(code=0, message=message, data={"id": doc_id}, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- else:
- return ApiResponse(code=500, message=message, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- except Exception as e:
- logger.exception("新增基本信息失败")
- return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- @router.post("/basic-info/edit")
- async def edit_basic_info(type: str, id: str, data: dict, credentials: HTTPAuthorizationCredentials = Depends(security)):
- """编辑基本信息"""
- try:
- payload = verify_token(credentials.credentials)
- if not payload:
- return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- sample_service = SampleService()
- success, message = await sample_service.edit_basic_info(type, id, data, payload.get("username", "admin"))
-
- if success:
- return ApiResponse(code=0, message=message, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- else:
- return ApiResponse(code=500, message=message, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- except Exception as e:
- logger.exception("编辑基本信息失败")
- return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- @router.post("/basic-info/delete")
- async def delete_basic_info(type: str, id: str, credentials: HTTPAuthorizationCredentials = Depends(security)):
- """删除基本信息"""
- try:
- payload = verify_token(credentials.credentials)
- if not payload:
- return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- sample_service = SampleService()
- success, message = await sample_service.delete_basic_info(type, id)
-
- if success:
- return ApiResponse(code=0, message=message, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- else:
- return ApiResponse(code=500, message=message, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- except Exception as e:
- logger.exception("删除基本信息失败")
- return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- @router.get("/documents/categories/primary")
- async def get_primary_categories(credentials: HTTPAuthorizationCredentials = Depends(security)):
- """获取所有一级分类(仅保留指定的分类)"""
- try:
- payload = verify_token(credentials.credentials)
- if not payload or not payload.get("is_superuser"):
- return ApiResponse(code=403, message="权限不足", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- # 仅保留用户要求的分类
- default_categories = ["办公制度", "行业标准", "法律法规", "施工方案", "施工图片"]
- categories = [{"id": name, "name": name} for name in default_categories]
- return ApiResponse(code=0, message="获取成功", data=categories, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- except Exception as e:
- return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- @router.get("/documents/categories/secondary")
- async def get_secondary_categories(primaryId: str, credentials: HTTPAuthorizationCredentials = Depends(security)):
- """根据一级分类获取二级分类(仅保留指定的分类)"""
- try:
- payload = verify_token(credentials.credentials)
- if not payload or not payload.get("is_superuser"):
- return ApiResponse(code=403, message="权限不足", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-
- # 针对“办公制度”的预设二级分类,其他分类暂时没有二级分类
- categories = []
- if primaryId == "办公制度":
- secondary_names = ["采购", "报销", "审批"]
- categories = [{"id": name, "name": name} for name in secondary_names]
-
- return ApiResponse(code=0, message="获取成功", data=categories, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- except Exception as e:
- return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
- @router.get("/documents/search")
- async def search_documents(
- keyword: str,
- whether_to_enter: Optional[int] = None,
- table_type: Optional[str] = "basis",
- page: int = 1,
- size: int = 50,
- credentials: HTTPAuthorizationCredentials = Depends(security)
- ):
- """关键词搜索文档,统一调用 get_document_list 以支持组合过滤"""
- return await get_document_list(
- whether_to_enter=whether_to_enter,
- keyword=keyword,
- table_type=table_type,
- page=page,
- size=size,
- credentials=credentials
- )
|