from flask import Flask, request, jsonify, send_from_directory import os import time # 导入自定义模块 from utils.logger import logger, reset_process_id from utils.file_utils import get_date_folder, get_safe_filename, allowed_file from utils.docx_processor import process_word_template, check_variables_in_document, verify_replacement from scheduler import init_scheduler # 初始化Flask应用 app = Flask(__name__) # 启用跨域支持 from flask_cors import CORS CORS(app) # 配置常量 app.config['TEMPLATE_FOLDER'] = 'template' # 模板文件目录 app.config['OUTPUT_FOLDER'] = 'outputs' # 处理后文件保存目录 app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 限制上传文件大小为16MB app.config['DEBUG_MODE'] = False # 调试模式:True启用变量检查和验证,False禁用 app.config['LOG_FOLDER'] = 'logs' # 日志保存目录 # 确保模板、输出和日志目录存在 os.makedirs(app.config['TEMPLATE_FOLDER'], exist_ok=True) os.makedirs(app.config['OUTPUT_FOLDER'], exist_ok=True) os.makedirs(app.config['LOG_FOLDER'], exist_ok=True) # 允许的Word文档扩展名 ALLOWED_EXTENSIONS = {'.doc', '.docx'} # 添加路由来提供upload.html文件 @app.route('/') @app.route('/upload.html') def serve_upload_html(): """ 提供upload.html页面 """ return send_from_directory('.', 'upload.html') @app.route('/process_file', methods=['POST']) def process_file(): """ 处理Word文档模板,替换其中的模板变量 请求参数: - JSON格式的数据,包含要替换的变量键值对 返回: JSON响应,包含处理状态和输出文件名 """ # 重置处理ID,用于日志跟踪 reset_process_id() # 记录请求开始 start_time = time.time() logger.info("接收到文档处理请求") # 检查是否有JSON数据 if not request.is_json: logger.warning("请求中没有JSON数据") return jsonify({'error': '请求必须包含JSON数据'}), 400 # 获取JSON数据 data = request.get_json() if not data: logger.warning("JSON数据为空") return jsonify({'error': 'JSON数据为空'}), 400 logger.info(f"接收到的变量数据: {data}") try: # 获取模板文件,过滤掉Word临时文件(以~$开头) template_files = [f for f in os.listdir(app.config['TEMPLATE_FOLDER']) if f.lower().endswith('.docx') and not f.startswith('~$')] if not template_files: logger.error("模板文件夹中没有找到有效的.docx文件") return jsonify({'error': '没有找到有效的模板文件'}), 500 # 使用第一个模板文件 template_filename = template_files[0] template_path = os.path.join(app.config['TEMPLATE_FOLDER'], template_filename) logger.info(f"使用模板文件: {template_filename}") # 生成唯一的输出文件名 file_name_without_ext = os.path.splitext(template_filename)[0] extension = os.path.splitext(template_filename)[1] output_filename = get_safe_filename(file_name_without_ext, extension) # 获取当天的输出文件夹 output_date_folder = get_date_folder(app.config['OUTPUT_FOLDER']) # 处理文件(替换变量) output_path = os.path.join(output_date_folder, output_filename) # 构建变量字典,为每个键添加{} variables = {} for key, value in data.items(): var_key = '{' + key + '}' variables[var_key] = str(value) if value is not None else '' logger.info(f"将替换以下变量: {list(variables.keys())}") # 检查文档中是否包含这些变量(仅在调试模式下执行) if app.config['DEBUG_MODE']: check_variables_in_document(template_path, variables) # 处理文档并替换变量 process_word_template(template_path, output_path, variables) logger.info(f"变量替换完成") # 验证替换是否成功(仅在调试模式下执行) if app.config['DEBUG_MODE']: verify_replacement(output_path, variables) # 计算处理时间 process_time = time.time() - start_time logger.info(f"文档处理完成,耗时: {process_time:.2f}秒") # 返回成功信息 return jsonify({ 'message': '文档处理成功', 'output_filename': output_filename }) except PermissionError as e: error_msg = f"权限错误: {str(e)}。可能是文件被占用或没有写入权限。" logger.error(error_msg) return jsonify({'error': error_msg}), 500 except Exception as e: error_msg = f"处理文档时出错: {str(e)}" logger.error(error_msg, exc_info=True) return jsonify({'error': error_msg}), 500 @app.route('/download/') def download_file(filename): """ 提供处理后文档的下载 参数: filename: 要下载的文件名 返回: 处理后的文档文件 """ logger.info(f"请求下载文件: {filename}") try: # 获取当天的日期文件夹 today_folder = get_date_folder(app.config['OUTPUT_FOLDER']) # 首先尝试从当天的文件夹中查找 if os.path.exists(os.path.join(today_folder, filename)): logger.info(f"文件在当天文件夹中找到: {os.path.join(today_folder, filename)}") return send_from_directory(today_folder, filename) # 如果当天文件夹中没有,则尝试在输出根目录查找(兼容旧文件) if os.path.exists(os.path.join(app.config['OUTPUT_FOLDER'], filename)): logger.info(f"文件在输出根目录中找到: {os.path.join(app.config['OUTPUT_FOLDER'], filename)}") return send_from_directory(app.config['OUTPUT_FOLDER'], filename) # 如果还是没找到,尝试在其他日期文件夹中查找 for item in os.listdir(app.config['OUTPUT_FOLDER']): item_path = os.path.join(app.config['OUTPUT_FOLDER'], item) if os.path.isdir(item_path): file_path = os.path.join(item_path, filename) if os.path.exists(file_path): logger.info(f"文件在日期文件夹中找到: {file_path}") return send_from_directory(item_path, filename) # 如果所有位置都没找到,返回404错误 logger.warning(f"找不到请求的文件: {filename}") return jsonify({'error': f'找不到文件: {filename}'}), 404 except Exception as e: error_msg = f"下载文件时出错: {str(e)}" logger.error(error_msg, exc_info=True) return jsonify({'error': error_msg}), 500 if __name__ == '__main__': # 初始化调度器 init_scheduler(app.config['OUTPUT_FOLDER']) logger.info("Flask应用程序开始运行") # 使用host='0.0.0.0'使Flask监听所有网络接口,这样局域网内的其他设备可以访问 # port=5000指定端口号 app.run(host='0.0.0.0', port=5000, debug=True)