xxl_endpoints.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. """XXL-JOB Admin → Executor 通信端点 —— 对标 Java XxlJobController.java
  2. 提供给 XXL-JOB Admin 调用的 HTTP API:
  3. POST /beat — 心跳检测
  4. POST /idleBeat — 空闲检测
  5. POST /run — 触发任务执行
  6. POST /kill — 强制终止任务
  7. POST /log — 读取执行日志
  8. """
  9. import logging
  10. from quart import Blueprint, request, jsonify
  11. logger = logging.getLogger(__name__)
  12. # 全局引用,由 create_xxl_bp 注入
  13. _executor_ref = None
  14. def set_xxl_executor(executor):
  15. """注入 XxlJobExecutor 实例(在 app 启动时调用)"""
  16. global _executor_ref
  17. _executor_ref = executor
  18. async def _parse_body():
  19. """解析请求体:优先 JSON,兜底 form-data
  20. XXL-JOB Admin 不同版本发送方式不同(JSON 或 form),统一处理。
  21. """
  22. data = await request.get_json(silent=True)
  23. if data:
  24. return data
  25. return await request.form
  26. def create_xxl_bp() -> Blueprint:
  27. """创建 XXL-JOB API Blueprint
  28. XXL-JOB Admin 通过此 Blueprint 与执行器通信。
  29. 端点路径与 XXL-JOB 标准一致:/beat, /idleBeat, /run, /kill, /log
  30. """
  31. bp = Blueprint("xxl_job", __name__)
  32. @bp.route("/beat", methods=["POST"])
  33. async def beat():
  34. """心跳检测"""
  35. result = _executor_ref.beat()
  36. return jsonify(result)
  37. @bp.route("/idleBeat", methods=["POST"])
  38. async def idle_beat():
  39. """空闲检测:检查指定 jobId 是否正在运行"""
  40. body = await _parse_body()
  41. job_id = int(body.get("jobId", 0))
  42. result = _executor_ref.idle_beat(job_id)
  43. return jsonify(result)
  44. @bp.route("/run", methods=["POST"])
  45. async def run():
  46. """触发任务执行"""
  47. body = await _parse_body()
  48. trigger_params = {
  49. "jobId": int(body.get("jobId", 0)),
  50. "executorHandler": body.get("executorHandler", "").strip(),
  51. "executorParams": body.get("executorParams", ""),
  52. "executorBlockStrategy": body.get("executorBlockStrategy", ""),
  53. "executorTimeout": int(body.get("executorTimeout", 0)),
  54. "logId": int(body.get("logId", 0)),
  55. "logDateTime": int(body.get("logDateTime", 0)),
  56. "glueType": body.get("glueType", ""),
  57. "glueSource": body.get("glueSource", ""),
  58. "glueUpdatetime": int(body.get("glueUpdatetime", 0)),
  59. "broadcastIndex": int(body.get("broadcastIndex", 0)),
  60. "broadcastTotal": int(body.get("broadcastTotal", 1)),
  61. }
  62. logger.info(
  63. "XXL-JOB run: handler=%s, jobId=%s, params=%s",
  64. trigger_params["executorHandler"],
  65. trigger_params["jobId"],
  66. trigger_params["executorParams"],
  67. )
  68. result = await _executor_ref.run(trigger_params)
  69. return jsonify(result)
  70. @bp.route("/kill", methods=["POST"])
  71. async def kill():
  72. """强制终止任务"""
  73. body = await _parse_body()
  74. job_id = int(body.get("jobId", 0))
  75. logger.info("XXL-JOB kill: jobId=%s", job_id)
  76. result = await _executor_ref.kill(job_id)
  77. return jsonify(result)
  78. @bp.route("/log", methods=["POST"])
  79. async def log():
  80. """读取执行日志"""
  81. body = await _parse_body()
  82. log_date_time = int(body.get("logDateTim", 0))
  83. log_id = int(body.get("logId", 0))
  84. from_line_num = int(body.get("fromLineNum", 1))
  85. result = _executor_ref.log(log_date_time, log_id, from_line_num)
  86. return jsonify(result)
  87. return bp