0x1 前言
1标签
java
public String jobLog(@RequestParam(value = "jobId", required = false) Long jobId, ModelMap mmap)
java
@RequiresPermissions("monitor:job:view")
@GetMapping()
public String jobLog(@RequestParam(value = "jobId", required = false) Long jobId, ModelMap mmap)
{
if (StringUtils.isNotNull(jobId))
{
//数据库查询返回条目
SysJob job = jobService.selectJobById(jobId);
//这里添加前端渲染
mmap.put("job", job);
}
return prefix + "/jobLog";
}
2 绑定进制
java
//这里自动发送
<input type="hidden" name="createBy" th:value="${@permission.getPrincipalProperty('loginName')}">
参考
https://cloud.tencent.com/developer/article/2258688
数据绑定是将用户提交的表单数据绑定到Java对象的过程。在Spring Boot中,数据绑定的主要工作是由DataBinder和WebDataBinder两个类来完成。
DataBinder
DataBinder是Spring框架中的一个重要组件,它可以将HTTP请求参数绑定到Java对象的属性上。它主要包括以下几个步骤:
(1)创建DataBinder对象:在Spring Boot应用程序中,我们可以使用@InitBinder注解来初始化DataBinder对象。
@Controller
public class MyController {
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
// ...
}
在上面的示例中,我们使用@InitBinder注解来初始化DataBinder对象。在initBinder()方法中,我们使用SimpleDateFormat类来格式化日期,并将格式化后的日期绑定到Date对象的属性上。
(2)绑定请求参数:在Spring Boot应用程序中,我们可以使用@ModelAttribute注解将请求参数绑定到Java对象的属性上。
代码语言:javascript
AI代码解释
java
@RequestMapping(value = "/saveUser", method = RequestMethod.POST)
public String saveUser(@ModelAttribute("user") User user) {
// ...
}
在上面的示例中,我们使用@ModelAttribute注解将请求参数绑定到User对象的属性上。
WebDataBinder
WebDataBinder是DataBinder的子类,它可以将HTTP请求参数绑定到Java对象的属性上,并提供了更多的数据绑定功能。例如,它可以将字符串类型的请求参数自动转换为Java中的基本数据类型,如Integer、Double等。在Spring Boot应用程序中,我们可以使用@InitBinder注解来初始化WebDataBinder对象。
代码语言:javascript
AI代码解释
java
@Controller
public class MyController {
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
binder.registerCustomEditor(Integer.class, new CustomNumberEditor(Integer.class, true));
}
// ...
}
在上面的示例中,我们使用@InitBinder注解初始化了一个WebDataBinder对象,并为Date和Integer类型的属性分别注册了日期编辑器和数字编辑器。这样,在处理HTTP请求时,WebDataBinder对象就可以将请求参数自动转换为Java中的相应类型,并将它们绑定到Java对象的属性上。
java
/**
* 将前台传递过来的日期格式的字符串,自动转化为Date类型
*/
@InitBinder
public void initBinder(WebDataBinder binder)
{
// Date 类型转换
binder.registerCustomEditor(Date.class, new PropertyEditorSupport()
{
@Override
public void setAsText(String text)
{
setValue(DateUtils.parseDate(text));
}
});
}
https://www.cnblogs.com/shijinglu2018/p/10389325.html
https://blog.csdn.net/chengxuzaza/article/details/60580779
3 数组绑定
- 路径决定了目的地
假设你的实体对象里同时有:
```java
private Long[] roleIds; // 角色ID数组
private List<Long> deptIds; // 部门ID列表
private Map<String, Object> params; // 参数Map
```
那么前端传参时必须用不同的"键"来区分:
· roleIds=1&roleIds=2 → 只绑定给 roleIds 属性
· deptIds=5&deptIds=6 → 只绑定给 deptIds 属性
· params[key1]=value1 → 只绑定给 params 这个 Map
路径解析时,第一段就是属性名。
Spring 的 BeanWrapperImpl 会先通过反射拿到 getRoleIds()、getDeptIds()、getParams(),它们返回的对象各自独立,互不干扰。
- 对于数组/List,内部如何"放入"?
当参数名是 roleIds(无下标)且目标类型是数组/List,Spring 会:
-
收集所有名叫 roleIds 的参数值 → 得到一个 String[](如 ["1","2"])
-
根据目标类型(Long[])进行类型转换,得到 Long[]。
-
调用 setRoleIds(convertedArray) 整个替换。
如果有下标,比如 users[0].name=张三,Spring 会:
-
解析到 users 是 List<User>。
-
找索引 0 位置的元素,如果不存在就调用默认构造函数创建一个 User 对象并放入该位置。
-
然后再对该对象的 name 属性赋值。
所以,无论有多少个不同的数组,只要参数名的"头部"(属性名)不同,Spring 就会精准地调用对应的 getter,然后对那个集合进行操作。
- 类型不同,行为也不同
Spring 会根据 getter 返回的类型,决定后续怎么处理:
· 数组:支持下标 [0] 或逗号分隔字符串、同名多值。
· List(有泛型):可以自动增长,通过下标或 add。
· Set(有泛型):默认通过 add,不能用下标(会报错)。
· Map:用中括号内的作为键,调用 put。
哪怕是同一个对象里有 10 个不同类型的集合,Spring 也会按照属性名一一分辨,完全不会混乱。
简而言之
Spring 绑定到"对应的数组"主要是按名导航:
· 请求参数名中的第一级名称(如 roleIds、deptIds、params)就是属性名。
· Spring 利用反射获取该属性对应的集合实例,然后根据集合类型使用对应的注入策略(整体赋值 / put 键值对 / 索引追加)。
所以,你看到的 params[treeParentCode] 只是众多可能中的一种,Spring 同样能正确处理 roleIds、userList[0].name 等,只要有正确的属性路径命名即可,绝不会把给 roleIds 的值塞到 deptIds 里去。
java
/**
* 修改业务
*
* @param genTable 业务信息
* @return 结果
*/
@Override
@Transactional
public void updateGenTable(GenTable genTable)
{
String options = JSON.toJSONString(genTable.getParams());
genTable.setOptions(options);
int row = genTableMapper.updateGenTable(genTable);
if (row > 0)
{
for (GenTableColumn genTableColumn : genTable.getColumns())
{
genTableColumnMapper.updateGenTableColumn(genTableColumn);
}
}
}
java
public Map<String, Object> getParams()
{
if (params == null)
{
params = new HashMap<>();
}
return params;
}
2
java
// 上传文件
function sendFile(file, obj) {
var data = new FormData();
data.append("file", file);
$.ajax({
type: "POST",
url: ctx + "common/upload",
data: data,
cache: false,
contentType: false,
processData: false,
dataType: 'json',
success: function(result) {
if (result.code == web_status.SUCCESS) {
$(obj).summernote('editor.insertImage', result.url, result.fileName);
} else {
$.modal.alertError(result.msg);
}
},
error: function(error) {
$.modal.alertWarning("图片上传失败。");
}
});
}
$("#form-notice-add").validate({
focusCleanup: true
});
function submitHandler() {
if ($.validate.form()) {
var sHTML = $('.summernote').summernote('code');
$("#noticeContent").val(sHTML);
$.operate.save(prefix + "/add", $('#form-notice-add').serialize());
}
}
java
/**
* 通用上传请求(单个)
*/
@PostMapping("/upload")
@ResponseBody
public AjaxResult uploadFile(MultipartFile file) throws Exception
{
try
{
// 上传文件路径
String filePath = RuoYiConfig.getUploadPath();
// 上传并返回新文件名称
String fileName = FileUploadUtils.upload(filePath, file);
//
String url = serverConfig.getUrl() + fileName;
AjaxResult ajax = AjaxResult.success();
ajax.put("url", url);
ajax.put("fileName", fileName);
ajax.put("newFileName", FileUtils.getName(fileName));
ajax.put("originalFilename", file.getOriginalFilename());
return ajax;
}
catch (Exception e)
{
return AjaxResult.error(e.getMessage());
}
}
java
/**
* 获取上传路径
*/
public static String getUploadPath()
{
return getProfile() + "/upload";
}
}
java
public static String getProfile()
{//这里可能可控?
return profile;
}
java
/**
* 根据文件路径上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @return 文件名称
* @throws IOException
*/
public static final String upload(String baseDir, MultipartFile file) throws IOException
{
try
{
//上传路径,文件信息,数组
return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
}
catch (Exception e)
{
throw new IOException(e.getMessage(), e);
}
}
/**
java
public static final String[] DEFAULT_ALLOWED_EXTENSION = {
// 图片
"bmp", "gif", "jpg", "jpeg", "png",
// word excel powerpoint
"doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
// 压缩文件
"rar", "zip", "gz", "bz2",
// 视频格式
"mp4", "avi", "rmvb",
// pdf
"pdf" };
java
/**
* 文件上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @param allowedExtension 上传文件类型
* @return 返回上传成功的文件名
* @throws FileSizeLimitExceededException 如果超出最大大小
* @throws FileNameLengthLimitExceededException 文件名太长
* @throws IOException 比如读写文件出错时
* @throws InvalidExtensionException 文件校验异常
*/
//上传路径,文件信息,数组
public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
InvalidExtensionException
{
//判断文件名长度是不是空的
int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length();
//如果文件名长度大于100
if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
{
throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
}
//判断后缀
assertAllowed(file, allowedExtension);
//
String fileName = extractFilename(file);
String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
file.transferTo(Paths.get(absPath));
return getPathFileName(baseDir, fileName);
}
java
/**
* 编码文件名
*/
public static final String extractFilename(MultipartFile file)
{
return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
}
java
/**
* 格式化文本, {} 表示占位符<br>
* 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
* 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
* 例:<br>
* 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br>
* 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
* 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
*
* @param template 文本模板,被替换的部分用 {} 表示
* @param params 参数值
* @return 格式化后的文本
*/
//模板构造
public static String format(String template, Object... params)
{
//如果有一个是空
if (isEmpty(params) || isEmpty(template))
{
//返回模板
return template;
}
return StrFormatter.format(template, params);
}
java
/**
* 获取文件名的后缀
*
* @param file 表单文件
* @return 后缀名
*/
public static final String getExtension(MultipartFile file)
{
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
if (StringUtils.isEmpty(extension))
{
extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
}
return extension;
}
}
java
/**
* 格式化字符串<br>
* 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
* 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
* 例:<br>
* 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br>
* 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
* 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
*
* @param strPattern 字符串模板
* @param argArray 参数列表
* @return 结果
*/
public static String format(final String strPattern, final Object... argArray)
{
if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray))
{
return strPattern;
}
final int strPatternLength = strPattern.length();
// 初始化定义好的长度以获得更好的性能
StringBuilder sbuf = new StringBuilder(strPatternLength + 50);
int handledPosition = 0;
int delimIndex;// 占位符所在位置
for (int argIndex = 0; argIndex < argArray.length; argIndex++)
{
delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition);
if (delimIndex == -1)
{
if (handledPosition == 0)
{
return strPattern;
}
else
{ // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果
sbuf.append(strPattern, handledPosition, strPatternLength);
return sbuf.toString();
}
}
else
{
if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH)
{
if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH)
{
// 转义符之前还有一个转义符,占位符依旧有效
sbuf.append(strPattern, handledPosition, delimIndex - 1);
sbuf.append(Convert.utf8Str(argArray[argIndex]));
handledPosition = delimIndex + 2;
}
else
{
// 占位符被转义
argIndex--;
sbuf.append(strPattern, handledPosition, delimIndex - 1);
sbuf.append(C_DELIM_START);
handledPosition = delimIndex + 1;
}
}
else
{
// 正常占位符
sbuf.append(strPattern, handledPosition, delimIndex);
sbuf.append(Convert.utf8Str(argArray[argIndex]));
handledPosition = delimIndex + 2;
}
}
}
// 加入最后一个占位符后所有的字符
sbuf.append(strPattern, handledPosition, strPattern.length());
return sbuf.toString();
}
}
java
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
java
/**
* 默认大小 50M
*/
public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024L;
java
public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" };
public static final String[] FLASH_EXTENSION = { "swf", "flv" };
public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
"asf", "rm", "rmvb" };
public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" };
java
**
* 文件大小校验
*
* @param file 上传的文件
* @return
* @throws FileSizeLimitExceededException 如果超出最大大小
* @throws InvalidExtensionException
*/
public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
throws FileSizeLimitExceededException, InvalidExtensionException
{
//获取文件大小
long size = file.getSize();
//判断大小
if (size > DEFAULT_MAX_SIZE)
{
throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
}
//截取文件名
String fileName = file.getOriginalFilename();
//获取后缀
String extension = getExtension(file);
//判断白名单,这里过滤
//不是空,并且不是白名单
if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension))
{
//判断
if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION)
{
throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
fileName);
}
//判断
else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION)
{
throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
fileName);
}
else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION)
{
throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
fileName);
}
else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION)
{
throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
fileName);
}
else
{
throw new InvalidExtensionException(allowedExtension, extension, fileName);
}
}
}
java
/**
* 判断MIME类型是否是允许的MIME类型
*
* @param extension
* @param allowedExtension
* @return
*/
//后缀,数组
public static final boolean isAllowedExtension(String extension, String[] allowedExtension)
{
//循环
for (String str : allowedExtension)
{//判断
if (str.equalsIgnoreCase(extension))
{
return true;
}
}
return false;
}
java
/**
* 默认大小 50M
*/
public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024L;
java
public String getOriginalFilename() {
String filename = this.fileItem.getName();
if (filename == null) {
return "";
//private boolean preserveFilename = false;
} else if (this.preserveFilename) {
return filename;
} else {
//查找最后一个/的位置
int unixSep = filename.lastIndexOf(47);
//返回最后一个\的位置
int winSep = filename.lastIndexOf(92);
//查找哪一个位置最大
int pos = Math.max(winSep, unixSep);
//substring 是一种用于从字符串中提取子字符串的常用方法
//System.out.println(s.substring(7)); // "World!"
//最大加1,截取文件名
return pos != -1 ? filename.substring(pos + 1) : filename;
}
}
java
/**
* 获取文件名的后缀
*
* @param file 表单文件
* @return 后缀名
*/
public static final String getExtension(MultipartFile file)
{//返回后缀
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
//判断是否为空
if (StringUtils.isEmpty(extension))
{
//获取文件名判断是否为空
extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
}
//返回后缀
return extension;
}
}
java
public static String getExtension(String prefix)
{
switch (prefix)
{
case IMAGE_PNG:
return "png";
case IMAGE_JPG:
return "jpg";
case IMAGE_JPEG:
return "jpeg";
case IMAGE_BMP:
return "bmp";
case IMAGE_GIF:
return "gif";
default:
return "";
}
}
java
/**
* * 判断一个字符串是否为空串
*
* @param str String
* @return true:为空 false:非空
*/
public static boolean isEmpty(String str)
{
return isNull(str) || NULLSTR.equals(str.trim());
}
java
public static String getExtension(String fileName) throws IllegalArgumentException {
if (fileName == null) {
return null;
} else {
//返回后缀数字
int index = indexOfExtension(fileName);
//截断后缀,如果-1返回空
return index == -1 ? "" : fileName.substring(index + 1);
}
}
java
public static int indexOfExtension(String fileName) throws IllegalArgumentException {
if (fileName == null) {
return -1;
} else {
// static boolean isSystemWindows() {
//return SYSTEM_NAME_SEPARATOR == '\\';
//}
if (isSystemWindows()) {
int offset = fileName.indexOf(58, getAdsCriticalOffset(fileName));
if (offset != -1) {
throw new IllegalArgumentException("NTFS ADS separator (':') in file name is forbidden.");
}
}
//这里可能防../../qqq这种没有后缀的
//检查最后一个点出现的位置
int extensionPos = fileName.lastIndexOf(46);
//返回最大位置数字
int lastSeparator = indexOfLastSeparator(fileName);
//判断大小,返回数字
//如果没有这里的判断可能可以绕过../jsp这种
return lastSeparator > extensionPos ? -1 : extensionPos;
}
}
java
public static int indexOfLastSeparator(String fileName) {
if (fileName == null) {
return -1;
} else {
int lastUnixPos = fileName.lastIndexOf(47);
int lastWindowsPos = fileName.lastIndexOf(92);
//返回最大位置
// /\
return Math.max(lastUnixPos, lastWindowsPos);
}
}
代码注入
java
/**
* 新增保存调度
*/
@Log(title = "定时任务", businessType = BusinessType.INSERT)
@RequiresPermissions("monitor:job:add")
@PostMapping("/add")
@ResponseBody
public AjaxResult addSave(@Validated SysJob job) throws SchedulerException, TaskException
{
if (!CronUtils.isValid(job.getCronExpression()))
{
return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确");
}
else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS }))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规");
}
else if (!ScheduleUtils.whiteList(job.getInvokeTarget()))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
}
job.setCreateBy(getLoginName());
return toAjax(jobService.insertJob(job));
}
java
/**
* 新增任务
*
* @param job 调度信息 调度信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int insertJob(SysJob job) throws SchedulerException, TaskException
{
//把任务设置为暂停
job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
//是否保存成功
int rows = jobMapper.insertJob(job);
if (rows > 0)
{
//创建任务
ScheduleUtils.createScheduleJob(scheduler, job);
}
return rows;
}
java
/**
* 新增调度任务信息
*
* @param job 调度任务信息
* @return 结果
*/
public int insertJob(SysJob job);
java
<insert id="insertJob" parameterType="SysJob" useGeneratedKeys="true" keyProperty="jobId">
insert into sys_job(
<if test="jobId != null and jobId != 0">job_id,</if>
<if test="jobName != null and jobName != ''">job_name,</if>
<if test="jobGroup != null and jobGroup != ''">job_group,</if>
<if test="invokeTarget != null and invokeTarget != ''">invoke_target,</if>
<if test="cronExpression != null and cronExpression != ''">cron_expression,</if>
<if test="misfirePolicy != null and misfirePolicy != ''">misfire_policy,</if>
<if test="concurrent != null and concurrent != ''">concurrent,</if>
<if test="status != null and status != ''">status,</if>
<if test="remark != null and remark != ''">remark,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
create_time
)values(
<if test="jobId != null and jobId != 0">#{jobId},</if>
<if test="jobName != null and jobName != ''">#{jobName},</if>
<if test="jobGroup != null and jobGroup != ''">#{jobGroup},</if>
<if test="invokeTarget != null and invokeTarget != ''">#{invokeTarget},</if>
<if test="cronExpression != null and cronExpression != ''">#{cronExpression},</if>
<if test="misfirePolicy != null and misfirePolicy != ''">#{misfirePolicy},</if>
<if test="concurrent != null and concurrent != ''">#{concurrent},</if>
<if test="status != null and status != ''">#{status},</if>
<if test="remark != null and remark != ''">#{remark},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
sysdate()
)
</insert>
java
/**
* 创建定时任务
*/
public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException
{
//检查是否允许并发
Class<? extends Job> jobClass = getQuartzJobClass(job);
// 构建job信息
//获取信息
Long jobId = job.getJobId();
String jobGroup = job.getJobGroup();
//在软件开发中,build()通常针对构建对象或系统的操作
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();
// 表达式调度构建器
///** cron执行表达式 */
// @Excel(name = "执行表达式 ")
// private String cronExpression;
//cronSchedule通常是指基于Cron 表达式的定时任务调度,广泛用于软件系统中按预定时间自动执行任务。
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
//
cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
.withSchedule(cronScheduleBuilder).build();
// 放入参数,运行时的方法可以获取
jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
// 判断是否存在
if (scheduler.checkExists(getJobKey(jobId, jobGroup)))
{
// 防止创建时存在数据问题 先移除,然后在执行创建操作
scheduler.deleteJob(getJobKey(jobId, jobGroup));
}
// 判断任务是否过期
if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression())))
{
// 执行调度任务
//
scheduler.scheduleJob(jobDetail, trigger);
}
// 暂停任务
if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))
{
scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
}
java
/**
* 设置定时任务策略
*/
public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb)
throws TaskException
{
switch (job.getMisfirePolicy())
{
case ScheduleConstants.MISFIRE_DEFAULT:
return cb;
case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
return cb.withMisfireHandlingInstructionIgnoreMisfires();
case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
return cb.withMisfireHandlingInstructionFireAndProceed();
case ScheduleConstants.MISFIRE_DO_NOTHING:
return cb.withMisfireHandlingInstructionDoNothing();
default:
throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
+ "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR);
}
}
java
public static CronScheduleBuilder cronSchedule(String cronExpression) {
try {
return cronSchedule(new CronExpression(cronExpression));
} catch (ParseException e) {
throw new RuntimeException("CronExpression '" + cronExpression + "' is invalid.", e);
}
}
java
public static JobBuilder newJob(Class<? extends Job> jobClass) {
JobBuilder b = new JobBuilder();
b.ofType(jobClass);
return b;
}
java
/**
* 得到quartz任务类
*
* @param sysJob 执行计划
* @return 具体执行任务类
*/
private static Class<? extends Job> getQuartzJobClass(SysJob sysJob)
{
//检查是否允许并发
boolean isConcurrent = "0".equals(sysJob.getConcurrent());
//返回不同的类信息
return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
}
java
/** 是否并发执行(0允许 1禁止) */
@Excel(name = "并发执行", readConverterExp = "0=允许,1=禁止")
private String concurrent;
java
public JobDetail build() {
JobDetailImpl job = new JobDetailImpl();
job.setJobClass(this.jobClass);
job.setDescription(this.description);
if (this.key == null) {
this.key = new JobKey(Key.createUniqueName((String)null), (String)null);
}
job.setKey(this.key);
job.setDurability(this.durability);
job.setRequestsRecovery(this.shouldRecover);
if (!this.jobDataMap.isEmpty()) {
job.setJobDataMap(this.jobDataMap);
}
return job;
}
java
/** 任务ID */
@Excel(name = "任务序号", cellType = ColumnType.NUMERIC)
private Long jobId;
/** 任务名称 */
@Excel(name = "任务名称")
private String jobName;
/** 任务组名 */
@Excel(name = "任务组名")
private String jobGroup;
java
/**
* 立即运行任务
*
* @param job 调度信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean run(SysJob job) throws SchedulerException
{
boolean result = false;
Long jobId = job.getJobId();
SysJob tmpObj = selectJobById(job.getJobId());
// 参数
JobDataMap dataMap = new JobDataMap();
dataMap.put(ScheduleConstants.TASK_PROPERTIES, tmpObj);
JobKey jobKey = ScheduleUtils.getJobKey(jobId, tmpObj.getJobGroup());
if (scheduler.checkExists(jobKey))
{
result = true;
//这里
scheduler.triggerJob(jobKey, dataMap);
}
return result;
}
java
package com.ruoyi.quartz.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.quartz.domain.SysJob;
/**
* 任务执行工具
*
* @author ruoyi
*/
public class JobInvokeUtil
{
/**
* 执行方法
*
* @param sysJob 系统任务
*/
public static void invokeMethod(SysJob sysJob) throws Exception
{
String invokeTarget = sysJob.getInvokeTarget();
String beanName = getBeanName(invokeTarget);
String methodName = getMethodName(invokeTarget);
List<Object[]> methodParams = getMethodParams(invokeTarget);
if (!isValidClassName(beanName))
{
Object bean = SpringUtils.getBean(beanName);
invokeMethod(bean, methodName, methodParams);
}
else
{
Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();
invokeMethod(bean, methodName, methodParams);
}
}
/**
* 调用任务方法
*
* @param bean 目标对象
* @param methodName 方法名称
* @param methodParams 方法参数
*/
private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0)
{
Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams));
method.invoke(bean, getMethodParamsValue(methodParams));
}
else
{
Method method = bean.getClass().getMethod(methodName);
method.invoke(bean);
}
}
/**
* 校验是否为为class包名
*
* @param invokeTarget 名称
* @return true是 false否
*/
public static boolean isValidClassName(String invokeTarget)
{
return StringUtils.countMatches(invokeTarget, ".") > 1;
}
/**
* 获取bean名称
*
* @param invokeTarget 目标字符串
* @return bean名称
*/
public static String getBeanName(String invokeTarget)
{
String beanName = StringUtils.substringBefore(invokeTarget, "(");
return StringUtils.substringBeforeLast(beanName, ".");
}
/**
* 获取bean方法
*
* @param invokeTarget 目标字符串
* @return method方法
*/
public static String getMethodName(String invokeTarget)
{
String methodName = StringUtils.substringBefore(invokeTarget, "(");
return StringUtils.substringAfterLast(methodName, ".");
}
/**
* 获取method方法参数相关列表
*
* @param invokeTarget 目标字符串
* @return method方法相关参数列表
*/
public static List<Object[]> getMethodParams(String invokeTarget)
{
String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
if (StringUtils.isEmpty(methodStr))
{
return null;
}
String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)");
List<Object[]> classs = new LinkedList<>();
for (int i = 0; i < methodParams.length; i++)
{
String str = StringUtils.trimToEmpty(methodParams[i]);
// String字符串类型,以'或"开头
if (StringUtils.startsWithAny(str, "'", "\""))
{
classs.add(new Object[] { StringUtils.substring(str, 1, str.length() - 1), String.class });
}
// boolean布尔类型,等于true或者false
else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str))
{
classs.add(new Object[] { Boolean.valueOf(str), Boolean.class });
}
// long长整形,以L结尾
else if (StringUtils.endsWith(str, "L"))
{
classs.add(new Object[] { Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class });
}
// double浮点类型,以D结尾
else if (StringUtils.endsWith(str, "D"))
{
classs.add(new Object[] { Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class });
}
// 其他类型归类为整形
else
{
classs.add(new Object[] { Integer.valueOf(str), Integer.class });
}
}
return classs;
}
/**
* 获取参数类型
*
* @param methodParams 参数相关列表
* @return 参数类型列表
*/
public static Class<?>[] getMethodParamsType(List<Object[]> methodParams)
{
Class<?>[] classs = new Class<?>[methodParams.size()];
int index = 0;
for (Object[] os : methodParams)
{
classs[index] = (Class<?>) os[1];
index++;
}
return classs;
}
/**
* 获取参数值
*
* @param methodParams 参数相关列表
* @return 参数值列表
*/
public static Object[] getMethodParamsValue(List<Object[]> methodParams)
{
Object[] classs = new Object[methodParams.size()];
int index = 0;
for (Object[] os : methodParams)
{
classs[index] = (Object) os[0];
index++;
}
return classs;
}
}
java
/**
* 调用任务方法
*
* @param bean 目标对象
* @param methodName 方法名称
* @param methodParams 方法参数
*/
private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0)
{
Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams));
method.invoke(bean, getMethodParamsValue(methodParams));
}
else
{
Method method = bean.getClass().getMethod(methodName);
method.invoke(bean);
}
}
java
**
* 执行方法
*
* @param sysJob 系统任务
*/
public static void invokeMethod(SysJob sysJob) throws Exception
{
String invokeTarget = sysJob.getInvokeTarget();
String beanName = getBeanName(invokeTarget);
String methodName = getMethodName(invokeTarget);
List<Object[]> methodParams = getMethodParams(invokeTarget);
if (!isValidClassName(beanName))
{
Object bean = SpringUtils.getBean(beanName);
invokeMethod(bean, methodName, methodParams);
}
else
{
//这里forName,bean可控
Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();
invokeMethod(bean, methodName, methodParams);
}
}
java
**
* 定时任务处理(禁止并发执行)
*
* @author ruoyi
*
*/
@DisallowConcurrentExecution
public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob
{
@Override
protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
{
JobInvokeUtil.invokeMethod(sysJob);
}
模板注入
java
/**
* @update zhixin wen <wenzhixin2010@gmail.com>
*/
var Utils = $.fn.bootstrapTable.utils
function printPageBuilderDefault (table, styles) {
return `
<html>
<head>
//这里
// ${styles}是关键
//若以用到了这个引擎
${styles}
<style type="text/css" media="print">
@page {
size: auto;
margin: 25px 0 25px 0;
}
</style>
java
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
//预处理,字符替换
//先解析,再拼接
//这个是关键字
<h1 th:href="@{__${name}__}">name1参数</h1>
//普通文本输出
<h1 th:text="${name2}">name2参数</h1>
</body>

https://blog.csdn.net/ljj20020718/article/details/131013126
java@RequiresPermissions("monitor:cache:view") @PostMapping("/getNames") public String getCacheNames(String fragment, ModelMap mmap) { mmap.put("cacheNames", cacheService.getCacheNames()); //这里可以任意拼接 //触发模板解析 //这种:: //前端@{__${}__} //a/.xx./b这种触发模板解析 //每个语言写法不一样 return prefix + "/cache::" + fragment; }
代码执行
java
POST /monitor/job/add HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:150.0) Gecko/20100101 Firefox/150.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,zh-HK;q=0.7,en-US;q=0.6,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 145
Origin: http://localhost
Connection: keep-alive
Referer: http://localhost/monitor/job/add
Cookie: JSESSIONID=b8f2b957-2e05-4312-8af1-82ebf41d25e6
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Priority: u=0
createBy=admin&jobName=123&jobGroup=DEFAULT&invokeTarget=ryTask.ryNoParams&cronExpression=0%2F10+*+*+*+*+%3F&misfirePolicy=1&concurrent=1&remark=
这种,然后利用反射

java
/**
* 新增保存调度
*/
@Log(title = "定时任务", businessType = BusinessType.INSERT)
@RequiresPermissions("monitor:job:add")
@PostMapping("/add")
@ResponseBody
public AjaxResult addSave(@Validated SysJob job) throws SchedulerException, TaskException
{
if (!CronUtils.isValid(job.getCronExpression()))
{
return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确");
}
else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS }))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规");
}
else if (!ScheduleUtils.whiteList(job.getInvokeTarget()))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
}
job.setCreateBy(getLoginName());
return toAjax(jobService.insertJob(job));
}
java
/**
* 新增任务
*
* @param job 调度信息 调度信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int insertJob(SysJob job) throws SchedulerException, TaskException
{
job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
int rows = jobMapper.insertJob(job);
if (rows > 0)
{
ScheduleUtils.createScheduleJob(scheduler, job);
}
return rows;
}
java
/**
* 创建定时任务
*/
public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException
{
//这里触发
Class<? extends Job> jobClass = getQuartzJobClass(job);
// 构建job信息
Long jobId = job.getJobId();
String jobGroup = job.getJobGroup();
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();
// 表达式调度构建器
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
.withSchedule(cronScheduleBuilder).build();
// 放入参数,运行时的方法可以获取
jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
// 判断是否存在
if (scheduler.checkExists(getJobKey(jobId, jobGroup)))
{
// 防止创建时存在数据问题 先移除,然后在执行创建操作
scheduler.deleteJob(getJobKey(jobId, jobGroup));
}
// 判断任务是否过期
if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression())))
{
// 执行调度任务
scheduler.scheduleJob(jobDetail, trigger);
}
// 暂停任务
if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))
{
scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
}
java
/**
* 得到quartz任务类
*
* @param sysJob 执行计划
* @return 具体执行任务类
*/
private static Class<? extends Job> getQuartzJobClass(SysJob sysJob)
{
boolean isConcurrent = "0".equals(sysJob.getConcurrent());
return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
}
java
/**
* 定时任务处理(允许并发执行)
*
* @author ruoyi
*
*/
public class QuartzJobExecution extends AbstractQuartzJob
{
@Override
protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
{
JobInvokeUtil.invokeMethod(sysJob);
}
}
forName好东西
java
**
* 执行方法
*
* @param sysJob 系统任务
*/
public static void invokeMethod(SysJob sysJob) throws Exception
{
String invokeTarget = sysJob.getInvokeTarget();
//这里
String beanName = getBeanName(invokeTarget);
//小过滤,可控
String methodName = getMethodName(invokeTarget);
//字符串
List<Object[]> methodParams = getMethodParams(invokeTarget);
if (!isValidClassName(beanName))
{
Object bean = SpringUtils.getBean(beanName);
invokeMethod(bean, methodName, methodParams);
}
else
{
//这里forName,bean可控
//只能调用无参构造类
Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();
invokeMethod(bean, methodName, methodParams);
}
}
java
/**
* 获取method方法参数相关列表
*
* @param invokeTarget 目标字符串
* @return method方法相关参数列表
*/
public static List<Object[]> getMethodParams(String invokeTarget)
{
String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
if (StringUtils.isEmpty(methodStr))
{
return null;
}
String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)");
List<Object[]> classs = new LinkedList<>();
for (int i = 0; i < methodParams.length; i++)
{
String str = StringUtils.trimToEmpty(methodParams[i]);
// String字符串类型,以'或"开头
if (StringUtils.startsWithAny(str, "'", "\""))
{
classs.add(new Object[] { StringUtils.substring(str, 1, str.length() - 1), String.class });
}
// boolean布尔类型,等于true或者false
else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str))
{
classs.add(new Object[] { Boolean.valueOf(str), Boolean.class });
}
// long长整形,以L结尾
else if (StringUtils.endsWith(str, "L"))
{
classs.add(new Object[] { Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class });
}
// double浮点类型,以D结尾
else if (StringUtils.endsWith(str, "D"))
{
classs.add(new Object[] { Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class });
}
// 其他类型归类为整形
else
{
classs.add(new Object[] { Integer.valueOf(str), Integer.class });
}
}
//返回相应类型
return classs;
}
java
@NotBlank(message = "调用目标字符串不能为空")
@Size(min = 0, max = 1000, message = "调用目标字符串长度不能超过500个字符")
public String getInvokeTarget()
{
return invokeTarget;
}
java
/** 调用目标字符串 */
@Excel(name = "调用目标字符串")
private String invokeTarget;
java
/**
* 获取bean名称
*
* @param invokeTarget 目标字符串
* @return bean名称
*/
public static String getBeanName(String invokeTarget)
{
//功能:返回第一个参数字符串中,在第一次出现第二个参数字符串之前的所有字符。
//返回(前面出现的所有参数
String beanName = StringUtils.substringBefore(invokeTarget, "(");
//返回.前面出现的所有参数
return StringUtils.substringBeforeLast(beanName, ".");
}