响应指令管理支持媒体上传并自动回填路径

1. 新增响应指令管理专用媒体上传接口,按图片语音视频白名单校验并分目录存储。

2. 在动作配置UI中为图片语音视频增加上传按钮,上传成功后自动回填本地绝对路径。

3. 保留结构化动作表单,进一步减少手工维护路径和JSON的场景。
This commit is contained in:
liuwei
2026-04-23 13:30:17 +08:00
parent 23b9d76b06
commit 3c7becd94f
2 changed files with 119 additions and 0 deletions

View File

@@ -1,16 +1,27 @@
# -*- coding: utf-8 -*-
"""趣味指令规则后台蓝图。"""
import os
import uuid
from datetime import datetime
from typing import Any, Dict
from flask import Blueprint, current_app, jsonify, render_template, request
from werkzeug.utils import secure_filename
from .auth import login_required
fun_command_rules_bp = Blueprint("fun_command_rules", __name__, url_prefix="/fun_command_rules")
# 媒体上传白名单:
# 这里仅允许趣味指令管理功能需要的媒体类型,避免上传任意可执行文件带来风险。
ALLOWED_EXTENSIONS = {
"image": {"png", "jpg", "jpeg", "gif", "webp"},
"voice": {"mp3", "wav", "amr"},
"video": {"mp4", "mov", "m4v"},
}
def _normalize_datetime_text(value):
"""统一时间字段展示格式。"""
@@ -70,12 +81,63 @@ def _validate_payload(payload: Dict[str, Any], service) -> str:
return ""
def _allowed_file(filename: str, media_type: str) -> bool:
"""判断上传文件扩展名是否允许。"""
if "." not in filename:
return False
ext = filename.rsplit(".", 1)[1].lower()
return ext in ALLOWED_EXTENSIONS.get(media_type, set())
@fun_command_rules_bp.route("/")
@login_required
def page_fun_command_rules():
return render_template("fun_command_rules.html")
@fun_command_rules_bp.route("/api/upload", methods=["POST"])
@login_required
def api_upload_media():
"""上传媒体文件并返回服务端绝对路径。
设计说明:
1. 使用独立目录 static/uploads/fun_command_rules与其它业务上传隔离。
2. 返回绝对路径,直接可用于插件发送媒体消息。
"""
if "file" not in request.files:
return jsonify({"success": False, "message": "未检测到上传文件"}), 400
media_type = str(request.form.get("media_type", "") or "").strip().lower()
if media_type not in ALLOWED_EXTENSIONS:
return jsonify({"success": False, "message": "media_type 非法,仅支持 image/voice/video"}), 400
upload_file = request.files.get("file")
if not upload_file or not upload_file.filename:
return jsonify({"success": False, "message": "文件名为空"}), 400
filename = secure_filename(upload_file.filename)
if not _allowed_file(filename, media_type):
return jsonify({"success": False, "message": f"不支持的文件类型: {filename}"}), 400
# 按媒体类型分目录,便于后续清理和排查。
upload_root = os.path.join(current_app.root_path, "static", "uploads", "fun_command_rules", media_type)
os.makedirs(upload_root, exist_ok=True)
unique_filename = f"{uuid.uuid4().hex}_{filename}"
abs_path = os.path.abspath(os.path.join(upload_root, unique_filename))
upload_file.save(abs_path)
return jsonify({
"success": True,
"message": "上传成功",
"data": {
"path": abs_path,
"filename": unique_filename,
"media_type": media_type,
}
})
@fun_command_rules_bp.route("/api/list", methods=["GET"])
@login_required
def api_list_rules():