接上篇:分布式定时任务—xxl-job学习(三)——调度中心(xxl-job-admin)的启动和任务调度过程源码分析
本篇我们从调度中心的wen页面端分析下api调用的源码。
也就是上图中的一些api调用类。
/**
* 权限限制
* @author xuxueli 2015-12-12 18:29:02
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionLimit {
/**
* 登录拦截 (默认拦截)
*/
boolean limit() default true;
/**
* 要求管理员权限
*
* @return
*/
boolean adminuser() default false;
}
分析:
自定义了一个方法上的权限限制注解,主要控制登录拦截和是否要求有管理员权限。
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Resource
private PermissionInterceptor permissionInterceptor;
@Resource
private CookieInterceptor cookieInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(permissionInterceptor).addPathPatterns("/**");
registry.addInterceptor(cookieInterceptor).addPathPatterns("/**");
}
}
分析:
WebMvcConfig 配置类实现了WebMvcConfigurer
接口,进行框架个性化定制,可以自定义一些Handler,Interceptor,ViewResolver,MessageConverter等。
在这里重写了void addInterceptors(InterceptorRegistry registry)
方法增加拦截器配置
addInterceptor
:需要一个实现HandlerInterceptor接口的拦截器实例addPathPatterns
:用于设置拦截器的过滤路径规则;addPathPatterns("/**")
对所有请求都拦截excludePathPatterns
:用于设置不需要拦截的过滤规则。@Component
public class PermissionInterceptor extends HandlerInterceptorAdapter {
@Resource
private LoginService loginService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return super.preHandle(request, response, handler);
}
// if need login
boolean needLogin = true;
boolean needAdminuser = false;
HandlerMethod method = (HandlerMethod)handler;
PermissionLimit permission = method.getMethodAnnotation(PermissionLimit.class);
if (permission!=null) {
needLogin = permission.limit();
needAdminuser = permission.adminuser();
}
if (needLogin) {
XxlJobUser loginUser = loginService.ifLogin(request, response);
if (loginUser == null) {
response.sendRedirect(request.getContextPath() + "/toLogin");
//request.getRequestDispatcher("/toLogin").forward(request, response);
return false;
}
if (needAdminuser && loginUser.getRole()!=1) {
throw new RuntimeException(I18nUtil.getString("system_permission_limit"));
}
request.setAttribute(LoginService.LOGIN_IDENTITY_KEY, loginUser);
}
return super.preHandle(request, response, handler);
}
}
分析: 该类继承HandlerInterceptorAdapter
适配器类,此处只重写了boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
方法,预处理回调方法,在方法被调用前执行。
handler
强转为HandlerMethod
,并取到方法上的PermissionLimit
注解;loginService.ifLogin(request, response)
方法判断Cookie中是否有用户信息,没有则跳转到登录页面;super.preHandle(request, response, handler)
。@Component
public class CookieInterceptor extends HandlerInterceptorAdapter {
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// cookie
if (modelAndView!=null && request.getCookies()!=null && request.getCookies().length>0) {
HashMap<String, Cookie> cookieMap = new HashMap<String, Cookie>();
for (Cookie ck : request.getCookies()) {
cookieMap.put(ck.getName(), ck);
}
modelAndView.addObject("cookieMap", cookieMap);
}
// static method
if (modelAndView != null) {
modelAndView.addObject("I18nUtil", FtlUtil.generateStaticModel(I18nUtil.class.getName()));
}
super.postHandle(request, response, handler, modelAndView);
}
}
分析: 该类继承HandlerInterceptorAdapter
适配器类,此处只重写了postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
后处理方法,在主方法执行后渲染视图前执行。
HashMap cookieMap
的形式存入视图中;包括登录、图表展示等方法
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}
注意:
该类有一个数据绑定方法:Spring在绑定请求参数到HandlerMethod的时候会借助WebDataBinder进行数据转换,"yyyy-MM-dd HH:mm:ss"这种类型的字符串直接使用Date类型接收。
该类提供了三个api方法供执行器组件端调用,具体包括"callback"、“registry"和"registryRemove”。
@Override
public ReturnT<String> callback(List<HandleCallbackParam> callbackParamList) {
for (HandleCallbackParam handleCallbackParam: callbackParamList) {
ReturnT<String> callbackResult = callback(handleCallbackParam);
logger.debug(">>>>>>>>> JobApiController.callback {}, handleCallbackParam={}, callbackResult={}",
(callbackResult.getCode()==IJobHandler.SUCCESS.getCode()?"success":"fail"), handleCallbackParam, callbackResult);
}
return ReturnT.SUCCESS;
}
private ReturnT<String> callback(HandleCallbackParam handleCallbackParam) {
// valid log item
XxlJobLog log = xxlJobLogDao.load(handleCallbackParam.getLogId());
if (log == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "log item not found.");
}
if (log.getHandleCode() > 0) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "log repeate callback."); // avoid repeat callback, trigger child job etc
}
// trigger success, to trigger child job
String callbackMsg = null;
if (IJobHandler.SUCCESS.getCode() == handleCallbackParam.getExecuteResult().getCode()) {
XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(log.getJobId());
if (xxlJobInfo!=null && xxlJobInfo.getChildJobId()!=null && xxlJobInfo.getChildJobId().trim().length()>0) {
callbackMsg = "
>>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_child_run") +"<<<<<<<<<<<
";
String[] childJobIds = xxlJobInfo.getChildJobId().split(",");
for (int i = 0; i < childJobIds.length; i++) {
int childJobId = (childJobIds[i]!=null && childJobIds[i].trim().length()>0 && isNumeric(childJobIds[i]))?Integer.valueOf(childJobIds[i]):-1;
if (childJobId > 0) {
JobTriggerPoolHelper.trigger(childJobId, TriggerTypeEnum.PARENT, -1, null, null, null);
ReturnT<String> triggerChildResult = ReturnT.SUCCESS;
// add msg
callbackMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg1"),
(i+1),
childJobIds.length,
childJobIds[i],
(triggerChildResult.getCode()==ReturnT.SUCCESS_CODE?I18nUtil.getString("system_success"):I18nUtil.getString("system_fail")),
triggerChildResult.getMsg());
} else {
callbackMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg2"),
(i+1),
childJobIds.length,
childJobIds[i]);
}
}
}
}
// handle msg
StringBuffer handleMsg = new StringBuffer();
if (log.getHandleMsg()!=null) {
handleMsg.append(log.getHandleMsg()).append("
");
}
if (handleCallbackParam.getExecuteResult().getMsg() != null) {
handleMsg.append(handleCallbackParam.getExecuteResult().getMsg());
}
if (callbackMsg != null) {
handleMsg.append(callbackMsg);
}
if (handleMsg.length() > 15000) {
handleMsg = new StringBuffer(handleMsg.substring(0, 15000)); // text最大64kb 避免长度过长
}
// success, save log
log.setHandleTime(new Date());
log.setHandleCode(handleCallbackParam.getExecuteResult().getCode());
log.setHandleMsg(handleMsg.toString());
xxlJobLogDao.updateHandleInfo(log);
return ReturnT.SUCCESS;
}
分析:
handleCode
字段是否大于0?(避免重复回调,触发子作业等); @Override
public ReturnT<String> registry(RegistryParam registryParam) {
// valid
if (!StringUtils.hasText(registryParam.getRegistryGroup())
|| !StringUtils.hasText(registryParam.getRegistryKey())
|| !StringUtils.hasText(registryParam.getRegistryValue())) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "Illegal Argument.");
}
int ret = xxlJobRegistryDao.registryUpdate(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
if (ret < 1) {
xxlJobRegistryDao.registrySave(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
// fresh
freshGroupRegistryInfo(registryParam);
}
return ReturnT.SUCCESS;
}
分析:
freshGroupRegistryInfo
方法------------作者还在考虑中,这个方法无具体内容 private void freshGroupRegistryInfo(RegistryParam registryParam){
// Under consideration, prevent affecting core tables
}
@Override
public ReturnT<String> registryRemove(RegistryParam registryParam) {
// valid
if (!StringUtils.hasText(registryParam.getRegistryGroup())
|| !StringUtils.hasText(registryParam.getRegistryKey())
|| !StringUtils.hasText(registryParam.getRegistryValue())) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "Illegal Argument.");
}
int ret = xxlJobRegistryDao.registryDelete(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue());
if (ret > 0) {
// fresh
freshGroupRegistryInfo(registryParam);
}
return ReturnT.SUCCESS;
}
分析:
freshGroupRegistryInfo
方法------------作者还在考虑中,这个方法无具体内容 private void freshGroupRegistryInfo(RegistryParam registryParam){
// Under consideration, prevent affecting core tables
}
该类主要是GLUE(xxx)模式的可在线编辑的任务的查询、修改和保存;
涉及:xxl_job_info任务表和xxl_job_logglue任务日志表(glue类型);
注意:
每次保存会清楚30次之前的备份记录。
该类主要是执行器管理控制层:包括执行器的查询、保存、更新、删除。涉及xxl_job_group
表。
注意:
xxl_job_registry
表匹配出最近90秒更新的执行器地址列表。该类主要负责任务的新增、修改、删除、查询下次调度时间、启动、停止、调度一次。涉及xxl_job_info
表。
注意:
xxl_job_info
表trigger_status
字段0-停止,1-运行;该类负责调度日志的查询、清理、中止任务等。
日志展示和中止会调用ExecutorBizClient
的log、kill方法远程调度执行器。
该类主要负责用户的新增、更新和删除。
xxl-job这款轻量级的分布式定时任务还是比较适合我们大多数场景的。后续我们继续学习下其他框架(比如:Elastic-Job等)