AppName 限制不可重复;JobGoup本地缓存;

master
xuxueli 3 months ago
parent cf991ac505
commit fbfaf710cd

@ -2904,7 +2904,8 @@ alter table xxl_job_log
### 7.47 版本 v3.5.0 Release Notes[ING] ### 7.47 版本 v3.5.0 Release Notes[ING]
- 1、【新增】GLUE模式开关新增GLUE模式开关xxl.job.executor.glueenabled支持执行器维度设置是否启用GLUE模式 - 1、【新增】GLUE模式开关新增GLUE模式开关xxl.job.executor.glueenabled支持执行器维度设置是否启用GLUE模式
- 2、【TODO】执行器AccessToken执行器维度AccessToken线上化动态管理执行器间AccessToken隔离提升安全性 - 2、【TODO】执行器AccessToken执行器维度AccessToken线上化动态管理执行器间AccessToken隔离提升安全性
- 执行器 AccessToken: - 执行器ExecutorBiz 服务处理、AdminBiz请求发起统一附加 appname + accessToken
- 调度中心AppName 限制不可重复JobGoup本地缓存
- 3、【TODO】调度中心OpenAPI增强提供任务管理能力封装Agent Skill并推送ClawHub - 3、【TODO】调度中心OpenAPI增强提供任务管理能力封装Agent Skill并推送ClawHub
- 4、【TODO】配置线上化发送邮箱配置线上管理、线程池配置调整 - 4、【TODO】配置线上化发送邮箱配置线上管理、线程池配置调整
- 5、【TODO】任务告警拆分“告警类型、告警配置”属性支持Webhook、邮箱多种方式 - 5、【TODO】任务告警拆分“告警类型、告警配置”属性支持Webhook、邮箱多种方式

@ -17,7 +17,8 @@ CREATE TABLE `xxl_job_group`
`address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型0=自动注册、1=手动录入', `address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型0=自动注册、1=手动录入',
`address_list` text COMMENT '执行器地址列表,多地址逗号分隔', `address_list` text COMMENT '执行器地址列表,多地址逗号分隔',
`update_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`),
UNIQUE KEY `i_app_name` (`app_name`) USING BTREE
) ENGINE = InnoDB ) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4; DEFAULT CHARSET = utf8mb4;

@ -111,6 +111,11 @@ public class JobGroupController {
} }
} }
// valid appname duplicate
if (xxlJobGroupMapper.loadByAppname(xxlJobGroup.getAppname()) != null) {
return Response.ofFail("AppName "+I18nUtil.getString("system_repeat"));
}
// process // process
xxlJobGroup.setUpdateTime(new Date()); xxlJobGroup.setUpdateTime(new Date());
@ -123,15 +128,10 @@ public class JobGroupController {
@XxlSso(role = Consts.ADMIN_ROLE) @XxlSso(role = Consts.ADMIN_ROLE)
public Response<String> update(XxlJobGroup xxlJobGroup){ public Response<String> update(XxlJobGroup xxlJobGroup){
// valid appname // load existing, appname not allowed to change
if (StringTool.isBlank(xxlJobGroup.getAppname())) { XxlJobGroup exists = xxlJobGroupMapper.load(xxlJobGroup.getId());
return Response.ofFail((I18nUtil.getString("system_please_input")+"AppName") ); if (exists == null) {
} return Response.ofFail(I18nUtil.getString("system_opt_error"));
if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) {
return Response.ofFail( I18nUtil.getString("jobgroup_field_appname_length") );
}
if (XssUtil.hasXss(xxlJobGroup.getAppname())) {
return Response.ofFail( "AppName"+I18nUtil.getString("system_invalid") );
} }
// valid title // valid title

@ -24,6 +24,8 @@ public interface XxlJobGroupMapper {
public XxlJobGroup load(@Param("id") int id); public XxlJobGroup load(@Param("id") int id);
public XxlJobGroup loadByAppname(@Param("appname") String appname);
public List<XxlJobGroup> pageList(@Param("offset") int offset, public List<XxlJobGroup> pageList(@Param("offset") int offset,
@Param("pagesize") int pagesize, @Param("pagesize") int pagesize,
@Param("appname") String appname, @Param("appname") String appname,

@ -76,4 +76,17 @@ public class XxlJobGroup {
this.addressList = addressList; this.addressList = addressList;
} }
@Override
public String toString() {
return "XxlJobGroup{" +
"id=" + id +
", appname='" + appname + '\'' +
", title='" + title + '\'' +
", addressType=" + addressType +
", addressList='" + addressList + '\'' +
", updateTime=" + updateTime +
", registryList=" + registryList +
'}';
}
} }

@ -7,7 +7,9 @@ import com.xxl.job.core.constant.Const;
import com.xxl.job.core.constant.RegistTypeEnum; import com.xxl.job.core.constant.RegistTypeEnum;
import com.xxl.job.core.openapi.admin.dto.RegistryRequest; import com.xxl.job.core.openapi.admin.dto.RegistryRequest;
import com.xxl.tool.concurrent.CyclicThread; import com.xxl.tool.concurrent.CyclicThread;
import com.xxl.tool.core.CollectionTool;
import com.xxl.tool.core.StringTool; import com.xxl.tool.core.StringTool;
import com.xxl.tool.json.GsonTool;
import com.xxl.tool.response.Response; import com.xxl.tool.response.Response;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -34,6 +36,12 @@ public class JobRegistryHelper {
*/ */
private CyclicThread registryMonitorThread; private CyclicThread registryMonitorThread;
/**
* job group cache
*/
private volatile Map<String, XxlJobGroup> appname2GroupCache = new ConcurrentHashMap<>();
private volatile Map<Integer, XxlJobGroup> id2GroupCache = new ConcurrentHashMap<>();
/** /**
* start * start
*/ */
@ -64,24 +72,24 @@ public class JobRegistryHelper {
registryMonitorThread = new CyclicThread("JobRegistryHelper#registryMonitorThread", true, new Runnable() { registryMonitorThread = new CyclicThread("JobRegistryHelper#registryMonitorThread", true, new Runnable() {
@Override @Override
public void run() { public void run() {
// auto registry group // 2.1、refresh auto-registry group
List<XxlJobGroup> groupList = XxlJobAdminBootstrap.getInstance().getXxlJobGroupMapper().findByAddressType(0); List<XxlJobGroup> groupList = XxlJobAdminBootstrap.getInstance().getXxlJobGroupMapper().findByAddressType(0);
if (groupList!=null && !groupList.isEmpty()) { if (groupList!=null && !groupList.isEmpty()) {
// remove dead address (admin/executor) // a、remove dead address (admin/executor)
List<Integer> ids = XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().findDead(Const.DEAD_TIMEOUT, new Date()); List<Integer> ids = XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().findDead(Const.DEAD_TIMEOUT, new Date());
if (ids!=null && !ids.isEmpty()) { if (ids!=null && !ids.isEmpty()) {
XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().removeDead(ids); XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().removeDead(ids);
} }
// fresh online address (admin/executor) // b、fresh online address (appname List<address>)
HashMap<String, List<String>> appAddressMap = new HashMap<String, List<String>>(); HashMap<String, List<String>> appnameAddressMap = new HashMap<>();
List<XxlJobRegistry> list = XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().findAll(Const.DEAD_TIMEOUT, new Date()); List<XxlJobRegistry> list = XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().findAll(Const.DEAD_TIMEOUT, new Date());
if (list != null) { if (list != null) {
for (XxlJobRegistry item: list) { for (XxlJobRegistry item: list) {
if (RegistTypeEnum.EXECUTOR.name().equals(item.getRegistryGroup())) { if (RegistTypeEnum.EXECUTOR.name().equals(item.getRegistryGroup())) {
String appname = item.getRegistryKey(); String appname = item.getRegistryKey();
List<String> registryList = appAddressMap.get(appname); List<String> registryList = appnameAddressMap.get(appname);
if (registryList == null) { if (registryList == null) {
registryList = new ArrayList<String>(); registryList = new ArrayList<String>();
} }
@ -89,30 +97,46 @@ public class JobRegistryHelper {
if (!registryList.contains(item.getRegistryValue())) { if (!registryList.contains(item.getRegistryValue())) {
registryList.add(item.getRegistryValue()); registryList.add(item.getRegistryValue());
} }
appAddressMap.put(appname, registryList); appnameAddressMap.put(appname, registryList);
} }
} }
} }
// fresh group address // c、write group address
for (XxlJobGroup group: groupList) { for (XxlJobGroup group: groupList) {
List<String> registryList = appAddressMap.get(group.getAppname()); List<String> registryList = appnameAddressMap.get(group.getAppname());
// generate address list
String addressListStr = null; String addressListStr = null;
if (registryList!=null && !registryList.isEmpty()) { if (CollectionTool.isNotEmpty(registryList)) {
Collections.sort(registryList); Collections.sort(registryList);
StringBuilder addressListSB = new StringBuilder(); addressListStr = StringTool.join(registryList, ",");
for (String item:registryList) {
addressListSB.append(item).append(",");
}
addressListStr = addressListSB.toString();
addressListStr = addressListStr.substring(0, addressListStr.length()-1);
} }
// fill address
group.setAddressList(addressListStr); group.setAddressList(addressListStr);
group.setUpdateTime(new Date()); group.setUpdateTime(new Date());
XxlJobAdminBootstrap.getInstance().getXxlJobGroupMapper().update(group); XxlJobAdminBootstrap.getInstance().getXxlJobGroupMapper().update(group);
} }
} }
// 2.2、refresh localcache
List<XxlJobGroup> jobGroupList = XxlJobAdminBootstrap.getInstance().getXxlJobGroupMapper().findAll();
Map<String, XxlJobGroup> appname2GroupCacheNew = new ConcurrentHashMap<>();
Map<Integer, XxlJobGroup> id2GroupCacheNew = new ConcurrentHashMap<>();
if (CollectionTool.isNotEmpty(jobGroupList)) {
for (XxlJobGroup group: jobGroupList) {
group.setUpdateTime(null);
appname2GroupCacheNew.put(group.getAppname(), group);
id2GroupCacheNew.put(group.getId(), group);
}
}
if (!GsonTool.toJson(appname2GroupCacheNew).equals(GsonTool.toJson(appname2GroupCache))) {
appname2GroupCache = appname2GroupCacheNew;
id2GroupCache = id2GroupCacheNew;
logger.info(">>>>>>>>>>> xxl-job, JobRegistryHelper, detect changes and refresh JobGroupCache success, appname2GroupCache:{}, id2GroupCache:{}", appname2GroupCache, id2GroupCache);
}
logger.debug(">>>>>>>>>>> xxl-job, JobRegistryHelper, refresh JobGroupCache success, appname2GroupCache:{}, id2GroupCache:{}", appname2GroupCache, id2GroupCache);
} }
}, Const.BEAT_TIMEOUT * 1000L, true); }, Const.BEAT_TIMEOUT * 1000L, true);
registryMonitorThread.start(); registryMonitorThread.start();
@ -200,5 +224,21 @@ public class JobRegistryHelper {
// Under consideration, prevent affecting core tables // Under consideration, prevent affecting core tables
} }
// ---------------------- cache ----------------------
/**
* load JobGroup by id
*/
public XxlJobGroup load(int jobGroup){
return id2GroupCache.get(jobGroup);
}
/**
* load JobGroup by appname
*/
public XxlJobGroup loadByAppName(String appname){
return appname2GroupCache.get(appname);
}
} }

@ -38,6 +38,7 @@ system_num_range=Numerical range limit
system_one=One system_one=One
system_data=Data system_data=Data
system_selected_nothing=No data selected system_selected_nothing=No data selected
system_repeat=Repeat
## tab ## tab
tab_opt=Tab Operation tab_opt=Tab Operation

@ -38,6 +38,7 @@ system_num_range=数值范围限制
system_one=一条 system_one=一条
system_data=数据 system_data=数据
system_selected_nothing=未选择 system_selected_nothing=未选择
system_repeat=重复
## tab ## tab
tab_opt=页签操作 tab_opt=页签操作

@ -38,6 +38,7 @@ system_num_range=數值範圍限制
system_one=一條 system_one=一條
system_data=數據 system_data=數據
system_selected_nothing=请選擇 system_selected_nothing=请選擇
system_repeat=重複
## tab ## tab
tab_opt=頁籤操作 tab_opt=頁籤操作

@ -41,8 +41,7 @@
<update id="update" parameterType="com.xxl.job.admin.business.model.XxlJobGroup" > <update id="update" parameterType="com.xxl.job.admin.business.model.XxlJobGroup" >
UPDATE xxl_job_group UPDATE xxl_job_group
SET `app_name` = #{appname}, SET `title` = #{title},
`title` = #{title},
`address_type` = #{addressType}, `address_type` = #{addressType},
`address_list` = #{addressList}, `address_list` = #{addressList},
`update_time` = #{updateTime} `update_time` = #{updateTime}
@ -60,6 +59,12 @@
WHERE t.id = #{id} WHERE t.id = #{id}
</select> </select>
<select id="findByAppname" parameterType="java.lang.String" resultMap="XxlJobGroup">
SELECT <include refid="Base_Column_List" />
FROM xxl_job_group AS t
WHERE t.app_name = #{appname}
</select>
<select id="pageList" parameterType="java.util.HashMap" resultMap="XxlJobGroup"> <select id="pageList" parameterType="java.util.HashMap" resultMap="XxlJobGroup">
SELECT <include refid="Base_Column_List" /> SELECT <include refid="Base_Column_List" />
FROM xxl_job_group AS t FROM xxl_job_group AS t

@ -139,7 +139,7 @@
<form class="form-horizontal form" role="form" > <form class="form-horizontal form" role="form" >
<div class="form-group"> <div class="form-group">
<label for="lastname" class="col-sm-2 control-label">AppName<font color="red">*</font></label> <label for="lastname" class="col-sm-2 control-label">AppName<font color="red">*</font></label>
<div class="col-sm-10"><input type="text" class="form-control" name="appname" placeholder="${I18n.system_please_input}AppName" maxlength="64" ></div> <div class="col-sm-10"><input type="text" class="form-control" name="appname" placeholder="${I18n.system_please_input}AppName" maxlength="64" readonly ></div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="lastname" class="col-sm-2 control-label">${I18n.jobgroup_field_title}<font color="red">*</font></label> <label for="lastname" class="col-sm-2 control-label">${I18n.jobgroup_field_title}<font color="red">*</font></label>

Loading…
Cancel
Save