前言
最近需要在Java后端操作xxl任务调度平台来完成任务的加入,在网上找了发现全都需要修改admin服务,对于已经上线的项目来说,明显不好操作切繁琐,修改admin服务的原因也很简单,是因为在外部直接调用添加、修改任务方法时,会被拦截告诉未登陆,因此需要在源码内多补充几个方法同时加上绕过登陆的注解。那么有没有一种办法可以不修改源码也可以操作admin服务内的方法呢。看了xxlJob的源码部分发现是可行的,在页面内我们可以发现登陆xxlJob后会返回一个cookie,那么我们就借助这个cookie来辅助我们登陆!
不了解xxlJob的朋友可以看一下上一篇:项目接入xxl-job
工具类介绍及使用
我自己已经封装了一个工具类用来帮助我们操作admin服务,使用也很简单。首先对工具类进行一个简单的介绍,流程框架为 调用登陆方法获取cookie >>> 携带cookie和参数访问指定方法 >>> 返回结果
在使用工具类之前,我们只需要手动修改三个值以及引入一个实体类即可,分别是xxlJob的地址、账号和密码。
实体类如下,主要是帮助我们转换为正确的调度任务对象集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| package com.sora.domain;
import lombok.Data;
import java.util.Date;
@Data public class XxlJobInfo {
private int id;
private int jobGroup; private String jobDesc;
private Date addTime; private Date updateTime;
private String author; private String alarmEmail;
private String scheduleType; private String scheduleConf; private String misfireStrategy;
private String executorRouteStrategy; private String executorHandler; private String executorParam; private String executorBlockStrategy; private int executorTimeout; private int executorFailRetryCount;
private String glueType; private String glueSource; private String glueRemark; private Date glueUpdatetime;
private String childJobId;
private int triggerStatus; private long triggerLastTime; private long triggerNextTime;
}
|
在工具类里,我对cookie做了一个存入本地map的操作,这样只需要访问一次登陆接口,后续直接从map获取即可(目前没有发现cookie过期,如果有可以在过期后再次访问登陆接口刷新cookie的值)
因为xxlJob的参数都是formData格式,所以我这里添加、修改任务的时候需要把整个map传递过去。下面简单说一下各个方法如何使用
login(登陆):用于获取访问admin服务的cookie,不需要我们去调用,工具类会自动调用该方法并存入cookieMap完成cookie的获取
getCookie(获取cookie):从cookieMap内获取cookie,没有cookie则访问login方法
addJob(添加调度任务):传入一个map,key为xxlJobInfo的字段名,value则为值,进行任务的添加
updateJob(修改调度任务):传入一个map,key为xxlJobInfo的字段名,value则为值,进行任务的修改
startJob(开启调度任务):传入一个long类型的任务id,将该任务的TriggerStatus字段设置为1,开始执行
stopJob(停止调度任务):传入一个long类型的任务id,将该任务的TriggerStatus字段设置为0,停止执行
removeJob(移除调度任务):传入一个long类型的任务id,删除该任务
pageList(获取所有调度任务):传入执行器id,获取该执行器下所有调度任务,返回XxlJobInfo类型的集合
link(访问admin服务):使用OkHttp远程调用访问admin,内部调用postFormDataResponse方法
postFormDataResponse(封装请求参数并获取响应):封装响应体并请求接口
getHeaders(获取请求头):获取headers,不需要我们去调用,工具类会自动调用该方法并返回对应headers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
| package com.sora.utils;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.sora.domain.XxlJobInfo; import com.sora.jackson.InitObjectMapper; import okhttp3.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType;
import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit;
public class XxlUtil {
private static final Logger logger = LoggerFactory.getLogger(XxlUtil.class);
private static String xxlJobAdminAddress = "http://127.0.0.1:7777/xxl-job-admin";
private static final String USER_NAME = "admin";
private static final String PASSWORD = "123456"; private static final String LOGIN_URL = "/login"; private static final String ADD_INFO_URL = "/jobinfo/add"; private static final String REMOVE_INFO_URL = "/jobinfo/remove"; private static final String UPDATE_INFO_URL = "/jobinfo/update"; private static final String START_URL = "/jobinfo/start"; private static final String STOP_URL = "/jobinfo/stop"; private static final String PAGELIST_URL = "/jobinfo/pageList";
private static HashMap<String, String> cookieMap = new HashMap<>(); private static final ObjectMapper objectMapper = new ObjectMapper();
private static OkHttpClient okHttpClient = new OkHttpClient.Builder() .connectTimeout(5L, TimeUnit.SECONDS) .readTimeout(5L, TimeUnit.SECONDS) .build();
private static void login() { HashMap<String, Object> paramMap = new HashMap<>(); paramMap.put("userName", USER_NAME); paramMap.put("password", PASSWORD);
HashMap<String, String> headerMap = new HashMap<>(); paramMap.put("Content-Type", MediaType.APPLICATION_FORM_URLENCODED.toString());
Response response = postFormDataResponse(xxlJobAdminAddress + LOGIN_URL, paramMap, headerMap);
if (response.isSuccessful()) { String cookie = response.headers().get("Set-Cookie"); cookieMap.put("cookie", cookie); response.close(); } else { logger.error("登录失败,响应体:" + response); } }
private static String getCookie() { String cookie = cookieMap.get("cookie"); for (int i = 0; i < 3; i++) { if (cookie == null) { login(); cookie = cookieMap.get("cookie"); } else { return cookie; } } return null; }
public static String addJob(HashMap<String, Object> xxlJobInfoMap) { return link(xxlJobAdminAddress + ADD_INFO_URL, xxlJobInfoMap); }
public static String updateJob(HashMap<String, Object> xxlJobInfoMap) { return link(xxlJobAdminAddress + UPDATE_INFO_URL, xxlJobInfoMap); }
public static String startJob(long jobId) { HashMap<String, Object> map = new HashMap<>(); map.put("id", String.valueOf(jobId)); return link(xxlJobAdminAddress + START_URL, map); }
public static String stopJob(long jobId) { HashMap<String, Object> map = new HashMap<>(); map.put("id", String.valueOf(jobId)); return link(xxlJobAdminAddress + STOP_URL, map); }
public static String removeJob(long jobId) { HashMap<String, Object> map = new HashMap<>(); map.put("id", String.valueOf(jobId)); return link(xxlJobAdminAddress + REMOVE_INFO_URL, map); }
public static List<XxlJobInfo> pageList(long group) { HashMap<String, Object> map = new HashMap<>(); map.put("jobGroup", group); map.put("triggerStatus", -1); String response = link(xxlJobAdminAddress + PAGELIST_URL, map); try { String data = objectMapper.readTree(response).get("data").toString(); List<XxlJobInfo> xxlJobInfos = objectMapper.readValue(data, new TypeReference<List<XxlJobInfo>>() { }); return xxlJobInfos; } catch (JsonProcessingException e) { logger.error("json解析错误,[xxl-job工具类]调用pageList方法发生异常!!", e); } return new ArrayList<>(); }
private static String link(String url, HashMap<String, Object> paramMap) { String cookie = getCookie(); if (cookie == null) { return null; } HashMap<String, String> headerMap = new HashMap<>(); headerMap.put("Cookie", cookie); try { Response response = postFormDataResponse(url, paramMap, headerMap); String body = response.body().string(); response.close(); return body; } catch (IOException e) { logger.error("错误请求,请求xxlJob发生异常!", e); } return null; }
private static Response postFormDataResponse(String URL, HashMap<String, Object> paramMap,HashMap<String,String> headerMap) { Headers headers = getHeaders(headerMap);
MultipartBody.Builder data = new MultipartBody.Builder() .setType(MultipartBody.FORM);
paramMap.forEach((k,v) -> { data.addFormDataPart(k, v.toString()); });
Request request = new Request.Builder() .post(data.build()) .url(URL) .headers(headers) .build();
try { Response execute = okHttpClient.newCall(request).execute(); if (execute.isSuccessful()) { return execute; } } catch (IOException e) { logger.error("发起请求失败!", e); } return null; }
private static Headers getHeaders(Map<String, String> headersMap) { Headers.Builder builder = new Headers.Builder(); headersMap.forEach(builder::add); return builder.build(); } }
|
依赖引入
OkHttp:
1 2 3 4 5
| <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.11.0</version> </dependency>
|
jackson:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.15.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.15.2</version> </dependency>
|
项目实践测试
新增:
这里的key要和XxlJobInfo保持一致,因为xxlJob需要formData请求,需要提交表单,所以我们需要对对象进行拆分存入map内,大部分的参数我都直接用值表示了,如果有疑问或者好奇,可以直接去看XxlJobInfo类的注释。添加成功的content后返回的是新增的任务id,jobGroup是执行器id,可以直接去数据库的xxl_job_group里面查看,因为执行器我们通常不会改变,所以这里我并没有写针对执行器的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @GetMapping("/jobTest") public Result jobTest() { HashMap<String, Object> paramMap = new HashMap<>(); paramMap.put("JobGroup",2); paramMap.put("JobDesc","任务描述"); paramMap.put("executorParam","我是参数"); paramMap.put("ExecutorHandler","testHandler(handler名称)"); paramMap.put("alarmEmail","告警邮件地址@gmail.com"); paramMap.put("ScheduleType","CRON"); paramMap.put("ScheduleConf","0/10 * * * * ?"); paramMap.put("GlueType","BEAN"); paramMap.put("MisfireStrategy","DO_NOTHING"); paramMap.put("ExecutorBlockStrategy","SERIAL_EXECUTION"); paramMap.put("ExecutorRouteStrategy","FIRST"); paramMap.put("TriggerStatus",0); paramMap.put("Author","sora33"); String string = XxlUtil.addJob(paramMap); try { JsonNode jsonNode = objectMapper.readTree(string); JsonNode content = jsonNode.get("content"); return Result.success(content); } catch (JsonProcessingException e) { return Result.success(null); } }
|
查看数据库,数据添加成功
修改:
修改其实和添加一样,只不过需要我们多加入一个参数id,传入一个已经存在的任务id,并传入修改后的值,例如我需要把告警邮件修改了则可以如下设置(这里要把任务所有的属性都传入,哪怕是不修改的属性,不然会报缺少xx字段的提示)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public Result jobTest() { HashMap<String, Object> paramMap = new HashMap<>(); paramMap.put("JobGroup",2); paramMap.put("id",47); paramMap.put("JobDesc","任务描述"); paramMap.put("executorParam","我是参数"); paramMap.put("ExecutorHandler","testHandler(handler名称)"); paramMap.put("alarmEmail","二次告警邮件地址@gmail.com"); paramMap.put("ScheduleType","CRON"); paramMap.put("ScheduleConf","0/10 * * * * ?"); paramMap.put("GlueType","BEAN"); paramMap.put("MisfireStrategy","DO_NOTHING"); paramMap.put("ExecutorBlockStrategy","SERIAL_EXECUTION"); paramMap.put("ExecutorRouteStrategy","FIRST"); paramMap.put("TriggerStatus",0); paramMap.put("Author","sora33"); String string = XxlUtil.updateJob(paramMap); }
|
数据库修改成功
删除/开启任务/暂停任务:
这三类任务很简单,直接传入需要的任务id即可。
1 2 3 4 5 6
| public Result jobTest() { XxlUtil.startJob(47L); XxlUtil.stopJob(48L); XxlUtil.removeJob(45); return Result.success(); }
|
如下,启用id为47的调度任务,停用48的调度任务,删除45的调度任务
获取所有任务:
调用pageList方法,并传入执行器id,获取该执行器下的所有任务
1 2 3 4 5 6 7 8 9
| public Result jobTest() { List<XxlJobInfo> xxlJobInfos = XxlUtil.pageList(2); XxlJobInfo jobInfo = xxlJobInfos.stream() .filter(data -> data.getId() == 44) .findFirst() .orElse(null); return Result.success(jobInfo); }
|
结束语
工具类的介绍及使用到这里就结束了,有任何想法或者疑问的可以联系我,如果本篇文章对你有用,欢迎分享给其他人,最后也到了元旦,祝各位元旦快乐~