374 lines
14 KiB
Python
374 lines
14 KiB
Python
import logging
|
||
import shutil
|
||
import sys
|
||
import time
|
||
import uuid
|
||
from datetime import datetime, timedelta
|
||
from flask import Flask, request, jsonify, Response, stream_with_context, g
|
||
import json
|
||
import os
|
||
from flask_app.main.download import download_file
|
||
from flask_app.main.招标文件解析 import main_processing
|
||
|
||
app = Flask(__name__)
|
||
|
||
|
||
class CSTFormatter(logging.Formatter):
|
||
"""自定义的 Formatter,将日志的时间戳调整为中国标准时间(UTC+8)"""
|
||
|
||
def formatTime(self, record, datefmt=None):
|
||
ct = datetime.fromtimestamp(record.created) + timedelta(hours=8)
|
||
if datefmt:
|
||
s = ct.strftime(datefmt)
|
||
else:
|
||
try:
|
||
s = ct.strftime("%Y-%m-%d %H:%M:%S")
|
||
if self.usesTime():
|
||
s = f"{s},{record.msecs:03d}"
|
||
except ValueError:
|
||
s = ct.strftime("%Y-%m-%d %H:%M:%S")
|
||
return s
|
||
|
||
|
||
@app.before_request
|
||
def before_request():
|
||
# 每个请求开始前初始化 logger
|
||
create_logger() # 确保这个函数中设置了 g.logger
|
||
|
||
|
||
def create_logger():
|
||
unique_id = str(uuid.uuid4())
|
||
g.unique_id = unique_id
|
||
output_folder = f"flask_app/static/output/{unique_id}"
|
||
os.makedirs(output_folder, exist_ok=True)
|
||
log_filename = "log.txt"
|
||
log_path = os.path.join(output_folder, log_filename)
|
||
logger = logging.getLogger(unique_id)
|
||
if not logger.handlers:
|
||
file_handler = logging.FileHandler(log_path)
|
||
file_formatter = CSTFormatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||
file_handler.setFormatter(file_formatter)
|
||
logger.addHandler(file_handler)
|
||
stream_handler = logging.StreamHandler()
|
||
stream_handler.setFormatter(logging.Formatter('%(message)s'))
|
||
logger.addHandler(stream_handler)
|
||
logger.setLevel(logging.INFO)
|
||
g.logger = logger
|
||
|
||
|
||
# @app.route('/upload', methods=['POST'])
|
||
# def zbparse():
|
||
# logger=g.logger
|
||
# file_url = validate_request()
|
||
# if isinstance(file_url, tuple): # Check if the returned value is an error response
|
||
# return file_url
|
||
# try:
|
||
# logger.info("starting parsing url:" + file_url)
|
||
# final_json_path, output_folder= download_and_process_file(file_url)
|
||
# if not final_json_path:
|
||
# return jsonify({'error': 'File processing failed'}), 500
|
||
# response = generate_response(final_json_path) # 先获取响应内容
|
||
# # remove_directory(output_folder) # 然后删除文件夹
|
||
# return response # 最后返回获取的响应
|
||
# except Exception as e:
|
||
# logger.error('Exception occurred: ' + str(e)) # 使用全局 logger 记录
|
||
# return jsonify({'error': str(e)}), 500
|
||
|
||
|
||
# 流式
|
||
@app.route('/upload', methods=['POST'])
|
||
def zbparse():
|
||
logger = g.logger
|
||
logger.info("start!!!")
|
||
# 获取并显示接收到的 JSON 数据
|
||
received_data = request.get_json()
|
||
logger.info("Received JSON data: " + str(received_data))
|
||
file_url = validate_request()
|
||
if isinstance(file_url, tuple): # Check if the returned value is an error response
|
||
return file_url
|
||
try:
|
||
logger.info("starting parsing url:" + file_url)
|
||
return Response(stream_with_context(process_and_stream(file_url)), content_type='text/event-stream')
|
||
except Exception as e:
|
||
logger.error('Exception occurred: ' + str(e))
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
|
||
# 分段返回
|
||
def process_and_stream(file_url):
|
||
logger = g.logger
|
||
unique_id = g.unique_id
|
||
output_folder = f"flask_app/static/output/{unique_id}"
|
||
filename = "ztbfile"
|
||
downloaded_filename = os.path.join(output_folder, filename)
|
||
|
||
downloaded_filepath, file_type = download_file(file_url, downloaded_filename)
|
||
|
||
if downloaded_filepath is None or file_type == 3:
|
||
logger.error("Unsupported file type or failed to download file")
|
||
error_response = {
|
||
'message': 'File processing failed',
|
||
'filename': None,
|
||
'data': json.dumps({'error': 'File processing failed'})
|
||
}
|
||
yield f"data: {json.dumps(error_response)}\n\n"
|
||
return
|
||
|
||
logger.info("Local file path: " + downloaded_filepath)
|
||
|
||
combined_data = {}
|
||
|
||
# 从 main_processing 获取数据
|
||
for data in main_processing(output_folder, downloaded_filepath, file_type, unique_id):
|
||
response = {
|
||
'message': 'Processing',
|
||
'filename': os.path.basename(downloaded_filepath),
|
||
'data': data
|
||
}
|
||
# 日志记录和流式响应
|
||
yield f"data: {json.dumps(response, ensure_ascii=False)}\n\n"
|
||
|
||
if not data:
|
||
logger.error(f"Empty data received: {data}")
|
||
continue
|
||
|
||
# 解析 data 作为 JSON 格式数据
|
||
if not data.strip():
|
||
logger.error("Received empty data, skipping JSON parsing.")
|
||
else:
|
||
try:
|
||
parsed_data = json.loads(data)
|
||
except json.JSONDecodeError as e:
|
||
logger.error(f"Failed to decode JSON: {e}")
|
||
logger.error(f"Data received: {data}")
|
||
continue # 跳过该数据处理
|
||
|
||
# 遍历 parsed_data 只提取内层内容进行合并
|
||
for outer_key, inner_dict in parsed_data.items():
|
||
if isinstance(inner_dict, dict):
|
||
combined_data.update(inner_dict)
|
||
|
||
# 等待所有数据都处理完后,发送整合后的完整数据
|
||
complete_response = {
|
||
'message': 'Combined data',
|
||
'filename': os.path.basename(downloaded_filepath),
|
||
'data': json.dumps(combined_data, ensure_ascii=False)
|
||
}
|
||
yield f"data: {json.dumps(complete_response, ensure_ascii=False)}\n\n"
|
||
|
||
# 发送最终响应
|
||
final_response = {
|
||
'message': 'File uploaded and processed successfully',
|
||
'filename': os.path.basename(downloaded_filepath),
|
||
'data': 'END'
|
||
}
|
||
yield f"data: {json.dumps(final_response, ensure_ascii=False)}\n\n"
|
||
|
||
|
||
def validate_request():
|
||
if not request.is_json:
|
||
return jsonify({'error': 'Missing JSON in request'}), 400
|
||
file_url = request.json.get('file_url')
|
||
if not file_url:
|
||
return jsonify({'error': 'No file URL provided'}), 400
|
||
return file_url
|
||
|
||
|
||
def download_and_process_file(file_url):
|
||
logger = g.logger
|
||
unique_id = g.unique_id
|
||
output_folder = f"flask_app/static/output/{unique_id}" # 直接使用全局 unique_id 构建路径
|
||
filename = "ztbfile"
|
||
downloaded_filename = os.path.join(output_folder, filename)
|
||
|
||
# 下载文件,假设 download_file 函数已正确处理异常并返回文件路径
|
||
downloaded_filepath, file_type = download_file(file_url, downloaded_filename)
|
||
|
||
if downloaded_filepath is None or file_type == 3:
|
||
logger.error("Unsupported file type or failed to download file")
|
||
return None, output_folder
|
||
|
||
logger.info("Local file path: " + downloaded_filepath)
|
||
processed_file_path = main_processing(output_folder, downloaded_filepath, file_type, unique_id)
|
||
return processed_file_path, output_folder
|
||
|
||
|
||
@app.route('/api/test_zbparse', methods=['POST'])
|
||
def test_zbparse():
|
||
try:
|
||
return Response(stream_with_context(test_process_and_stream()), content_type='text/event-stream')
|
||
except Exception as e:
|
||
app.logger.error('Exception occurred: ' + str(e))
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
|
||
def test_process_and_stream():
|
||
# 模拟七段数据,每段包含指定的中文键名和更多详细数据
|
||
data_segments = [
|
||
{
|
||
"base_info": {
|
||
"基础信息": {
|
||
"project_name": "测试项目1",
|
||
"project_code": "TP001",
|
||
"project_manager": "张三",
|
||
"start_date": "2024-01-10",
|
||
"end_date": "2024-12-31"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"qualification_review": {
|
||
"资格审查": {
|
||
"review_criteria": ["公司资质", "过往业绩", "财务报表"],
|
||
"required_documents": ["营业执照", "资质证书", "近三年财务报告"],
|
||
"minimum_requirements": {
|
||
"company_age": "至少5年",
|
||
"past_projects": "至少3个大型项目"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"technical_standards": {
|
||
"技术标": {
|
||
"technical_requirements": ["设备质量要求", "施工工艺", "安全标准"],
|
||
"materials_list": ["钢筋", "水泥", "电缆"],
|
||
"equipment_specs": {
|
||
"excavator": "型号X123",
|
||
"concrete_mixer": "型号Y456"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"commercial_standards": {
|
||
"商务标": {
|
||
"pricing_method": "固定总价",
|
||
"payment_schedule": ["30%合同签订", "40%中期支付", "30%项目完成支付"],
|
||
"contract_conditions": {
|
||
"warranty_period": "2年",
|
||
"penalty_clauses": "延期每周罚款5%"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"invalid_requirements": {
|
||
"无效标与废标项": {
|
||
"common_issues": ["未按要求提交保证金", "技术标不达标"],
|
||
"invalidation_reasons": {
|
||
"missing_documents": "缺少必要文件",
|
||
"unqualified_technical_specs": "技术规格不合要求"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"bidding_documents_requirements": {
|
||
"投标文件要求": {
|
||
"file_format": "PDF",
|
||
"submission_deadline": "2024-08-01 17:00",
|
||
"submission_location": "北京市某某大厦5楼",
|
||
"required_sections": ["公司简介", "技术方案", "商务报价"]
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"opening_bid": {
|
||
"开评定标流程": {
|
||
"bid_opening_time": "2024-09-01 10:00",
|
||
"location": "会议室A",
|
||
"evaluation_criteria": ["价格", "技术能力", "项目经验"],
|
||
"evaluation_process": {
|
||
"first_round": "资格审查",
|
||
"second_round": "技术评分",
|
||
"final_round": "商务报价评定"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
]
|
||
|
||
filename = "test_file.pdf"
|
||
|
||
for i, data in enumerate(data_segments, 1):
|
||
response = {
|
||
'message': f'Processing segment {i}',
|
||
'filename': filename,
|
||
'data': data
|
||
}
|
||
yield f"data: {json.dumps(response, ensure_ascii=False)}\n\n"
|
||
time.sleep(3) # 每隔5秒发送一段数据
|
||
|
||
# 在结束信号之前发送完整的数据
|
||
combined_data = {}
|
||
for segment in data_segments:
|
||
for outer_key, inner_dict in segment.items():
|
||
# 获取内层字典的第一个(也是唯一的)键值对
|
||
inner_key, inner_value = next(iter(inner_dict.items()))
|
||
combined_data[inner_key] = inner_value
|
||
|
||
# 发送完整的大字典
|
||
complete_response = {
|
||
'message': 'Combined data',
|
||
'filename': filename,
|
||
'data': combined_data
|
||
}
|
||
yield f"data: {json.dumps(complete_response, ensure_ascii=False)}\n\n"
|
||
|
||
# 发送结束信号
|
||
final_response = {
|
||
'message': 'File processed successfully',
|
||
'filename': filename,
|
||
'data': 'END'
|
||
}
|
||
yield f"data: {json.dumps(final_response, ensure_ascii=False)}\n\n"
|
||
|
||
|
||
def generate_response(final_json_path):
|
||
logger = g.logger
|
||
# 检查final_json_path是否为空或None
|
||
if not final_json_path:
|
||
logger.error('Empty or None path provided for final_json.')
|
||
return jsonify({'error': 'No path provided for final_json.'}), 400
|
||
if not os.path.exists(final_json_path):
|
||
logger.error('final_json not found at path: ' + final_json_path)
|
||
return jsonify({'error': 'final_json not found'}), 404
|
||
with open(final_json_path, 'r', encoding='utf-8') as f:
|
||
logger.info('final_json_path:' + final_json_path)
|
||
zbparse_data = json.load(f)
|
||
json_str = json.dumps(zbparse_data, ensure_ascii=False)
|
||
return jsonify({
|
||
'message': 'File uploaded and processed successfully',
|
||
'filename': os.path.basename(final_json_path),
|
||
'data': json_str
|
||
})
|
||
|
||
|
||
# @app.route('/get_json', methods=['POST'])
|
||
# def testjson():
|
||
# final_json_path="C:\\Users\\Administrator\\Desktop\\fsdownload\\temp4\\fd55f067-2cf6-475c-b7ce-4498f6606bf6\\final_result.json"
|
||
# with open(final_json_path, 'r', encoding='utf-8') as f:
|
||
# print('final_json_path:'+final_json_path)
|
||
# zbparse_data = json.load(f)
|
||
# json_str = json.dumps(zbparse_data, ensure_ascii=False)
|
||
# print(json_str)
|
||
# return jsonify({
|
||
# 'message': 'File uploaded and processed successfully',
|
||
# 'filename': os.path.basename(final_json_path),
|
||
# 'data': json_str
|
||
# })
|
||
|
||
|
||
def remove_directory(path):
|
||
logger = g.logger
|
||
try:
|
||
shutil.rmtree(path)
|
||
logger.info(f"Successfully removed directory: {path}") # 使用全局 logger 记录
|
||
except Exception as e:
|
||
logger.error(f"Failed to remove directory {path}: {str(e)}") # 使用全局 logger 记录
|
||
|
||
|
||
if __name__ == '__main__':
|
||
app.run(debug=True, host='0.0.0.0', port=5000)
|