初始化项目
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
省12345对接模块。
|
||||
"""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
省12345对接 API 基础功能。
|
||||
"""
|
||||
from tornado.httpclient import AsyncHTTPClient
|
||||
|
||||
import dock
|
||||
from paste.core import config
|
||||
|
||||
ApiUrl = "http://172.26.192.104/api"
|
||||
"""
|
||||
对接 API 根目录。
|
||||
"""
|
||||
|
||||
|
||||
ProxyConfig = config.get_config('dock.govs.proxy')
|
||||
"""
|
||||
代理服务器配置。
|
||||
"""
|
||||
if ProxyConfig and ProxyConfig.get('proxy_host', None) and ProxyConfig.get('proxy_port', None):
|
||||
# 切换到底层实现,以便代理服务器生效
|
||||
AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient")
|
||||
|
||||
|
||||
async def new_api_request(api_url: str, request_body: dict, method: str = 'POST',
|
||||
timeout: float = dock.DEFAULT_TIMEOUT, use_form: bool = False, headers: dict = None):
|
||||
"""
|
||||
构造一个 API 请求对象
|
||||
|
||||
:param api_url: API 地址,以斜杠开头的 URI 地址,非完整 URL
|
||||
:param request_body: 请求体,即所有请求参数
|
||||
:param method: 请求提交方式
|
||||
:param timeout: 超时时长
|
||||
:param use_form: 是否使用表单(Form)方式提交
|
||||
:param headers: 头数据,最高优先级
|
||||
:return: HTTPRequest 对象
|
||||
"""
|
||||
# Token
|
||||
from dock.govs import govs_security
|
||||
token = await govs_security.get_token()
|
||||
|
||||
# 构建扩展头
|
||||
user_agent, browser_ver, os_name = dock.get_random_user_agent()
|
||||
extra_headers = {
|
||||
"Authorization": f"Bearer {token}",
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
'User-Agent': user_agent,
|
||||
}
|
||||
if headers is not None:
|
||||
extra_headers = {**extra_headers, **headers}
|
||||
|
||||
# 构造请求对象
|
||||
request = dock.new_http_request(
|
||||
url=f"{ApiUrl}{api_url}",
|
||||
body=request_body,
|
||||
method=method,
|
||||
timeout=timeout,
|
||||
use_form=use_form,
|
||||
extra_headers=extra_headers,
|
||||
** ProxyConfig
|
||||
)
|
||||
return request
|
||||
@@ -0,0 +1,113 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import json
|
||||
|
||||
from tornado.httpclient import HTTPResponse, HTTPRequest
|
||||
|
||||
import dock
|
||||
import apps
|
||||
from dock.govs import govs_api
|
||||
from dock.oa import oa_result_notify, PushException
|
||||
from models.govs_order_master import GovsOrderMaster
|
||||
from models.govs_create_delay import GovsApplicationForDelay
|
||||
from paste.core.logging import echo_log
|
||||
from paste.web import requests
|
||||
|
||||
|
||||
async def get_create_delay_request(govs_delay: GovsApplicationForDelay, govs_order: GovsOrderMaster):
|
||||
"""
|
||||
创建申请延期请求对象。方法仅创建请求对象,并未实际提交请求,具体由调度方法处理。
|
||||
|
||||
:param govs_delay: 申请延期对象
|
||||
:param govs_order: 工单对象
|
||||
:return: HTTPRequest 对象
|
||||
"""
|
||||
|
||||
api_url = '/orderhandler/OrderDelayApply/createOrderDelay'
|
||||
body = {
|
||||
"finallyTimeAfterApprove": govs_delay.finally_time_after_approve,
|
||||
"finallyTimeBeforeApprove": govs_delay.finally_time_before_approve,
|
||||
"requestDelay": govs_delay.request_delay,
|
||||
"isNatureDay": govs_delay.is_nature_day,
|
||||
"alreadyNotifyOrderUser": govs_delay.already_notify_order_user,
|
||||
"requestReason": govs_delay.request_reason,
|
||||
"remarks": govs_delay.remarks,
|
||||
"contactName": govs_delay.contact_name,
|
||||
"contactTime": govs_delay.contact_time,
|
||||
"contactType": govs_delay.contact_type,
|
||||
"contactTypeName": govs_delay.contact_type_name,
|
||||
"replyScript": govs_delay.reply_script,
|
||||
"fileList": [],
|
||||
"masterId": govs_order.master_id,
|
||||
"orderNo": govs_order.order_no,
|
||||
"processInstanceId": govs_order.process_instance_id,
|
||||
"requestDelayTime": govs_delay.request_delay_time,
|
||||
"id": "",
|
||||
"orderId": govs_order.order_id
|
||||
}
|
||||
return await govs_api.new_api_request(api_url, body)
|
||||
|
||||
|
||||
async def after_create_delay_request(response: HTTPResponse, retry_queue: asyncio.Queue[HTTPRequest]):
|
||||
"""
|
||||
提交省12345后的处理程序。
|
||||
|
||||
:param response: 响应对象
|
||||
:param retry_queue: 重试队列
|
||||
"""
|
||||
echo_log(response.body.decode())
|
||||
echo_log('申请延期请求成功.')
|
||||
|
||||
|
||||
async def create_delay(govs_delay: GovsApplicationForDelay, govs_order: GovsOrderMaster):
|
||||
"""
|
||||
推送申请延期请求。
|
||||
|
||||
:param govs_delay: 保存在数据库的申请延期对象
|
||||
:param govs_order: 数据库中的工单对象
|
||||
"""
|
||||
try:
|
||||
delay_request = await get_create_delay_request(govs_delay, govs_order)
|
||||
queue = asyncio.Queue()
|
||||
await queue.put(delay_request)
|
||||
# 仅生产环境真实提交,其他环境不实际提交
|
||||
if apps.get_active_env() not in ('dev', '', None):
|
||||
delay_response_list = await requests.async_concurrency(queue, con_count=dock.CONCURRENCY_COUNT,
|
||||
retry=dock.MAX_RETRY_COUNT,
|
||||
after_request=after_create_delay_request)
|
||||
# 检查申请延期响应是否成功
|
||||
if len(delay_response_list) != 1:
|
||||
raise PushException("申请延期请求发生错误.", govs_delay.flow_token, 3)
|
||||
return_response = delay_response_list[0]
|
||||
return_response_data = return_response.body.decode()
|
||||
return_response_data = json.loads(return_response_data)
|
||||
if return_response_data.get('code') != 200:
|
||||
raise PushException("申请延期请求发生错误.", govs_delay.flow_token, 3)
|
||||
else:
|
||||
echo_log(f"非生产环境,不实际提交.")
|
||||
# 保存成功状态
|
||||
govs_delay.status = 1
|
||||
await govs_delay.async_save()
|
||||
# 申请延期请求提交后,通知申请延期成功
|
||||
await oa_result_notify.push_result_notify(
|
||||
govs_delay.flow_token,
|
||||
'申请延期成功',
|
||||
1
|
||||
)
|
||||
except PushException as e:
|
||||
# 任何异常都意味着失败,通知 OA
|
||||
echo_log(f'申请延期发生错误.', logging.ERROR)
|
||||
echo_log(e, logging.ERROR, is_log_exc=True)
|
||||
|
||||
# 保存失败状态
|
||||
govs_delay.status = 0
|
||||
await govs_delay.async_save()
|
||||
|
||||
# 申请延期发生异常,通知申请延期失败
|
||||
await oa_result_notify.push_result_notify(
|
||||
e.flow_token, f"{e}", e.return_code
|
||||
)
|
||||
except Exception as e:
|
||||
# 其他异常都意味着失败,通知 OA
|
||||
echo_log(f'申请延期发生错误.', logging.ERROR)
|
||||
echo_log(e, logging.ERROR, is_log_exc=True)
|
||||
@@ -0,0 +1,301 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import io
|
||||
import json
|
||||
import time
|
||||
|
||||
from tornado.httpclient import HTTPResponse, HTTPRequest
|
||||
|
||||
import dock
|
||||
import apps
|
||||
from dock.govs import govs_api, govs_upload_file
|
||||
from dock.oa import oa_result_notify, oa_api_request, PushException
|
||||
from models.govs_order_master import GovsOrderMaster
|
||||
from models.govs_create_return import GovsWorkOrderReturnFormal
|
||||
from paste.core.logging import echo_log
|
||||
from paste.web import requests
|
||||
from paste.util.ufile import inspect_type
|
||||
|
||||
|
||||
async def get_create_return_request(govs_return: GovsWorkOrderReturnFormal, govs_order: GovsOrderMaster,
|
||||
file_list: list = None):
|
||||
"""
|
||||
创建申请退回请求对象。方法仅创建请求对象,并未实际提交请求,具体由调度方法处理。
|
||||
|
||||
:param govs_return: 申请退回对象
|
||||
:param govs_order: 工单对象
|
||||
:param file_list: 已上传到省12345的文件列表数据
|
||||
:return: HTTPRequest 对象
|
||||
"""
|
||||
|
||||
api_url = '/orderhandler/sendBackApply/saveSendBackApply'
|
||||
body = {
|
||||
"slaveDeptIsCompetent": [],
|
||||
"duties": "",
|
||||
"nextOrgIds": "",
|
||||
"adviceMasterOrgName": "",
|
||||
"adviceSlaveOrgNames": "",
|
||||
"searchValueList": [],
|
||||
"inforRetrieval": "",
|
||||
"nextProcessing": "",
|
||||
"assignedUnit": "",
|
||||
"duration": "",
|
||||
"dateChoose": "",
|
||||
"plannedDuration": "",
|
||||
"completionTime": "",
|
||||
"returnAuditorName": govs_return.return_auditor_name,
|
||||
'returnAuditorId': govs_return.return_auditor_id,
|
||||
"handlingSuggestion": "",
|
||||
"remark": govs_return.remark,
|
||||
"fileList": [],
|
||||
"isContact": "是",
|
||||
"contactName": "",
|
||||
"contactTime": "",
|
||||
"contactType": "",
|
||||
"nextFeedbackTime": "",
|
||||
"advice": "",
|
||||
"answer": "",
|
||||
"applyReason": "",
|
||||
"applyBasis": "",
|
||||
"platformOpinion": "",
|
||||
"shortMessage": "",
|
||||
"distributor": "",
|
||||
"distributors": [],
|
||||
"positionSelection": "",
|
||||
"difficultReason": "",
|
||||
"directCompletionType": "",
|
||||
"applyType": "",
|
||||
"returnReason": govs_return.return_reason_name,
|
||||
"returnReasonName": govs_return.return_reason_name,
|
||||
"replyResult": "",
|
||||
"informPublic": "是",
|
||||
"approveAttachmentIds": "",
|
||||
"formalReply": "",
|
||||
"flowMap": {
|
||||
"nextHandleName": "工单退回",
|
||||
"nextHandle": "工单退回"
|
||||
},
|
||||
"adviceMasterOrgId": "",
|
||||
"adviceSlaveOrgIdsList": [],
|
||||
"id": "",
|
||||
"key": "",
|
||||
"nextHandler": "",
|
||||
"nextOrgId": "",
|
||||
"processInstanceId": govs_order.process_instance_id,
|
||||
"reason": govs_return.reason,
|
||||
"taskHandlerId": "",
|
||||
"value": "",
|
||||
"vote": "",
|
||||
"assignedUnitList": [],
|
||||
"assignedUnitLabel": "",
|
||||
"nextOrgIdList": [],
|
||||
"nextOrgIdStr": "",
|
||||
"knowledgeQuote": "[]",
|
||||
"defineAuditorId": "",
|
||||
"defineAuditorName": "",
|
||||
"visitTypes": " ",
|
||||
"appeal1": "",
|
||||
"appeal2": "",
|
||||
"unreasonableDemands": "",
|
||||
"complainant": "",
|
||||
"pollutionType": "",
|
||||
"pollutionType1": "",
|
||||
"pollutionType2": "",
|
||||
"involvedTargets": "",
|
||||
"problemCategory": "生态环境类",
|
||||
"defendantType": "",
|
||||
"reportingPurpose": "投诉举报",
|
||||
"industryType": "",
|
||||
"industryType1": "",
|
||||
"industryType2": "",
|
||||
"complainants": [
|
||||
{
|
||||
"complainant": "",
|
||||
"region": "",
|
||||
"street": "",
|
||||
"detailedAddress": "",
|
||||
"show": True,
|
||||
"disabled": False
|
||||
}
|
||||
],
|
||||
"inforAddress": "",
|
||||
"associatedDefendantType": "",
|
||||
"adminLawEnf": "",
|
||||
"approveResult": "",
|
||||
"approveContent": "",
|
||||
"nextOrgIdsName": [],
|
||||
"noticeOrgId": "",
|
||||
"completeType": "",
|
||||
"caseAccordTypeOneName": govs_order.case_accord_type_one_name,
|
||||
"caseAccordTypeTwoName": govs_order.case_accord_type_two_name,
|
||||
"caseAccordTypeThreeName": govs_order.case_accord_type_three_name,
|
||||
"caseAccordTypeFourName": "",
|
||||
"caseAccordTypeFiveName": "",
|
||||
"fileVos": [],
|
||||
"dealOpinion": govs_return.deal_opinion,
|
||||
"actionName": govs_return.action_name,
|
||||
"orderId": govs_order.order_id,
|
||||
"taskId": govs_order.next_task_id,
|
||||
"submitType": "0",
|
||||
"adviceSlaveOrgIds": "",
|
||||
"masterId": str(govs_return.master_id),
|
||||
"orderNo": govs_order.order_no,
|
||||
}
|
||||
if govs_return.return_auditor_name and file_list:
|
||||
body['fileList'] = file_list
|
||||
body['fileVos'] = file_list
|
||||
return await govs_api.new_api_request(api_url, body)
|
||||
|
||||
|
||||
async def after_create_return_request(response: HTTPResponse, retry_queue: asyncio.Queue[HTTPRequest]):
|
||||
"""
|
||||
提交省12345后的处理程序。
|
||||
|
||||
:param response: 响应对象
|
||||
:param retry_queue: 重试队列
|
||||
"""
|
||||
echo_log(response.body.decode())
|
||||
echo_log('申请退回请求成功.')
|
||||
|
||||
|
||||
async def done_file_download(response_list: list[HTTPResponse]):
|
||||
"""
|
||||
所有附件下载完成执行的处理程序。
|
||||
|
||||
:param response_list: 附件下载响应列表
|
||||
:return: 返回附件字典列表,每个元素包含文件名和io对象
|
||||
"""
|
||||
file_info_list = []
|
||||
for response in response_list:
|
||||
file_type = inspect_type(response.body)
|
||||
basename = os.path.basename(response.request.url)
|
||||
file_io = io.BytesIO(response.body)
|
||||
file_info_list.append({
|
||||
'file_name': f'{basename}.{file_type}',
|
||||
'file_io': file_io
|
||||
})
|
||||
return file_info_list
|
||||
|
||||
|
||||
async def done_file_upload(response_list: list[HTTPResponse]):
|
||||
"""
|
||||
文件上传完成后的处理程序
|
||||
|
||||
:param response_list: 附件上传响应列表
|
||||
:return: 返回上次后的文件信息列表,包含文件名、文件路径
|
||||
"""
|
||||
uploaded_list = []
|
||||
for response in response_list:
|
||||
response_body = response.body.decode()
|
||||
response_data = json.loads(response_body)
|
||||
if response_data['msg'] == '附件上传成功!':
|
||||
uploaded_list.append({
|
||||
'file_name': getattr(response.request, 'file_name', 'file.bin'),
|
||||
'path': response_data['data']
|
||||
})
|
||||
else:
|
||||
echo_log(f'文件上传到省12345失败,{response_data}')
|
||||
return uploaded_list
|
||||
|
||||
|
||||
async def download_and_upload_files(file_id_str: str):
|
||||
"""
|
||||
从OA下载文件,上传到省12345,返回上传后的文件信息列表
|
||||
|
||||
:param file_id_str: 英文逗号分隔的OA文件id
|
||||
"""
|
||||
file_id_list = file_id_str.strip(',').split(',')
|
||||
download_queue = asyncio.Queue()
|
||||
for file_id in file_id_list:
|
||||
download_request = await oa_api_request.get_download_request(file_id)
|
||||
await download_queue.put(download_request)
|
||||
file_info_list = await requests.async_concurrency(download_queue, con_count=dock.CONCURRENCY_COUNT,
|
||||
retry=dock.MAX_RETRY_COUNT, after_done=done_file_download)
|
||||
upload_queue = asyncio.Queue()
|
||||
for file_info in file_info_list:
|
||||
upload_request = await govs_upload_file.get_upload_request(file_info['file_name'], file_info['file_io'])
|
||||
setattr(upload_request, 'file_name', file_info['file_name'])
|
||||
await upload_queue.put(upload_request)
|
||||
uploaded_list = await requests.async_concurrency(upload_queue, con_count=dock.CONCURRENCY_COUNT,
|
||||
retry=dock.MAX_RETRY_COUNT, after_done=done_file_upload)
|
||||
return uploaded_list
|
||||
|
||||
|
||||
async def create_return(govs_return: GovsWorkOrderReturnFormal, govs_order: GovsOrderMaster):
|
||||
"""
|
||||
推送申请退回请求。
|
||||
|
||||
:param govs_return: 保存在数据库的申请退回对象
|
||||
:param govs_order: 数据库中的工单对象
|
||||
"""
|
||||
try:
|
||||
# 仅生产环境真实提交,其他环境不实际提交
|
||||
if apps.get_active_env() not in ('dev', '', None):
|
||||
if govs_return.file_id_str:
|
||||
# 根据OA传过来的文件id,下载并上传到省12345
|
||||
file_info_list = await download_and_upload_files(govs_return.file_id_str)
|
||||
file_info_list = [{
|
||||
"name": info['file_name'],
|
||||
"filePath": info['path'],
|
||||
"orderId": govs_order.order_id,
|
||||
"uid": int(time.time() * 1000),
|
||||
"status": "success"
|
||||
} for info in file_info_list]
|
||||
else:
|
||||
file_info_list = None
|
||||
return_request = await get_create_return_request(govs_return, govs_order, file_info_list)
|
||||
queue = asyncio.Queue()
|
||||
await queue.put(return_request)
|
||||
return_response_list = await requests.async_concurrency(queue, con_count=dock.CONCURRENCY_COUNT,
|
||||
retry=dock.MAX_RETRY_COUNT,
|
||||
after_request=after_create_return_request)
|
||||
# 检查申请退回响应是否成功
|
||||
if len(return_response_list) != 1:
|
||||
raise PushException("申请退回请求发生错误.", govs_return.flow_token, 3)
|
||||
return_response = return_response_list[0]
|
||||
return_response_data = return_response.body.decode()
|
||||
return_response_data = json.loads(return_response_data)
|
||||
if return_response_data.get('code') != 200 or '退回申请提交成功' not in return_response_data.get('data'):
|
||||
raise PushException("申请退回请求发生错误.", govs_return.flow_token, 3)
|
||||
else:
|
||||
echo_log(f"非生产环境,不实际提交.")
|
||||
# 保存成功状态
|
||||
govs_return.status = 1
|
||||
await govs_return.async_save()
|
||||
# 申请退回请求提交后,通知申请退回成功
|
||||
await oa_result_notify.push_result_notify(
|
||||
govs_return.flow_token,
|
||||
'申请退回成功',
|
||||
1
|
||||
)
|
||||
except PushException as e:
|
||||
# 任何异常都意味着失败,通知 OA
|
||||
echo_log(f'申请退回发生错误.', logging.ERROR)
|
||||
echo_log(e, logging.ERROR, is_log_exc=True)
|
||||
|
||||
# 保存失败状态
|
||||
govs_return.status = 0
|
||||
await govs_return.async_save()
|
||||
|
||||
# 申请退回发生异常,通知申请退回失败
|
||||
await oa_result_notify.push_result_notify(
|
||||
e.flow_token, f"{e}", e.return_code
|
||||
)
|
||||
except Exception as e:
|
||||
# 其他异常都意味着失败,通知 OA
|
||||
echo_log(f'申请退回发生错误.', logging.ERROR)
|
||||
echo_log(e, logging.ERROR, is_log_exc=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
async def push():
|
||||
task = await GovsOrderMaster.async_find_by_id(2060477579990339586)
|
||||
return_request = await GovsWorkOrderReturnFormal.async_find_by_id(2061344445634318336)
|
||||
await create_return(return_request, task)
|
||||
|
||||
|
||||
from paste.core import aio_pool
|
||||
|
||||
_runner = aio_pool.get_aio_runner()
|
||||
_runner(push())
|
||||
@@ -0,0 +1,306 @@
|
||||
import asyncio
|
||||
import os
|
||||
import io
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from tornado.httpclient import HTTPResponse, HTTPRequest
|
||||
|
||||
import dock
|
||||
import apps
|
||||
from models.govs_create_reply import GovsReplyFormal
|
||||
from models.govs_order_master import GovsOrderMaster
|
||||
from dock.govs import govs_api, govs_upload_file
|
||||
from dock.oa import oa_api_request, oa_result_notify, PushException
|
||||
from paste.core.logging import echo_log
|
||||
from paste.util.ufile import inspect_type
|
||||
from paste.web import requests
|
||||
|
||||
|
||||
async def get_reply_request(govs_reply: GovsReplyFormal, govs_order: GovsOrderMaster, file_list: list = None):
|
||||
"""
|
||||
创建答复办结请求对象。方法仅创建请求对象,并未实际提交请求,具体由调度方法处理。
|
||||
|
||||
:param govs_reply: 答复办结对象
|
||||
:param govs_order: 工单对象
|
||||
:param file_list: 已上传到省12345的文件列表数据
|
||||
:return: HTTPRequest 对象
|
||||
"""
|
||||
api_url = '/workflow/approveTask/orderApprove'
|
||||
# 默认不加前缀
|
||||
prefix = ""
|
||||
|
||||
# 只有 contact_name 和 contact_time 都存在时才拼接前缀
|
||||
if govs_reply.contact_name and govs_reply.contact_time:
|
||||
# 格式化时间
|
||||
try:
|
||||
dt = datetime.fromisoformat(govs_reply.contact_time.replace('Z', '+00:00'))
|
||||
formatted_contact_time = dt.strftime('%Y-%m-%d %H:%M')
|
||||
except Exception:
|
||||
formatted_contact_time = govs_reply.contact_time
|
||||
prefix = f"您好,{govs_reply.contact_name}于{formatted_contact_time}通过{govs_reply.contact_type}方式联系您;"
|
||||
body = {
|
||||
"slaveDeptIsCompetent": [],
|
||||
"duties": "",
|
||||
"nextOrgIds": "",
|
||||
"adviceMasterOrgName": "",
|
||||
"adviceSlaveOrgNames": "",
|
||||
"searchValueList": [],
|
||||
"inforRetrieval": "",
|
||||
"nextProcessing": "",
|
||||
"assignedUnit": "",
|
||||
"duration": "",
|
||||
"dateChoose": "",
|
||||
"plannedDuration": "",
|
||||
"completionTime": "",
|
||||
"returnAuditorName": "",
|
||||
"handlingSuggestion": "",
|
||||
"remark": govs_reply.remarks,
|
||||
"fileList": file_list,
|
||||
"isContact": "是",
|
||||
"contactName": govs_reply.contact_name,
|
||||
"contactTime": govs_reply.contact_time,
|
||||
"contactType": govs_reply.contact_type,
|
||||
"nextFeedbackTime": "",
|
||||
"advice": prefix + (govs_reply.advice or ""),
|
||||
"answer": "",
|
||||
"applyReason": "",
|
||||
"applyBasis": "",
|
||||
"platformOpinion": "",
|
||||
"shortMessage": "",
|
||||
"distributor": "",
|
||||
"distributors": [],
|
||||
"positionSelection": "",
|
||||
"difficultReason": "",
|
||||
"directCompletionType": "",
|
||||
"applyType": "",
|
||||
"returnReason": "",
|
||||
"returnReasonName": "",
|
||||
"replyResult": "",
|
||||
"informPublic": govs_reply.is_contact, # 这个还要确认一遍
|
||||
"approveAttachmentIds": "",
|
||||
"formalReply": "",
|
||||
"flowMap": {
|
||||
"nextHandleName": "答复办结",
|
||||
"nextHandle": "答复办结"
|
||||
},
|
||||
"adviceMasterOrgId": "",
|
||||
"adviceSlaveOrgIdsList": [],
|
||||
"id": govs_order.next_task_id,
|
||||
"key": "",
|
||||
"nextHandler": "",
|
||||
"nextOrgId": "",
|
||||
"processInstanceId": govs_order.process_instance_id,
|
||||
"reason": prefix + (govs_reply.reason or ""),
|
||||
"taskHandlerId": "",
|
||||
"value": "",
|
||||
"vote": "",
|
||||
"assignedUnitList": [],
|
||||
"assignedUnitLabel": "",
|
||||
"nextOrgIdList": [],
|
||||
"nextOrgIdStr": "",
|
||||
"knowledgeQuote": "[]",
|
||||
"defineAuditorId": "",
|
||||
"defineAuditorName": "",
|
||||
"visitTypes": " ",
|
||||
"appeal1": "",
|
||||
"appeal2": "",
|
||||
"unreasonableDemands": "",
|
||||
"complainant": "",
|
||||
"pollutionType": "-",
|
||||
"pollutionType1": "",
|
||||
"pollutionType2": "",
|
||||
"involvedTargets": "",
|
||||
"problemCategory": "生态环境类",
|
||||
"defendantType": "",
|
||||
"reportingPurpose": "投诉举报",
|
||||
"industryType": "-",
|
||||
"industryType1": "",
|
||||
"industryType2": "",
|
||||
"complainants": [
|
||||
{
|
||||
"complainant": "",
|
||||
"region": "",
|
||||
"street": "",
|
||||
"detailedAddress": "",
|
||||
"show": True,
|
||||
"disabled": False
|
||||
}
|
||||
],
|
||||
"inforAddress": "",
|
||||
"associatedDefendantType": "",
|
||||
"adminLawEnf": "",
|
||||
"approveResult": "",
|
||||
"approveContent": "",
|
||||
"nextOrgIdsName": [],
|
||||
"noticeOrgId": "",
|
||||
"completeType": "",
|
||||
"caseAccordTypeOneName": govs_order.case_accord_type_one_name,
|
||||
"caseAccordTypeTwoName": govs_order.case_accord_type_two_name,
|
||||
"caseAccordTypeThreeName": govs_order.case_accord_type_three_name,
|
||||
"caseAccordTypeFourName": "",
|
||||
"caseAccordTypeFiveName": "",
|
||||
"fileVos": file_list,
|
||||
"reasonableLabels": "-",
|
||||
"visitType": "",
|
||||
"actionName": govs_reply.action_name,
|
||||
"businessKey": govs_order.order_id,
|
||||
"masterId": govs_reply.master_id,
|
||||
"orderNo": govs_order.order_no
|
||||
}
|
||||
return await govs_api.new_api_request(api_url, body)
|
||||
|
||||
|
||||
async def after_create_reply_request(response: HTTPResponse, retry_queue: asyncio.Queue[HTTPRequest]):
|
||||
"""
|
||||
提交省12345后的处理程序。
|
||||
|
||||
:param response: 响应对象
|
||||
:param retry_queue: 重试队列
|
||||
"""
|
||||
echo_log(response.body.decode())
|
||||
echo_log('答复办结请求成功.')
|
||||
|
||||
|
||||
async def done_file_download(response_list: list[HTTPResponse]):
|
||||
"""
|
||||
所有附件下载完成执行的处理程序。
|
||||
|
||||
:param response_list: 附件下载响应列表
|
||||
:return: 返回附件字典列表,每个元素包含文件名和io对象
|
||||
"""
|
||||
file_info_list = []
|
||||
for response in response_list:
|
||||
file_type = inspect_type(response.body)
|
||||
basename = os.path.basename(response.request.url)
|
||||
file_io = io.BytesIO(response.body)
|
||||
file_info_list.append({
|
||||
'file_name': f'{basename}.{file_type}',
|
||||
'file_io': file_io
|
||||
})
|
||||
return file_info_list
|
||||
|
||||
|
||||
async def done_file_upload(response_list: list[HTTPResponse]):
|
||||
"""
|
||||
文件上传完成后的处理程序
|
||||
|
||||
:param response_list: 附件上传响应列表
|
||||
:return: 返回上次后的文件信息列表,包含文件名、文件路径
|
||||
"""
|
||||
uploaded_list = []
|
||||
for response in response_list:
|
||||
response_body = response.body.decode()
|
||||
response_data = json.loads(response_body)
|
||||
if response_data['msg'] == '附件上传成功!':
|
||||
uploaded_list.append({
|
||||
'file_name': getattr(response.request, 'file_name', 'file.bin'),
|
||||
'path': response_data['data']
|
||||
})
|
||||
else:
|
||||
echo_log(f'文件上传到省12345失败,{response_data}')
|
||||
return uploaded_list
|
||||
|
||||
|
||||
async def download_and_upload_files(file_id_str: str):
|
||||
"""
|
||||
从OA下载文件,上传到省12345,返回上传后的文件信息列表
|
||||
|
||||
:param file_id_str: 英文逗号分隔的OA文件id
|
||||
"""
|
||||
file_id_list = file_id_str.strip(',').split(',')
|
||||
download_queue = asyncio.Queue()
|
||||
for file_id in file_id_list:
|
||||
download_request = await oa_api_request.get_download_request(file_id)
|
||||
await download_queue.put(download_request)
|
||||
file_info_list = await requests.async_concurrency(download_queue, con_count=dock.CONCURRENCY_COUNT,
|
||||
retry=dock.MAX_RETRY_COUNT, after_done=done_file_download)
|
||||
upload_queue = asyncio.Queue()
|
||||
for file_info in file_info_list:
|
||||
upload_request = await govs_upload_file.get_upload_request(file_info['file_name'], file_info['file_io'])
|
||||
setattr(upload_request, 'file_name', file_info['file_name'])
|
||||
await upload_queue.put(upload_request)
|
||||
uploaded_list = await requests.async_concurrency(upload_queue, con_count=dock.CONCURRENCY_COUNT,
|
||||
retry=dock.MAX_RETRY_COUNT, after_done=done_file_upload)
|
||||
return uploaded_list
|
||||
|
||||
|
||||
async def create_reply(govs_reply: GovsReplyFormal, govs_order: GovsOrderMaster):
|
||||
"""
|
||||
推送答复办结请求。
|
||||
|
||||
:param govs_reply: 保存在数据库的答复办结对象
|
||||
:param govs_order: 数据库中的工单对象
|
||||
"""
|
||||
try:
|
||||
# 仅生产环境真实提交,其他环境不实际提交
|
||||
if apps.get_active_env() not in ('dev', '', None):
|
||||
if govs_reply.file_id_str:
|
||||
# 根据OA传过来的文件id,下载并上传到省12345
|
||||
file_info_list = await download_and_upload_files(govs_reply.file_id_str)
|
||||
file_info_list = [{
|
||||
"name": info['file_name'],
|
||||
"filePath": info['path'],
|
||||
"orderId": govs_order.order_id,
|
||||
"uid": int(time.time() * 1000),
|
||||
"status": "success"
|
||||
} for info in file_info_list]
|
||||
else:
|
||||
file_info_list = None
|
||||
reply_request = await get_reply_request(govs_reply, govs_order, file_info_list)
|
||||
queue = asyncio.Queue()
|
||||
await queue.put(reply_request)
|
||||
reply_response_list = await requests.async_concurrency(queue, con_count=dock.CONCURRENCY_COUNT,
|
||||
retry=dock.MAX_RETRY_COUNT,
|
||||
after_request=after_create_reply_request)
|
||||
# 检查答复办结响应是否成功
|
||||
if len(reply_response_list) != 1:
|
||||
raise PushException("答复办结请求发生错误.", govs_reply.flow_token, 3)
|
||||
return_response = reply_response_list[0]
|
||||
return_response_data = return_response.body.decode()
|
||||
return_response_data = json.loads(return_response_data)
|
||||
if return_response_data.get('code') != 200 or return_response_data.get('data') != 'ok':
|
||||
raise PushException("答复办结请求发生错误.", govs_reply.flow_token, 3)
|
||||
else:
|
||||
echo_log(f"非生产环境,不实际提交.")
|
||||
# 保存成功状态
|
||||
govs_reply.status = 1
|
||||
await govs_reply.async_save()
|
||||
# 答复办结请求提交后,通知答复办结成功
|
||||
await oa_result_notify.push_result_notify(
|
||||
govs_reply.flow_token,
|
||||
'答复办结成功',
|
||||
1
|
||||
)
|
||||
except PushException as e:
|
||||
# 任何异常都意味着失败,通知 OA
|
||||
echo_log(f'答复办结发生错误.', logging.ERROR)
|
||||
echo_log(e, logging.ERROR, is_log_exc=True)
|
||||
|
||||
# 保存失败状态
|
||||
govs_reply.status = 0
|
||||
await govs_reply.async_save()
|
||||
|
||||
# 答复办结发生异常,通知答复办结失败
|
||||
await oa_result_notify.push_result_notify(
|
||||
e.flow_token, f"{e}", e.return_code
|
||||
)
|
||||
except Exception as e:
|
||||
# 其他异常都意味着失败
|
||||
echo_log(f'答复办结发生错误.', logging.ERROR)
|
||||
echo_log(e, logging.ERROR, is_log_exc=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
async def push():
|
||||
task = await GovsOrderMaster.async_find_by_id(2060173985047351297)
|
||||
reply = await GovsReplyFormal.async_find_by_id(2061328980866371584)
|
||||
await create_reply(reply, task)
|
||||
|
||||
|
||||
from paste.core import aio_pool
|
||||
|
||||
_runner = aio_pool.get_aio_runner()
|
||||
_runner(push())
|
||||
@@ -0,0 +1,22 @@
|
||||
from typing import Union
|
||||
import base64
|
||||
from urllib.parse import quote, urlencode
|
||||
from dock.govs import govs_api
|
||||
|
||||
|
||||
async def get_download_request(tenant_id: Union[int, str], file_url: str):
|
||||
"""
|
||||
创建从省12345下载文件的请求对象。方法仅创建请求对象,并未实际提交请求,具体由调度方法处理。
|
||||
|
||||
:param tenant_id: 租户id
|
||||
:param file_url: 文件url
|
||||
"""
|
||||
api_url = '/file/api/system/downloadPermission'
|
||||
b64_file_url = base64.b64encode(file_url.encode()).decode()
|
||||
b64_file_url = quote(b64_file_url, safe="~*'()!.-_")
|
||||
body = {
|
||||
'tenantId': tenant_id,
|
||||
'fileUrl': b64_file_url
|
||||
}
|
||||
api_url += f'?{urlencode(body)}'
|
||||
return await govs_api.new_api_request(api_url, {})
|
||||
@@ -0,0 +1,281 @@
|
||||
import asyncio
|
||||
import os
|
||||
import io
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
|
||||
from tornado.httpclient import HTTPResponse, HTTPRequest
|
||||
|
||||
import dock
|
||||
import apps
|
||||
from models.govs_phase_wise_completion import GovsPhaseWiseCompletion
|
||||
from models.govs_order_master import GovsOrderMaster
|
||||
from dock.govs import govs_api, govs_upload_file
|
||||
from dock.oa import oa_api_request, oa_result_notify, PushException
|
||||
from paste.core.logging import echo_log
|
||||
from paste.util.ufile import inspect_type
|
||||
from paste.web import requests
|
||||
|
||||
|
||||
async def get_phase_request(phase_wise_completion: GovsPhaseWiseCompletion, govs_order: GovsOrderMaster,
|
||||
file_list: list = None):
|
||||
"""
|
||||
创建阶段性办结请求对象。方法仅创建请求对象,并未实际提交请求,具体由调度方法处理。
|
||||
|
||||
:param phase_wise_completion: 阶段性办结对象
|
||||
:param govs_order: 工单对象
|
||||
:param file_list: 已上传到省12345的文件列表数据
|
||||
:return: HTTPRequest 对象
|
||||
"""
|
||||
api_url = '/orderhandler/remAndSup/savePeriodicCompletion'
|
||||
body = {
|
||||
"slaveDeptIsCompetent": [],
|
||||
"duties": "",
|
||||
"nextOrgIds": "",
|
||||
"adviceMasterOrgName": "",
|
||||
"adviceSlaveOrgNames": "",
|
||||
"searchValueList": [],
|
||||
"inforRetrieval": "",
|
||||
"nextProcessing": "",
|
||||
"assignedUnit": "",
|
||||
"duration": "",
|
||||
"dateChoose": "",
|
||||
"plannedDuration": "",
|
||||
"completionTime": "",
|
||||
"returnAuditorName": "",
|
||||
"handlingSuggestion": "",
|
||||
"remark": phase_wise_completion.remark,
|
||||
"fileList": file_list,
|
||||
"isContact": phase_wise_completion.is_contact,
|
||||
"contactName": phase_wise_completion.contact_name,
|
||||
"contactTime": phase_wise_completion.contact_time,
|
||||
"contactType": phase_wise_completion.contact_type,
|
||||
"nextFeedbackTime": phase_wise_completion.next_feedback_time,
|
||||
"advice": phase_wise_completion.advice,
|
||||
"answer": "",
|
||||
"applyReason": "",
|
||||
"applyBasis": "",
|
||||
"platformOpinion": "",
|
||||
"shortMessage": "",
|
||||
"distributor": "",
|
||||
"distributors": [],
|
||||
"positionSelection": "",
|
||||
"difficultReason": "",
|
||||
"directCompletionType": "",
|
||||
"applyType": "",
|
||||
"returnReason": "",
|
||||
"returnReasonName": "",
|
||||
"replyResult": "",
|
||||
"informPublic": "是",
|
||||
"approveAttachmentIds": "",
|
||||
"formalReply": "",
|
||||
"flowMap": {
|
||||
"nextHandleName": "阶段性办结",
|
||||
"nextHandle": "阶段性办结"
|
||||
},
|
||||
"adviceMasterOrgId": "",
|
||||
"adviceSlaveOrgIdsList": [],
|
||||
"id": "",
|
||||
"key": "",
|
||||
"nextHandler": "",
|
||||
"nextOrgId": "",
|
||||
"processInstanceId": govs_order.process_instance_id,
|
||||
"reason": phase_wise_completion.reason,
|
||||
"taskHandlerId": "",
|
||||
"value": "",
|
||||
"vote": "",
|
||||
"assignedUnitList": [],
|
||||
"assignedUnitLabel": "",
|
||||
"nextOrgIdList": [],
|
||||
"nextOrgIdStr": "",
|
||||
"knowledgeQuote": "[]",
|
||||
"defineAuditorId": "",
|
||||
"defineAuditorName": "",
|
||||
"visitTypes": " ",
|
||||
"appeal1": "",
|
||||
"appeal2": "",
|
||||
"unreasonableDemands": "",
|
||||
"complainant": "",
|
||||
"pollutionType": "",
|
||||
"pollutionType1": "",
|
||||
"pollutionType2": "",
|
||||
"involvedTargets": "",
|
||||
"problemCategory": "生态环境类",
|
||||
"defendantType": "",
|
||||
"reportingPurpose": "投诉举报",
|
||||
"industryType": "",
|
||||
"industryType1": "",
|
||||
"industryType2": "",
|
||||
"complainants": [
|
||||
{
|
||||
"complainant": "",
|
||||
"region": "",
|
||||
"street": "",
|
||||
"detailedAddress": "",
|
||||
"show": True,
|
||||
"disabled": False
|
||||
}
|
||||
],
|
||||
"inforAddress": "",
|
||||
"associatedDefendantType": "",
|
||||
"adminLawEnf": "",
|
||||
"approveResult": "",
|
||||
"approveContent": "",
|
||||
"nextOrgIdsName": [],
|
||||
"noticeOrgId": "",
|
||||
"completeType": "",
|
||||
"caseAccordTypeOneName": govs_order.case_accord_type_one_name,
|
||||
"caseAccordTypeTwoName": govs_order.case_accord_type_two_name,
|
||||
"caseAccordTypeThreeName": govs_order.case_accord_type_three_name,
|
||||
"caseAccordTypeFourName": "",
|
||||
"caseAccordTypeFiveName": "",
|
||||
"fileVos": file_list,
|
||||
"actionName": phase_wise_completion.action_name,
|
||||
"orderId": govs_order.order_id,
|
||||
"taskId": govs_order.next_task_id,
|
||||
"submitType": "0",
|
||||
"masterId": phase_wise_completion.master_id,
|
||||
"orderNo": govs_order.order_no
|
||||
}
|
||||
return await govs_api.new_api_request(api_url, body)
|
||||
|
||||
|
||||
async def after_phase_request(response: HTTPResponse, retry_queue: asyncio.Queue[HTTPRequest]):
|
||||
"""
|
||||
提交省12345后的处理程序。
|
||||
|
||||
:param response: 响应对象
|
||||
:param retry_queue: 重试队列
|
||||
"""
|
||||
echo_log(response.body.decode())
|
||||
echo_log('阶段性办结请求成功.')
|
||||
|
||||
|
||||
async def done_file_download(response_list: list[HTTPResponse]):
|
||||
"""
|
||||
所有附件下载完成执行的处理程序。
|
||||
|
||||
:param response_list: 附件下载响应列表
|
||||
:return: 返回附件字典列表,每个元素包含文件名和io对象
|
||||
"""
|
||||
file_info_list = []
|
||||
for response in response_list:
|
||||
file_type = inspect_type(response.body)
|
||||
basename = os.path.basename(response.request.url)
|
||||
file_io = io.BytesIO(response.body)
|
||||
file_info_list.append({
|
||||
'file_name': f'{basename}.{file_type}',
|
||||
'file_io': file_io
|
||||
})
|
||||
return file_info_list
|
||||
|
||||
|
||||
async def done_file_upload(response_list: list[HTTPResponse]):
|
||||
"""
|
||||
文件上传完成后的处理程序
|
||||
|
||||
:param response_list: 附件上传响应列表
|
||||
:return: 返回上次后的文件信息列表,包含文件名、文件路径
|
||||
"""
|
||||
uploaded_list = []
|
||||
for response in response_list:
|
||||
response_body = response.body.decode()
|
||||
response_data = json.loads(response_body)
|
||||
if response_data['msg'] == '附件上传成功!':
|
||||
uploaded_list.append({
|
||||
'file_name': getattr(response.request, 'file_name', 'file.bin'),
|
||||
'path': response_data['data']
|
||||
})
|
||||
else:
|
||||
echo_log(f'文件上传到省12345失败,{response_data}')
|
||||
return uploaded_list
|
||||
|
||||
|
||||
async def download_and_upload_files(file_id_str: str):
|
||||
"""
|
||||
从OA下载文件,上传到省12345,返回上传后的文件信息列表
|
||||
|
||||
:param file_id_str: 英文逗号分隔的OA文件id
|
||||
"""
|
||||
file_id_list = file_id_str.strip(',').split(',')
|
||||
download_queue = asyncio.Queue()
|
||||
for file_id in file_id_list:
|
||||
download_request = await oa_api_request.get_download_request(file_id)
|
||||
await download_queue.put(download_request)
|
||||
file_info_list = await requests.async_concurrency(download_queue, con_count=dock.CONCURRENCY_COUNT,
|
||||
retry=dock.MAX_RETRY_COUNT, after_done=done_file_download)
|
||||
upload_queue = asyncio.Queue()
|
||||
for file_info in file_info_list:
|
||||
upload_request = await govs_upload_file.get_upload_request(file_info['file_name'], file_info['file_io'])
|
||||
setattr(upload_request, 'file_name', file_info['file_name'])
|
||||
await upload_queue.put(upload_request)
|
||||
uploaded_list = await requests.async_concurrency(upload_queue, con_count=dock.CONCURRENCY_COUNT,
|
||||
retry=dock.MAX_RETRY_COUNT, after_done=done_file_upload)
|
||||
return uploaded_list
|
||||
|
||||
|
||||
async def create_phase_wise_completion(phase_wise_completion: GovsPhaseWiseCompletion, govs_order: GovsOrderMaster):
|
||||
"""
|
||||
推送阶段性办结请求。
|
||||
|
||||
:param phase_wise_completion: 保存在数据库的阶段性办结对象
|
||||
:param govs_order: 数据库中的工单对象
|
||||
"""
|
||||
try:
|
||||
# 仅生产环境真实提交,其他环境不实际提交
|
||||
if apps.get_active_env() not in ('dev', '', None):
|
||||
if phase_wise_completion.file_id_str:
|
||||
# 根据OA传过来的文件id,下载并上传到省12345
|
||||
file_info_list = await download_and_upload_files(phase_wise_completion.file_id_str)
|
||||
file_info_list = [{
|
||||
"name": info['file_name'],
|
||||
"filePath": info['path'],
|
||||
"orderId": govs_order.order_id,
|
||||
"uid": int(time.time() * 1000),
|
||||
"status": "success"
|
||||
} for info in file_info_list]
|
||||
else:
|
||||
file_info_list = None
|
||||
phase_request = await get_phase_request(phase_wise_completion, govs_order, file_info_list)
|
||||
queue = asyncio.Queue()
|
||||
await queue.put(phase_request)
|
||||
phase_response_list = await requests.async_concurrency(queue, con_count=dock.CONCURRENCY_COUNT,
|
||||
retry=dock.MAX_RETRY_COUNT,
|
||||
after_request=after_phase_request)
|
||||
# 检查阶段性办结响应是否成功
|
||||
if len(phase_response_list) != 1:
|
||||
raise PushException("阶段性办结请求发生错误.", phase_wise_completion.flow_token, 3)
|
||||
return_response = phase_response_list[0]
|
||||
return_response_data = return_response.body.decode()
|
||||
return_response_data = json.loads(return_response_data)
|
||||
if return_response_data.get('code') != 200:
|
||||
raise PushException("阶段性办结请求发生错误.", phase_wise_completion.flow_token, 3)
|
||||
else:
|
||||
echo_log(f"非生产环境,不实际提交.")
|
||||
# 保存成功状态
|
||||
phase_wise_completion.status = 1
|
||||
await phase_wise_completion.async_save()
|
||||
# 阶段性办结请求提交后,通知阶段性办结成功
|
||||
await oa_result_notify.push_result_notify(
|
||||
phase_wise_completion.flow_token,
|
||||
'阶段性办结成功',
|
||||
1
|
||||
)
|
||||
except PushException as e:
|
||||
# 任何异常都意味着失败,通知 OA
|
||||
echo_log(f'阶段性办结发生错误.', logging.ERROR)
|
||||
echo_log(e, logging.ERROR, is_log_exc=True)
|
||||
|
||||
# 保存失败状态
|
||||
phase_wise_completion.status = 0
|
||||
await phase_wise_completion.async_save()
|
||||
|
||||
# 阶段性办结发生异常,通知阶段性办结失败
|
||||
await oa_result_notify.push_result_notify(
|
||||
e.flow_token, f"{e}", e.return_code
|
||||
)
|
||||
except Exception as e:
|
||||
# 其他异常都意味着失败
|
||||
echo_log(f'阶段性办结发生错误.', logging.ERROR)
|
||||
echo_log(e, logging.ERROR, is_log_exc=True)
|
||||
@@ -0,0 +1,123 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from tornado.httpclient import HTTPResponse, HTTPRequest
|
||||
from sqlalchemy import select
|
||||
|
||||
import dock
|
||||
import apps
|
||||
from dock.govs import govs_api
|
||||
from dock.oa import oa_result_notify, PushException
|
||||
from models.govs_order_master import GovsOrderMaster
|
||||
from models.govs_save_sign import GovsSaveSign
|
||||
from paste.core.logging import echo_log
|
||||
from paste.web import requests
|
||||
|
||||
|
||||
async def get_sign_request(govs_order: GovsOrderMaster):
|
||||
"""
|
||||
创建省12345上工单确认签收的请求对象。方法仅创建请求对象,并未实际提交请求,具体由调度方法处理。
|
||||
|
||||
:param govs_order: 工单对象
|
||||
:return: HTTPRequest 对象
|
||||
"""
|
||||
api_url = '/orderhandler/claimTask/claimTask'
|
||||
body = {
|
||||
"orderId": govs_order.order_id,
|
||||
"orderNo": govs_order.order_no,
|
||||
"masterId": govs_order.master_id,
|
||||
"orderProcessId": govs_order.id,
|
||||
"taskId": govs_order.next_task_id,
|
||||
"flag": "签收"
|
||||
}
|
||||
return await govs_api.new_api_request(api_url, body)
|
||||
|
||||
|
||||
async def after_sign_request(response: HTTPResponse, retry_queue: asyncio.Queue[HTTPRequest]):
|
||||
"""
|
||||
提交省12345后的处理程序。
|
||||
|
||||
:param response: 响应对象
|
||||
:param retry_queue: 重试队列
|
||||
"""
|
||||
echo_log(response.body.decode())
|
||||
govs_order = getattr(response.request, 'govs_order', None)
|
||||
if govs_order:
|
||||
govs_order.govs_sign = 1
|
||||
await govs_order.async_save()
|
||||
echo_log('省12345确认签收请求成功.')
|
||||
|
||||
|
||||
async def sign_order(govs_sign: GovsSaveSign, govs_order: GovsOrderMaster):
|
||||
"""
|
||||
推送工单确认签收请求。
|
||||
|
||||
:param govs_sign: 保存在数据库的工单签收对象
|
||||
:param govs_order: 数据库中的工单对象
|
||||
"""
|
||||
try:
|
||||
sign_request = await get_sign_request(govs_order)
|
||||
queue = asyncio.Queue()
|
||||
setattr(sign_request, 'govs_order', govs_order)
|
||||
await queue.put(sign_request)
|
||||
# 仅生产环境真实提交,其他环境不实际提交
|
||||
if apps.get_active_env() not in ('dev', '', None):
|
||||
sign_response_list = await requests.async_concurrency(queue, con_count=dock.CONCURRENCY_COUNT,
|
||||
retry=dock.MAX_RETRY_COUNT,
|
||||
after_request=after_sign_request)
|
||||
# 检查工单签收响应是否成功
|
||||
if len(sign_response_list) != 1:
|
||||
raise PushException("工单签收请求发生错误.", govs_sign.flow_token, 3)
|
||||
else:
|
||||
echo_log(f"非生产环境,不实际提交.")
|
||||
# 保存成功状态
|
||||
govs_sign.status = 1
|
||||
await govs_sign.async_save()
|
||||
# 工单签收请求提交后,通知工单签收成功
|
||||
await oa_result_notify.push_result_notify(
|
||||
govs_sign.flow_token,
|
||||
'工单签收成功',
|
||||
1
|
||||
)
|
||||
except PushException as e:
|
||||
# 任何异常都意味着失败,通知 OA
|
||||
echo_log(f'工单签收发生错误.', logging.ERROR)
|
||||
echo_log(e, logging.ERROR, is_log_exc=True)
|
||||
|
||||
# 保存失败状态
|
||||
govs_sign.status = 0
|
||||
await govs_sign.async_save()
|
||||
|
||||
# 工单签收发生异常,通知工单签收失败
|
||||
await oa_result_notify.push_result_notify(
|
||||
e.flow_token, f"{e}", e.return_code
|
||||
)
|
||||
except Exception as e:
|
||||
# 其他异常都意味着失败
|
||||
echo_log(f'工单签收发生错误.', logging.ERROR)
|
||||
echo_log(e, logging.ERROR, is_log_exc=True)
|
||||
|
||||
|
||||
async def sign_order_bypass_api(task_id_list: list):
|
||||
"""
|
||||
不经过工单确认签收的api接口,签收指定的工单
|
||||
|
||||
:param task_id_list: 工单id列表
|
||||
"""
|
||||
try:
|
||||
query = select(GovsOrderMaster).where(GovsOrderMaster.id.in_(task_id_list))
|
||||
govs_orders = await GovsOrderMaster.orm_execute(query)
|
||||
sign_queue = asyncio.Queue()
|
||||
for row in govs_orders.all():
|
||||
sign_request = await get_sign_request(row[0])
|
||||
setattr(sign_request, 'govs_order', row[0])
|
||||
await sign_queue.put(sign_request)
|
||||
# 仅生产环境真实提交,其他环境不实际提交
|
||||
if apps.get_active_env() in ('dev', '', None):
|
||||
echo_log(f"非生产环境,不实际提交.")
|
||||
return
|
||||
await requests.async_concurrency(sign_queue, con_count=dock.CONCURRENCY_COUNT,
|
||||
retry=dock.MAX_RETRY_COUNT, after_request=after_sign_request)
|
||||
except Exception as e:
|
||||
echo_log(f'签收工单发生错误.', logging.ERROR)
|
||||
echo_log(e, logging.ERROR, is_log_exc=True)
|
||||
@@ -0,0 +1,106 @@
|
||||
"""
|
||||
数据抓取模块。
|
||||
"""
|
||||
import asyncio
|
||||
from typing import Optional, Union
|
||||
|
||||
from sqlalchemy import select, desc
|
||||
|
||||
import dock
|
||||
from dock.govs import govs_scrape_order_master, govs_scrape_order_detail, govs_scrape_order_process
|
||||
from models.govs_order_master import GovsOrderMaster
|
||||
from paste.core.logging import echo_log
|
||||
from paste.web import requests
|
||||
|
||||
|
||||
async def fetch_govs_task(dept_page_tag: int = 1, num_per_page: int = 60, task_id: Optional[Union[str, int]] = None):
|
||||
"""
|
||||
抓取待办数据及其明细数据。
|
||||
|
||||
:param num_per_page: 读取多少任务进行明细抓取
|
||||
:param dept_page_tag: 0代表全部工单,1代表待签收工单,2代表待交办工单
|
||||
:param task_id: 可选的指定的工单id
|
||||
"""
|
||||
echo_log(f"开始抓取待办数据...")
|
||||
task_request = await govs_scrape_order_master.get_task_request(
|
||||
dept_page_tag=dept_page_tag, num_per_page=num_per_page
|
||||
)
|
||||
request_queue = asyncio.Queue()
|
||||
await request_queue.put(task_request)
|
||||
await requests.async_concurrency(
|
||||
request_queue, retry=dock.MAX_RETRY_COUNT,
|
||||
after_request=govs_scrape_order_master.after_task_request
|
||||
)
|
||||
echo_log(f"待办数据抓取完成...")
|
||||
|
||||
# 读取任务数据,以便能对最新数据抓取详细数据
|
||||
query = select(
|
||||
GovsOrderMaster.id, GovsOrderMaster.order_id, GovsOrderMaster.order_no, GovsOrderMaster.tenant_id,
|
||||
GovsOrderMaster.master_id, GovsOrderMaster.area_code
|
||||
).order_by(
|
||||
desc(GovsOrderMaster.id)
|
||||
)
|
||||
# 如果dept_page_tag=1,只抓取待签收的,如果dept_page_tag不是0或者1,只抓取已签收的,针对性抓取特定状态的工单数据
|
||||
if dept_page_tag == 1:
|
||||
query = query.where(GovsOrderMaster.govs_sign == 0)
|
||||
elif dept_page_tag != 0:
|
||||
query = query.where(GovsOrderMaster.govs_sign == 1)
|
||||
if task_id:
|
||||
if isinstance(task_id, list):
|
||||
query = query.where(GovsOrderMaster.id.in_(task_id))
|
||||
echo_log(f"开始抓取待办列表:{task_id} 的详细数据...")
|
||||
else:
|
||||
query = query.where(GovsOrderMaster.id == task_id)
|
||||
echo_log(f"开始抓取待办:{task_id} 的详细数据...")
|
||||
else:
|
||||
echo_log(f"开始抓取前 {num_per_page} 条待办的详细数据...")
|
||||
query = query.limit(num_per_page)
|
||||
task_df = await GovsOrderMaster.query_as_df(query)
|
||||
|
||||
# 构建请求队列
|
||||
detail_queue = asyncio.Queue()
|
||||
process_queue = asyncio.Queue()
|
||||
# 向队列中填充请求对象
|
||||
echo_log(f"正在准备请求队列...")
|
||||
for _h, _row in task_df.iterrows():
|
||||
order_id = _row.get(GovsOrderMaster.order_id.key)
|
||||
order_no = _row.get(GovsOrderMaster.order_no.key)
|
||||
tenant_id = int(_row.get(GovsOrderMaster.tenant_id.key))
|
||||
master_id = int(_row.get(GovsOrderMaster.master_id.key))
|
||||
area_code = _row.get(GovsOrderMaster.area_code.key)
|
||||
|
||||
_detail_request = await govs_scrape_order_detail.get_task_request(order_id, master_id, tenant_id)
|
||||
setattr(_detail_request, 'order_id', order_id)
|
||||
setattr(_detail_request, 'order_no', order_no)
|
||||
setattr(_detail_request, 'master_id', master_id)
|
||||
setattr(_detail_request, 'tenant_id', tenant_id)
|
||||
await detail_queue.put(_detail_request)
|
||||
|
||||
_process_request = await govs_scrape_order_process.get_task_request(
|
||||
order_id, order_no, master_id, tenant_id, '1700467981117980074', area_code
|
||||
)
|
||||
setattr(_process_request, 'order_id', order_id)
|
||||
setattr(_process_request, 'order_no', order_no)
|
||||
setattr(_process_request, 'master_id', master_id)
|
||||
setattr(_process_request, 'tenant_id', tenant_id)
|
||||
await process_queue.put(_process_request)
|
||||
|
||||
echo_log(f"抓取待办详细数据...")
|
||||
tasks = [
|
||||
requests.async_concurrency(
|
||||
detail_queue, con_count=dock.CONCURRENCY_COUNT, retry=dock.MAX_RETRY_COUNT,
|
||||
after_request=govs_scrape_order_detail.after_task_request
|
||||
),
|
||||
requests.async_concurrency(
|
||||
process_queue, con_count=dock.CONCURRENCY_COUNT, retry=dock.MAX_RETRY_COUNT,
|
||||
after_request=govs_scrape_order_process.after_task_request
|
||||
)
|
||||
]
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from paste.core import aio_pool
|
||||
|
||||
_runner = aio_pool.get_aio_runner()
|
||||
_runner(fetch_govs_task(dept_page_tag=1, num_per_page=50))
|
||||
@@ -0,0 +1,159 @@
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Union
|
||||
|
||||
import pandas as pd
|
||||
from dateutil import parser
|
||||
from tornado.httpclient import HTTPResponse, HTTPRequest
|
||||
|
||||
import dock
|
||||
import models
|
||||
from dock.govs import govs_api
|
||||
from models.govs_order_attachment import GovsOrderAttachment
|
||||
from models.govs_order_detail import GovsOrderDetail
|
||||
from models.govs_order_user import GovsOrderUser
|
||||
from paste.core.logging import echo_log
|
||||
from paste.util import udict
|
||||
from paste.web import requests
|
||||
|
||||
|
||||
async def get_task_request(order_id: str, master_id: Union[str, int], tenant_id: Union[str, int]):
|
||||
"""
|
||||
获取省12345任务详情数据。
|
||||
|
||||
通过 POST 请求向省12345的任务详情接口提交表单数据,获取任务详情数据。
|
||||
自动注入有效的 Cookie(如 JSESSIONID)至请求头,并解析返回的 JSON 响应。
|
||||
|
||||
Args:
|
||||
order_id (str): 待办任务ID
|
||||
master_id (int): 关联订单主表ID
|
||||
tenant_id (int): 租户ID
|
||||
"""
|
||||
api_url = f"/orderreceive/orderMaster/queryOrderDetail"
|
||||
request_body = {
|
||||
"orderId": order_id,
|
||||
"masterId": master_id,
|
||||
"tenantId": tenant_id
|
||||
}
|
||||
# 构造 API 请求
|
||||
return await govs_api.new_api_request(api_url, request_body)
|
||||
|
||||
|
||||
async def after_task_request(response: HTTPResponse, retry_queue: asyncio.Queue[HTTPRequest]):
|
||||
"""
|
||||
任务请求响应后的处理程序。
|
||||
|
||||
:param response: 响应对象
|
||||
:param retry_queue: 重试队列
|
||||
"""
|
||||
order_id = getattr(response.request, 'order_id')
|
||||
order_no = getattr(response.request, 'order_no')
|
||||
master_id = getattr(response.request, 'master_id')
|
||||
tenant_id = getattr(response.request, 'tenant_id')
|
||||
|
||||
response_body = response.body.decode()
|
||||
response_data = json.loads(response_body)
|
||||
order_detail_data = udict.get_by_path(response_data, 'result')
|
||||
mapped_df = pd.DataFrame([order_detail_data])
|
||||
# 更换映射方向,用于将源数据列名改为与数据库表对应
|
||||
forward_mapping = {dict_f: table_f for table_f, dict_f in GovsOrderDetail.FieldMapping.items()}
|
||||
mapped_df = mapped_df.rename(columns=forward_mapping)
|
||||
# 把数组和字典转换为json字符串
|
||||
mapped_df[GovsOrderDetail.order_custom_form_fields.key] = mapped_df[
|
||||
GovsOrderDetail.order_custom_form_fields.key].apply(
|
||||
lambda x: json.dumps(x, ensure_ascii=False) if x is not None else None
|
||||
)
|
||||
mapped_df[GovsOrderDetail.order_phone_dto.key] = mapped_df[GovsOrderDetail.order_phone_dto.key].apply(
|
||||
lambda x: json.dumps(x, ensure_ascii=False) if x is not None else None
|
||||
)
|
||||
mapped_df[GovsOrderDetail.order_user.key] = mapped_df[GovsOrderDetail.order_user.key].apply(
|
||||
lambda x: json.dumps(x, ensure_ascii=False) if x is not None else None
|
||||
)
|
||||
mapped_df[GovsOrderDetail.order_attachment_list.key] = mapped_df[GovsOrderDetail.order_attachment_list.key].apply(
|
||||
lambda x: json.dumps(x, ensure_ascii=False) if x is not None else None
|
||||
)
|
||||
mapped_df[GovsOrderDetail.pre_process_list.key] = mapped_df[GovsOrderDetail.pre_process_list.key].apply(
|
||||
lambda x: json.dumps(x, ensure_ascii=False) if x is not None else None
|
||||
)
|
||||
mapped_df[GovsOrderDetail.tripartite_call_records_list.key] = mapped_df[
|
||||
GovsOrderDetail.tripartite_call_records.key].apply(
|
||||
lambda x: json.dumps(x, ensure_ascii=False) if x is not None else None
|
||||
)
|
||||
mapped_df[GovsOrderDetail.plan_finish_time.key] = mapped_df[GovsOrderDetail.plan_finish_time.key].apply(
|
||||
lambda x: parser.parse(x).strftime('%Y-%m-%d %H:%M:%S') if isinstance(x, str) and x.strip() else None
|
||||
)
|
||||
mapped_df[GovsOrderDetail.order_finish_time.key] = mapped_df[GovsOrderDetail.order_finish_time.key].apply(
|
||||
lambda x: parser.parse(x).strftime('%Y-%m-%d %H:%M:%S') if isinstance(x, str) and x.strip() else None
|
||||
)
|
||||
mapped_df[GovsOrderDetail.plan_sign_time.key] = mapped_df[GovsOrderDetail.plan_sign_time.key].apply(
|
||||
lambda x: parser.parse(x).strftime('%Y-%m-%d %H:%M:%S') if isinstance(x, str) and x.strip() else None
|
||||
)
|
||||
|
||||
# 这里把空数据都换成 None,以便存入数据库时是 null
|
||||
mapped_df.replace(models.EmptyInDF + models.EmptyDatetimeInDF, None, inplace=True)
|
||||
_created, _updated = await GovsOrderDetail.save_batch(mapped_df)
|
||||
|
||||
# 存储用户信息
|
||||
user_data = udict.get_by_path(response_data, 'result.orderUser')
|
||||
if user_data:
|
||||
user_df = pd.DataFrame([user_data])
|
||||
# 更换映射方向,用于将源数据列名改为与数据库表对应
|
||||
forward_mapping = {dict_f: table_f for table_f, dict_f in GovsOrderUser.FieldMapping.items()}
|
||||
user_mapped_df = user_df.rename(columns=forward_mapping)
|
||||
# 比较字段转字符串
|
||||
user_mapped_df[GovsOrderUser.id.key] = user_mapped_df[GovsOrderUser.id.key].astype(str)
|
||||
user_mapped_df[GovsOrderUser.master_id.key] = user_mapped_df[GovsOrderUser.master_id.key].astype(str)
|
||||
# 转换日期时间
|
||||
user_mapped_df[GovsOrderUser.created_at.key] = user_mapped_df[GovsOrderUser.created_at.key].apply(
|
||||
lambda x: parser.parse(x).strftime('%Y-%m-%d %H:%M:%S') if isinstance(x, str) and x.strip() else None
|
||||
)
|
||||
user_mapped_df[GovsOrderUser.updated_at.key] = user_mapped_df[GovsOrderUser.updated_at.key].apply(
|
||||
lambda x: parser.parse(x).strftime('%Y-%m-%d %H:%M:%S') if isinstance(x, str) and x.strip() else None
|
||||
)
|
||||
# 这里把空数据都换成 None,以便存入数据库时是 null
|
||||
user_mapped_df.replace(models.EmptyInDF + models.EmptyDatetimeInDF, None, inplace=True)
|
||||
await GovsOrderUser.save_batch(user_mapped_df)
|
||||
|
||||
# 存储附件信息
|
||||
attachment_list = udict.get_by_path(response_data, 'result.orderAttachmentList')
|
||||
if attachment_list:
|
||||
attachment_df = pd.DataFrame(attachment_list)
|
||||
# 更换映射方向,用于将源数据列名改为与数据库表对应
|
||||
forward_mapping = {dict_f: table_f for table_f, dict_f in GovsOrderAttachment.FieldMapping.items()}
|
||||
attachment_mapped_df = attachment_df.rename(columns=forward_mapping)
|
||||
attachment_mapped_df[GovsOrderAttachment.master_id.key] = master_id
|
||||
attachment_mapped_df[GovsOrderAttachment.order_id.key] = order_id
|
||||
# 比较字段转字符串
|
||||
attachment_mapped_df[GovsOrderAttachment.id.key] = attachment_mapped_df[GovsOrderAttachment.id.key].astype(str)
|
||||
attachment_mapped_df[GovsOrderAttachment.master_id.key] = attachment_mapped_df[GovsOrderAttachment.master_id.key].astype(str)
|
||||
# 这里把空数据都换成 None,以便存入数据库时是 null
|
||||
attachment_mapped_df.replace(models.EmptyInDF + models.EmptyDatetimeInDF, None, inplace=True)
|
||||
await GovsOrderAttachment.save_batch(attachment_mapped_df)
|
||||
|
||||
# 输出数据创建状态
|
||||
echo_log(f"成功创建租户:{tenant_id} 的待办工单:{master_id}({order_id},{order_no}) 详情.")
|
||||
if retry_queue:
|
||||
echo_log(f"待办工单详情重试队列中有:{retry_queue.qsize()} 个请求在等待.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from paste.core import aio_pool
|
||||
|
||||
|
||||
async def scrape(order_id: Union[str, int], master_id: Union[str, int], tenant_id: Union[str, int],
|
||||
order_no: Union[str, int], ):
|
||||
task_request = await get_task_request(order_id, master_id, tenant_id)
|
||||
setattr(task_request, 'order_id', order_id)
|
||||
setattr(task_request, 'master_id', master_id)
|
||||
setattr(task_request, 'tenant_id', tenant_id)
|
||||
setattr(task_request, 'order_no', order_no)
|
||||
request_queue = asyncio.Queue()
|
||||
await request_queue.put(task_request)
|
||||
await requests.async_concurrency(
|
||||
request_queue, retry=dock.MAX_RETRY_COUNT,
|
||||
after_request=after_task_request
|
||||
)
|
||||
|
||||
|
||||
_runner = aio_pool.get_aio_runner()
|
||||
_runner(scrape('DH050826052517663', '2058851271599333378', '1773611023340371969', 'DH050826052517663*3'))
|
||||
@@ -0,0 +1,156 @@
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from tornado.httpclient import HTTPResponse, HTTPRequest
|
||||
|
||||
import dock
|
||||
import models
|
||||
from dock.govs import govs_api
|
||||
from models.govs_order_master import GovsOrderMaster
|
||||
from paste.core.logging import echo_log
|
||||
from paste.util import udict
|
||||
from paste.web import requests
|
||||
|
||||
|
||||
async def get_task_request(order_id: Union[str, int] = '', dept_page_tag: int = 1,
|
||||
current_page: int = 1, num_per_page: int = 200):
|
||||
"""
|
||||
获取省12345任务列表数据。
|
||||
通过 POST 请求向省12345的任务列表接口提交表单数据,获取任务分页数据。
|
||||
|
||||
Args:
|
||||
order_id (int): 任务列表类型 ID,默认为企业待办:600058
|
||||
dept_page_tag (int): 分页标志
|
||||
current_page (int): 当前页码
|
||||
num_per_page (int): 每页显示数据量,默认 200
|
||||
"""
|
||||
api_url = f"/orderhandler/taskQuery/getDeptAllToDoOrderProcess"
|
||||
request_body = {
|
||||
"data": {
|
||||
"deptPageTag": dept_page_tag,
|
||||
"orderId": f"{order_id}",
|
||||
"keyWord": "",
|
||||
"andOrFlag": "0",
|
||||
"serviceObjectType": [],
|
||||
"callNumber": "",
|
||||
"orderSource": [],
|
||||
"orderSourceDetailList": [],
|
||||
"signedStatus": [],
|
||||
"firstOrderStatus": [],
|
||||
"secordOrderStatus": [],
|
||||
"status": [],
|
||||
"overDue": "",
|
||||
"existQuotoInfo": [],
|
||||
"isSupervise": [],
|
||||
"planFinishTime": "",
|
||||
"caseIsUrgent": [],
|
||||
"areaCodeCity": "",
|
||||
"areaCodeArea": "",
|
||||
"areaCodeStreet": "",
|
||||
"addressDetail": "",
|
||||
"infoProtect": [],
|
||||
"firstLevelAffiliations": [],
|
||||
"secondLevelAffiliations": [],
|
||||
"thirdLevelAffiliations": [],
|
||||
"fourthLevelAffiliations": [],
|
||||
"fifthLevelAffiliations": [],
|
||||
"caseAccordTypeOneNames": [],
|
||||
"caseAccordTypeTwoNames": [],
|
||||
"caseAccordTypeThreeNames": [],
|
||||
"caseAccordTypeFourNames": [],
|
||||
"caseAccordTypeFiveNames": [],
|
||||
"creatorId": "",
|
||||
"assigneeUserId": "",
|
||||
"callTimeEnd": "",
|
||||
"callTimeFrom": "",
|
||||
"caseLabels": [],
|
||||
"contactNumber": "",
|
||||
"createBy": "",
|
||||
"deptName": "",
|
||||
"deptType": "",
|
||||
"fileExist": [],
|
||||
"hotspot": [],
|
||||
"claimStatus": "",
|
||||
"orderSourceDetail": "",
|
||||
"orderType": [],
|
||||
"orgName": [],
|
||||
"sortField": "",
|
||||
"sortRule": "",
|
||||
"actionName": "",
|
||||
"returnReasonNameList": [],
|
||||
"createDateFrom": "",
|
||||
"createDateEnd": "",
|
||||
"planBackTimeStart": "",
|
||||
"planBackTimeEnd": "",
|
||||
"planFinishTimeStart": "",
|
||||
"planFinishTimeEnd": ""
|
||||
},
|
||||
"pageSize": num_per_page,
|
||||
"pageNum": current_page
|
||||
}
|
||||
# 构造 API 请求
|
||||
return await govs_api.new_api_request(api_url, request_body)
|
||||
|
||||
|
||||
async def after_task_request(response: HTTPResponse, retry_queue: asyncio.Queue[HTTPRequest]):
|
||||
"""
|
||||
任务请求响应后的处理程序。
|
||||
|
||||
:param response: 响应对象
|
||||
:param retry_queue: 重试队列
|
||||
"""
|
||||
response_body = response.body.decode()
|
||||
response_data = json.loads(response_body)
|
||||
list_data: list[dict] = udict.get_by_path(response_data, 'data.list')
|
||||
order_master_list: list[dict] = []
|
||||
for d in list_data:
|
||||
order_master_dto = d.get('orderMasterDTO')
|
||||
order_master_dto['nextTaskId'] = d.get('nextTaskId')
|
||||
order_master_dto['claimStatus'] = d.get('claimStatus')
|
||||
order_master_list.append(order_master_dto)
|
||||
if order_master_list:
|
||||
mapped_df = pd.DataFrame(order_master_list)
|
||||
# 更换映射方向,用于将源数据列名改为与数据库表对应
|
||||
forward_mapping = {dict_f: table_f for table_f, dict_f in GovsOrderMaster.FieldMapping.items()}
|
||||
mapped_df = mapped_df.rename(columns=forward_mapping)
|
||||
# 把数组转换为 JSON 字符串
|
||||
mapped_df[GovsOrderMaster.attachment_list.key] = mapped_df[GovsOrderMaster.attachment_list.key].apply(
|
||||
lambda x: json.dumps(x, ensure_ascii=False) if x is not None else None
|
||||
)
|
||||
mapped_df[GovsOrderMaster.back_count.key] = mapped_df[GovsOrderMaster.back_count.key].apply(
|
||||
lambda x: json.dumps(x, ensure_ascii=False) if x is not None else None
|
||||
)
|
||||
# 根据claim_status字段,更新govs_sign字段
|
||||
mapped_df[GovsOrderMaster.govs_sign.key] = np.where(
|
||||
mapped_df[GovsOrderMaster.claim_status.key] == '已签收', 1, 0
|
||||
)
|
||||
# 这里把空数据都换成 None,以便存入数据库时是 null
|
||||
mapped_df.replace(models.EmptyInDF + models.EmptyDatetimeInDF, None, inplace=True)
|
||||
# 筛选数据状态
|
||||
_created, _updated = await GovsOrderMaster.save_batch(mapped_df)
|
||||
echo_log(f"成功创建企业待办:{_created}条,更新:{_updated}条.")
|
||||
else:
|
||||
echo_log('未获取到企业待办数据')
|
||||
if retry_queue:
|
||||
echo_log(f"企业待办重试队列中有:{retry_queue.qsize()} 个请求在等待.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from paste.core import aio_pool
|
||||
|
||||
|
||||
async def scrape():
|
||||
task_request = await get_task_request(dept_page_tag=0, order_id='DH058726052903006')
|
||||
request_queue = asyncio.Queue()
|
||||
await request_queue.put(task_request)
|
||||
await requests.async_concurrency(
|
||||
request_queue, retry=dock.MAX_RETRY_COUNT,
|
||||
after_request=after_task_request
|
||||
)
|
||||
|
||||
|
||||
_runner = aio_pool.get_aio_runner()
|
||||
_runner(scrape())
|
||||
@@ -0,0 +1,154 @@
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Union
|
||||
|
||||
import pandas as pd
|
||||
from dateutil import parser
|
||||
from tornado.httpclient import HTTPResponse, HTTPRequest
|
||||
|
||||
import dock
|
||||
import models
|
||||
from dock.govs import govs_api
|
||||
from models.govs_order_process import GovsOrderProcess
|
||||
from paste.core.logging import echo_log
|
||||
from paste.util import udict
|
||||
from paste.web import requests
|
||||
|
||||
|
||||
async def get_task_request(order_id: str, order_no: str, master_id: Union[str, int],
|
||||
tenant_id: Union[str, int], dept_id: Union[str, int], area_code: str,
|
||||
sort: str = ""):
|
||||
"""
|
||||
获取省12345任务处理过程数据。
|
||||
|
||||
通过 POST 请求向省12345的任务处理过程接口提交表单数据,获取任务处理过程数据。
|
||||
自动注入有效的 Cookie(如 JSESSIONID)至请求头,并解析返回的 JSON 响应。
|
||||
|
||||
Args:
|
||||
order_id (str): 待办任务ID
|
||||
order_no (str): 待办任务号
|
||||
master_id (int): 关联订单主表ID
|
||||
tenant_id (str, int): 租户ID
|
||||
dept_id (str, int): 部门ID
|
||||
area_code (str): 邮编
|
||||
sort (str): 排序
|
||||
"""
|
||||
api_url = f"/orderreceive/orderMaster/queryOrderProcess"
|
||||
request_body = {
|
||||
"orderId": order_id,
|
||||
"orderNo": order_no,
|
||||
"masterId": master_id,
|
||||
"tenantId": tenant_id,
|
||||
"deptId": dept_id,
|
||||
"areaCode": area_code,
|
||||
"sort": sort,
|
||||
}
|
||||
# 构造 API 请求
|
||||
return await govs_api.new_api_request(api_url, request_body)
|
||||
|
||||
|
||||
async def after_task_request(response: HTTPResponse, retry_queue: asyncio.Queue[HTTPRequest]):
|
||||
"""
|
||||
任务请求响应后的处理程序。
|
||||
|
||||
:param response: 响应对象
|
||||
:param retry_queue: 重试队列
|
||||
"""
|
||||
order_id = getattr(response.request, 'order_id')
|
||||
order_no = getattr(response.request, 'order_no')
|
||||
master_id = getattr(response.request, 'master_id')
|
||||
tenant_id = getattr(response.request, 'tenant_id')
|
||||
|
||||
response_body = response.body.decode()
|
||||
response_data = json.loads(response_body)
|
||||
list_data = udict.get_by_path(response_data, 'result')
|
||||
task_df = pd.DataFrame(list_data)
|
||||
# 更换映射方向,用于将源数据列名改为与数据库表对应
|
||||
forward_mapping = {dict_f: table_f for table_f, dict_f in GovsOrderProcess.FieldMapping.items()}
|
||||
mapped_df = task_df.rename(columns=forward_mapping)
|
||||
mapped_df[GovsOrderProcess.master_id.key] = master_id
|
||||
mapped_df[GovsOrderProcess.tenant_id.key] = tenant_id
|
||||
# 比较字段转字符串
|
||||
mapped_df[GovsOrderProcess.id.key] = mapped_df[GovsOrderProcess.id.key].astype(str)
|
||||
mapped_df[GovsOrderProcess.master_id.key] = mapped_df[GovsOrderProcess.master_id.key].astype(str)
|
||||
# 过滤掉 id 和 order_id 为空的数据
|
||||
mapped_df = mapped_df[
|
||||
mapped_df[GovsOrderProcess.id.key].notna() & (mapped_df[GovsOrderProcess.id.key] != "")
|
||||
]
|
||||
mapped_df = mapped_df[
|
||||
mapped_df[GovsOrderProcess.order_id.key].notna() & (mapped_df[GovsOrderProcess.order_id.key] != "")
|
||||
]
|
||||
# 字典转化为字符串
|
||||
mapped_df[GovsOrderProcess.child_order_processes.key] = mapped_df[GovsOrderProcess.child_order_processes.key].apply(
|
||||
lambda x: json.dumps(x, ensure_ascii=False) if x is not None else None
|
||||
)
|
||||
mapped_df[GovsOrderProcess.handler_user_ids.key] = mapped_df[GovsOrderProcess.handler_user_ids.key].apply(
|
||||
lambda x: json.dumps(x, ensure_ascii=False) if x is not None else None
|
||||
)
|
||||
mapped_df[GovsOrderProcess.handler_org_ids.key] = mapped_df[GovsOrderProcess.handler_org_ids.key].apply(
|
||||
lambda x: json.dumps(x, ensure_ascii=False) if x is not None else None
|
||||
)
|
||||
mapped_df[GovsOrderProcess.next_handler_user_ids.key] = mapped_df[GovsOrderProcess.next_handler_user_ids.key].apply(
|
||||
lambda x: json.dumps(x, ensure_ascii=False) if x is not None else None
|
||||
)
|
||||
mapped_df[GovsOrderProcess.attachment_dto_list.key] = mapped_df[GovsOrderProcess.attachment_dto_list.key].apply(
|
||||
lambda x: json.dumps(x, ensure_ascii=False) if x is not None else None
|
||||
)
|
||||
# 时间字段转化为日期对象
|
||||
mapped_df[GovsOrderProcess.plan_sign_time.key] = mapped_df[GovsOrderProcess.plan_sign_time.key].apply(
|
||||
lambda x: parser.parse(x).strftime('%Y-%m-%d %H:%M:%S') if isinstance(x, str) and x.strip() else None
|
||||
)
|
||||
mapped_df[GovsOrderProcess.plan_finish_time.key] = mapped_df[GovsOrderProcess.plan_finish_time.key].apply(
|
||||
lambda x: parser.parse(x).strftime('%Y-%m-%d %H:%M:%S') if isinstance(x, str) and x.strip() else None
|
||||
)
|
||||
mapped_df[GovsOrderProcess.plan_back_time.key] = mapped_df[GovsOrderProcess.plan_back_time.key].apply(
|
||||
lambda x: parser.parse(x).strftime('%Y-%m-%d %H:%M:%S') if isinstance(x, str) and x.strip() else None
|
||||
)
|
||||
mapped_df[GovsOrderProcess.contact_time.key] = mapped_df[GovsOrderProcess.contact_time.key].apply(
|
||||
lambda x: parser.parse(x).strftime('%Y-%m-%d %H:%M:%S') if isinstance(x, str) and x.strip() else None
|
||||
)
|
||||
mapped_df[GovsOrderProcess.contact_time.key] = mapped_df[GovsOrderProcess.contact_time.key].apply(
|
||||
lambda x: parser.parse(x).strftime('%Y-%m-%d %H:%M:%S') if isinstance(x, str) and x.strip() else None
|
||||
)
|
||||
mapped_df[GovsOrderProcess.origin_plan_finish_time.key] = mapped_df[
|
||||
GovsOrderProcess.origin_plan_finish_time.key].apply(
|
||||
lambda x: parser.parse(x).strftime('%Y-%m-%d %H:%M:%S') if isinstance(x, str) and x.strip() else None
|
||||
)
|
||||
mapped_df[GovsOrderProcess.origin_plan_sign_time.key] = mapped_df[GovsOrderProcess.origin_plan_sign_time.key].apply(
|
||||
lambda x: parser.parse(x).strftime('%Y-%m-%d %H:%M:%S') if isinstance(x, str) and x.strip() else None
|
||||
)
|
||||
# 这里把空数据都换成 None,以便存入数据库时是 null
|
||||
mapped_df.replace(models.EmptyInDF + models.EmptyDatetimeInDF, None, inplace=True)
|
||||
_created, _updated = await GovsOrderProcess.save_batch(mapped_df)
|
||||
# 输出数据创建状态
|
||||
echo_log(
|
||||
f"成功创建租户:{tenant_id} 的待办工单:{master_id}({order_id},{order_no}) 处理流程:{_created}条,更新:{_updated}条.")
|
||||
if retry_queue:
|
||||
echo_log(f"待办工单处理流程重试队列中有:{retry_queue.qsize()} 个请求在等待.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from paste.core import aio_pool
|
||||
|
||||
|
||||
async def scrape(order_id: str, order_no: str, master_id: Union[str, int],
|
||||
tenant_id: Union[str, int], dept_id: Union[str, int], area_code: str,
|
||||
sort: str = ""):
|
||||
task_request = await get_task_request(order_id, order_no, master_id, tenant_id, dept_id, area_code, sort)
|
||||
setattr(task_request, 'order_id', order_id)
|
||||
setattr(task_request, 'order_no', order_no)
|
||||
setattr(task_request, 'master_id', master_id)
|
||||
setattr(task_request, 'tenant_id', tenant_id)
|
||||
request_queue = asyncio.Queue()
|
||||
await request_queue.put(task_request)
|
||||
await requests.async_concurrency(
|
||||
request_queue, retry=dock.MAX_RETRY_COUNT,
|
||||
after_request=after_task_request
|
||||
)
|
||||
|
||||
|
||||
_runner = aio_pool.get_aio_runner()
|
||||
_runner(scrape(
|
||||
'DH050826052517663', 'DH050826052517663*3', '2058851271599333378',
|
||||
'1773611023340371969', '1700467981117980074', '320500',
|
||||
))
|
||||
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
安全模块。
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from tornado.httpclient import HTTPResponse, HTTPRequest
|
||||
|
||||
import dock
|
||||
from dock.govs import govs_api
|
||||
from models.token import TokenModel
|
||||
from paste.core import config
|
||||
from paste.core.logging import echo_log
|
||||
from paste.security import cryp_rsa
|
||||
from paste.util import udict
|
||||
from paste.web import requests
|
||||
|
||||
|
||||
public_key = """-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0Jr1NzVUQMburkZT6Rkt0eaPm
|
||||
H8TN6E258l2tZMJgVCP/sL4oKjroKYmNPBkSSiLKFr9wwJqfesMeef6ChGRUXjG6
|
||||
DX0oxQRe0f5/UnyEm/NicJwz9xwkU34gbuo1VB/EA2QZ5dl1rj9iSsiqKLK6/QFl
|
||||
VuzslRdAXYZC79vprwIDAQAB
|
||||
-----END PUBLIC KEY-----"""
|
||||
"""
|
||||
固定公钥,确保登录成功。
|
||||
"""
|
||||
|
||||
|
||||
async def login():
|
||||
"""
|
||||
登录政务服务 12345 系统并获取认证 Token。
|
||||
|
||||
流程:
|
||||
1. 密码需要加密,密码明文使用PKCS1_v1_5进行RSA加密。
|
||||
2. 从响应的['data']['access_token']内获取token。
|
||||
|
||||
Args:
|
||||
无参数。
|
||||
|
||||
Returns:
|
||||
tuple: 包含两个元素的元组:
|
||||
- dict: DCM 接口返回的完整 JSON 响应数据
|
||||
|
||||
Raises:
|
||||
AssertionError: 登录失败(`resultInfo.success` 为 False)
|
||||
ValueError: 响应体非合法 JSON
|
||||
HTTPError: 网络请求失败(由 `async_request` 抛出)
|
||||
"""
|
||||
login_url = f"{govs_api.ApiUrl}/system/sysLogin"
|
||||
|
||||
# 构建扩展头
|
||||
user_agent, browser_ver, os_name = dock.get_random_user_agent()
|
||||
extra_headers = {
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
'User-Agent': user_agent,
|
||||
}
|
||||
|
||||
# 构造请求
|
||||
request_body = {
|
||||
"username": config.get_config("dock.govs.account.username"),
|
||||
"password": cryp_rsa.rsa_encrypt_pkcs1_v1_5(public_key, config.get_config("dock.govs.account.password")),
|
||||
"tenantAccount": "suzhou",
|
||||
"rememberme": 1,
|
||||
"code": "",
|
||||
"uuid": "",
|
||||
}
|
||||
|
||||
# 构造请求对象
|
||||
request = dock.new_http_request(
|
||||
url=login_url,
|
||||
body=request_body,
|
||||
method='POST',
|
||||
timeout=dock.DEFAULT_TIMEOUT,
|
||||
use_form=False,
|
||||
extra_headers=extra_headers,
|
||||
** govs_api.ProxyConfig
|
||||
)
|
||||
|
||||
queue = asyncio.Queue()
|
||||
await queue.put(request)
|
||||
await requests.async_concurrency(
|
||||
queue, con_count=1, retry=dock.MAX_RETRY_COUNT,
|
||||
after_request=after_login
|
||||
)
|
||||
|
||||
|
||||
async def after_login(response: HTTPResponse, retry_queue: asyncio.Queue[HTTPRequest]):
|
||||
response_body = response.body.decode()
|
||||
response_data = json.loads(response_body)
|
||||
success = udict.get_by_path(response_data, 'data.access_token', '')
|
||||
if success:
|
||||
await TokenModel.refresh(platform='GOVS', token=success)
|
||||
echo_log(f"成功刷新省12345登录令牌.")
|
||||
else:
|
||||
echo_log(f"省12345登录失败,无法刷新令牌,响应:{response_body}")
|
||||
if retry_queue:
|
||||
echo_log(f"登录重试队列中有:{retry_queue.qsize()} 个请求在等待.")
|
||||
return response_data
|
||||
|
||||
|
||||
async def get_token(platform: str = 'GOVS'):
|
||||
"""
|
||||
取得可用 Token。
|
||||
|
||||
:param platform: 要查询的平台,默认是:GOVS,省12345
|
||||
:return: Cookies 字符串
|
||||
"""
|
||||
_token = await TokenModel.find_by_platform(platform)
|
||||
return _token.token
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from paste.core import aio_pool
|
||||
_runner = aio_pool.get_aio_runner()
|
||||
_runner(login())
|
||||
@@ -0,0 +1,17 @@
|
||||
import io
|
||||
from dock.govs import govs_api
|
||||
|
||||
|
||||
async def get_upload_request(file_name: str, file_io: io.IOBase):
|
||||
"""
|
||||
创建上传文件到省12345的请求对象。方法仅创建请求对象,并未实际提交请求,具体由调度方法处理。
|
||||
|
||||
:param file_name: 文件名
|
||||
:param file_io: 文件io对象
|
||||
"""
|
||||
|
||||
api_url = '/file/api/system/uploadcircuit'
|
||||
body = {
|
||||
file_name: file_io
|
||||
}
|
||||
return await govs_api.new_api_request(api_url, body, use_form=True)
|
||||
Reference in New Issue
Block a user