From c38f25a58da4b13b2868eabe1545481725b33ea4 Mon Sep 17 00:00:00 2001 From: Edan's Win11 <961150665@qq.com> Date: Tue, 3 Jun 2025 16:51:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=90=8E=E7=AB=AF=20?= =?UTF-8?q?api=20=E5=90=AF=E5=8A=A8=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sau_backend/web/app.py | 415 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 415 insertions(+) create mode 100644 sau_backend/web/app.py diff --git a/sau_backend/web/app.py b/sau_backend/web/app.py new file mode 100644 index 0000000..333969f --- /dev/null +++ b/sau_backend/web/app.py @@ -0,0 +1,415 @@ +import asyncio +import os +import sqlite3 +import threading +import time +import uuid +from pathlib import Path +from queue import Queue +from flask_cors import CORS +from sau_backend.myUtils.auth import check_cookie +from flask import Flask, request, jsonify, Response, render_template, send_from_directory +from sau_backend.conf import BASE_DIR +from sau_backend.myUtils.login import get_tencent_cookie, douyin_cookie_gen, get_ks_cookie +from sau_backend.myUtils.postVideo import post_video_tencent, post_video_DouYin, post_video_ks + +active_queues = {} +app = Flask(__name__) + +#允许所有来源跨域访问 +CORS(app) + +# 限制上传文件大小为160MB +app.config['MAX_CONTENT_LENGTH'] = 160 * 1024 * 1024 + +# 获取当前目录(假设 index.html 和 assets 在这里) +current_dir = os.path.dirname(os.path.abspath(__file__)) + +# 处理所有静态资源请求(未来打包用) +@app.route('/assets/') +def custom_static(filename): + return send_from_directory(os.path.join(current_dir, 'assets'), filename) + +# 处理 favicon.ico 静态资源(未来打包用) +@app.route('/favicon.ico') +def favicon(filename): + return send_from_directory(os.path.join(current_dir, 'assets'), 'favicon.ico') + +# (未来打包用) +@app.route('/') +def hello_world(): # put application's code here + return render_template('index.html') + +@app.route('/upload', methods=['POST']) +@app.route('/api/upload', methods=['POST']) +def upload_file(): + if 'file' not in request.files: + return jsonify({ + "code": 200, + "data": None, + "msg": "No file part in the request" + }), 400 + file = request.files['file'] + if file.filename == '': + return jsonify({ + "code": 200, + "data": None, + "msg": "No selected file" + }), 400 + try: + # 保存文件到指定位置 + uuid_v1 = uuid.uuid1() + print(f"UUID v1: {uuid_v1}") + filepath = Path(BASE_DIR / "videoFile" / f"{uuid_v1}_{file.filename}") + file.save(filepath) + return jsonify({"code":200,"msg": "File uploaded successfully", "data": f"{uuid_v1}_{file.filename}"}), 200 + except Exception as e: + return jsonify({"code":200,"msg": str(e),"data":None}), 500 + +@app.route('/getFile', methods=['GET']) +@app.route('/api/getFile', methods=['GET']) +def get_file(): + # 获取 filename 参数 + filename = request.args.get('filename') + + if not filename: + return {"error": "filename is required"}, 400 + + # 防止路径穿越攻击 + if '..' in filename or filename.startswith('/'): + return {"error": "Invalid filename"}, 400 + + # 拼接完整路径 + file_path = str(Path(BASE_DIR / "videoFile")) + + # 返回文件 + return send_from_directory(file_path,filename) + + +@app.route('/uploadSave', methods=['POST']) +@app.route('/api/uploadSave', methods=['POST']) +def upload_save(): + if 'file' not in request.files: + return jsonify({ + "code": 400, + "data": None, + "msg": "No file part in the request" + }), 400 + + file = request.files['file'] + if file.filename == '': + return jsonify({ + "code": 400, + "data": None, + "msg": "No selected file" + }), 400 + + # 获取表单中的自定义文件名(可选) + custom_filename = request.form.get('filename', None) + if custom_filename: + filename = custom_filename + "." + file.filename.split('.')[-1] + else: + filename = file.filename + + try: + # 生成 UUID v1 + uuid_v1 = uuid.uuid1() + print(f"UUID v1: {uuid_v1}") + + # 构造文件名和路径 + final_filename = f"{uuid_v1}_{filename}" + filepath = Path(BASE_DIR / "videoFile" / f"{uuid_v1}_{filename}") + + # 保存文件 + file.save(filepath) + + with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: + cursor = conn.cursor() + cursor.execute(''' + INSERT INTO file_records (filename, filesize, file_path) + VALUES (?, ?, ?) + ''', (filename, round(float(os.path.getsize(filepath)) / (1024 * 1024),2), final_filename)) + conn.commit() + print("✅ 上传文件已记录") + + return jsonify({ + "code": 200, + "msg": "File uploaded and saved successfully", + "data": { + "filename": filename, + "filepath": final_filename + } + }), 200 + + except Exception as e: + return jsonify({ + "code": 500, + "msg": str("upload failed!"), + "data": None + }), 500 + +@app.route('/getFiles', methods=['GET']) +@app.route('/api/getFiles', methods=['GET']) +def get_all_files(): + try: + # 使用 with 自动管理数据库连接 + with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: + conn.row_factory = sqlite3.Row # 允许通过列名访问结果 + cursor = conn.cursor() + + # 查询所有记录 + cursor.execute("SELECT * FROM file_records") + rows = cursor.fetchall() + + # 将结果转为字典列表 + data = [dict(row) for row in rows] + + return jsonify({ + "code": 200, + "msg": "success", + "data": data + }), 200 + except Exception as e: + return jsonify({ + "code": 500, + "msg": str("get file failed!"), + "data": None + }), 500 + + +@app.route("/getValidAccounts",methods=['GET']) +@app.route("/api/getValidAccounts",methods=['GET']) +async def getValidAccounts(): + with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: + cursor = conn.cursor() + cursor.execute(''' + SELECT * FROM user_info''') + rows = cursor.fetchall() + rows_list = [list(row) for row in rows] + print("\n📋 当前数据表内容:") + for row in rows: + print(row) + for row in rows_list: + flag = await check_cookie(row[1],row[2]) + if not flag: + row[4] = 0 + cursor.execute(''' + UPDATE user_info + SET status = ? + WHERE id = ? + ''', (0,row[0])) + conn.commit() + print("✅ 用户状态已更新") + for row in rows: + print(row) + return jsonify( + { + "code": 200, + "msg": None, + "data": rows_list + }),200 + +@app.route('/deleteFile', methods=['GET']) +@app.route('/api/deleteFile', methods=['GET']) +def delete_file(): + file_id = request.args.get('id') + + if not file_id or not file_id.isdigit(): + return jsonify({ + "code": 400, + "msg": "Invalid or missing file ID", + "data": None + }), 400 + + try: + # 获取数据库连接 + with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: + conn.row_factory = sqlite3.Row + cursor = conn.cursor() + + # 查询要删除的记录 + cursor.execute("SELECT * FROM file_records WHERE id = ?", (file_id,)) + record = cursor.fetchone() + + if not record: + return jsonify({ + "code": 404, + "msg": "File not found", + "data": None + }), 404 + + record = dict(record) + + # 删除数据库记录 + cursor.execute("DELETE FROM file_records WHERE id = ?", (file_id,)) + conn.commit() + + return jsonify({ + "code": 200, + "msg": "File deleted successfully", + "data": { + "id": record['id'], + "filename": record['filename'] + } + }), 200 + + except Exception as e: + return jsonify({ + "code": 500, + "msg": str("delete failed!"), + "data": None + }), 500 + + +# SSE 登录接口 +@app.route('/login') +@app.route('/api/login') +def login(): + # 1 小红书 2 视频号 3 抖音 4 快手 + type = request.args.get('type') + # 账号名 + id = request.args.get('id') + + # 模拟一个用于异步通信的队列 + status_queue = Queue() + active_queues[id] = status_queue + + def on_close(): + print(f"清理队列: {id}") + del active_queues[id] + # 启动异步任务线程 + thread = threading.Thread(target=run_async_function, args=(type,id,status_queue), daemon=True) + thread.start() + response = Response(sse_stream(status_queue,), mimetype='text/event-stream') + response.headers['Cache-Control'] = 'no-cache' + response.headers['X-Accel-Buffering'] = 'no' # 关键:禁用 Nginx 缓冲 + response.headers['Content-Type'] = 'text/event-stream' + response.headers['Connection'] = 'keep-alive' + return response + +@app.route('/postVideo', methods=['POST']) +@app.route('/api/postVideo', methods=['POST']) +def postVideo(): + # 获取JSON数据 + data = request.get_json() + + # 从JSON数据中提取fileList和accountList + file_list = data.get('fileList', []) + account_list = data.get('accountList', []) + type = data.get('type') + title = data.get('title') + tags = data.get('tags') + category = data.get('category') + enableTimer = data.get('enableTimer') + if category == 0: + category = None + + videos_per_day = data.get('videosPerDay') + daily_times = data.get('dailyTimes') + start_days = data.get('startDays') + # 打印获取到的数据(仅作为示例) + print("File List:", file_list) + print("Account List:", account_list) + match type: + case 1: + return + case 2: + post_video_tencent(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, + start_days) + case 3: + post_video_DouYin(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, + start_days) + case 4: + post_video_ks(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, + start_days) + # 返回响应给客户端 + return (jsonify( + { + "code": 200, + "msg": None, + "data": None + }), 200 + +@app.route('/postVideoBatch', methods=['POST'])) + +@app.route('/postVideoBatch', methods=['POST']) +@app.route('/api/postVideoBatch', methods=['POST']) +def postVideoBatch(): + data_list = request.get_json() + + if not isinstance(data_list, list): + return jsonify({"error": "Expected a JSON array"}), 400 + for data in data_list: + # 从JSON数据中提取fileList和accountList + file_list = data.get('fileList', []) + account_list = data.get('accountList', []) + type = data.get('type') + title = data.get('title') + tags = data.get('tags') + category = data.get('category') + enableTimer = data.get('enableTimer') + if category == 0: + category = None + + videos_per_day = data.get('videosPerDay') + daily_times = data.get('dailyTimes') + start_days = data.get('startDays') + # 打印获取到的数据(仅作为示例) + print("File List:", file_list) + print("Account List:", account_list) + match type: + case 1: + return + case 2: + post_video_tencent(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, + start_days) + case 3: + post_video_DouYin(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, + start_days) + case 4: + post_video_ks(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, + start_days) + # 返回响应给客户端 + return jsonify( + { + "code": 200, + "msg": None, + "data": None + }), 200 + +# 包装函数:在线程中运行异步函数 +def run_async_function(type,id,status_queue): + match type: + case '1': + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(get_tencent_cookie(id, status_queue)) + loop.close() + case '2': + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(get_tencent_cookie(id,status_queue)) + loop.close() + case '3': + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(douyin_cookie_gen(id,status_queue)) + loop.close() + case '4': + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(get_ks_cookie(id,status_queue)) + loop.close() + +# SSE 流生成器函数 +def sse_stream(status_queue): + while True: + if not status_queue.empty(): + msg = status_queue.get() + yield f"data: {msg}\n\n" + else: + # 避免 CPU 占满 + time.sleep(0.1) + +if __name__ == '__main__': + app.run(host='0.0.0.0' ,port=5409)