- 在 models 中新增 Version、HtsClass 和 HtsItem 三个模型 - 在 api 中新增 hts 路由和相关接口 - 更新 app.py 和 code.py 以支持海关税率管理功能
1423 lines
61 KiB
Python
1423 lines
61 KiB
Python
# _*_ coding : UTF-8 _*_
|
||
# @Time : 2025/02/13 19:20
|
||
# @UpdateTime : 2025/02/13 19:20
|
||
# @Author : sonder
|
||
# @File : code.py
|
||
# @Software : PyCharm
|
||
# @Comment : 本程序
|
||
import json
|
||
import os
|
||
import re
|
||
import time
|
||
import uuid
|
||
from datetime import datetime
|
||
from typing import Optional
|
||
|
||
import pandas as pd
|
||
from elasticsearch.helpers import async_bulk
|
||
from fastapi import APIRouter, Depends, Path, Request, Query
|
||
from fastapi.responses import JSONResponse, FileResponse
|
||
from tortoise.transactions import in_transaction
|
||
|
||
from annotation.auth import Auth, hasAuth
|
||
from annotation.log import Log
|
||
from config.constant import BusinessType
|
||
from config.env import ElasticSearchConfig
|
||
from controller.login import LoginController
|
||
from exceptions.exception import ServiceException, PermissionException
|
||
from models import File, Code, QueryCode, QueryCodeLog, CodeFeedback, CodeImport, HtsItem
|
||
from schemas.code import GetCodeInfoResponse, GetCodeListResponse, GetQueryCodeParams, QueryCodeResponse, AddCodeParams, \
|
||
GetQueryCodeLogResponse, GetQueryCodeLogDetailResponse, \
|
||
GetCodeLogAllResponse, AddCodeFeedbackParams, GetCodeFeedbackResponse, GetCodeFeedbackListResponse, \
|
||
UpdateCodeFeedbackStatusParams, GetCodeImportListResponse, UpdateCodeImportStatusParams
|
||
from schemas.common import BaseResponse, DeleteListParams
|
||
from utils.log import logger
|
||
from utils.response import Response
|
||
|
||
codeAPI = APIRouter(
|
||
prefix="/code"
|
||
)
|
||
|
||
|
||
@codeAPI.get("/template/{type}", summary="获取上传编码模板")
|
||
@Log(title="获取上传编码模板", business_type=BusinessType.SELECT)
|
||
@Auth(permission_list=["code:btn:uploadTemplate"])
|
||
async def get_upload_template(request: Request, type: str = Path(description="文件类型"),
|
||
current_user=Depends(LoginController.get_current_user)):
|
||
if type not in ["xlsx", "xls", "csv"]:
|
||
raise ServiceException(message="文件类型错误!")
|
||
template_path = os.path.join(os.path.abspath(os.getcwd()), 'assets', 'templates', f'上传模版.{type}')
|
||
media_type = {
|
||
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||
"xls": "application/vnd.ms-excel",
|
||
"csv": "text/csv"
|
||
}.get(type)
|
||
if not os.path.exists(template_path):
|
||
raise ServiceException(message="文件不存在!")
|
||
return FileResponse(
|
||
path=template_path,
|
||
filename=f"上传模版.{type}",
|
||
media_type=media_type
|
||
)
|
||
|
||
|
||
@codeAPI.get("/queryTemplate/{type}", summary="获取查询编码模板")
|
||
@Log(title="获取查询编码模板", business_type=BusinessType.SELECT)
|
||
@Auth(permission_list=["code:btn:queryTemplate"])
|
||
async def get_query_template(request: Request, type: str = Path(description="文件类型"),
|
||
current_user=Depends(LoginController.get_current_user)):
|
||
if type not in ["xlsx", "xls", "csv"]:
|
||
raise ServiceException(message="文件类型错误!")
|
||
template_path = os.path.join(os.path.abspath(os.getcwd()), 'assets', 'templates', f'查询模版.{type}')
|
||
if not os.path.exists(template_path):
|
||
raise ServiceException(message="文件不存在!")
|
||
media_type = {
|
||
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||
"xls": "application/vnd.ms-excel",
|
||
"csv": "text/csv"
|
||
}.get(type)
|
||
return FileResponse(
|
||
path=template_path,
|
||
filename=f"查询模版.{type}",
|
||
media_type=media_type
|
||
)
|
||
|
||
|
||
@codeAPI.post("/add", response_class=JSONResponse, response_model=BaseResponse, summary="添加编码")
|
||
@Log(title="添加编码", business_type=BusinessType.INSERT)
|
||
@Auth(permission_list=["code:btn:add"])
|
||
async def add_code(request: Request, params: AddCodeParams, current_user=Depends(LoginController.get_current_user)):
|
||
params.code = params.code.replace(".", "").replace("/", "").replace("_", "").replace("-", "").replace("?",
|
||
"").replace(
|
||
":", "").replace(":", "").replace("?", "").strip()
|
||
if await CodeImport.get_or_none(code=params.code, del_flag=1):
|
||
return Response.failure(msg="编码已存在")
|
||
else:
|
||
user_id = current_user.get("id")
|
||
code_import = await CodeImport.create(
|
||
code=params.code,
|
||
description=params.description,
|
||
status=3,
|
||
user_id=user_id
|
||
)
|
||
if code_import:
|
||
return Response.success(msg="添加成功")
|
||
else:
|
||
return Response.failure(msg="添加失败")
|
||
|
||
|
||
SPECIAL_CHARS_PATTERN = r"[.,\/_\-?::?!!@#$%^&*()+=<>|{}[\]\\]"
|
||
|
||
|
||
@codeAPI.post("/addCode", response_class=JSONResponse, response_model=BaseResponse, summary="导入编码")
|
||
@Log(title="导入编码", business_type=BusinessType.INSERT)
|
||
@Auth(permission_list=["code:btn:import"])
|
||
async def add_code_by_file(
|
||
request: Request,
|
||
params: DeleteListParams, # 这里的 params 传入的是 {"ids": [...]}
|
||
current_user=Depends(LoginController.get_current_user)
|
||
):
|
||
user_id = current_user.get("id")
|
||
|
||
# 查询所有文件
|
||
files = await File.filter(id__in=set(params.ids), del_flag=1).values(id="id", uploader_id="uploader__id",
|
||
file_type="file_type",
|
||
absolute_path="absolute_path")
|
||
if not files:
|
||
return Response.failure(msg="文件不存在")
|
||
|
||
# 确保用户对所有文件都有权限
|
||
unauthorized_files = []
|
||
for file in files:
|
||
if str(file["uploader_id"]) != user_id:
|
||
unauthorized_files.append(file['id'])
|
||
if unauthorized_files:
|
||
return Response.failure(msg=f"权限不足,文件ID: {unauthorized_files}")
|
||
|
||
total_imported = 0 # 统计导入的总数量
|
||
try:
|
||
for file in files:
|
||
logger.info(f"正在处理文件: {file['id']}, 类型: {file['file_type']},{file['absolute_path']}")
|
||
# **确保 df 在每次循环都重新创建**
|
||
if file["file_type"] in ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||
"application/vnd.ms-excel"]:
|
||
df = pd.read_excel(file["absolute_path"], dtype=str)
|
||
elif file["file_type"] == "text/csv":
|
||
df = pd.read_csv(file["absolute_path"], dtype=str)
|
||
else:
|
||
logger.error(f"文件 {file['id']} 类型错误!")
|
||
continue # 跳过错误文件
|
||
|
||
df.columns = df.columns.str.strip().str.lower() # 统一列名
|
||
df = df.dropna(how="all") # 删除空行
|
||
df = df.drop_duplicates() # 删除重复行
|
||
|
||
# 处理 code 列,确保格式一致
|
||
if "code" in df.columns:
|
||
df["code"] = df["code"].astype(str).str.zfill(8)
|
||
df["code"] = df["code"].apply(lambda x: re.sub(SPECIAL_CHARS_PATTERN, "", x).strip())
|
||
else:
|
||
logger.error(f"文件 {file['id']} 缺少 'code' 列")
|
||
continue # 跳过错误文件
|
||
|
||
logger.info(f"文件 {file['id']} 解析出 {df.shape[0]} 条数据")
|
||
|
||
# **确保 all_records 在每个文件都是新的**
|
||
all_records = []
|
||
for _, row in df.iterrows():
|
||
all_records.append(CodeImport(
|
||
code=row["code"],
|
||
description=row.get("description", ""),
|
||
status=3,
|
||
user_id=user_id
|
||
))
|
||
|
||
# **每个文件独立事务处理**
|
||
async with in_transaction():
|
||
await CodeImport.bulk_create(all_records)
|
||
|
||
logger.info(f"文件 {file['id']} 经过清理后成功导入 {len(all_records)} 条数据")
|
||
total_imported += len(all_records) # 累加总数
|
||
|
||
# **清除 df 变量,确保下一个文件不会复用**
|
||
del df
|
||
|
||
except Exception as e:
|
||
logger.error(f"文件导入失败: {str(e)}")
|
||
return Response.failure(msg="文件读取失败")
|
||
|
||
logger.info(f"✅ 所有文件导入完成,共导入 {total_imported} 条数据")
|
||
return Response.success(msg="添加成功")
|
||
|
||
|
||
@codeAPI.delete("/delete/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="删除编码")
|
||
@codeAPI.post("/delete/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="删除编码")
|
||
@Log(title="删除编码", business_type=BusinessType.DELETE)
|
||
@Auth(permission_list=["code:btn:delete"])
|
||
async def delete_code_by_id(request: Request, id: str = Path(description="编码ID"),
|
||
current_user=Depends(LoginController.get_current_user)):
|
||
if code := await Code.get_or_none(id=id, del_flag=1):
|
||
code.del_flag = 0
|
||
await code.save()
|
||
await request.app.state.es.delete(index=ElasticSearchConfig.ES_INDEX, id=code.id)
|
||
return Response.success(msg="删除成功")
|
||
else:
|
||
return Response.failure(msg="编码不存在")
|
||
|
||
|
||
@codeAPI.delete("/deleteList", response_class=JSONResponse, response_model=BaseResponse, summary="批量删除编码")
|
||
@codeAPI.post("/deleteList", response_class=JSONResponse, response_model=BaseResponse, summary="批量删除编码")
|
||
@Log(title="批量删除编码", business_type=BusinessType.DELETE)
|
||
@Auth(permission_list=["code:btn:delete"])
|
||
async def delete_code_by_ids(request: Request, params: DeleteListParams,
|
||
current_user=Depends(LoginController.get_current_user)):
|
||
# 异步批量查询 Code
|
||
codes = await Code.filter(id__in=set(params.ids), del_flag=1)
|
||
|
||
if not codes:
|
||
return Response.error(msg="未找到相关数据")
|
||
|
||
# 修改删除标志
|
||
for code in codes:
|
||
code.del_flag = 0
|
||
|
||
# 异步批量更新 Code
|
||
await Code.bulk_update(codes, fields=["del_flag"])
|
||
|
||
# 构造 Elasticsearch 删除操作
|
||
actions = [
|
||
{
|
||
"_op_type": "delete", # 指定为删除操作
|
||
"_index": ElasticSearchConfig.ES_INDEX,
|
||
"_id": code.id
|
||
}
|
||
for code in codes
|
||
]
|
||
|
||
# 异步批量删除 Elasticsearch 数据
|
||
es_client = request.app.state.es
|
||
if actions:
|
||
success, failed = await async_bulk(es_client, actions)
|
||
logger.info(f"成功删除 {success} 条数据,失败 {failed} 条")
|
||
|
||
return Response.success(msg="删除成功")
|
||
|
||
|
||
@codeAPI.put("/update/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="更新编码")
|
||
@codeAPI.post("/update/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="更新编码")
|
||
@Log(title="更新编码", business_type=BusinessType.UPDATE)
|
||
@Auth(permission_list=["code:btn:update"])
|
||
async def update_code(request: Request,
|
||
params: AddCodeParams,
|
||
id: str = Path(description="编码ID"),
|
||
current_user=Depends(LoginController.get_current_user)):
|
||
if code := await Code.get_or_none(id=id, del_flag=1):
|
||
code.code = params.code
|
||
code.description = params.description
|
||
await code.save()
|
||
await request.app.state.es.update(index=ElasticSearchConfig.ES_INDEX, id=code.id,
|
||
body={"doc": {"id": code.id, "code": code.code,
|
||
"description": params.description}})
|
||
return Response.success(msg="更新成功")
|
||
return Response.failure(msg="编码不存在")
|
||
|
||
|
||
@codeAPI.get("/info/{id}", response_class=JSONResponse, response_model=GetCodeInfoResponse, summary="获取编码信息")
|
||
@Log(title="获取编码信息", business_type=BusinessType.SELECT)
|
||
@Auth(permission_list=["code:btn:info"])
|
||
async def get_code_info(request: Request, id: str = Path(description="编码ID"),
|
||
current_user=Depends(LoginController.get_current_user)):
|
||
if code := await Code.get_or_none(id=id, del_flag=1).values(
|
||
id="id",
|
||
code="code",
|
||
description="description",
|
||
create_time="create_time",
|
||
create_by="create_by",
|
||
update_time="update_time",
|
||
update_by="update_by",
|
||
user_id="user__id",
|
||
username="user__username",
|
||
nickname="user__nickname",
|
||
department_id="user__department__id",
|
||
department_name="user__department__name",
|
||
):
|
||
return Response.success(data=code)
|
||
return Response.failure(msg="编码不存在")
|
||
|
||
|
||
@codeAPI.get("/list", response_class=JSONResponse, response_model=GetCodeListResponse, summary="获取编码列表")
|
||
@Log(title="获取编码列表", business_type=BusinessType.SELECT)
|
||
@Auth(permission_list=["code:btn:list"])
|
||
async def get_code_list(request: Request,
|
||
page: int = Query(default=1, description="页码"),
|
||
pageSize: int = Query(default=10, description="每页数量"),
|
||
code: Optional[str] = Query(default=None, description="编码"),
|
||
description: Optional[str] = Query(default=None, description="商品描述"),
|
||
department_id: Optional[str] = Query(default=None, description="部门ID"),
|
||
username: Optional[str] = Query(default=None, description="用户账号"),
|
||
nickname: Optional[str] = Query(default=None, description="用户昵称"),
|
||
startTime: Optional[str] = Query(default=None, description="开始时间"),
|
||
endTime: Optional[str] = Query(default=None, description="结束时间"),
|
||
current_user=Depends(LoginController.get_current_user)):
|
||
filter_args = {
|
||
f'{k}__icontains': v for k, v in {
|
||
'user__username': username,
|
||
'user__nickname': nickname,
|
||
'code': code,
|
||
}.items() if v
|
||
}
|
||
|
||
if description:
|
||
# 使用全文索引优化模糊查询
|
||
filter_args['description__full_text_search'] = description
|
||
|
||
if startTime and endTime:
|
||
startTime = float(startTime) / 1000
|
||
endTime = float(endTime) / 1000
|
||
startTime = datetime.fromtimestamp(startTime)
|
||
endTime = datetime.fromtimestamp(endTime)
|
||
filter_args['create_time__range'] = [startTime, endTime]
|
||
|
||
if department_id:
|
||
filter_args['user__department__id'] = department_id
|
||
|
||
total = await Code.filter(**filter_args, del_flag=1).count()
|
||
data = await Code.filter(**filter_args, del_flag=1).offset((page - 1) * pageSize).limit(pageSize).values(
|
||
"id", "code", "description", "create_time", "create_by", "update_time", "update_by",
|
||
user_id="user__id", username="user__username", nickname="user__nickname",
|
||
department_id="user__department__id", department_name="user__department__name"
|
||
)
|
||
return Response.success(data={
|
||
"page": page,
|
||
"pageSize": pageSize,
|
||
"total": total,
|
||
"result": data
|
||
})
|
||
|
||
|
||
@codeAPI.post("/query", response_class=JSONResponse, response_model=QueryCodeResponse, summary="查询编码")
|
||
@Log(title="查询编码", business_type=BusinessType.SELECT)
|
||
@Auth(permission_list=["code:btn:query"])
|
||
async def get_code_list(
|
||
request: Request,
|
||
params: GetQueryCodeParams,
|
||
current_user: dict = Depends(LoginController.get_current_user),
|
||
):
|
||
start_time = time.time()
|
||
user_id = current_user.get("id")
|
||
|
||
# 预创建查询日志
|
||
log = await QueryCodeLog.create(
|
||
operator_id=user_id,
|
||
query_count=0,
|
||
result_count=0,
|
||
cost_time=0,
|
||
request_params=params.query_text,
|
||
response_result={},
|
||
status=0,
|
||
del_flag=0,
|
||
)
|
||
descriptions = list(desc.strip() for desc in params.query_text.split("\n") if desc.strip())
|
||
if not descriptions:
|
||
return Response.failure(msg="查询失败!")
|
||
query_count = len(descriptions)
|
||
|
||
async def execute_es_queries(description_list: list | set) -> dict:
|
||
"""执行 Elasticsearch 批量查询"""
|
||
es_queries = []
|
||
for desc in description_list:
|
||
es_queries.append({})
|
||
es_queries.append({
|
||
"query": {
|
||
"match": {
|
||
"description": {
|
||
"query": desc,
|
||
"fuzziness": "AUTO"
|
||
}
|
||
}
|
||
},
|
||
"size": 5,
|
||
"_source": ["id", "code", "description"],
|
||
"sort": [{"_score": {"order": "desc"}}],
|
||
"timeout": "600s"
|
||
})
|
||
return await request.app.state.es.msearch(index=ElasticSearchConfig.ES_INDEX, body=es_queries)
|
||
|
||
async def process_es_results(es_results: dict, description_list: list | set) -> list:
|
||
"""处理 Elasticsearch 查询结果"""
|
||
data_list = []
|
||
for i, desc in enumerate(description_list):
|
||
hits = es_results["responses"][i]["hits"]["hits"]
|
||
max_score = max(hit["_score"] for hit in hits) if hits else 1
|
||
matches = [
|
||
{
|
||
"id": hit["_source"].get("id"),
|
||
"code": hit["_source"].get("code"),
|
||
"description": hit["_source"].get("description"),
|
||
"match_rate": round((hit["_score"] / max_score) * 100, 2) if max_score else 0,
|
||
}
|
||
for hit in hits
|
||
]
|
||
data_list.append({
|
||
"id": uuid.uuid4().__str__(),
|
||
"query_text": desc,
|
||
"result_text": json.dumps(matches, ensure_ascii=False),
|
||
"status": 1 if matches else 0,
|
||
})
|
||
return data_list
|
||
|
||
async def update_query_log(log: QueryCodeLog, data_list: list, query_count: int, cost_time: float):
|
||
"""更新查询日志"""
|
||
await QueryCodeLog.filter(id=log.id).update(
|
||
request_params="\n".join(descriptions),
|
||
query_count=query_count,
|
||
result_count=len(data_list),
|
||
cost_time=cost_time,
|
||
status=1 if data_list else 0,
|
||
response_result=json.dumps(data_list, ensure_ascii=False),
|
||
del_flag=1,
|
||
)
|
||
|
||
try:
|
||
# 批量查询 Elasticsearch
|
||
BATCH_SIZE = 300 # 每批查询的数量
|
||
description_batches = [descriptions[i:i + BATCH_SIZE] for i in range(0, len(descriptions), BATCH_SIZE)]
|
||
all_results = []
|
||
|
||
for batch in description_batches:
|
||
es_results = await execute_es_queries(batch)
|
||
batch_results = await process_es_results(es_results, batch)
|
||
all_results.extend(batch_results)
|
||
|
||
# 批量插入查询结果
|
||
query_tasks = [
|
||
QueryCode(
|
||
id=item["id"],
|
||
query_text=desc,
|
||
result_text=item["result_text"],
|
||
session_id=log.id,
|
||
status=item["status"],
|
||
)
|
||
for desc, item in zip(descriptions, all_results)
|
||
]
|
||
await QueryCode.bulk_create(query_tasks)
|
||
|
||
# 更新查询日志
|
||
cost_time = round((time.time() - start_time) * 100, 2)
|
||
await update_query_log(log, all_results, query_count, cost_time)
|
||
|
||
return Response.success(data={
|
||
"id": log.id,
|
||
"result_count": len(all_results),
|
||
"query": "\n".join(descriptions),
|
||
"response_result": json.dumps(all_results, ensure_ascii=False),
|
||
"query_count": query_count,
|
||
"cost_time": cost_time,
|
||
"status": 1 if all_results else 0,
|
||
"operation_time": log.operation_time,
|
||
})
|
||
except Exception as e:
|
||
logger.error(f"查询失败:{e}")
|
||
await log.delete()
|
||
raise ServiceException(message="查询失败!")
|
||
|
||
|
||
@codeAPI.get("/query/{id}", response_class=JSONResponse, response_model=QueryCodeResponse, summary="查询编码")
|
||
@Log(title="查询编码", business_type=BusinessType.SELECT)
|
||
@Auth(permission_list=["code:btn:importQuery"])
|
||
async def get_code_list(
|
||
request: Request,
|
||
id: str = Path(description="文件ID"),
|
||
current_user: dict = Depends(LoginController.get_current_user),
|
||
):
|
||
start_time = time.time()
|
||
user_id = current_user.get("id")
|
||
|
||
# 获取文件信息和上传者 ID
|
||
file = await File.get_or_none(id=id, del_flag=1).values(uploader_id="uploader__id", file_type="file_type",
|
||
absolute_path="absolute_path")
|
||
if not file:
|
||
return Response.failure(msg="文件不存在")
|
||
if str(file["uploader_id"]) != user_id:
|
||
raise PermissionException(message="权限不足")
|
||
|
||
# 预创建查询日志
|
||
log = await QueryCodeLog.create(
|
||
operator_id=user_id,
|
||
query_count=0,
|
||
result_count=0,
|
||
cost_time=0,
|
||
request_params="",
|
||
response_result={},
|
||
status=0,
|
||
del_flag=0,
|
||
)
|
||
|
||
try:
|
||
media_types = {
|
||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "excel",
|
||
"application/vnd.ms-excel": "excel",
|
||
"text/csv": "csv"
|
||
}
|
||
file_type = media_types.get(file["file_type"])
|
||
if not file_type:
|
||
raise ServiceException(message="文件类型错误!")
|
||
|
||
# 读取 Excel 或 CSV
|
||
df = pd.read_excel(file["absolute_path"], dtype={"code": str}) if file_type == "excel" else pd.read_csv(
|
||
file["absolute_path"], dtype={"code": str})
|
||
descriptions = list({row["text"].strip() for _, row in df.iterrows() if row["text"].strip()})
|
||
if not descriptions:
|
||
raise ServiceException(message="文件内容为空!")
|
||
query_count = len(descriptions)
|
||
|
||
async def execute_es_queries(description_list: list | set) -> dict:
|
||
"""执行 Elasticsearch 批量查询"""
|
||
es_queries = []
|
||
for desc in description_list:
|
||
es_queries.append({})
|
||
es_queries.append({
|
||
"query": {
|
||
"match": {
|
||
"description": {
|
||
"query": desc,
|
||
"fuzziness": "AUTO"
|
||
}
|
||
}
|
||
},
|
||
"size": 5,
|
||
"_source": ["id", "code", "description"],
|
||
"sort": [{"_score": {"order": "desc"}}],
|
||
"timeout": "600s"
|
||
})
|
||
return await request.app.state.es.msearch(index=ElasticSearchConfig.ES_INDEX, body=es_queries)
|
||
|
||
async def process_es_results(es_results: dict, description_list: list | set) -> list:
|
||
"""处理 Elasticsearch 查询结果"""
|
||
data_list = []
|
||
for i, desc in enumerate(description_list):
|
||
hits = es_results["responses"][i]["hits"]["hits"]
|
||
max_score = max(hit["_score"] for hit in hits) if hits else 1
|
||
matches = [
|
||
{
|
||
"id": hit["_source"].get("id"),
|
||
"code": hit["_source"].get("code"),
|
||
"description": hit["_source"].get("description"),
|
||
"match_rate": round((hit["_score"] / max_score) * 100, 2) if max_score else 0,
|
||
}
|
||
for hit in hits
|
||
]
|
||
data_list.append({
|
||
"id": uuid.uuid4().__str__(),
|
||
"query_text": desc,
|
||
"result_text": json.dumps(matches, ensure_ascii=False),
|
||
"status": 1 if matches else 0,
|
||
})
|
||
return data_list
|
||
|
||
async def update_query_log(log: QueryCodeLog, data_list: list, query_count: int, cost_time: float):
|
||
"""更新查询日志"""
|
||
await QueryCodeLog.filter(id=log.id).update(
|
||
request_params="\n".join(descriptions),
|
||
query_count=query_count,
|
||
result_count=len(data_list),
|
||
cost_time=cost_time,
|
||
status=1 if data_list else 0,
|
||
response_result=json.dumps(data_list, ensure_ascii=False),
|
||
del_flag=1,
|
||
)
|
||
|
||
# 批量查询 Elasticsearch
|
||
BATCH_SIZE = 300 # 每批查询的数量
|
||
description_batches = [descriptions[i:i + BATCH_SIZE] for i in range(0, len(descriptions), BATCH_SIZE)]
|
||
all_results = []
|
||
|
||
for batch in description_batches:
|
||
es_results = await execute_es_queries(batch)
|
||
batch_results = await process_es_results(es_results, batch)
|
||
all_results.extend(batch_results)
|
||
|
||
# 批量插入查询结果
|
||
query_tasks = [
|
||
QueryCode(
|
||
id=item["id"],
|
||
query_text=desc,
|
||
result_text=item["result_text"],
|
||
session_id=log.id,
|
||
status=item["status"],
|
||
)
|
||
for desc, item in zip(descriptions, all_results)
|
||
]
|
||
await QueryCode.bulk_create(query_tasks)
|
||
|
||
# 更新查询日志
|
||
cost_time = round((time.time() - start_time) * 100, 2)
|
||
await update_query_log(log, all_results, query_count, cost_time)
|
||
|
||
return Response.success(data={
|
||
"id": log.id,
|
||
"result_count": len(all_results),
|
||
"query": "\n".join(descriptions),
|
||
"response_result": json.dumps(all_results, ensure_ascii=False),
|
||
"query_count": query_count,
|
||
"cost_time": cost_time,
|
||
"status": 1 if all_results else 0,
|
||
"operation_time": log.operation_time,
|
||
})
|
||
except Exception as e:
|
||
logger.error(f"查询失败:{e}")
|
||
await log.delete()
|
||
raise ServiceException(message="查询失败!")
|
||
|
||
|
||
@codeAPI.get("/logList", response_class=JSONResponse, response_model=GetQueryCodeLogResponse,
|
||
summary="查询编码日志列表")
|
||
@Log(title="查询编码日志列表", business_type=BusinessType.SELECT)
|
||
@Auth(permission_list=["code:btn:logList"])
|
||
async def get_code_log_list(request: Request,
|
||
page: int = Query(default=1, description="当前页码"),
|
||
pageSize: int = Query(default=10, description="每页数量"),
|
||
department_id: Optional[str] = Query(default=None, description="部门ID"),
|
||
username: Optional[str] = Query(default=None, description="用户账号"),
|
||
nickname: Optional[str] = Query(default=None, description="用户昵称"),
|
||
startTime: Optional[str] = Query(default=None, description="开始时间"),
|
||
endTime: Optional[str] = Query(default=None, description="结束时间"),
|
||
current_user: dict = Depends(LoginController.get_current_user),
|
||
):
|
||
sub_departments = current_user.get("sub_departments")
|
||
filterArgs = {
|
||
f'{k}__icontains': v for k, v in {
|
||
'operator__username': username,
|
||
'operator__nickname': nickname,
|
||
}.items() if v
|
||
}
|
||
if startTime and endTime:
|
||
startTime = float(startTime) / 1000
|
||
endTime = float(endTime) / 1000
|
||
startTime = datetime.fromtimestamp(startTime)
|
||
endTime = datetime.fromtimestamp(endTime)
|
||
filterArgs['operation_time__range'] = [startTime, endTime]
|
||
if await hasAuth(request, "code:btn:logAdmin"):
|
||
if department_id:
|
||
filterArgs['operator__department__id'] = department_id
|
||
else:
|
||
filterArgs['operator__department__id__in'] = sub_departments
|
||
else:
|
||
if department_id:
|
||
filterArgs['operator__department__id'] = department_id
|
||
else:
|
||
filterArgs['operator__department__id'] = current_user.get("department_id")
|
||
count = await QueryCodeLog.filter(**filterArgs, operator__del_flag=1, del_flag=1).count()
|
||
data = await QueryCodeLog.filter(**filterArgs, operator__del_flag=1, del_flag=1).order_by("-operation_time").offset(
|
||
(page - 1) * pageSize).limit(pageSize).values(
|
||
id="id",
|
||
query_count="query_count",
|
||
result_count="result_count",
|
||
cost_time="cost_time",
|
||
operation_time="operation_time",
|
||
status="status",
|
||
request_params="request_params",
|
||
response_result="response_result",
|
||
create_time="create_time",
|
||
update_time="update_time",
|
||
operator_id="operator__id",
|
||
operator_name="operator__username",
|
||
operator_nickname="operator__nickname",
|
||
department_id="operator__department__id",
|
||
department_name="operator__department__name",
|
||
)
|
||
return Response.success(data={
|
||
"page": page,
|
||
"pageSize": pageSize,
|
||
"result": data,
|
||
"total": count
|
||
})
|
||
|
||
|
||
@codeAPI.get("/logList/all", response_class=JSONResponse, response_model=GetCodeLogAllResponse,
|
||
summary="查询所有编码日志列表")
|
||
@Log(title="查询所有编码日志列表", business_type=BusinessType.EXPORT)
|
||
@Auth(permission_list=["code:btn:export"])
|
||
async def get_code_log_list(request: Request,
|
||
department_id: Optional[str] = Query(default=None, description="部门ID"),
|
||
username: Optional[str] = Query(default=None, description="用户账号"),
|
||
nickname: Optional[str] = Query(default=None, description="用户昵称"),
|
||
startTime: Optional[str] = Query(default=None, description="开始时间"),
|
||
endTime: Optional[str] = Query(default=None, description="结束时间"),
|
||
current_user: dict = Depends(LoginController.get_current_user),
|
||
):
|
||
sub_departments = current_user.get("sub_departments")
|
||
filterArgs = {
|
||
f'{k}__icontains': v for k, v in {
|
||
'operator__username': username,
|
||
'operator__nickname': nickname,
|
||
}.items() if v
|
||
}
|
||
if startTime and endTime:
|
||
startTime = float(startTime) / 1000
|
||
endTime = float(endTime) / 1000
|
||
startTime = datetime.fromtimestamp(startTime)
|
||
endTime = datetime.fromtimestamp(endTime)
|
||
filterArgs['operation_time__range'] = [startTime, endTime]
|
||
if await hasAuth(request, "code:btn:logAdmin"):
|
||
if department_id:
|
||
filterArgs['operator__department__id'] = department_id
|
||
else:
|
||
filterArgs['operator__department__id__in'] = sub_departments
|
||
else:
|
||
if department_id:
|
||
filterArgs['operator__department__id'] = department_id
|
||
else:
|
||
filterArgs['operator__department__id'] = current_user.get("department_id")
|
||
count = await QueryCodeLog.filter(**filterArgs, operator__del_flag=1, del_flag=1).count()
|
||
data = await QueryCodeLog.filter(**filterArgs, operator__del_flag=1, del_flag=1).order_by("-operation_time").values(
|
||
id="id",
|
||
query_count="query_count",
|
||
result_count="result_count",
|
||
cost_time="cost_time",
|
||
operation_time="operation_time",
|
||
status="status",
|
||
request_params="request_params",
|
||
response_result="response_result",
|
||
create_time="create_time",
|
||
update_time="update_time",
|
||
operator_id="operator__id",
|
||
operator_name="operator__username",
|
||
operator_nickname="operator__nickname",
|
||
department_id="operator__department__id",
|
||
department_name="operator__department__name",
|
||
)
|
||
return Response.success(data={
|
||
"result": data,
|
||
"total": count
|
||
})
|
||
|
||
|
||
@codeAPI.get("/logInfo/{id}", response_class=JSONResponse, response_model=GetQueryCodeLogDetailResponse,
|
||
summary="查询编码日志详情")
|
||
@Log(title="查询编码日志详情", business_type=BusinessType.SELECT)
|
||
@Auth(permission_list=["code:btn:logInfo"])
|
||
async def get_code_log_detail(request: Request,
|
||
id: str = Path(..., description="日志ID"),
|
||
current_user: dict = Depends(LoginController.get_current_user),
|
||
):
|
||
sub_departments = current_user.get("sub_departments")
|
||
if log := await QueryCodeLog.get_or_none(id=id, operator__department__id__in=sub_departments, del_flag=1):
|
||
data = await log.get(id=id).values(
|
||
id="id",
|
||
query_count="query_count",
|
||
result_count="result_count",
|
||
cost_time="cost_time",
|
||
operation_time="operation_time",
|
||
status="status",
|
||
request_params="request_params",
|
||
response_result="response_result",
|
||
create_time="create_time",
|
||
update_time="update_time",
|
||
operator_id="operator__id",
|
||
operator_name="operator__username",
|
||
operator_nickname="operator__nickname",
|
||
department_id="operator__department__id",
|
||
department_name="operator__department__name",
|
||
)
|
||
return Response.success(data=data)
|
||
return Response.failure(msg="日志不存在!")
|
||
|
||
|
||
@codeAPI.delete("/deleteFeedback/{id}", response_class=JSONResponse, response_model=BaseResponse,
|
||
summary="删除编码反馈")
|
||
@codeAPI.post("/deleteFeedback/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="删除编码反馈")
|
||
@Log(title="删除编码反馈", business_type=BusinessType.DELETE)
|
||
@Auth(permission_list=["code:btn:deleteFeedback"])
|
||
async def delete_feedback(request: Request,
|
||
id: str = Path(..., description="编码反馈ID"),
|
||
current_user: dict = Depends(LoginController.get_current_user),
|
||
):
|
||
sub_departments = current_user.get("sub_departments")
|
||
if feedback := await CodeFeedback.get_or_none(id=id, user__department__id__in=sub_departments, del_flag=1):
|
||
feedback.del_flag = 0
|
||
await feedback.save()
|
||
return Response.success(msg="删除成功!")
|
||
return Response.failure(msg="删除失败!")
|
||
|
||
|
||
@codeAPI.post("/addFeedback", response_class=JSONResponse, response_model=BaseResponse, summary="添加编码反馈")
|
||
@Log(title="添加编码反馈", business_type=BusinessType.INSERT)
|
||
@Auth(permission_list=["code:btn:addFeedback"])
|
||
async def add_feedback(request: Request,
|
||
params: AddCodeFeedbackParams,
|
||
current_user: dict = Depends(LoginController.get_current_user),
|
||
):
|
||
user_id = current_user.get("id")
|
||
if code := await Code.get_or_none(id=params.code_id, del_flag=1):
|
||
feedback = await CodeFeedback.create(
|
||
code_id=code.id,
|
||
feedback_code=params.feedback_code,
|
||
feedback_description=params.feedback_description,
|
||
user_id=str(user_id),
|
||
)
|
||
else:
|
||
feedback = await CodeFeedback.create(
|
||
code=None,
|
||
feedback_code=params.feedback_code,
|
||
feedback_description=params.feedback_description,
|
||
user_id=str(user_id),
|
||
)
|
||
if feedback:
|
||
return Response.success(msg="添加成功!")
|
||
return Response.failure(msg="添加失败!")
|
||
|
||
|
||
@codeAPI.delete("/deleteFeedbackList", response_class=JSONResponse, response_model=BaseResponse,
|
||
summary="批量删除编码反馈")
|
||
@codeAPI.post("/deleteFeedbackList", response_class=JSONResponse, response_model=BaseResponse,
|
||
summary="批量删除编码反馈")
|
||
@Log(title="批量删除编码反馈", business_type=BusinessType.DELETE)
|
||
@Auth(permission_list=["code:btn:deleteFeedback"])
|
||
async def delete_feedback_list(request: Request,
|
||
params: DeleteListParams,
|
||
current_user: dict = Depends(LoginController.get_current_user),
|
||
):
|
||
sub_departments = current_user.get("sub_departments")
|
||
# 批量查询 CodeFeedback
|
||
feedbacks = await CodeFeedback.filter(
|
||
id__in=set(params.ids),
|
||
user__department__id__in=sub_departments,
|
||
del_flag=1
|
||
)
|
||
if not feedbacks:
|
||
return Response.error(msg="未找到相关数据")
|
||
# 修改删除标志
|
||
for feedback in feedbacks:
|
||
feedback.del_flag = 0
|
||
# 批量更新数据
|
||
async with in_transaction():
|
||
await CodeFeedback.bulk_update(feedbacks, fields=["del_flag"])
|
||
return Response.success(msg="删除成功!")
|
||
|
||
|
||
@codeAPI.put("/updateFeedback/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="修改编码反馈")
|
||
@codeAPI.post("/updateFeedback/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="修改编码反馈")
|
||
@Log(title="修改编码反馈", business_type=BusinessType.UPDATE)
|
||
@Auth(permission_list=["code:btn:updateFeedback"])
|
||
async def update_feedback(request: Request,
|
||
params: AddCodeFeedbackParams,
|
||
id: str = Path(..., description="编码反馈ID"),
|
||
current_user: dict = Depends(LoginController.get_current_user),
|
||
):
|
||
sub_departments = current_user.get("sub_departments")
|
||
if feedback := await CodeFeedback.get_or_none(id=id, user__department__id__in=sub_departments, del_flag=1):
|
||
code = await Code.get_or_none(id=params.code_id, del_flag=1)
|
||
if code:
|
||
feedback.code = code
|
||
else:
|
||
feedback.code = None
|
||
feedback.feedback_description = params.feedback_description
|
||
feedback.feedback_code = params.feedback_code
|
||
await feedback.save()
|
||
return Response.success(msg="修改成功!")
|
||
return Response.failure(msg="编码反馈不存在!")
|
||
|
||
|
||
@codeAPI.get("/feedbackInfo/{id}", response_class=JSONResponse, response_model=GetCodeFeedbackResponse,
|
||
summary="编码反馈详情")
|
||
@Log(title="编码反馈详情", business_type=BusinessType.SELECT)
|
||
@Auth(permission_list=["code:btn:feedbackInfo"])
|
||
async def feedback_info(request: Request,
|
||
id: str = Path(..., description="编码反馈ID"),
|
||
current_user: dict = Depends(LoginController.get_current_user),
|
||
):
|
||
sub_departments = current_user.get("sub_departments")
|
||
if feedback := await CodeFeedback.get_or_none(id=id, user__department__id__in=sub_departments, del_flag=1):
|
||
data = await feedback.first().values(
|
||
id="id",
|
||
code_id="code__id",
|
||
code="code__code",
|
||
description="code__description",
|
||
feedback_code="feedback_code",
|
||
feedback_description="feedback_description",
|
||
create_time="create_time",
|
||
update_time="update_time",
|
||
user_id="user__id",
|
||
username="user__username",
|
||
nickname="user__nickname",
|
||
department_id="user__department__id",
|
||
department_name="user__department__name",
|
||
)
|
||
return Response.success(data=data)
|
||
return Response.failure(msg="编码反馈不存在!")
|
||
|
||
|
||
@codeAPI.get("/feedbackList", response_class=JSONResponse, response_model=GetCodeFeedbackListResponse,
|
||
summary="编码反馈列表")
|
||
@Log(title="编码反馈列表", business_type=BusinessType.SELECT)
|
||
@Auth(permission_list=["code:btn:feedbackList"])
|
||
async def feedback_list(request: Request,
|
||
page: int = Query(default=1, description="当前页码"),
|
||
pageSize: int = Query(default=10, description="每页数量"),
|
||
code: Optional[str] = Query(default=None, description="编码"),
|
||
feedback_code: Optional[str] = Query(default=None, description="反馈编码"),
|
||
feedback_description: Optional[str] = Query(default=None, description="反馈文本"),
|
||
username: Optional[str] = Query(default=None, description="用户名"),
|
||
status: Optional[str] = Query(default=None, description="状态"),
|
||
nickname: Optional[str] = Query(default=None, description="用户昵称"),
|
||
department_id: Optional[str] = Query(default=None, description="部门ID"),
|
||
startTime: Optional[str] = Query(default=None, description="开始时间"),
|
||
endTime: Optional[str] = Query(default=None, description="结束时间"),
|
||
current_user: dict = Depends(LoginController.get_current_user),
|
||
):
|
||
sub_departments = current_user.get("sub_departments")
|
||
filterArgs = {
|
||
f'{k}__icontains': v for k, v in {
|
||
'user__username': username,
|
||
'user__nickname': nickname,
|
||
'code__code': code,
|
||
'feedback_code': feedback_code,
|
||
'feedback_description': feedback_description,
|
||
}.items() if v
|
||
}
|
||
if startTime and endTime:
|
||
startTime = float(startTime) / 1000
|
||
endTime = float(endTime) / 1000
|
||
startTime = datetime.fromtimestamp(startTime)
|
||
endTime = datetime.fromtimestamp(endTime)
|
||
filterArgs['create_time__range'] = [startTime, endTime]
|
||
if await hasAuth(request, "code:btn:feedbackAdmin"):
|
||
if department_id:
|
||
filterArgs['user__department__id'] = department_id
|
||
else:
|
||
filterArgs['user__department__id__in'] = sub_departments
|
||
else:
|
||
if department_id:
|
||
filterArgs['user__department__id'] = department_id
|
||
else:
|
||
filterArgs['user__department__id'] = current_user.get("department_id")
|
||
if status is not None:
|
||
filterArgs['status'] = int(status)
|
||
|
||
total = await CodeFeedback.filter(**filterArgs, del_flag=1).count()
|
||
data = await CodeFeedback.filter(**filterArgs, del_flag=1).order_by('-create_time').offset(
|
||
(page - 1) * pageSize).limit(pageSize).values(
|
||
id="id",
|
||
code_id="code__id",
|
||
code="code__code",
|
||
description="code__description",
|
||
feedback_code="feedback_code",
|
||
feedback_description="feedback_description",
|
||
create_time="create_time",
|
||
update_time="update_time",
|
||
user_id="user__id",
|
||
username="user__username",
|
||
nickname="user__nickname",
|
||
department_id="user__department__id",
|
||
department_name="user__department__name",
|
||
status="status",
|
||
)
|
||
return Response.success(data={
|
||
"page": page,
|
||
"pageSize": pageSize,
|
||
"result": data,
|
||
"total": total,
|
||
})
|
||
|
||
|
||
@codeAPI.put("/feedbackAudit", response_class=JSONResponse, response_model=BaseResponse, summary="编码反馈审核")
|
||
@codeAPI.post("/feedbackAudit", response_class=JSONResponse, response_model=BaseResponse, summary="编码反馈审核")
|
||
@Log(title="编码反馈审核", business_type=BusinessType.UPDATE)
|
||
@Auth(permission_list=["code:btn:feedbackAudit"])
|
||
async def feedback_audit(request: Request,
|
||
params: UpdateCodeFeedbackStatusParams,
|
||
current_user: dict = Depends(LoginController.get_current_user),
|
||
):
|
||
sub_departments = current_user.get("sub_departments")
|
||
# 获取所有符合条件的反馈数据
|
||
feedback_list = await CodeFeedback.filter(
|
||
id__in=set(params.ids),
|
||
user__department__id__in=sub_departments,
|
||
del_flag=1
|
||
).prefetch_related("user") # 预加载 user 关联数据,减少查询
|
||
if not feedback_list:
|
||
return Response.failure(msg="编码反馈不存在!")
|
||
code_updates = [] # 需要更新的 Code 对象
|
||
code_creates = [] # 需要创建的 Code 对象
|
||
es_bulk_operations = [] # 批量更新 Elasticsearch
|
||
async with in_transaction():
|
||
for feedback in feedback_list:
|
||
feedback.status = params.status
|
||
if params.status == 1:
|
||
# 查找对应的 Code
|
||
code = await Code.get_or_none(id=feedback.code_id, del_flag=1)
|
||
if code:
|
||
# 更新 Code
|
||
code.code = re.sub(SPECIAL_CHARS_PATTERN, "", str(feedback.feedback_code)).strip()
|
||
code.description = re.sub(SPECIAL_CHARS_PATTERN, "", str(feedback.feedback_description)).strip()
|
||
code_updates.append(code)
|
||
else:
|
||
# 创建新 Code
|
||
code = Code(
|
||
user_id=feedback.user_id,
|
||
code=re.sub(SPECIAL_CHARS_PATTERN, "", str(feedback.feedback_code)).strip(),
|
||
description=re.sub(SPECIAL_CHARS_PATTERN, "", str(feedback.feedback_description)).strip()
|
||
)
|
||
code_creates.append(code)
|
||
# Elasticsearch 更新或创建操作
|
||
es_bulk_operations.append({
|
||
"update" if code.id else "create": {
|
||
"_index": ElasticSearchConfig.ES_INDEX,
|
||
"_id": code.id or None,
|
||
"doc" if code.id else "doc_as_upsert": {
|
||
"id": code.id,
|
||
"code": code.code,
|
||
"description": code.description
|
||
}
|
||
}
|
||
})
|
||
# 记录更新 feedback
|
||
await feedback.save()
|
||
# 批量更新 Code
|
||
if code_updates:
|
||
await Code.bulk_update(code_updates, fields=["code", "description"])
|
||
# 批量创建 Code
|
||
if code_creates:
|
||
await Code.bulk_create(code_creates)
|
||
# 批量更新 Elasticsearch
|
||
if es_bulk_operations:
|
||
await request.app.state.es.bulk(index=ElasticSearchConfig.ES_INDEX, body=es_bulk_operations)
|
||
return Response.success(msg="审核成功!")
|
||
|
||
|
||
@codeAPI.delete("/deleteCodeImport/{id}", response_class=JSONResponse, response_model=BaseResponse,
|
||
summary="删除编码导入")
|
||
@codeAPI.post("/deleteCodeImport/{id}", response_class=JSONResponse, response_model=BaseResponse,
|
||
summary="删除编码导入")
|
||
@Log(title="删除编码导入", business_type=BusinessType.DELETE)
|
||
@Auth(permission_list=["code:btn:deleteCodeImport"])
|
||
async def delete_code_import(request: Request, id: str = Path(description="编码导入ID"),
|
||
current_user: dict = Depends(LoginController.get_current_user)):
|
||
sub_departments = current_user.get("sub_departments")
|
||
if code_import := await CodeImport.get_or_none(id=id, user__department__id__in=sub_departments, del_flag=1):
|
||
code_import.del_flag = 0
|
||
await code_import.save()
|
||
return Response.success()
|
||
return Response.failure(msg="编码导入不存在!")
|
||
|
||
|
||
@codeAPI.delete("/deleteCodeImportList", response_class=JSONResponse, response_model=BaseResponse,
|
||
summary="批量删除编码导入")
|
||
@codeAPI.post("/deleteCodeImportList", response_class=JSONResponse, response_model=BaseResponse,
|
||
summary="批量删除编码导入")
|
||
@Log(title="批量删除编码导入", business_type=BusinessType.DELETE)
|
||
@Auth(permission_list=["code:btn:deleteCodeImport"])
|
||
async def delete_code_import_list(request: Request, params: DeleteListParams,
|
||
current_user: dict = Depends(LoginController.get_current_user)):
|
||
sub_departments = current_user.get("sub_departments")
|
||
# 获取符合条件的所有 CodeImport 记录
|
||
code_imports = await CodeImport.filter(
|
||
id__in=set(params.ids),
|
||
user__department__id__in=sub_departments,
|
||
del_flag=1
|
||
)
|
||
if not code_imports:
|
||
return Response.failure(msg="编码导入不存在!")
|
||
# 批量修改 del_flag
|
||
for code_import in code_imports:
|
||
code_import.del_flag = 0
|
||
# 事务内批量更新
|
||
async with in_transaction():
|
||
await CodeImport.bulk_update(code_imports, fields=["del_flag"])
|
||
return Response.success()
|
||
|
||
|
||
@codeAPI.put("/updateCodeImport/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="修改编码导入")
|
||
@codeAPI.post("/updateCodeImport/{id}", response_class=JSONResponse, response_model=BaseResponse,
|
||
summary="修改编码导入")
|
||
@Log(title="修改编码导入", business_type=BusinessType.UPDATE)
|
||
@Auth(permission_list=["code:btn:updateCodeImport"])
|
||
async def update_code_import(request: Request,
|
||
params: AddCodeParams,
|
||
id: str = Path(description="编码导入ID"),
|
||
current_user: dict = Depends(LoginController.get_current_user)):
|
||
sub_departments = current_user.get("sub_departments")
|
||
if code_import := await CodeImport.get_or_none(id=id, user__department__id__in=sub_departments, del_flag=1):
|
||
code_import.code = params.code
|
||
code_import.description = params.description
|
||
code_import.status = 3
|
||
await code_import.save()
|
||
return Response.success()
|
||
return Response.failure(msg="编码导入不存在!")
|
||
|
||
|
||
@codeAPI.get("/codeImportList", response_class=JSONResponse, response_model=GetCodeImportListResponse,
|
||
summary="查询编码导入列表")
|
||
@Auth(permission_list=["code:btn:codeImportList"])
|
||
async def get_code_import_list(
|
||
request: Request,
|
||
page: int = Query(default=1, description="页码"),
|
||
pageSize: int = Query(default=10, description="每页数量"),
|
||
code: Optional[str] = Query(default=None, description="编码"),
|
||
description: Optional[str] = Query(default=None, description="商品描述"),
|
||
username: Optional[str] = Query(default=None, description="用户名"),
|
||
status: Optional[str] = Query(default=None, description="状态"),
|
||
nickname: Optional[str] = Query(default=None, description="用户昵称"),
|
||
department_id: Optional[str] = Query(default=None, description="部门ID"),
|
||
startTime: Optional[str] = Query(default=None, description="开始时间"),
|
||
endTime: Optional[str] = Query(default=None, description="结束时间"),
|
||
current_user: dict = Depends(LoginController.get_current_user),
|
||
):
|
||
sub_departments = current_user.get("sub_departments")
|
||
filter_args = {
|
||
f'{k}__icontains': v for k, v in {
|
||
'user__username': username,
|
||
'user__nickname': nickname,
|
||
'code': code,
|
||
}.items() if v
|
||
}
|
||
if description:
|
||
# 使用全文索引优化模糊查询
|
||
filter_args['description__full_text_search'] = description
|
||
if startTime and endTime:
|
||
startTime = float(startTime) / 1000
|
||
endTime = float(endTime) / 1000
|
||
startTime = datetime.fromtimestamp(startTime)
|
||
endTime = datetime.fromtimestamp(endTime)
|
||
filter_args['create_time__range'] = [startTime, endTime]
|
||
if await hasAuth(request, "code:btn:codeImportAdmin"):
|
||
if department_id:
|
||
filter_args['user__department__id'] = department_id
|
||
else:
|
||
filter_args['user__department__id__in'] = sub_departments
|
||
else:
|
||
if department_id:
|
||
filter_args['user__department__id'] = department_id
|
||
else:
|
||
filter_args['user__department__id'] = current_user.get("department_id")
|
||
if status is not None:
|
||
filter_args['status'] = int(status)
|
||
# 查询总记录数
|
||
total = await CodeImport.filter(**filter_args, del_flag=1).count()
|
||
# 分页查询
|
||
data = await CodeImport.filter(**filter_args, del_flag=1).order_by('-create_time').offset(
|
||
(page - 1) * pageSize).limit(pageSize).values(
|
||
id="id",
|
||
code="code",
|
||
description="description",
|
||
create_time="create_time",
|
||
update_time="update_time",
|
||
user_id="user__id",
|
||
username="user__username",
|
||
nickname="user__nickname",
|
||
department_id="user__department__id",
|
||
department_name="user__department__name",
|
||
status="status",
|
||
)
|
||
return Response.success(data={
|
||
"page": page,
|
||
"pageSize": pageSize,
|
||
"result": data,
|
||
"total": total,
|
||
})
|
||
|
||
|
||
@codeAPI.put("/codeImportAudit", response_class=JSONResponse, response_model=BaseResponse, summary="编码导入审核")
|
||
@codeAPI.post("/codeImportAudit", response_class=JSONResponse, response_model=BaseResponse, summary="编码导入审核")
|
||
@Log(title="编码导入审核", business_type=BusinessType.INSERT)
|
||
@Auth(permission_list=["code:btn:codeImportAudit"])
|
||
async def code_import_audit(request: Request, params: UpdateCodeImportStatusParams,
|
||
current_user: dict = Depends(LoginController.get_current_user)):
|
||
sub_departments = current_user.get("sub_departments")
|
||
user_id = current_user.get("id")
|
||
|
||
# 批量查询 CodeImport 记录
|
||
code_imports = await CodeImport.filter(
|
||
id__in=set(params.ids),
|
||
user__department__id__in=sub_departments,
|
||
del_flag=1
|
||
)
|
||
if not code_imports:
|
||
return Response.error() # 如果没有找到符合条件的记录,返回错误
|
||
|
||
actions = [] # Elasticsearch 批量操作
|
||
codes_to_create = [] # 需要批量创建的 Code 数据
|
||
|
||
# 处理每个 CodeImport
|
||
for code_import in code_imports:
|
||
code_import.status = params.status # 更新状态
|
||
if params.status == 1: # 只有审核通过时才需要创建 Code
|
||
# 清理代码中的特殊字符
|
||
clean_code = re.sub(SPECIAL_CHARS_PATTERN, "", code_import.code).strip()
|
||
clean_description = re.sub(SPECIAL_CHARS_PATTERN, "", code_import.description).strip()
|
||
codes_to_create.append(Code(
|
||
code=clean_code,
|
||
description=clean_description,
|
||
user_id=user_id
|
||
))
|
||
|
||
if not codes_to_create: # 如果没有要创建的 Code 数据,直接返回
|
||
return Response.error("没有需要创建的编码数据")
|
||
|
||
# 执行批量数据库操作
|
||
async with in_transaction():
|
||
# 批量更新 CodeImport 状态
|
||
await CodeImport.bulk_update(code_imports, fields=["status"])
|
||
# 批量创建 Code 数据
|
||
await Code.bulk_create(codes_to_create) # 不需要接收返回值
|
||
|
||
# 构建 Elasticsearch 批量操作数据
|
||
for code_info in codes_to_create: # 使用 codes_to_create 列表中的对象
|
||
actions.append({
|
||
"_index": ElasticSearchConfig.ES_INDEX,
|
||
"_id": code_info.id, # 使用 Code 的 ID 作为 Elasticsearch 的文档 ID
|
||
"_source": {
|
||
"id": code_info.id,
|
||
"code": code_info.code,
|
||
"description": code_info.description
|
||
}
|
||
})
|
||
|
||
# 确保 Elasticsearch 索引存在
|
||
es_client = request.app.state.es
|
||
if not await es_client.indices.exists(index=ElasticSearchConfig.ES_INDEX):
|
||
await es_client.indices.create(index=ElasticSearchConfig.ES_INDEX, ignore=400)
|
||
|
||
# 批量写入 Elasticsearch 数据
|
||
if actions:
|
||
success, failed = await async_bulk(es_client, actions)
|
||
logger.info(f"成功导入 {success} 条数据,失败 {failed} 条")
|
||
|
||
return Response.success()
|
||
|
||
|
||
@codeAPI.put("/codeImportAudit/all", response_class=JSONResponse, response_model=BaseResponse, summary="全部审核通过")
|
||
@codeAPI.post("/codeImportAudit/all", response_class=JSONResponse, response_model=BaseResponse, summary="全部审核通过")
|
||
@Log(title="全部审核通过", business_type=BusinessType.UPDATE)
|
||
@Auth(permission_list=["code:btn:codeImportAuditAll"])
|
||
async def code_import_audit_all(request: Request, current_user: dict = Depends(LoginController.get_current_user)):
|
||
sub_departments = current_user.get("sub_departments")
|
||
user_id = current_user.get("id")
|
||
|
||
actions = [] # Elasticsearch 批量操作
|
||
codes_to_create = [] # 批量插入的 Code 数据
|
||
code_ids_to_update = [] # 批量更新的 CodeImport 数据
|
||
BATCH_SIZE = 10000 # 每批处理的数量
|
||
|
||
offset = 0 # 分批查询游标
|
||
|
||
# 确保 Elasticsearch 索引存在
|
||
es_client = request.app.state.es
|
||
if not await es_client.indices.exists(index=ElasticSearchConfig.ES_INDEX):
|
||
await es_client.indices.create(index=ElasticSearchConfig.ES_INDEX, ignore=400)
|
||
|
||
while True:
|
||
# 分批查询 CodeImport 数据
|
||
code_imports = await CodeImport.filter(user__department__id__in=sub_departments, del_flag=1, status=3).offset(
|
||
offset).limit(BATCH_SIZE).all()
|
||
|
||
if not code_imports:
|
||
break # 如果没有更多数据,退出循环
|
||
|
||
# 遍历处理每个 CodeImport
|
||
for code_import in code_imports:
|
||
code_import.status = 1 # 审核通过,更新状态
|
||
|
||
# 清理 code 字段中的特殊字符
|
||
cleaned_code = re.sub(SPECIAL_CHARS_PATTERN, "", str(code_import.code)).strip()
|
||
cleaned_description = re.sub(SPECIAL_CHARS_PATTERN, "", str(code_import.description)).strip()
|
||
|
||
# 批量创建 Code 数据
|
||
codes_to_create.append(Code(
|
||
code=cleaned_code,
|
||
description=cleaned_description,
|
||
user_id=user_id,
|
||
))
|
||
|
||
# 构造 Elasticsearch 批量操作数据
|
||
actions.append(
|
||
{
|
||
"_index": ElasticSearchConfig.ES_INDEX,
|
||
"_id": code_import.id, # 这里用 code_import 的 ID 作为 Elasticsearch 的 ID
|
||
"_source": {
|
||
"id": code_import.id,
|
||
"code": cleaned_code,
|
||
"description": code_import.description
|
||
}
|
||
}
|
||
)
|
||
|
||
# 保存需要更新的 CodeImport 数据
|
||
code_ids_to_update.append(code_import.id)
|
||
|
||
# 批量更新 CodeImport 的状态
|
||
if code_ids_to_update:
|
||
async with in_transaction():
|
||
await CodeImport.filter(id__in=code_ids_to_update).update(status=1)
|
||
|
||
# 批量插入 Code 数据
|
||
if codes_to_create:
|
||
await Code.bulk_create(codes_to_create)
|
||
|
||
# 批量写入 Elasticsearch
|
||
if actions:
|
||
success, failed = await async_bulk(es_client, actions)
|
||
logger.info(f"成功导入 {success} 条数据,失败 {failed} 条")
|
||
|
||
# 清空操作数组,为下一批数据准备
|
||
actions.clear()
|
||
codes_to_create.clear()
|
||
code_ids_to_update.clear()
|
||
|
||
offset += BATCH_SIZE # 更新查询游标,继续查询下一批数据
|
||
|
||
return Response.success()
|
||
|
||
|
||
@codeAPI.get("/hts", response_class=JSONResponse, response_model=BaseResponse, summary="查询HTS")
|
||
async def query_hts(request: Request, code=Query(description="编码"),
|
||
current_user: dict = Depends(LoginController.get_current_user)):
|
||
def normalize_numeric_code(code_str):
|
||
"""
|
||
处理编码字符串:
|
||
1. 只保留数字字符
|
||
2. 截取前10位(不足补0)
|
||
|
||
:param code_str: 原始编码字符串
|
||
:return: 处理后的10位纯数字编码
|
||
"""
|
||
# 1. 只保留数字
|
||
digits_only = re.sub(r'[^0-9]', '', code_str)
|
||
|
||
# 2. 锁定10位长度,右侧补0
|
||
normalized = digits_only.ljust(10, '0')[:10]
|
||
|
||
return normalized
|
||
|
||
code = normalize_numeric_code(code)
|
||
oneResult = await HtsItem.get_or_none(htsno=code[:2]).values(
|
||
id="id",
|
||
create_time="create_time",
|
||
update_time="update_time",
|
||
parent_id="parent_id",
|
||
htsno="htsno",
|
||
indent="indent",
|
||
description="description",
|
||
units="units",
|
||
general="general",
|
||
special="special",
|
||
other="other",
|
||
quota_quantity="quota_quantity",
|
||
additional_duties="additional_duties",
|
||
footnotes="footnotes",
|
||
)
|
||
secondResult = await HtsItem.get_or_none(htsno=code[:4]).values(
|
||
id="id",
|
||
create_time="create_time",
|
||
update_time="update_time",
|
||
parent_id="parent_id",
|
||
htsno="htsno",
|
||
indent="indent",
|
||
description="description",
|
||
units="units",
|
||
general="general",
|
||
special="special",
|
||
other="other",
|
||
quota_quantity="quota_quantity",
|
||
additional_duties="additional_duties",
|
||
footnotes="footnotes",
|
||
)
|
||
threeResult = await HtsItem.get_or_none(htsno=code[:6]).values(
|
||
id="id",
|
||
create_time="create_time",
|
||
update_time="update_time",
|
||
parent_id="parent_id",
|
||
htsno="htsno",
|
||
indent="indent",
|
||
description="description",
|
||
units="units",
|
||
general="general",
|
||
special="special",
|
||
other="other",
|
||
quota_quantity="quota_quantity",
|
||
additional_duties="additional_duties",
|
||
footnotes="footnotes",
|
||
)
|
||
fourResult = await HtsItem.get_or_none(htsno=code[:8]).values(
|
||
id="id",
|
||
create_time="create_time",
|
||
update_time="update_time",
|
||
parent_id="parent_id",
|
||
htsno="htsno",
|
||
indent="indent",
|
||
description="description",
|
||
units="units",
|
||
general="general",
|
||
special="special",
|
||
other="other",
|
||
quota_quantity="quota_quantity",
|
||
additional_duties="additional_duties",
|
||
footnotes="footnotes",
|
||
)
|
||
fiveResult = await HtsItem.get_or_none(htsno=code).values(
|
||
id="id",
|
||
create_time="create_time",
|
||
update_time="update_time",
|
||
parent_id="parent_id",
|
||
htsno="htsno",
|
||
indent="indent",
|
||
description="description",
|
||
units="units",
|
||
general="general",
|
||
special="special",
|
||
other="other",
|
||
quota_quantity="quota_quantity",
|
||
additional_duties="additional_duties",
|
||
footnotes="footnotes",
|
||
)
|
||
return Response.success(
|
||
data={
|
||
"oneResult": oneResult,
|
||
"secondResult": secondResult,
|
||
"threeResult": threeResult,
|
||
"fourResult": fourResult,
|
||
"fiveResult": fiveResult,
|
||
}
|
||
)
|