|
|
import subprocess
|
|
|
|
|
|
from flask import Flask, render_template, request, send_file, session, redirect, url_for
|
|
|
from datetime import datetime
|
|
|
from common.logger import tool_logger, error_logger, system_logger
|
|
|
|
|
|
import os, uuid
|
|
|
|
|
|
# =========================
|
|
|
# 工具模块导入
|
|
|
# =========================
|
|
|
from tools.file_convert import file_convert_page, file_convert_download
|
|
|
from tools.temp_upload import (
|
|
|
temp_upload_page,
|
|
|
download_temp_file
|
|
|
)
|
|
|
|
|
|
from tools.excel_merge import (
|
|
|
read_file,
|
|
|
merge_df,
|
|
|
export_excel,
|
|
|
build_preview
|
|
|
)
|
|
|
|
|
|
from tools.smart_data_v2 import (
|
|
|
smart_data_page_v2,
|
|
|
smart_download_v2
|
|
|
)
|
|
|
|
|
|
from tools.weekly_permission import weekly_permission_page
|
|
|
|
|
|
# ⭐ 新增:邮件通知工具
|
|
|
from tools.mail_notify import mail_notify_page, mail_notify_progress
|
|
|
|
|
|
# ⭐ 新增:Base64 编解码工具
|
|
|
from tools.base64_codec import page as base64_page
|
|
|
|
|
|
# ⭐ 新增:JSON 格式化工具
|
|
|
from tools.json_tool import page as json_page
|
|
|
|
|
|
# ⭐ 新增:URL 编解码工具
|
|
|
from tools.url_codec import page as url_page
|
|
|
|
|
|
# ⭐ 新增:图片压缩工具
|
|
|
from tools.image_compress import page as image_compress_page
|
|
|
|
|
|
# ⭐ 新增:文本差异对比工具
|
|
|
from tools.text_diff import page as text_diff_page
|
|
|
|
|
|
|
|
|
# =========================
|
|
|
# Flask app
|
|
|
# =========================
|
|
|
app = Flask(__name__)
|
|
|
app.secret_key = "excel_merge_secret_key"
|
|
|
|
|
|
UPLOAD_FOLDER = "uploads"
|
|
|
OUTPUT_FOLDER = "output"
|
|
|
|
|
|
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
|
|
os.makedirs(OUTPUT_FOLDER, exist_ok=True)
|
|
|
|
|
|
|
|
|
# =========================
|
|
|
# reset
|
|
|
# =========================
|
|
|
@app.route("/reset")
|
|
|
def reset():
|
|
|
session.clear()
|
|
|
return redirect(url_for("index"))
|
|
|
|
|
|
|
|
|
# =========================
|
|
|
# 首页
|
|
|
# =========================
|
|
|
@app.route("/")
|
|
|
def index():
|
|
|
return render_template("index.html")
|
|
|
|
|
|
|
|
|
# =========================
|
|
|
# Excel merge(原逻辑不变)
|
|
|
# =========================
|
|
|
def handle_excel_merge():
|
|
|
preview_data = None
|
|
|
download_file = None
|
|
|
message = None
|
|
|
|
|
|
if request.method == "POST":
|
|
|
try:
|
|
|
file1 = request.files.get("file1")
|
|
|
file2 = request.files.get("file2")
|
|
|
|
|
|
if file1 and file1.filename:
|
|
|
p1 = os.path.join(UPLOAD_FOLDER, f"{uuid.uuid4()}_{file1.filename}")
|
|
|
file1.save(p1)
|
|
|
session["file1_path"] = p1
|
|
|
session["file1_name"] = file1.filename
|
|
|
|
|
|
if file2 and file2.filename:
|
|
|
p2 = os.path.join(UPLOAD_FOLDER, f"{uuid.uuid4()}_{file2.filename}")
|
|
|
file2.save(p2)
|
|
|
session["file2_path"] = p2
|
|
|
session["file2_name"] = file2.filename
|
|
|
|
|
|
if not session.get("file1_path") or not session.get("file2_path"):
|
|
|
raise Exception("请上传两个文件")
|
|
|
|
|
|
key1 = request.form.get("key1", "").strip()
|
|
|
key2 = request.form.get("key2", "").strip()
|
|
|
join_type = request.form.get("join_type", "left")
|
|
|
|
|
|
session["key1"] = key1
|
|
|
session["key2"] = key2
|
|
|
session["join_type"] = join_type
|
|
|
|
|
|
df1 = read_file(session["file1_path"])
|
|
|
df2 = read_file(session["file2_path"])
|
|
|
|
|
|
merged_df = merge_df(df1, df2, key1, key2, join_type)
|
|
|
|
|
|
out_name = f"merged_{uuid.uuid4().hex}.xlsx"
|
|
|
out_path = os.path.join(OUTPUT_FOLDER, out_name)
|
|
|
|
|
|
export_excel(merged_df, out_path)
|
|
|
|
|
|
preview_data = build_preview(merged_df)
|
|
|
download_file = out_name
|
|
|
|
|
|
tool_logger.info(
|
|
|
f"[ExcelMerge] file1={session.get('file1_name')} "
|
|
|
f"file2={session.get('file2_name')} rows={len(merged_df)}"
|
|
|
)
|
|
|
|
|
|
message = (
|
|
|
f"整合成功,共 {len(merged_df)} 条记录 | "
|
|
|
f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
|
|
)
|
|
|
|
|
|
except Exception as e:
|
|
|
error_logger.exception("[ExcelMergeError]")
|
|
|
message = f"错误:{e}"
|
|
|
|
|
|
return render_template(
|
|
|
"excel.html",
|
|
|
preview_data=preview_data,
|
|
|
download_file=download_file,
|
|
|
message=message,
|
|
|
file1_name=session.get("file1_name", ""),
|
|
|
file2_name=session.get("file2_name", ""),
|
|
|
key1=session.get("key1", ""),
|
|
|
key2=session.get("key2", ""),
|
|
|
join_type=session.get("join_type", "left"),
|
|
|
)
|
|
|
|
|
|
|
|
|
# =========================
|
|
|
# download
|
|
|
# =========================
|
|
|
@app.route("/download/<filename>")
|
|
|
def download(filename):
|
|
|
return send_file(
|
|
|
os.path.join(OUTPUT_FOLDER, filename),
|
|
|
as_attachment=True
|
|
|
)
|
|
|
|
|
|
|
|
|
# =========================
|
|
|
# TOOL HUB(统一入口)
|
|
|
# =========================
|
|
|
TOOL_ROUTES = {}
|
|
|
|
|
|
|
|
|
def tool_dispatch(name):
|
|
|
handler = TOOL_ROUTES.get(name)
|
|
|
if handler:
|
|
|
return handler()
|
|
|
|
|
|
return render_template(f"{name}.html")
|
|
|
|
|
|
|
|
|
@app.route("/tool/mail_notify/progress/<task_id>")
|
|
|
def mail_progress(task_id):
|
|
|
return mail_notify_progress(task_id)
|
|
|
|
|
|
|
|
|
@app.route("/tool/<name>", methods=["GET", "POST"])
|
|
|
def tool(name):
|
|
|
return tool_dispatch(name)
|
|
|
|
|
|
|
|
|
# =========================
|
|
|
# 工具注册(核心扩展点)
|
|
|
# =========================
|
|
|
TOOL_ROUTES.update({
|
|
|
|
|
|
"excel": handle_excel_merge,
|
|
|
|
|
|
"smart_data_v2": smart_data_page_v2,
|
|
|
|
|
|
"file_convert": file_convert_page,
|
|
|
|
|
|
"weekly_permission": weekly_permission_page,
|
|
|
|
|
|
# ⭐ 新增邮件通知工具
|
|
|
"mail_notify": mail_notify_page,
|
|
|
|
|
|
# ⭐ 新增 Base64 编解码工具
|
|
|
"base64": base64_page,
|
|
|
|
|
|
# ⭐ 新增 JSON 格式化工具
|
|
|
"json": json_page,
|
|
|
|
|
|
# ⭐ 新增 URL 编解码工具
|
|
|
"url": url_page,
|
|
|
|
|
|
# ⭐ 新增图片压缩工具
|
|
|
"image_compress": image_compress_page,
|
|
|
|
|
|
# ⭐ 新增文本差异对比工具
|
|
|
"text_diff": text_diff_page,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
# =========================
|
|
|
# temp upload
|
|
|
# =========================
|
|
|
@app.route("/tool/temp_upload", methods=["GET", "POST"])
|
|
|
def temp_upload():
|
|
|
return temp_upload_page()
|
|
|
|
|
|
|
|
|
@app.route("/temp/download/<filename>")
|
|
|
def temp_download(filename):
|
|
|
return download_temp_file(filename)
|
|
|
|
|
|
|
|
|
# =========================
|
|
|
# smart data download
|
|
|
# =========================
|
|
|
@app.route("/tool/smart_data_v2/download/<filename>")
|
|
|
def smart_data_v2_download(filename):
|
|
|
return smart_download_v2(filename)
|
|
|
|
|
|
|
|
|
# =========================
|
|
|
# file convert download
|
|
|
# =========================
|
|
|
@app.route("/download/file_convert/<filename>")
|
|
|
def file_convert_download_route(filename):
|
|
|
return file_convert_download(filename)
|
|
|
|
|
|
|
|
|
# =========================
|
|
|
# image compress download
|
|
|
# =========================
|
|
|
@app.route("/tool/image_compress/download/<filename>")
|
|
|
def image_compress_download(filename):
|
|
|
return send_file(os.path.join(OUTPUT_FOLDER, filename), as_attachment=True)
|
|
|
|
|
|
|
|
|
cleanup_process = None
|
|
|
def start_cleanup_task():
|
|
|
"""
|
|
|
启动定时清理任务
|
|
|
"""
|
|
|
global cleanup_process
|
|
|
cleanup_path = os.path.join(
|
|
|
os.path.dirname(__file__),
|
|
|
"cleanup_upload.py"
|
|
|
)
|
|
|
|
|
|
cleanup_process = subprocess.Popen(
|
|
|
["python3", cleanup_path]
|
|
|
)
|
|
|
|
|
|
system_logger.info(
|
|
|
f"[cleanup] started pid="
|
|
|
f"{cleanup_process.pid}"
|
|
|
)
|
|
|
print(
|
|
|
f"[cleanup] started pid="
|
|
|
f"{cleanup_process.pid}"
|
|
|
)
|
|
|
# =========================
|
|
|
# main
|
|
|
# =========================
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
# 防止 Flask debug 启动两次
|
|
|
if os.environ.get("WERKZEUG_RUN_MAIN") == "true":
|
|
|
start_cleanup_task()
|
|
|
|
|
|
app.run(host="0.0.0.0", port=8209, debug=True)
|