feat(admin): 新增任务审计日志功能

- 在JobCodeController和XxlJobServiceImpl中添加操作日志记录
- 记录任务新建、更新、删除、启停、触发及GLUE代码更新等敏感操作
- 日志包含操作人、操作类型和操作内容,便于安全审计和问题追溯
- 优化字符串判空逻辑,使用StringTool工具类替代手动判断
- 更新文档,增加审计日志特性说明和接入公司名单
- 调整HTTP任务参数示例展示方式,提升可读性
- 重构部分校验逻辑,提高代码健壮性和可维护性
3.3.0-release
xuxueli 3 weeks ago
parent 41354cfd35
commit 08696b5715

@ -113,6 +113,7 @@ XXL-JOB 是一个开源且免费项目,其正在进行的开发完全得益于
- 35、用户管理支持在线管理系统用户存在管理员、普通用户两种角色
- 36、权限控制执行器维度进行权限控制管理员拥有全量权限普通用户需要分配执行器权限后才允许相关操作
- 37、AI任务原生提供AI执行器并内置多个AI任务Handler与spring-ai、ollama、dify等集成打通支持快速开发AI类任务。
- 38、审计日志记录任务操作敏感信息用于系统监控、审计和安全分析可快速追溯异常行为以及定位排查问题。
## Development
于2015年中我在github上创建XXL-JOB项目仓库并提交第一个commit随之进行系统结构设计UI选型交互设计……
@ -845,6 +846,20 @@ XXL-JOB 是一个开源且免费项目,其正在进行的开发完全得益于
- 691、联通云
- 692、北京爱话本科技有限公司
- 693、北京起创科技有限公司
- 694、平安证券【平安证券】
- 695、合肥中科类脑智能技术有限公司
- 696、南京同仁堂健康产业有限公司【同仁堂】
- 697、铜仁市碧江区智惠加油站
- 698、惟客数据
- 699、凤凰新闻【凤凰新闻】
- 700、深圳王力智能
- 701、返利网数字科技股份有限公司
- 702、上海阜能信息科技有限公司
- 703、深圳市极能超电数字科技有限公司
- 704、海目星激光科技集团股份有限公司
- 705、深圳市极能超电数字科技有限公司
- 706、安克创新科技股份有限公司【安克】
- 707、大庆点神科技有限公司
- ……
> 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。

@ -58,6 +58,8 @@ XXL-JOB是一个分布式任务调度平台其核心设计目标是开发迅
- 35、用户管理支持在线管理系统用户存在管理员、普通用户两种角色
- 36、权限控制执行器维度进行权限控制管理员拥有全量权限普通用户需要分配执行器权限后才允许相关操作
- 37、AI任务原生提供AI执行器并内置多个AI任务Handler与spring-ai、ollama、dify等集成打通支持快速开发AI类任务。
- 38、审计日志记录任务操作敏感信息用于系统监控、审计和安全分析可快速追溯异常行为以及定位排查问题。
### 1.4 发展
于2015年中我在github上创建XXL-JOB项目仓库并提交第一个commit随之进行系统结构设计UI选型交互设计……
@ -788,6 +790,20 @@ XXL-JOB是一个分布式任务调度平台其核心设计目标是开发迅
- 691、联通云
- 692、北京爱话本科技有限公司
- 693、北京起创科技有限公司
- 694、平安证券【平安证券】
- 695、合肥中科类脑智能技术有限公司
- 696、南京同仁堂健康产业有限公司【同仁堂】
- 697、铜仁市碧江区智惠加油站
- 698、惟客数据
- 699、凤凰新闻【凤凰新闻】
- 700、深圳王力智能
- 701、返利网数字科技股份有限公司
- 702、上海阜能信息科技有限公司
- 703、深圳市极能超电数字科技有限公司
- 704、海目星激光科技集团股份有限公司
- 705、深圳市极能超电数字科技有限公司
- 706、安克创新科技股份有限公司【安克】
- 707、大庆点神科技有限公司
- ……
> 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。
@ -2606,11 +2622,11 @@ public void execute() {
- 10、【修复】脚本任务process销毁逻辑优化解决风险情况下脚本进程无法终止问题
- 11、【修复】调度预读任务数量调整改为调度线程池大小x10降低事务颗粒度提升性能及稳定性
- 12、【修复】合并PR-2369修复脚本任务参数取值问题
- 13、【强化】通用HTTP任务httpJobHandler强化支持更丰富请求参数设置完整参数示例如下
- 13、【强化】通用HTTP任务httpJobHandler强化支持更丰富请求参数设置完整参数示例如下
<details>
<summary>完整参数示例参考:</summary>
<summary>完整参数示例参考:</summary>
```
{
"url": "http://www.baidu.com",
@ -2636,15 +2652,19 @@ public void execute() {
- 16、【重构】规范API交互协议通用响应结构体调整为Response调度中心API统一为Response封装数据
注意响应结构体从ReturnT升级为Response其中属性值“content”会调整为“data”取值逻辑需注意
- 17、【升级】Http通讯组件升级基于接口代理方式重构通讯组件提升组件性能及扩展性
- 18、【ING】UI框架重构升级提升交互体验
- 19、【ING】调整资源加载逻辑移除不必要的拦截器逻辑提升页面加载效率
- 18、【新增】任务审计日志记录任务操作敏感日志信息如任务新建/更新/删除/启停/触发以及GLUE代码更新等用于系统监控、审计和安全分析可快速追溯异常行为以及定位排查问题等。
当前任务审计日志以Info级别输出在系统日志中可通过关键词 "xxl-job operation log:" 检索过滤)
- 19、【ING】UI框架重构升级提升交互体验
- 20、【ING】调整资源加载逻辑移除不必要的拦截器逻辑提升页面加载效率
### TODO LIST
- 1、调度隔离调度中心针对不同执行器各自维护不同的调度和远程触发组件。
- 2、任务优先级调度与执行阶段按照优先级分配资源。
- 3、多数据库支持DAO层通过JPA实现不限制数据库类型。
- 4、执行器Log清理功能调度中心Log删除时同步删除执行器中的Log文件
- 4、OpenApi
- 执行器Log文件清理支持调度中心远程删除执行器中指定任务的Log文件
- 5、性能优化任务、执行器数据全量本地缓存新增消息表广播通知
- 6、DAG流程任务
- 子任务:废弃
@ -2653,10 +2673,8 @@ public void execute() {
- 分片任务:全部完成后才会出发后置节点;
- 配置并列的"a-b、b-c"路径列表构成串行、并行、dag任务流程"dagre-d3"绘图;任务依赖,流程图,子任务+会签任务,各节点日志;支持根据成功、失败选择分支;
- 7、任务标签方便搜索
- 8、告警增强
- 邮件告警:支持自定义标题、模板格式;
- webhook告警支持自定义告警URL、请求体格式
- 9、安全强化AccessToken动态生成、动态启停控制调度、回调
- 8、GLUE 模式 Web Ide 版本对比功能;
- 9、自定义失败重试时间间隔
- 10、任务导入导出工具灵活支持版本升级、迁移等场景。
- 11、任务日志重构一次调度只记录一条主任务维护起止时间和状态。
- 普通任务:只记录一条主任务;
@ -2664,13 +2682,21 @@ public void execute() {
- 重试任务:失败时,新增主任务。所有调度记录,包括入口调度和重试调度,均挂载主任务上。
- 12、分片任务全部完成后才会出发后置节点
- 13、日期过滤支持多个时间段排除
- 13、GLUE 模式 Web Ide 版本对比功能;
- 14、提供执行器Docker镜像
- 15、脚本任务支持数据参数新版本仅支持单参数不支持需要兼容
- 17、批量调度调度请求入queue调度线程批量获取调度请求并发起远程调度提高线程效率
- 18、执行器端口复用复用容器端口提供通讯服务
- 19、自定义失败重试时间间隔
- 20、安全功能增强通讯加密参数改用加密数据避免AccessToken明文 降低token泄漏风险
- 19、安全功能增强通讯加密参数改用加密数据避免AccessToken明文 降低token泄漏风险
- 20、告警增强
- 邮件告警:支持自定义标题、模板格式;
- webhook告警支持自定义告警URL、请求体格式
- 21、公共告警策略执行器维度设置多告警策略任务勾选启用待评估任务或执行器维度
- 20、日志策略
- 调度日志:全局配置:废弃; 新增“调度日志策略”任务维度自定义保留3天、7天、1个月、3个月、一年、永久
- 执行日志新增“执行RollingLog开关”任务维度自定义支持RollingLog、普通日志slf4j输出、关闭不输出
- 21、AccessToken废弃全局配置支持在线管理动态生成、动态启停
## 八、其他

@ -7,10 +7,14 @@ import com.xxl.job.admin.model.XxlJobLogGlue;
import com.xxl.job.admin.util.I18nUtil;
import com.xxl.job.admin.util.JobGroupPermissionUtil;
import com.xxl.job.core.glue.GlueTypeEnum;
import com.xxl.sso.core.model.LoginInfo;
import com.xxl.tool.core.StringTool;
import com.xxl.tool.gson.GsonTool;
import com.xxl.tool.response.Response;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@ -27,6 +31,7 @@ import java.util.List;
@Controller
@RequestMapping("/jobcode")
public class JobCodeController {
private static final Logger logger = LoggerFactory.getLogger(JobCodeController.class);
@Resource
private XxlJobInfoMapper xxlJobInfoMapper;
@ -79,7 +84,7 @@ public class JobCodeController {
}
// valid jobGroup permission
JobGroupPermissionUtil.validJobGroupPermission(request, existsJobInfo.getJobGroup());
LoginInfo loginInfo = JobGroupPermissionUtil.validJobGroupPermission(request, existsJobInfo.getJobGroup());
// update new code
existsJobInfo.setGlueSource(glueSource);
@ -103,7 +108,10 @@ public class JobCodeController {
// remove code backup more than 30
xxlJobLogGlueMapper.removeOld(existsJobInfo.getId(), 30);
// write operation log
logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}",
loginInfo.getUserName(), "jobcode-update", GsonTool.toJson(xxlJobLogGlue));
return Response.ofSuccess();
}
}

@ -18,6 +18,8 @@ import com.xxl.job.core.constant.ExecutorBlockStrategyEnum;
import com.xxl.job.core.glue.GlueTypeEnum;
import com.xxl.sso.core.model.LoginInfo;
import com.xxl.tool.core.DateTool;
import com.xxl.tool.core.StringTool;
import com.xxl.tool.gson.GsonTool;
import com.xxl.tool.response.PageModel;
import com.xxl.tool.response.Response;
import jakarta.annotation.Resource;
@ -70,10 +72,10 @@ public class XxlJobServiceImpl implements XxlJobService {
if (group == null) {
return Response.ofFail (I18nUtil.getString("system_please_choose")+I18nUtil.getString("jobinfo_field_jobgroup"));
}
if (jobInfo.getJobDesc()==null || jobInfo.getJobDesc().trim().length()==0) {
if (StringTool.isBlank(jobInfo.getJobDesc())) {
return Response.ofFail ( (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_jobdesc")) );
}
if (jobInfo.getAuthor()==null || jobInfo.getAuthor().trim().length()==0) {
if (StringTool.isBlank(jobInfo.getAuthor())) {
return Response.ofFail ( (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_author")) );
}
@ -91,7 +93,7 @@ public class XxlJobServiceImpl implements XxlJobService {
return Response.ofFail ( (I18nUtil.getString("schedule_type")) );
}
try {
int fixSecond = Integer.valueOf(jobInfo.getScheduleConf());
int fixSecond = Integer.parseInt(jobInfo.getScheduleConf());
if (fixSecond < 1) {
return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
}
@ -104,7 +106,7 @@ public class XxlJobServiceImpl implements XxlJobService {
if (GlueTypeEnum.match(jobInfo.getGlueType()) == null) {
return Response.ofFail ( (I18nUtil.getString("jobinfo_field_gluetype")+I18nUtil.getString("system_unvalid")) );
}
if (GlueTypeEnum.BEAN==GlueTypeEnum.match(jobInfo.getGlueType()) && (jobInfo.getExecutorHandler()==null || jobInfo.getExecutorHandler().trim().length()==0) ) {
if (GlueTypeEnum.BEAN==GlueTypeEnum.match(jobInfo.getGlueType()) && StringTool.isBlank(jobInfo.getExecutorHandler()) ) {
return Response.ofFail ( (I18nUtil.getString("system_please_input")+"JobHandler") );
}
// 》fix "\r" in shell
@ -124,10 +126,10 @@ public class XxlJobServiceImpl implements XxlJobService {
}
// 》ChildJobId valid
if (jobInfo.getChildJobId()!=null && jobInfo.getChildJobId().trim().length()>0) {
if (StringTool.isNotBlank(jobInfo.getChildJobId())) {
String[] childJobIds = jobInfo.getChildJobId().split(",");
for (String childJobIdItem: childJobIds) {
if (childJobIdItem!=null && childJobIdItem.trim().length()>0 && isNumeric(childJobIdItem)) {
if (StringTool.isNotBlank(childJobIdItem) && StringTool.isNumeric(childJobIdItem)) {
XxlJobInfo childJobInfo = xxlJobInfoMapper.loadById(Integer.parseInt(childJobIdItem));
if (childJobInfo==null) {
return Response.ofFail (
@ -165,26 +167,21 @@ public class XxlJobServiceImpl implements XxlJobService {
return Response.ofFail ( (I18nUtil.getString("jobinfo_field_add")+I18nUtil.getString("system_fail")) );
}
return Response.ofSuccess(String.valueOf(jobInfo.getId()));
}
// write operation log
logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}",
loginInfo.getUserName(), "jobinfo-save", GsonTool.toJson(jobInfo));
private boolean isNumeric(String str){
try {
int result = Integer.valueOf(str);
return true;
} catch (NumberFormatException e) {
return false;
}
return Response.ofSuccess(String.valueOf(jobInfo.getId()));
}
@Override
public Response<String> update(XxlJobInfo jobInfo, LoginInfo loginInfo) {
// valid base
if (jobInfo.getJobDesc()==null || jobInfo.getJobDesc().trim().length()==0) {
if (StringTool.isBlank(jobInfo.getJobDesc())) {
return Response.ofFail ( (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_jobdesc")) );
}
if (jobInfo.getAuthor()==null || jobInfo.getAuthor().trim().length()==0) {
if (StringTool.isBlank(jobInfo.getAuthor())) {
return Response.ofFail ( (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_author")) );
}
@ -202,7 +199,7 @@ public class XxlJobServiceImpl implements XxlJobService {
return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
}
try {
int fixSecond = Integer.valueOf(jobInfo.getScheduleConf());
int fixSecond = Integer.parseInt(jobInfo.getScheduleConf());
if (fixSecond < 1) {
return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
}
@ -223,10 +220,10 @@ public class XxlJobServiceImpl implements XxlJobService {
}
// 》ChildJobId valid
if (jobInfo.getChildJobId()!=null && jobInfo.getChildJobId().trim().length()>0) {
if (StringTool.isNotBlank(jobInfo.getChildJobId())) {
String[] childJobIds = jobInfo.getChildJobId().split(",");
for (String childJobIdItem: childJobIds) {
if (childJobIdItem!=null && childJobIdItem.trim().length()>0 && isNumeric(childJobIdItem)) {
if (StringTool.isNotBlank(childJobIdItem) && StringTool.isNumeric(childJobIdItem)) {
// parse child
int childJobId = Integer.parseInt(childJobIdItem);
if (childJobId == jobInfo.getId()) {
@ -310,6 +307,10 @@ public class XxlJobServiceImpl implements XxlJobService {
exists_jobInfo.setUpdateTime(new Date());
xxlJobInfoMapper.update(exists_jobInfo);
// write operation log
logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}",
loginInfo.getUserName(), "jobinfo-update", GsonTool.toJson(exists_jobInfo));
return Response.ofSuccess();
}
@ -329,6 +330,11 @@ public class XxlJobServiceImpl implements XxlJobService {
xxlJobInfoMapper.delete(id);
xxlJobLogMapper.delete(id);
xxlJobLogGlueMapper.deleteByJobId(id);
// write operation log
logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}",
loginInfo.getUserName(), "jobinfo-remove", id);
return Response.ofSuccess();
}
@ -372,6 +378,11 @@ public class XxlJobServiceImpl implements XxlJobService {
xxlJobInfo.setUpdateTime(new Date());
xxlJobInfoMapper.update(xxlJobInfo);
// write operation log
logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}",
loginInfo.getUserName(), "jobinfo-start", id);
return Response.ofSuccess();
}
@ -395,6 +406,11 @@ public class XxlJobServiceImpl implements XxlJobService {
xxlJobInfo.setUpdateTime(new Date());
xxlJobInfoMapper.update(xxlJobInfo);
// write operation log
logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}",
loginInfo.getUserName(), "jobinfo-stop", id);
return Response.ofSuccess();
}
@ -417,6 +433,11 @@ public class XxlJobServiceImpl implements XxlJobService {
}
XxlJobAdminBootstrap.getInstance().getJobTriggerPoolHelper().trigger(jobId, TriggerTypeEnum.MANUAL, -1, null, executorParam, addressList);
// write operation log
logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}",
loginInfo.getUserName(), "jobinfo-trigger", jobId);
return Response.ofSuccess();
}

Loading…
Cancel
Save