From 6cb6f9fc65c660cea13472a1dd4213149f9a8374 Mon Sep 17 00:00:00 2001 From: 24024 <240241002@qq.com> Date: Thu, 5 Feb 2026 20:47:14 +0800 Subject: [PATCH 1/4] =?UTF-8?q?```=20feat(api):=20=E6=B7=BB=E5=8A=A0Base64?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E5=A4=84=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在后端API中添加Base64图片处理逻辑,自动去除data URL头部信息 - 处理前端传来的包含header的Base64图片数据 refactor(video.js): 优化图片上传处理流程 - 将图片上传逻辑改为纯前端Base64处理,移除对后端上传接口的依赖 - 实现图片压缩功能,限制最大尺寸为2048像素 - 支持PNG和JPEG格式的图片压缩处理 - 移除原有的文件上传FormData方式,改用直接处理Base64数据 ``` --- blueprints/api.py | 7 +++++ static/js/video.js | 76 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 65 insertions(+), 18 deletions(-) diff --git a/blueprints/api.py b/blueprints/api.py index 1c2e399..38b51e2 100644 --- a/blueprints/api.py +++ b/blueprints/api.py @@ -129,6 +129,13 @@ def video_generate(): "images": data.get('images', []), "aspect_ratio": data.get('aspect_ratio', '9:16') } + + # 处理 Base64 图片 (去掉 header) + if payload.get("images"): + payload["images"] = [ + img.split(',', 1)[1] if ',' in img else img + for img in payload["images"] + ] # 4. 启动异步视频任务 app = current_app._get_current_object() diff --git a/static/js/video.js b/static/js/video.js index d6faf24..d0a8ceb 100644 --- a/static/js/video.js +++ b/static/js/video.js @@ -149,32 +149,72 @@ document.addEventListener('DOMContentLoaded', () => { showToast('已应用提示词模板', 'success'); }; - // 上传图片逻辑 + // 图片处理辅助函数 + const processImageFile = (file) => new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = (e) => { + const img = new Image(); + img.onload = () => { + const maxDim = 2048; + let w = img.width; + let h = img.height; + + if (w <= maxDim && h <= maxDim) { + resolve(e.target.result); + return; + } + + if (w > h) { + if (w > maxDim) { + h = Math.round(h * (maxDim / w)); + w = maxDim; + } + } else { + if (h > maxDim) { + w = Math.round(w * (maxDim / h)); + h = maxDim; + } + } + + const canvas = document.createElement('canvas'); + canvas.width = w; + canvas.height = h; + const ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0, w, h); + // 保持原格式,如果不是 png 则默认 jpeg (0.9 质量) + const outType = file.type === 'image/png' ? 'image/png' : 'image/jpeg'; + resolve(canvas.toDataURL(outType, 0.9)); + }; + img.onerror = reject; + img.src = e.target.result; + }; + reader.onerror = reject; + reader.readAsDataURL(file); + }); + + // 上传图片逻辑 (改为纯前端 Base64 处理) fileInput.onchange = async (e) => { const files = e.target.files; if (files.length === 0) return; - const formData = new FormData(); - formData.append('images', files[0]); - try { submitBtn.disabled = true; - const r = await fetch('/api/upload', { method: 'POST', body: formData }); - const d = await r.json(); - if (d.urls && d.urls.length > 0) { - uploadedImageUrl = d.urls[0]; - imagePreview.innerHTML = ` -
- - + // 压缩并转换为 Base64 + uploadedImageUrl = await processImageFile(files[0]); + + imagePreview.innerHTML = ` +
+ + - `; - lucide.createIcons(); - } +
+ `; + lucide.createIcons(); + showToast('图片已就绪', 'success'); } catch (err) { - showToast('图片上传失败', 'error'); + console.error(err); + showToast('图片处理失败', 'error'); } finally { submitBtn.disabled = false; } From 5e1f037d4c66738d3b7e0240a64891f8f1106339 Mon Sep 17 00:00:00 2001 From: 24024 <240241002@qq.com> Date: Thu, 5 Feb 2026 20:58:25 +0800 Subject: [PATCH 2/4] =?UTF-8?q?```=20feat(task-service):=20=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E8=A7=86=E9=A2=91=E7=94=9F=E6=88=90=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E5=A4=84=E7=90=86=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 支持 FAILURE 状态识别并添加循环结束时的状态检查, 防止极端情况下失败状态未被正确抛出异常的问题 ``` --- services/task_service.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/services/task_service.py b/services/task_service.py index c2464de..4db5f9b 100644 --- a/services/task_service.py +++ b/services/task_service.py @@ -359,10 +359,14 @@ def process_video_generation(app, user_id, internal_task_id, payload, api_key, c elif 'url' in poll_result: video_url = poll_result['url'] break - elif status in ['FAILED', 'ERROR']: - raise Exception(f"视频生成失败: {poll_result.get('fail_reason') or poll_result.get('message') or '未知错误'}") + elif status in ['FAILURE', 'FAILED', 'ERROR']: + reason = poll_result.get('fail_reason') or poll_result.get('message') or '未知错误' + raise Exception(f"视频生成失败: {reason}") if not video_url: + if status in ['FAILURE', 'FAILED', 'ERROR']: # 防止循环结束时正好是失败状态但未抛出的极端情况 + reason = poll_result.get('fail_reason') or poll_result.get('message') or '未知错误' + raise Exception(f"视频生成失败: {reason}") raise Exception("超时未获取到视频地址") # 3. 持久化记录 From bd80414c4d8ee01bde3615bac1c51f7903232469 Mon Sep 17 00:00:00 2001 From: 24024 <240241002@qq.com> Date: Sun, 8 Feb 2026 20:39:35 +0800 Subject: [PATCH 3/4] =?UTF-8?q?```=20feat(admin):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E8=AF=A6=E6=83=85=E9=A1=B5=E9=9D=A2=E5=92=8C?= =?UTF-8?q?API=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 `/admin/orders/` 路由用于显示订单详情页面 - 在管理后台的订单列表中添加查看操作按钮 - 实现 `get_order_detail` API 接口,提供订单详细信息 - 添加权限控制,确保只有管理员或订单所有者可访问 - 在充值历史页面也增加订单详情查看功能 - 更新表格布局以适应新增的操作列 ``` --- app.py | 5 + blueprints/admin.py | 38 ++++- templates/buy.html | 13 +- templates/order_detail.html | 238 ++++++++++++++++++++++++++++++++ templates/orders.html | 80 +++++++---- templates/recharge_history.html | 10 +- 6 files changed, 348 insertions(+), 36 deletions(-) create mode 100644 templates/order_detail.html diff --git a/app.py b/app.py index b4e25ba..068ac28 100644 --- a/app.py +++ b/app.py @@ -156,6 +156,11 @@ def create_app(): def video_page(): return render_template('video.html') + @app.route('/admin/orders/') + def order_detail(order_id): + # 权限检查可以在这里做,或者让模板里的 JS 向 API 请求时做 (API 已有权限检查) + return render_template('order_detail.html') + @app.route('/files/') def get_file(filename): """Proxy route to serve files from MinIO via the backend""" diff --git a/blueprints/admin.py b/blueprints/admin.py index 4b372b1..2e27470 100644 --- a/blueprints/admin.py +++ b/blueprints/admin.py @@ -1,8 +1,8 @@ -from flask import Blueprint, request, jsonify +from flask import Blueprint, request, jsonify, g from datetime import timedelta from extensions import db from models import User, Role, Permission, SystemDict, SystemNotification, Order, PointsGrant, InviteReward, to_bj_time, get_bj_now -from middlewares.auth import permission_required +from middlewares.auth import permission_required, login_required from services.logger import system_logger admin_bp = Blueprint('admin', __name__, url_prefix='/api/admin') @@ -303,6 +303,40 @@ def get_orders(): } for o in orders] }) +@admin_bp.route('/orders/', methods=['GET']) +@login_required +def get_order_detail(order_id): + order = db.session.get(Order, order_id) + if not order: + return jsonify({"error": "订单不存在"}), 404 + + # 权限检查:必须是管理员或者订单所有者 + if not g.user.has_permission('manage_system') and order.user_id != g.user.id: + return jsonify({"error": "无权访问此订单"}), 403 + + user = order.user + return jsonify({ + "order": { + "id": order.id, + "out_trade_no": order.out_trade_no, + "trade_no": order.trade_no, + "amount": float(order.amount), + "points": order.points, + "status": order.status, + "created_at": order.created_at_bj.strftime('%Y-%m-%d %H:%M:%S'), + "paid_at": order.paid_at_bj.strftime('%Y-%m-%d %H:%M:%S') if order.paid_at else None + }, + "buyer": { + "id": user.id, + "phone": user.phone, + "current_points": user.points, + "created_at": user.created_at_bj.strftime('%Y-%m-%d %H:%M:%S'), + "is_banned": user.is_banned, + "role": user.role.name if user.role else "普通用户" + }, + "current_is_admin": g.user.has_permission('manage_system') + }) + # --- 积分发放管理 --- @admin_bp.route('/points/grant', methods=['POST']) @permission_required('manage_system') diff --git a/templates/buy.html b/templates/buy.html index 99ff7c4..4d434ed 100644 --- a/templates/buy.html +++ b/templates/buy.html @@ -181,7 +181,8 @@ 订单信息 积分/金额 状态 - 时间 + 时间 + 操作 @@ -216,15 +217,21 @@ 已取消 {% endif %} - + {{ order.paid_at_bj.strftime('%Y-%m-%d %H:%M') if order.paid_at_bj else order.created_at_bj.strftime('%Y-%m-%d %H:%M') }} + + + + + {% endfor %} {% else %} - + 暂无充值记录 diff --git a/templates/order_detail.html b/templates/order_detail.html new file mode 100644 index 0000000..ca78fd4 --- /dev/null +++ b/templates/order_detail.html @@ -0,0 +1,238 @@ +{% extends "base.html" %} + +{% block title %}订单详情 - 管理后台{% endblock %} + +{% block content %} +
+
+ +
+
+ + + +
+

订单详情

+

+ + 订单号: ... +

+
+
+
+ +
+
+ +
+ +
+ +
+
+

+ + 商品信息 +

+
+ +
+
+
+
+ +
+
+

账户充值积分

+

虚拟商品 · 即时到账

+
+
+
+
+0 Pts +
+
¥0.00
+
+
+ +
+
+ 商品总额 + ¥0.00 +
+
+ 优惠金额 + -¥0.00 +
+
+ 实付款 + ¥0.00 +
+
+
+
+ + +
+

+ + 支付信息 +

+ +
+
+ +
+ 支付宝 (Alipay) +
+
+
+ +

--

+
+
+ +

--

+
+
+ +

--

+
+
+
+
+ + +
+
+

+ + 买家信息 +

+ +
+
+ +
+
+

--

+
+ -- + +
+
+
+ +
+
+ 用户 ID + #0 +
+
+ 注册时间 + -- +
+
+ 当前余额 +
+ + 0 +
+
+
+ + +
+ + +
+
+ +

系统提示

+
+

+ 该订单由系统自动处理并完成积分发放。如有异常,请及时联系技术人员查看服务器日志。 +

+
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/templates/orders.html b/templates/orders.html index a91d21b..479704f 100644 --- a/templates/orders.html +++ b/templates/orders.html @@ -16,31 +16,41 @@
- +
-
-
+
- - - - - + + + + + + - - - - `; - lucide.createIcons(); + renderEmptyState(); return; } @@ -116,28 +116,48 @@ - + `).join(''); lucide.createIcons(); } + function renderEmptyState() { + const list = document.getElementById('orderList'); + list.innerHTML = ` + + + + `; + lucide.createIcons(); + } + document.getElementById('orderSearch').oninput = (e) => { const val = e.target.value.toLowerCase(); - const filtered = allOrders.filter(o => - o.user_phone.includes(val) || + const filtered = allOrders.filter(o => + o.user_phone.includes(val) || o.out_trade_no.toLowerCase().includes(val) ); renderOrders(filtered); @@ -145,4 +165,4 @@ loadOrders(); -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/recharge_history.html b/templates/recharge_history.html index 5b196f3..4ae2133 100644 --- a/templates/recharge_history.html +++ b/templates/recharge_history.html @@ -38,6 +38,8 @@ + @@ -84,11 +86,17 @@ {% endif %} + {% endfor %} {% else %} -
用户信息订单详情积分/金额状态时间用户信息 + 订单详情 + 积分/金额 + 状态 + 时间 + 操作 +
+

正在获取记录...

@@ -73,17 +83,7 @@ function renderOrders(orders) { const list = document.getElementById('orderList'); if (orders.length === 0) { - list.innerHTML = ` -
-
- -

暂无记录

-
-
- ${o.status === 'PAID' - ? '已完成' - : o.status === 'PENDING' - ? '待支付' - : '已取消' - } + ${o.status === 'PAID' + ? '已完成' + : o.status === 'PENDING' + ? '待支付' + : '已取消' + } +
创建: ${o.created_at} ${o.paid_at ? '完成: ' + o.paid_at : ''}
+ + + +
+
+ +

暂无记录

+
+
支付时间 操作 +
+ + + +
+

暂无充值记录

From dd140d88d714f56bd9832e0c451fe3efd0affa3c Mon Sep 17 00:00:00 2001 From: 24024 <240241002@qq.com> Date: Sun, 15 Feb 2026 10:07:38 +0800 Subject: [PATCH 4/4] =?UTF-8?q?```=20docs(README):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E6=9C=8D=E5=8A=A1=E6=96=87=E6=A1=A3=E5=B9=B6?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=94=9F=E4=BA=A7=E7=8E=AF=E5=A2=83=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了开发环境启动说明 - 新增生产环境 Gunicorn 部署指南 - 包含 Gunicorn 安装和配置参数说明 ``` --- README.md | 12 ++++++++++++ requirements.txt | Bin 2118 -> 2156 bytes 2 files changed, 12 insertions(+) diff --git a/README.md b/README.md index 71dc125..a81ed41 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,23 @@ pip install -r requirements.txt ``` ### 2. 启动服务 + +**开发环境** ```bash python app.py ``` 服务默认运行在 `http://127.0.0.1:5000`。 +**生产环境 (Gunicorn)** +在 Linux 生产环境中,建议使用 Gunicorn 作为 WSGI 服务器以获得更好的性能和稳定性: +```bash +pip install gunicorn # 如果尚未安装 +gunicorn -w 4 -b 0.0.0.0:5000 app:app +``` +* `-w 4`: 使用 4 个工作进程(通常设为 CPU 核心数 * 2 + 1)。 +* `-b 0.0.0.0:5000`: 绑定所有 IP 且端口为 5000。 +* `app:app`: 加载 `app.py` 中的 `app` 实例。 + --- ## 🛠️ 常用维护命令 diff --git a/requirements.txt b/requirements.txt index 8b2ee9d5f60e76655bef84b38ee3bae781c07428..19d1a2dba9eea112b2a93c69ed7c5832612cb227 100644 GIT binary patch delta 46 vcmX>m@J3*R8;2SfLpnn#LmopWLo!1?LlKZ=3xq}t#teE424Kv~z{LOn`j`k9 delta 7 OcmaDOa78wUUlodVGS