/* * Copyright (c) 2018-2025, hnqz All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: hnqz */ package net.yyc.quartz.util; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpUtil; import lombok.extern.slf4j.Slf4j; import net.yyc.common.core.constant.CommonConstants; import net.yyc.common.core.constant.SecurityConstants; import net.yyc.quartz.entity.SysJob; import net.yyc.quartz.exception.TaskException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; /** * 定时任务 REST 反射实现 * 支持 service-name 模式(http://service-name/path)和 host:port 模式(http://host:port/path) * * @author 郑健楠 */ @Slf4j @Component public class RestTaskInvok implements ITaskInvok { @Autowired private LoadBalancerClient loadBalancer; @Override public void invokMethod(SysJob sysJob) throws TaskException { try { String executePath = sysJob.getExecutePath(); String resolvedUrl = resolveUrl(executePath); HttpRequest request = HttpUtil.createPost(resolvedUrl); // 租户 header if (sysJob.getTenantId() != null) { request.header(CommonConstants.TENANT_ID, sysJob.getTenantId().toString()); } // 内部调用 header(绕过安全拦截器) request.header(SecurityConstants.FROM, SecurityConstants.FROM_IN); // 预留扩展 header(可由子类或配置覆盖) addExtraHeaders(request, sysJob); // 表单参数(params 对应 Controller @RequestParam) String methodParams = sysJob.getMethodParamsValue(); if (StrUtil.isNotBlank(methodParams)) { Map formData = new HashMap<>(); formData.put("params", methodParams); request.form(formData); } request.execute(); log.debug("REST任务调用完成, url={}, params={}", resolvedUrl, methodParams); } catch (Exception e) { log.error("定时任务restTaskInvok异常,执行任务:{}", sysJob.getExecutePath()); throw new TaskException("定时任务restTaskInvok业务执行失败,任务:" + sysJob.getExecutePath()); } } /** * 解析 URL,支持两种模式: * 1. service-name 模式:http://service-name/path → 通过 LoadBalancer 解析为实际地址 * 2. host:port 模式:http://host:port/path → 直接使用 * * @param executePath 原始执行路径 * @return 解析后的实际 URL */ private String resolveUrl(String executePath) { if (executePath == null || !executePath.startsWith("http://")) { return executePath; } try { // 提取服务名和路径,例如: http://hnqz-upms-biz/admin/schedule-task/xxx String path = executePath.substring(7); int slashIndex = path.indexOf('/'); if (slashIndex == -1) { return executePath; } String serviceName = path.substring(0, slashIndex); String servicePath = path.substring(slashIndex); // 尝试通过 LoadBalancer 解析服务名 ServiceInstance instance = loadBalancer.choose(serviceName); if (instance != null) { return instance.getUri().toString() + servicePath; } } catch (Exception e) { log.warn("服务名解析失败,使用原路径: {}", executePath, e); } // host:port 模式直接返回原路径 return executePath; } /** * 扩展点:子类可覆盖此方法添加额外的 header * 目前预留,未来可通过 SysJob 新增字段(如 invokeHeaders)实现动态配置 * * @param request HTTP 请求 * @param sysJob 任务实体 */ protected void addExtraHeaders(HttpRequest request, SysJob sysJob) { // 预留扩展点,可通过 sysJob.invokeHeaders 或配置文件动态添加 header } }