如何使用feign直接调用XXL-JOB平台API

一、应用背景

项目中需要后端以无入侵的方式,调用调度中心API服务。然而调度中心设置了登录,调度中心API接口对cookie进行了验证,feign访问调度中心API服务时,需通过其登录验证。

二、实现原理

通过FeignClient客户端声明式调用调度中心Api服务与普通FeignClient相比作了一下几点处理:

  • 调度中心登录Api服务返回值改为feign.Response,原始的http请求响应,方便获取cookie值;
  • 调度中心其他Api服务,新增@RequestHeader("Cookie") String cookie参数,传递cookie值,通过调度中心登录验证;
image.png
三、潜在问题
  1. 网络开销:
    每次调用接口如果都请求一次登录接口,难免会产生额外的网络开销,可以通过redis缓存cookie值去处理。
  2. 登录失效:
    由于调度中心cookie有效时间为2小时,需每两小时登录一次,获取新的cookie,可以通过重试机制,实现过期重新登录
    解决方案:可以参考 XxlJobComponent.java
四、代码实现
  1. 引入相关jar
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
  <groupId>com.xuxueli</groupId>
  <artifactId>xxl-job-core</artifactId>
  <version>2.3.0</version>
</dependency>

2.HttpResultForXxlJob.java

/**
 * xxl-job Api接口响应包装类
 *
 * @author liudong
 * @date 2021/4/25 16:37
 */
@Data
public class HttpResultForXxlJob<T> implements Serializable {

    private static final long serialVersionUID = 6512789515344894483L;
    /**
     * 请求状态码
     */
    private int code;
    /**
     * 消息
     */
    private String msg;
    /**
     * 返回数据信息
     */
    private T content;


    /**
     * 序列化为Json
     *
     * @return json字符串
     */
    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }

}

  1. XxlJobClient.java
/**
 * xxl-job客户端
 *
 * @author liudong
 * @date 2021/4/25 16:33
 */
@FeignClient(name = "xxlJobClient", url = "${third-party.config.xxl-job.host:not found xxl-job service url}")
public interface XxlJobClient {
    /**
     * xxl-job登录接口
     *
     * @param params 参数
     * @return 响应信息
     */
    @PostMapping(value = "/xxl-job-admin/login", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    Response login(@RequestBody Map<String, ?> params);
 
    /**
     * 创建定时任务
     *
     * @param cookie cookie
     * @param params 定时任务参数
     * @return 定时任务ID
     */
    @PostMapping(value = "/xxl-job-admin/jobinfo/add", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    HttpResultForXxlJob<Integer> add(@RequestHeader("Cookie") String cookie, @RequestBody Map<String, ?> params);
 
    /**
     * 更新定时任务
     *
     * @param cookie cookie
     * @param params 定时任务更新参数
     * @return 执行结果
     */
    @PutMapping(value = "/xxl-job-admin/jobinfo/update", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    HttpResultForXxlJob<String> update(@RequestHeader("Cookie") String cookie, @RequestBody Map<String, ?> params);
 
    /**
     * 删除定时任务
     *
     * @param cookie cookie
     * @param id     定时任务更新参数
     * @return 执行结果
     */
    @DeleteMapping(value = "/xxl-job-admin/jobinfo/remove")
    HttpResultForXxlJob<String> remove(@RequestHeader("Cookie") String cookie, @RequestParam("id") int id);
 
    /**
     * 开启任务
     *
     * @param cookie cookie
     * @param id     定时任务ID
     * @return 执行结果
     */
    @PutMapping(value = "/xxl-job-admin/jobinfo/start")
    HttpResultForXxlJob<String> start(@RequestHeader("Cookie") String cookie, @RequestParam("id") int id);
 
    /**
     * 结束任务
     *
     * @param cookie cookie
     * @param id     定时任务ID
     * @return 执行结果
     */
    @PutMapping(value = "/xxl-job-admin/jobinfo/stop")
    HttpResultForXxlJob<String> stop(@RequestHeader("Cookie") String cookie, @RequestParam("id") int id);
 
    /**
     * 结束任务
     *
     * @param cookie cookie
     * @param params 查询参数
     * @return 执行结果
     */
    @GetMapping(value = "/xxl-job-admin/joblog/pageList")
    JSONObject log(@RequestHeader("Cookie") String cookie, @RequestParam("params") Map<String, Object> params);
}
  1. XxlJobComponent.java
/**
 * 任务管理处理器
 *
 * @author liudong
 * @date 2021/4/25 10:56
 */
@Slf4j
@Component
public class XxlJobComponent {
 
    /**
     * xxl job 账号
     */
    @Value("${xxl.job.user-name}")
    private String userName;
 
    /**
     * xxl job 密码
     */
    @Value("${xxl.job.password}")
    private String password;
 
    /**
     * xxl-job客户端
     */
    @Resource
    private XxlJobClient xxlJobClient;
 
    /**
     * 应用全局配置
     */
    @Resource
    private ApplicationConfig applicationConfig;
    /**
     * redis操作类
     */
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
 
    /**
     * 登录xxl-job
     */
    public void login() {
        Map<String, Object> userInfo = new HashMap<>(MapUtil.DEFAULT_INITIAL_CAPACITY);
        userInfo.put("userName", userName);
        userInfo.put("password", password);
        // 设置cookie永久有效,对应xxl-job记住密码
        userInfo.put("ifRemember", "on");
        Response response = xxlJobClient.login(userInfo);
        if (HttpStatus.HTTP_OK == response.status()) {
            response.headers().get(XxlJobConstant.COOKIE_KEY).forEach(e -> {
                if (e.contains(XxlJobConstant.XXL_JOB_LOGIN_IDENTITY)) {
                    redisTemplate.opsForValue().set(CacheConstant.XXL_JOB_COOKIE_REDIS_KEY, e, CacheConstant.XXL_JOB_COOKIE_REDIS_TIMEOUT, TimeUnit.HOURS);
                }
            });
        } else {
            throw ExceptionFactory.systemException(ErrorCode.LOGIN_XXL_JOB_FAILURE_EXCEPTION);
        }
    }
 
    /**
     * 创建任务
     *
     * @param addOrUpdateXxlJobInfoRequest 任务参数
     * @return 任务ID
     */
 
    public Integer add(AddOrUpdateXxlJobInfoRequest addOrUpdateXxlJobInfoRequest) {
        if (Boolean.FALSE.equals(redisTemplate.hasKey(CacheConstant.XXL_JOB_COOKIE_REDIS_KEY))) {
            login();
        }
        if (ObjectUtils.isNotEmpty(addOrUpdateXxlJobInfoRequest)) {
            HttpResultForXxlJob result = xxlJobClient.add(String.valueOf(redisTemplate.opsForValue().get(CacheConstant.XXL_JOB_COOKIE_REDIS_KEY)), BeanUtil.beanToMap(addOrUpdateXxlJobInfoRequest));
            if (HttpStatus.HTTP_OK == result.getCode()) {
                log.info(result.getMsg());
                return (int) result.getContent();
            }
        }
        return null;
    }
 
    /**
     * 更新任务
     *
     * @param addOrUpdateXxlJobInfoRequest 任务参数
     */
    public void update(AddOrUpdateXxlJobInfoRequest addOrUpdateXxlJobInfoRequest) {
        if (Boolean.FALSE.equals(redisTemplate.hasKey(CacheConstant.XXL_JOB_COOKIE_REDIS_KEY))) {
            login();
        }
        if (ObjectUtils.isNotEmpty(addOrUpdateXxlJobInfoRequest)) {
            HttpResultForXxlJob result = xxlJobClient.update(String.valueOf(redisTemplate.opsForValue().get(CacheConstant.XXL_JOB_COOKIE_REDIS_KEY)), BeanUtil.beanToMap(addOrUpdateXxlJobInfoRequest));
            log.info(result.getMsg());
            AbstractAssert.isTrue(HttpStatus.HTTP_OK == result.getCode(), ErrorCode.UPDATE_JOB_FAILURE_EXCEPTION);
        }
    }
 
    /**
     * 删除任务
     *
     * @param id 任务ID
     */
    public void remove(Integer id) {
        if (Boolean.FALSE.equals(redisTemplate.hasKey(CacheConstant.XXL_JOB_COOKIE_REDIS_KEY))) {
            login();
        }
        if (ObjectUtils.isNotEmpty(id)) {
            HttpResultForXxlJob result = xxlJobClient.remove(String.valueOf(redisTemplate.opsForValue().get(CacheConstant.XXL_JOB_COOKIE_REDIS_KEY)), id);
            log.info(result.getMsg());
            AbstractAssert.isTrue(HttpStatus.HTTP_OK == result.getCode(), ErrorCode.REMOVE_JOB_FAILURE_EXCEPTION);
        }
    }
 
 
    /**
     * 启动任务
     *
     * @param id 任务ID
     */
    public void start(Integer id) {
        if (Boolean.FALSE.equals(redisTemplate.hasKey(CacheConstant.XXL_JOB_COOKIE_REDIS_KEY))) {
            login();
        }
        if (ObjectUtils.isNotEmpty(id)) {
            HttpResultForXxlJob result = xxlJobClient.start(String.valueOf(redisTemplate.opsForValue().get(CacheConstant.XXL_JOB_COOKIE_REDIS_KEY)), id);
            log.info(result.getMsg());
            AbstractAssert.isTrue(HttpStatus.HTTP_OK == result.getCode(), ErrorCode.START_JOB_FAILURE_EXCEPTION);
        }
    }
 
    /**
     * 停止任务
     *
     * @param id 任务ID
     */
    public void stop(Integer id) {
        if (Boolean.FALSE.equals(redisTemplate.hasKey(CacheConstant.XXL_JOB_COOKIE_REDIS_KEY))) {
            login();
        }
        if (ObjectUtils.isNotEmpty(id)) {
            HttpResultForXxlJob result = xxlJobClient.stop(String.valueOf(redisTemplate.opsForValue().get(CacheConstant.XXL_JOB_COOKIE_REDIS_KEY)), id);
            log.info(result.getMsg());
            AbstractAssert.isTrue(HttpStatus.HTTP_OK == result.getCode(), ErrorCode.STOP_JOB_FAILURE_EXCEPTION);
        }
    }
 
    /**
     * 查询日志
     *
     * @param params 查询参数
     * @return 结果集
     */
    @Retryable(value = SystemException.class, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
    public List<XxlJobLogDTO> log(Map<String, Object> params) {
        try {
            if (Boolean.FALSE.equals(redisTemplate.hasKey(CacheConstant.XXL_JOB_COOKIE_REDIS_KEY))) {
                login();
            }
            if (ObjectUtils.isEmpty(params)) {
                return Lists.newArrayList();
            }
            JSONObject result = xxlJobClient.log(String.valueOf(redisTemplate.opsForValue().get(CacheConstant.XXL_JOB_COOKIE_REDIS_KEY)), params);
            if (ObjectUtils.isNotEmpty(result) && ObjectUtils.isNotEmpty(result.getString("data"))) {
                return JSON.parseArray(result.getString("data"), XxlJobLogDTO.class);
            }
 
        } catch (FeignException e) {
            redisTemplate.delete(CacheConstant.XXL_JOB_COOKIE_REDIS_KEY);
            throw ExceptionFactory.systemException("远程操作xxl-job失败,进行重试!", e);
        }
        return Lists.newArrayList();
    }
 
 
    /**
     * 重试次数达到最大后回调处理
     *
     * @param systemException 重试异常
     */
    @Recover
    public void recoverCallback(SystemException systemException) {
        log.error("远程操作xxl-job异常!", systemException);
    }
 
}
五、测试用例:
/**
 * xxl-job api Test
 *
 * @author liudong
 * @date 2021/4/26 9:45
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = CdpApplication.class)
public class XxlJobClientTest {

    @Resource
    private XxlJobClient xxlJobClient;

    @Test
    public void login() {
    }

    @Test
    public void add() {
        Map<String, Object> hashMap = new HashMap(2);
        hashMap.put("userName", "admin");
        hashMap.put("password", "123456");
        Response response = xxlJobClient.login(hashMap);
        AddOrUpdateXxlJobInfoRequest addOrUpdateXxlJobInfoRequest = new AddOrUpdateXxlJobInfoRequest();
        addOrUpdateXxlJobInfoRequest.setJobGroup(2);
        addOrUpdateXxlJobInfoRequest.setJobDesc("test");
        addOrUpdateXxlJobInfoRequest.setAuthor("liudong");
        addOrUpdateXxlJobInfoRequest.setAlarmEmail("");
        addOrUpdateXxlJobInfoRequest.setScheduleType("CRON");
        addOrUpdateXxlJobInfoRequest.setScheduleConf("0/6 * * * * ?");
        addOrUpdateXxlJobInfoRequest.setGlueType("BEAN");
        addOrUpdateXxlJobInfoRequest.setExecutorHandler("testHandler");
        addOrUpdateXxlJobInfoRequest.setExecutorRouteStrategy("FIRST");
        addOrUpdateXxlJobInfoRequest.setMisfireStrategy(MisfireStrategyEnum.DO_NOTHING.toString());
        addOrUpdateXxlJobInfoRequest.setExecutorBlockStrategy("SERIAL_EXECUTION");
        XxlJobInfoDTO xxlJobInfoDTO = new XxlJobInfoDTO();
        BeanUtils.copyProperties(addOrUpdateXxlJobInfoRequest, xxlJobInfoDTO);
        Map<String, Object> stringObjectMap = BeanUtil.beanToMap(addOrUpdateXxlJobInfoRequest);
        response.headers().get("set-cookie").forEach(e -> {
            if (e.contains("XXL_JOB_LOGIN_IDENTITY")) {
                System.out.println(xxlJobClient.add(e, stringObjectMap).toString());
            }
        });
    }

    @Test
    public void update() {
        Map<String, Object> hashMap = new HashMap(2);
        hashMap.put("userName", "admin");
        hashMap.put("password", "123456");
        Response response = xxlJobClient.login(hashMap);
        AddOrUpdateXxlJobInfoRequest addOrUpdateXxlJobInfoRequest = new AddOrUpdateXxlJobInfoRequest();
        addOrUpdateXxlJobInfoRequest.setId(14);
        addOrUpdateXxlJobInfoRequest.setJobGroup(2);
        addOrUpdateXxlJobInfoRequest.setJobDesc("update");
        addOrUpdateXxlJobInfoRequest.setAuthor("liudong");
        addOrUpdateXxlJobInfoRequest.setAlarmEmail("");
        addOrUpdateXxlJobInfoRequest.setScheduleType("CRON");
        addOrUpdateXxlJobInfoRequest.setScheduleConf("0/6 * * * * ?");
        addOrUpdateXxlJobInfoRequest.setGlueType("BEAN");
        addOrUpdateXxlJobInfoRequest.setExecutorHandler("testHandler");
        addOrUpdateXxlJobInfoRequest.setExecutorRouteStrategy("FIRST");
        addOrUpdateXxlJobInfoRequest.setMisfireStrategy(MisfireStrategyEnum.DO_NOTHING.toString());
        addOrUpdateXxlJobInfoRequest.setExecutorBlockStrategy("SERIAL_EXECUTION");
        XxlJobInfoDTO xxlJobInfoDTO = new XxlJobInfoDTO();
        BeanUtils.copyProperties(addOrUpdateXxlJobInfoRequest, xxlJobInfoDTO);
        Map<String, Object> stringObjectMap = BeanUtil.beanToMap(addOrUpdateXxlJobInfoRequest);
        response.headers().get("set-cookie").forEach(e -> {
            if (e.contains("XXL_JOB_LOGIN_IDENTITY")) {
                System.out.println(xxlJobClient.update(e, stringObjectMap).toString());
            }
        });
    }

    @Test
    public void remove() {
        Map<String, Object> hashMap = new HashMap(2);
        hashMap.put("userName", "admin");
        hashMap.put("password", "123456");
        Response response = xxlJobClient.login(hashMap);
        response.headers().get("set-cookie").forEach(e -> {
            if (e.contains("XXL_JOB_LOGIN_IDENTITY")) {
                System.out.println(xxlJobClient.remove(e, 23).toString());
            }
        });
    }

    @Test
    public void start() {
        Map<String, Object> hashMap = new HashMap(2);
        hashMap.put("userName", "admin");
        hashMap.put("password", "123456");
        Response response = xxlJobClient.login(hashMap);
        response.headers().get("set-cookie").forEach(e -> {
            if (e.contains("XXL_JOB_LOGIN_IDENTITY")) {
                System.out.println(xxlJobClient.start(e, 22).toString());
            }
        });
    }

    @Test
    public void stop() {
        Map<String, Object> hashMap = new HashMap(2);
        hashMap.put("userName", "admin");
        hashMap.put("password", "123456");
        Response response = xxlJobClient.login(hashMap);
        response.headers().get("set-cookie").forEach(e -> {
            if (e.contains("XXL_JOB_LOGIN_IDENTITY")) {
                System.out.println(xxlJobClient.stop(e, 22).toString());
            }
        });
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容