How to use feign to call xxl-job platform API directly

Time:2022-1-5
1、 Application background

A backend is required in the project toNo invasionCall the dispatching center API service. However, the dispatch center has set login, and the dispatch center API interface verifies the cookie. Feign needs to verify its login when accessing the dispatch center API service.

2、 Implementation principle

The feignclient client declaratively calls the API service of the dispatching center. Compared with the ordinary feignclient, the following processing is done:

  • The dispatching center logs in to the API service, and the return value is changed to feign Response, the original HTTP request response, which is convenient to obtain the cookie value;
  • For other API services in the dispatching center, add the @ requestheader (“cookie”) string cookie parameter, pass the cookie value, and log in and verify through the dispatching center;
How to use feign to call xxl-job platform API directly

image.png
3、 Potential problems
  1. Network overhead:
    If the login interface is requested every time the interface is called, it will inevitably incur additional network overhead, which can be handled by redis caching the cookie value.
  2. Login failure:
    Since the effective time of the cookie in the dispatching center is 2 hours, you need to log in every two hours to obtain a new cookie. You can log in again after expiration through the retry mechanism
    Solution:You can refer to xxljobcomponent java
4、 Code implementation
  1. Introducing related jars
<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

import com.alibaba.fastjson.JSON;
import lombok.Data;

import java.io.Serializable;

/**
 *XXL job API interface response wrapper class
 *
 * @author liudong
 * @date 2021/4/25 16:37
 */
@Data
public class HttpResultForXxlJob<T> implements Serializable {

    private static final long serialVersionUID = 6512789515344894483L;
    /**
     *Request status code
     */
    private int code;
    /**
     *News
     */
    private String msg;
    /**
     *Return data information
     */
    private T content;


    /**
     *Serialize to JSON
     *
     *@ return JSON string
     */
    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }

}
  1. XxlJobClient.java
import com.alibaba.fastjson.JSONObject;
import com.gaodun.pms.cdp.common.dto.external.xxljob.HttpResultForXxlJob;
import feign.Response;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
 
import java.util.Map;
 
/**
 *XXL job client
 *
 * @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 login interface
     *
     *@ param params parameter
     *@ return response information
     */
    @PostMapping(value = "/xxl-job-admin/login", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    Response login(@RequestBody Map<String, ?> params);
 
    /**
     *Create scheduled task
     *
     * @param cookie cookie
     *@ param params timed task parameters
     *@ return timed task 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);
 
    /**
     *Update scheduled tasks
     *
     * @param cookie cookie
     *@ param params timed task update parameters
     *@ return execution result
     */
    @PutMapping(value = "/xxl-job-admin/jobinfo/update", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    HttpResultForXxlJob<String> update(@RequestHeader("Cookie") String cookie, @RequestBody Map<String, ?> params);
 
    /**
     *Delete scheduled task
     *
     * @param cookie cookie
     *@ param ID scheduled task update parameters
     *@ return execution result
     */
    @DeleteMapping(value = "/xxl-job-admin/jobinfo/remove")
    HttpResultForXxlJob<String> remove(@RequestHeader("Cookie") String cookie, @RequestParam("id") int id);
 
    /**
     *Open task
     *
     * @param cookie cookie
     *@ param ID timed task ID
     *@ return execution result
     */
    @PutMapping(value = "/xxl-job-admin/jobinfo/start")
    HttpResultForXxlJob<String> start(@RequestHeader("Cookie") String cookie, @RequestParam("id") int id);
 
    /**
     *End task
     *
     * @param cookie cookie
     *@ param ID timed task ID
     *@ return execution result
     */
    @PutMapping(value = "/xxl-job-admin/jobinfo/stop")
    HttpResultForXxlJob<String> stop(@RequestHeader("Cookie") String cookie, @RequestParam("id") int id);
 
    /**
     *End task
     *
     * @param cookie cookie
     *@ param params query parameters
     *@ return execution result
     */
    @GetMapping(value = "/xxl-job-admin/joblog/pageList")
    JSONObject log(@RequestHeader("Cookie") String cookie, @RequestParam("params") Map<String, Object> params);
}
  1. XxlJobComponent.java
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.http.HttpStatus;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gaodun.pms.cdp.common.constant.CacheConstant;
import com.gaodun.pms.cdp.common.constant.XxlJobConstant;
import com.gaodun.pms.cdp.common.dto.external.xxljob.HttpResultForXxlJob;
import com.gaodun.pms.cdp.common.dto.xxljob.XxlJobLogDTO;
import com.gaodun.pms.cdp.common.exception.ErrorCode;
import com.gaodun.pms.cdp.common.request.xxljob.AddOrUpdateXxlJobInfoRequest;
import com.gaodun.pms.cdp.service.acm.ApplicationConfig;
import com.gaodun.pms.cdp.service.acm.JobConfig;
import com.gaodun.pms.cdp.service.external.feign.XxlJobClient;
import com.gaodunwangxiao.pms.exception.AbstractAssert;
import com.gaodunwangxiao.pms.exception.ExceptionFactory;
import com.gaodunwangxiao.pms.exception.SystemException;
import feign.FeignException;
import feign.Response;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.assertj.core.util.Lists;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;
 
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
 
/**
 *Task management processor
 *
 * @author liudong
 * @date 2021/4/25 10:56
 */
@Slf4j
@Component
public class XxlJobComponent {
 
    /**
     *XXL job account
     */
    @Value("${xxl.job.user-name}")
    private String userName;
 
    /**
     *XXL job password
     */
    @Value("${xxl.job.password}")
    private String password;
 
    /**
     *XXL job client
     */
    @Resource
    private XxlJobClient xxlJobClient;
 
    /**
     *Apply global configuration
     */
    @Resource
    private ApplicationConfig applicationConfig;
    /**
     *Redis operation class
     */
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
 
    /**
     *Log in to XXL job
     */
    public void login() {
        Map<String, Object> userInfo = new HashMap<>(MapUtil.DEFAULT_INITIAL_CAPACITY);
        userInfo.put("userName", userName);
        userInfo.put("password", password);
        //The cookie is set to be permanently valid, and the corresponding XXL job remembers the password
        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);
        }
    }
 
    /**
     *Create task
     *
     *@ param addorupdatexxljobinforequest task parameters
     *@ return task 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;
    }
 
    /**
     *Update task
     *
     *@ param addorupdatexxljobinforequest task parameters
     */
    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);
        }
    }
 
    /**
     *Delete task
     *
     *@ param ID task 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);
        }
    }
 
 
    /**
     *Start task
     *
     *@ param ID task 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);
        }
    }
 
    /**
     *Stop task
     *
     *@ param ID task 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);
        }
    }
 
    /**
     *Query log
     *
     *@ param params query parameters
     *@ return result set
     */
    @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 ("remote operation XXL job failed, retry!", e);
        }
        return Lists.newArrayList();
    }
 
 
    /**
     *Callback processing after the maximum number of retries is reached
     *
     *@ param SystemException retry exception
     */
    @Recover
    public void recoverCallback(SystemException systemException) {
        log. Error ("remote operation XXL job exception!", systemException);
    }
 
}
5、 Test case:
import cn.hutool.core.bean.BeanUtil;
import com.gaodun.pms.cdp.common.dto.xxljob.XxlJobInfoDTO;
import com.gaodun.pms.cdp.common.enums.MisfireStrategyEnum;
import com.gaodun.pms.cdp.common.request.xxljob.AddOrUpdateXxlJobInfoRequest;
import com.gaodun.pms.cdp.web.CdpApplication;
import feign.Response;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

/**
 * 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());
            }
        });
    }
}