从上传的 JSON 或 YAML 文本导入场景并立即还原到引擎。 Content-Type: application/json → JSON 解析 Content-Type: application/x-yaml 或其他 → YAML 解析 同时接受 multipart/form-data 中的 ``file`` 字段(前端文件上传)。
()
| 959 | @require_role('admin') |
| 960 | @rate_limit(10, 60) |
| 961 | def scenario_import(): |
| 962 | """从上传的 JSON 或 YAML 文本导入场景并立即还原到引擎。 |
| 963 | |
| 964 | Content-Type: application/json → JSON 解析 |
| 965 | Content-Type: application/x-yaml 或其他 → YAML 解析 |
| 966 | 同时接受 multipart/form-data 中的 ``file`` 字段(前端文件上传)。 |
| 967 | """ |
| 968 | content_type = request.content_type or "" |
| 969 | |
| 970 | # 支持 multipart 文件上传 |
| 971 | if "multipart/form-data" in content_type: |
| 972 | file = request.files.get("file") |
| 973 | if file is None: |
| 974 | return error_response("VALIDATION_ERROR", "multipart 请求中未找到 'file' 字段") |
| 975 | raw_text = file.read(1 * 1024 * 1024).decode("utf-8", errors="replace") |
| 976 | # 根据文件名后缀决定解析器 |
| 977 | filename = (file.filename or "").lower() |
| 978 | use_yaml = filename.endswith(".yaml") or filename.endswith(".yml") |
| 979 | else: |
| 980 | raw_bytes = request.get_data(limit=1 * 1024 * 1024) |
| 981 | raw_text = raw_bytes.decode("utf-8", errors="replace") |
| 982 | use_yaml = "yaml" in content_type |
| 983 | |
| 984 | if not raw_text.strip(): |
| 985 | return error_response("VALIDATION_ERROR", "请求体为空") |
| 986 | |
| 987 | try: |
| 988 | if use_yaml: |
| 989 | scene_data = _ScenarioManager.from_yaml(raw_text) |
| 990 | else: |
| 991 | scene_data = _ScenarioManager.from_json(raw_text) |
| 992 | except ValueError as ve: |
| 993 | return error_response("VALIDATION_ERROR", f"场景解析失败: {ve}") |
| 994 | |
| 995 | errors = _ScenarioManager.validate(scene_data) |
| 996 | if errors: |
| 997 | return error_response("VALIDATION_ERROR", "; ".join(errors)) |
| 998 | |
| 999 | try: |
| 1000 | result = _ScenarioManager.load(simulation_engine, scene_data) |
| 1001 | except ValueError as ve: |
| 1002 | return error_response("VALIDATION_ERROR", str(ve)) |
| 1003 | except Exception: |
| 1004 | logger.exception("场景导入并还原失败") |
| 1005 | return error_response("INTERNAL_ERROR") |
| 1006 | |
| 1007 | # 成功导入后同时更新内存中保存的场景 |
| 1008 | global _saved_scene |
| 1009 | _saved_scene = scene_data |
| 1010 | return ok(result) |
| 1011 | |
| 1012 | |
| 1013 | @app.route('/api/scenario/current', methods=['GET']) |