"""任务编排层:job_context / emit / finish、鉴权与错误转换。 领域业务在 ``service/match_service.py``(匹配内核)、 ``service/profile_service.py``(画像/赛道/报告)、 ``service/report_service.py``(HTML 简报),由本模块调用; 多入口(CLI / Action / 对话 / cron)复用同一内核,禁止按入口分叉业务。 """ from __future__ import annotations import asyncio import json import os import uuid from typing import Any, Optional from jiangchang_skill_core import collect_runtime_diagnostics, config, format_runtime_health_lines from jiangchang_skill_core.activity import JobStopped, emit, finish, job_context from jiangchang_skill_core.rpa.video_session import RpaVideoSession from service.entitlement_service import check_entitlement from service.task_run_support import ( _print_video_summary, build_video_info, merge_video_into_result_summary, ) from util.constants import LOG_LOGGER_NAME, PLATFORM_KIT_MIN_VERSION, SKILL_SLUG, SKILL_VERSION from util.logging_config import get_skill_logger, setup_skill_logging from util.runtime_paths import get_db_path, get_skill_root from db.connection import init_db def _get_task_logger(): try: return get_skill_logger() except RuntimeError: setup_skill_logging(SKILL_SLUG, LOG_LOGGER_NAME) return get_skill_logger() def _print_json(payload: dict[str, Any]) -> None: print(json.dumps(payload, ensure_ascii=False)) # --------------------------------------------------------------------------- # 模板演示入口(保留:满足 POLICY-RPA-003 录屏集成与 CLI run 契约;不承载本技能业务) # --------------------------------------------------------------------------- async def _run_template_demo(target: Optional[str], input_id: Optional[str]) -> tuple[int, dict[str, Any]]: """最小 RpaVideoSession 示范:不启动浏览器、不执行业务,仅演示录屏包裹范式。""" batch_id = uuid.uuid4().hex[:12] data_dir = get_skill_root() async with RpaVideoSession( skill_slug=SKILL_SLUG, skill_data_dir=data_dir, batch_id=batch_id, title="开始执行示例任务", closing_title="示例任务执行完成", ) as video: video.add_step("准备执行示例任务") if target: video.add_step(f"接收目标参数:{target}") if input_id: video.add_step(f"接收输入标识:{input_id}") video.add_step("示例任务执行完成") video_summary = video.summary() video_info = build_video_info(video_summary, {}, {}) return 1, video_info def cmd_run(target: Optional[str] = None, input_id: Optional[str] = None) -> int: """模板演示入口:本技能主能力请使用 broadcast 等业务命令。""" log = _get_task_logger() task_type = "demo" with job_context(skill=SKILL_SLUG): log.info( "task_start task_type=%s target_id=%s input_id=%s stage=run", task_type, target, input_id, ) emit("开始处理任务", skill=SKILL_SLUG, stage="run") try: ok, reason = check_entitlement(SKILL_SLUG) if not ok: log.warning( "entitlement_failed task_type=%s stage=auth error_code=ENTITLEMENT_DENIED", task_type, ) emit("鉴权失败,已记录诊断信息", type="warn", skill=SKILL_SLUG, stage="auth") finish( status="failed", message=reason, skill=SKILL_SLUG, error_code="ENTITLEMENT_DENIED", stage="auth", ) print(f"ERROR:ENTITLEMENT_DENIED {reason}") return 1 log.info("entitlement_ok task_type=%s stage=auth", task_type) log.info("rpa_start stage=demo_prepare task_type=%s", task_type) log.info("task_run_demo_start target_id=%s input_id=%s stage=demo_prepare", target, input_id) rc, video_info = asyncio.run(_run_template_demo(target, input_id)) log.info("rpa_done stage=demo_prepare status=ok") log.info( "task_run_demo_done target_id=%s input_id=%s stage=demo_finish status=failed", target, input_id, ) _print_video_summary(video_info) summary_payload = merge_video_into_result_summary( { "template_demo": True, "target": target, "input_id": input_id, }, video_info, ) log.warning( "task_failed task_type=%s stage=demo_finish error_code=TEMPLATE_STUB", task_type, ) finish( status="failed", message="模板演示入口未承载本技能业务", skill=SKILL_SLUG, error_code="TEMPLATE_STUB", **summary_payload, ) print("ERROR:TEMPLATE_STUB 这是模板演示入口,请使用 match 命令执行申报赛道匹配。") return rc except JobStopped: raise except Exception: log.exception( "task_failed task_type=%s target_id=%s input_id=%s", task_type, target, input_id, ) emit("任务失败,已记录诊断信息", type="warn", skill=SKILL_SLUG, stage="run") finish( status="failed", message="任务执行异常,详见统一日志", skill=SKILL_SLUG, stage="run", ) return 1 # --------------------------------------------------------------------------- # 基础命令 # --------------------------------------------------------------------------- def cmd_config_path() -> int: example_path = os.path.join(get_skill_root(), ".env.example") env_path = config.get_env_file_path() or "" _print_json( { "skill": SKILL_SLUG, "env_path": env_path, "example_path": os.path.abspath(example_path), } ) return 0 def cmd_health() -> int: runtime = collect_runtime_diagnostics( skill_slug=SKILL_SLUG, platform_kit_min_version=PLATFORM_KIT_MIN_VERSION, skill_root=get_skill_root(), ) example_path = os.path.join(get_skill_root(), ".env.example") env_path = config.get_env_file_path() or "" env_exists = bool(env_path and os.path.isfile(env_path)) health_status = "failed" if runtime.has_fatal_issues else "ok" lines = [ f"{SKILL_SLUG} health: {health_status}", *format_runtime_health_lines(runtime), f"env_path: {env_path}", f"env_exists: {env_exists}", f"example_path: {os.path.abspath(example_path)}", ] for line in lines: print(line) return 1 if runtime.has_fatal_issues else 0 def cmd_version() -> int: _print_json({"version": SKILL_VERSION, "skill": SKILL_SLUG}) return 0 def cmd_init_db() -> int: """幂等创建/迁移本地 SQLite 与数据管理元数据(宿主 install/upgrade 可调用)。""" log = _get_task_logger() try: init_db() except Exception as exc: # noqa: BLE001 — CLI 边界,统一成可机读失败 print( json.dumps( {"ok": False, "skill": SKILL_SLUG, "error": str(exc)}, ensure_ascii=False, ) ) return 1 _print_json({"ok": True, "skill": SKILL_SLUG, "db_path": get_db_path()}) return 0 # --------------------------------------------------------------------------- # 画像与赛道维护 # --------------------------------------------------------------------------- def _parse_json_arg(raw: str, label: str) -> list[dict[str, Any]]: import os import sys text = (raw or "").strip() if not text: raise ValueError(f"缺少导入 JSON({label})") if text == "-": text = sys.stdin.read().strip() elif text.startswith(("[", "{")): pass elif os.path.isfile(text): with open(text, encoding="utf-8") as f: text = f.read().strip() else: raise ValueError(f"{label} 不是有效文件路径或 JSON:{text[:80]}") try: payload = json.loads(text) except json.JSONDecodeError as exc: raise ValueError(f"导入 JSON 解析失败:{exc}") from exc if isinstance(payload, dict): for key in ("teachers", "tracks", "items"): if isinstance(payload.get(key), list): payload = payload[key] break else: payload = [payload] if not isinstance(payload, list): raise ValueError(f"{label} 应为对象数组") return payload def cmd_add_teacher( *, name: str, department: str = "", title: str = "", tags: str = "", papers: str = "", projects: str = "", sci_q1: Optional[int] = None, core_papers: Optional[int] = None, ) -> int: log = _get_task_logger() from service.profile_service import upsert_teacher with job_context(skill=SKILL_SLUG): log.info("task_start task_type=add_teacher teacher=%s stage=profile", name) emit("开始写入教师画像", skill=SKILL_SLUG, stage="profile") try: ok, reason = check_entitlement(SKILL_SLUG) if not ok: log.warning("entitlement_failed task_type=add_teacher stage=auth") finish(status="failed", message=reason, skill=SKILL_SLUG, error_code="ENTITLEMENT_DENIED", stage="auth") log.error("add_teacher_denied stage=auth") print(f"ERROR:ENTITLEMENT_DENIED {reason}") return 1 fields: dict[str, Any] = {"teacher_name": name} for key, value in (("department", department), ("title", title), ("research_tags", tags), ("recent_papers", papers), ("project_history", projects)): if value: fields[key] = value if sci_q1 is not None: fields["sci_q1_count"] = sci_q1 if core_papers is not None: fields["core_paper_count"] = core_papers payload = upsert_teacher(fields) _print_json(payload) finish(status="success", message=f"教师 {name} 画像已{('更新' if payload['action'] == 'updated' else '录入')}", skill=SKILL_SLUG, stage="profile") return 0 except ValueError as exc: log.warning("task_failed task_type=add_teacher error_code=TEACHER_INVALID detail=%s", exc) finish(status="failed", message=str(exc), skill=SKILL_SLUG, error_code="TEACHER_INVALID", stage="profile") print(f"ERROR:TEACHER_INVALID {exc}") return 1 except JobStopped: raise except Exception: log.exception("task_failed task_type=add_teacher stage=profile") finish(status="failed", message="画像写入异常,详见统一日志", skill=SKILL_SLUG, stage="profile") return 1 def cmd_import_teachers(teachers_json: str) -> int: log = _get_task_logger() from service.profile_service import import_teachers with job_context(skill=SKILL_SLUG): log.info("task_start task_type=import_teachers stage=profile") emit("开始批量导入教师画像", skill=SKILL_SLUG, stage="profile") try: ok, reason = check_entitlement(SKILL_SLUG) if not ok: log.warning("entitlement_failed task_type=import_teachers stage=auth") finish(status="failed", message=reason, skill=SKILL_SLUG, error_code="ENTITLEMENT_DENIED", stage="auth") print(f"ERROR:ENTITLEMENT_DENIED {reason}") return 1 rows = _parse_json_arg(teachers_json, "--teachers-json") payload = import_teachers(rows) _print_json(payload) finish(status="success", message=f"导入完成:新增 {payload['created']},更新 {payload['updated']}", skill=SKILL_SLUG, stage="profile") return 0 except ValueError as exc: log.warning("task_failed task_type=import_teachers error_code=TEACHER_INVALID detail=%s", exc) finish(status="failed", message=str(exc), skill=SKILL_SLUG, error_code="TEACHER_INVALID", stage="profile") print(f"ERROR:TEACHER_INVALID {exc}") return 1 except JobStopped: raise except Exception: log.exception("task_failed task_type=import_teachers stage=profile") finish(status="failed", message="画像导入异常,详见统一日志", skill=SKILL_SLUG, stage="profile") return 1 def cmd_import_tracks(tracks_json: str) -> int: log = _get_task_logger() from service.profile_service import import_tracks with job_context(skill=SKILL_SLUG): log.info("task_start task_type=import_tracks stage=track") emit("开始批量导入指南赛道", skill=SKILL_SLUG, stage="track") try: ok, reason = check_entitlement(SKILL_SLUG) if not ok: log.warning("entitlement_failed task_type=import_tracks stage=auth") finish(status="failed", message=reason, skill=SKILL_SLUG, error_code="ENTITLEMENT_DENIED", stage="auth") print(f"ERROR:ENTITLEMENT_DENIED {reason}") return 1 rows = _parse_json_arg(tracks_json, "--tracks-json") payload = import_tracks(rows) _print_json(payload) finish(status="success", message=f"导入完成:新增 {payload['created']},更新 {payload['updated']}", skill=SKILL_SLUG, stage="track") return 0 except ValueError as exc: log.warning("task_failed task_type=import_tracks error_code=TRACK_INVALID detail=%s", exc) finish(status="failed", message=str(exc), skill=SKILL_SLUG, error_code="TRACK_INVALID", stage="track") print(f"ERROR:TRACK_INVALID {exc}") return 1 except JobStopped: raise except Exception: log.exception("task_failed task_type=import_tracks stage=track") finish(status="failed", message="赛道导入异常,详见统一日志", skill=SKILL_SLUG, stage="track") return 1 def cmd_add_track( *, year: int, track_name: str, program: str = "", agency: str = "", tags: str = "", priority: str = "", min_title: str = "", min_sci_q1: Optional[int] = None, quota: Optional[int] = None, source: str = "", ) -> int: log = _get_task_logger() from service.profile_service import upsert_track with job_context(skill=SKILL_SLUG): log.info("task_start task_type=add_track year=%s track=%s stage=track", year, track_name) emit("开始写入指南赛道", skill=SKILL_SLUG, stage="track") try: ok, reason = check_entitlement(SKILL_SLUG) if not ok: log.warning("entitlement_failed task_type=add_track stage=auth") finish(status="failed", message=reason, skill=SKILL_SLUG, error_code="ENTITLEMENT_DENIED", stage="auth") print(f"ERROR:ENTITLEMENT_DENIED {reason}") return 1 fields: dict[str, Any] = {"year": year, "track_name": track_name} for key, value in (("agency", agency), ("program_name", program), ("track_tags", tags), ("priority_keywords", priority), ("min_title", min_title), ("source_doc", source)): if value: fields[key] = value if min_sci_q1 is not None: fields["min_sci_q1"] = min_sci_q1 if quota is not None: fields["quota"] = quota payload = upsert_track(fields) _print_json(payload) finish(status="success", message=f"赛道 {track_name} 已{('更新' if payload['action'] == 'updated' else '录入')}", skill=SKILL_SLUG, stage="track") return 0 except ValueError as exc: log.warning("task_failed task_type=add_track error_code=TRACK_INVALID detail=%s", exc) finish(status="failed", message=str(exc), skill=SKILL_SLUG, error_code="TRACK_INVALID", stage="track") print(f"ERROR:TRACK_INVALID {exc}") return 1 except JobStopped: raise except Exception: log.exception("task_failed task_type=add_track stage=track") finish(status="failed", message="赛道写入异常,详见统一日志", skill=SKILL_SLUG, stage="track") return 1 def cmd_list_teachers(department: str = "") -> int: log = _get_task_logger() from service.profile_service import list_teachers with job_context(skill=SKILL_SLUG): log.info("task_start task_type=list_teachers department=%s", department) try: teachers = list_teachers(department=department) _print_json({"ok": True, "count": len(teachers), "teachers": teachers}) return 0 except Exception: log.exception("task_failed task_type=list_teachers") print("ERROR:QUERY_FAILED 教师画像查询异常,详见统一日志") return 1 def cmd_list_tracks(year: Optional[int] = None) -> int: log = _get_task_logger() from service.profile_service import list_tracks with job_context(skill=SKILL_SLUG): log.info("task_start task_type=list_tracks year=%s", year) try: tracks = list_tracks(year=year) _print_json({"ok": True, "count": len(tracks), "tracks": tracks}) return 0 except Exception: log.exception("task_failed task_type=list_tracks") print("ERROR:QUERY_FAILED 赛道查询异常,详见统一日志") return 1 # --------------------------------------------------------------------------- # 匹配主命令 # --------------------------------------------------------------------------- def cmd_match(*, teacher: str = "", year: Optional[int] = None) -> int: """申报赛道匹配(同步):缺省匹配全部教师;可指定教师与指南年度。""" log = _get_task_logger() from service.match_service import load_teachers, load_tracks, run_match with job_context(skill=SKILL_SLUG): log.info("task_start task_type=match teacher=%s year=%s stage=match", teacher, year) emit("开始申报赛道匹配", skill=SKILL_SLUG, stage="match") try: ok, reason = check_entitlement(SKILL_SLUG) if not ok: log.warning("entitlement_failed task_type=match stage=auth") finish(status="failed", message=reason, skill=SKILL_SLUG, error_code="ENTITLEMENT_DENIED", stage="auth") print(f"ERROR:ENTITLEMENT_DENIED {reason}") return 1 teachers = load_teachers(teacher_name=teacher) tracks = load_tracks(year=year) if not teachers: message = "没有可匹配的教师画像:请先录入教师(add-teacher / import-teachers)" log.warning("task_failed task_type=match error_code=TEACHER_NOT_FOUND stage=match") finish(status="failed", message=message, skill=SKILL_SLUG, error_code="TEACHER_NOT_FOUND", stage="match") print(f"ERROR:TEACHER_NOT_FOUND {message}") return 1 if not tracks: if year: message = f"指南赛道库中没有 {year} 年度赛道:请核对年度或先录入(add-track / import-tracks)" else: message = "指南赛道库为空:请先录入赛道(add-track / import-tracks)" log.warning("task_failed task_type=match error_code=TRACK_EMPTY stage=match") finish(status="failed", message=message, skill=SKILL_SLUG, error_code="TRACK_EMPTY", stage="match") print(f"ERROR:TRACK_EMPTY {message}") return 1 season = str(year or tracks[0].get("year") or "") # 指定教师 → 严格模式,画像不完整直接 PROFILE_INCOMPLETE; # 缺省全量 → 跳过画像待补录教师并记录,不中断批次(E1)。 result = run_match(teachers, tracks, season, skip_incomplete=not teacher) payload = {k: v for k, v in result.items() if k != "reports"} payload["reports_brief"] = [ { "report_id": r["report_id"], "teacher_name": r["teacher_name"], "tier": (r["top3"][0]["tier"] if r["top3"] else "无可推荐"), "top_track": (r["top3"][0]["track"] if r["top3"] else ""), "match_pct": (r["top3"][0]["match_pct"] if r["top3"] else None), "report_file": r["report_file"], } for r in result["reports"] ] _print_json(payload) skipped_count = len(result.get("skipped") or []) message = f"匹配完成 {result['count']} 名教师,建议书已生成" if skipped_count: message += f";{skipped_count} 名教师画像待补录已跳过" log.info("match_skipped count=%s stage=match", skipped_count) finish( status="success", message=message, skill=SKILL_SLUG, stage="match", batch_no=result["batch_no"], ) return 0 except JobStopped: raise except ValueError as exc: log.warning("task_failed task_type=match error_code=PROFILE_INCOMPLETE detail=%s", exc) finish(status="failed", message=str(exc), skill=SKILL_SLUG, error_code="PROFILE_INCOMPLETE", stage="match") print(f"ERROR:PROFILE_INCOMPLETE {exc}") return 1 except Exception: log.exception("task_failed task_type=match stage=match") emit("匹配失败,已记录诊断信息", type="warn", skill=SKILL_SLUG, stage="match") finish(status="failed", message="匹配执行异常,详见统一日志", skill=SKILL_SLUG, stage="match") return 1 def cmd_match_batch_input() -> int: """批量匹配(async Action 入口):匹配 input/ 目录中的教师画像 JSON 与库内赛道。 坏文件(解析失败/非对象数组)跳过并告警,不中断整批;画像不完整教师同样跳过并记录(E1)。 缺省匹配赛道库最新年度赛道。 """ log = _get_task_logger() from service.match_service import collect_input_teachers, load_tracks, run_match from util.runtime_paths import get_input_dir with job_context(skill=SKILL_SLUG): log.info("task_start task_type=match_batch stage=batch") emit("开始批量赛道匹配", skill=SKILL_SLUG, stage="batch") try: ok, reason = check_entitlement(SKILL_SLUG) if not ok: log.warning("entitlement_failed task_type=match_batch stage=auth") finish(status="failed", message=reason, skill=SKILL_SLUG, error_code="ENTITLEMENT_DENIED", stage="auth") print(f"ERROR:ENTITLEMENT_DENIED {reason}") return 1 input_dir = get_input_dir() teachers, skipped_files = collect_input_teachers(input_dir) for entry in skipped_files: log.warning("batch_input_file_skipped file=%s reason=%s stage=batch", entry["file"], entry["reason"]) emit(f"{entry['file']}:{entry['reason']},该文件已跳过", type="warn", skill=SKILL_SLUG, stage="batch") if not teachers: message = f"input 目录没有可用的教师画像 JSON({len(skipped_files)} 个文件无法解析):{input_dir}" log.warning("task_failed task_type=match_batch error_code=TEACHER_NOT_FOUND stage=batch") finish(status="failed", message=message, skill=SKILL_SLUG, error_code="TEACHER_NOT_FOUND", stage="batch") print(f"ERROR:TEACHER_NOT_FOUND {message}") return 1 tracks = load_tracks() if not tracks: message = "指南赛道库为空:请先录入赛道(add-track / import-tracks)" log.warning("task_failed task_type=match_batch error_code=TRACK_EMPTY stage=batch") finish(status="failed", message=message, skill=SKILL_SLUG, error_code="TRACK_EMPTY", stage="batch") print(f"ERROR:TRACK_EMPTY {message}") return 1 season = str(tracks[0].get("year") or "") result = run_match(teachers, tracks, season, skip_incomplete=True) for index, report in enumerate(result["reports"], start=1): top = (report.get("top3") or [{}])[0] emit( f"[{index}/{len(result['reports'])}] {report['teacher_name']}:" f"{(top.get('tier') or '无可推荐')}{(' · ' + top.get('track', '')) if top.get('track') else ''}", skill=SKILL_SLUG, stage="batch", ) for entry in result.get("skipped") or []: emit( f"{entry.get('teacher_name')}:画像待补录,本期已跳过({entry.get('reason')})", type="warn", skill=SKILL_SLUG, stage="batch", ) _print_json( { "ok": True, "batch_no": result["batch_no"], "count": result["count"], "skipped": result.get("skipped") or [], "skipped_files": skipped_files, "tier_counts": result["tier_counts"], "brief_file": result["brief_file"], } ) message = f"批量匹配完成 {result['count']} 名教师" if result.get("skipped"): message += f",{len(result['skipped'])} 名画像待补录已跳过" finish(status="success", message=message, skill=SKILL_SLUG, stage="batch", batch_no=result["batch_no"]) return 0 except JobStopped: raise except Exception: log.exception("task_failed task_type=match_batch stage=batch") emit("批量匹配失败,已记录诊断信息", type="warn", skill=SKILL_SLUG, stage="batch") finish(status="failed", message="批量匹配执行异常,详见统一日志", skill=SKILL_SLUG, stage="batch") return 1 # --------------------------------------------------------------------------- # 报告与统计 # --------------------------------------------------------------------------- def cmd_list_reports(limit: int = 20) -> int: log = _get_task_logger() from service.profile_service import list_reports with job_context(skill=SKILL_SLUG): log.info("task_start task_type=list_reports limit=%s", limit) try: reports = list_reports(limit=limit) _print_json({"ok": True, "count": len(reports), "reports": reports}) return 0 except Exception: log.exception("task_failed task_type=list_reports") print("ERROR:QUERY_FAILED 推荐报告查询异常,详见统一日志") return 1 def cmd_show_report(report_id: int) -> int: log = _get_task_logger() from service.profile_service import get_report with job_context(skill=SKILL_SLUG): log.info("task_start task_type=show_report report_id=%s", report_id) try: report = get_report(int(report_id)) _print_json({"ok": True, "report": report}) return 0 except ValueError as exc: log.warning("task_failed task_type=show_report error_code=REPORT_NOT_FOUND detail=%s", exc) print(f"ERROR:REPORT_NOT_FOUND {exc}") return 1 except Exception: log.exception("task_failed task_type=show_report") print("ERROR:QUERY_FAILED 推荐报告查询异常,详见统一日志") return 1 def cmd_render_html(report_id: Optional[int] = None) -> int: log = _get_task_logger() from service.report_service import build_report_for_report_id from service.profile_service import list_reports with job_context(skill=SKILL_SLUG): log.info("task_start task_type=render_html report_id=%s stage=render", report_id) try: if report_id: targets = [int(report_id)] else: targets = [r["id"] for r in list_reports(limit=10)] if not targets: log.warning("task_failed task_type=render_html error_code=REPORT_NOT_FOUND stage=render") finish(status="failed", message="暂无可重建的推荐报告", skill=SKILL_SLUG, error_code="REPORT_NOT_FOUND", stage="render") print("ERROR:REPORT_NOT_FOUND 暂无可重建的推荐报告") return 1 written = [{"report_id": rid, "path": build_report_for_report_id(rid)} for rid in targets] _print_json({"ok": True, "count": len(written), "reports": written}) finish(status="success", message=f"已重建 {len(written)} 份建议书", skill=SKILL_SLUG, stage="render") return 0 except JobStopped: raise except ValueError as exc: log.warning("task_failed task_type=render_html error_code=REPORT_NOT_FOUND detail=%s", exc) finish(status="failed", message=str(exc), skill=SKILL_SLUG, error_code="REPORT_NOT_FOUND", stage="render") print(f"ERROR:REPORT_NOT_FOUND {exc}") return 1 except Exception: log.exception("task_failed task_type=render_html stage=render") finish(status="failed", message="建议书重建异常,详见统一日志", skill=SKILL_SLUG, stage="render") return 1 def cmd_stats() -> int: log = _get_task_logger() from service.profile_service import stats with job_context(skill=SKILL_SLUG): log.info("task_start task_type=stats") try: _print_json(stats()) return 0 except Exception: log.exception("task_failed task_type=stats") print("ERROR:QUERY_FAILED 统计查询异常,详见统一日志") return 1