Springboot Vue可配置調(diào)度任務(wù)實現(xiàn)示例詳解
正文
Springboot + Vue,定時任務(wù)調(diào)度的全套實現(xiàn)方案。
這里用了quartz這個框架,實現(xiàn)分布式調(diào)度任務(wù)很不錯,關(guān)于quarz的使用方式改天補一篇。相當簡單。
1、表結(jié)構(gòu):
| sys_job | |||||
|---|---|---|---|---|---|
| 列名 | 數(shù)據(jù)類型 | 長度 | 是否可空 | 是否主鍵 | 說明 |
| job_id | bigint | 否 | 是 | 任務(wù)ID | |
| job_name | varchar | 64 | 否 | 是 | 任務(wù)名稱 |
| job_group | varchar | 64 | 否 | 是 | 任務(wù)組名 |
| invoke_target | varchar | 500 | 否 | 否 | 調(diào)用目標字符串 |
| cron_expression | varchar | 255 | 是 | 否 | cron執(zhí)行表達式 |
| misfire_policy | varchar | 20 | 是 | 否 | 計劃執(zhí)行錯誤策略(1立即執(zhí)行 2執(zhí)行一次 3放棄執(zhí)行) |
| concurrent | char | 1 | 是 | 否 | 是否并發(fā)執(zhí)行(0允許 1禁止) |
| status | char | 1 | 是 | 否 | 狀態(tài)(0正常 1暫停) |
| create_by | varchar | 64 | 是 | 否 | 創(chuàng)建者 |
| create_time | datetime | 是 | 否 | 創(chuàng)建時間 | |
| update_by | varchar | 64 | 是 | 否 | 更新者 |
| update_time | datetime | 是 | 否 | 更新時間 | |
| remark | varchar | 500 | 是 | 否 | 備注信息 |
2、接口:
@RestController
@RequestMapping("/job")
public class SysJobController extends BaseController
{
@Autowired
private ISysJobService jobService;
?
/**
* 查詢定時任務(wù)列表
*/
@GetMapping("/list")
public TableDataInfo list(SysJob sysJob)
{
startPage();
List<SysJob> list = jobService.selectJobList(sysJob);
return getDataTable(list);
}
?
/**
* 導出定時任務(wù)列表
*/
@PostMapping("/export")
public void export(HttpServletResponse response, SysJob sysJob)
{
List<SysJob> list = jobService.selectJobList(sysJob);
ExcelUtil<SysJob> util = new ExcelUtil<SysJob>(SysJob.class);
util.exportExcel(response, list, "定時任務(wù)");
}
?
/**
* 獲取定時任務(wù)詳細信息
*/
@GetMapping(value = "/{jobId}")
public AjaxResult getInfo(@PathVariable("jobId") Long jobId)
{
return success(jobService.selectJobById(jobId));
}
?
/**
* 新增定時任務(wù)
*/
@PostMapping
public AjaxResult add(@RequestBody SysJob job) throws SchedulerException, TaskException
{
if (!CronUtils.isValid(job.getCronExpression()))
{
return error("新增任務(wù)'" + job.getJobName() + "'失敗,Cron表達式不正確");
}
else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
{
return error("新增任務(wù)'" + job.getJobName() + "'失敗,目標字符串不允許'rmi'調(diào)用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS }))
{
return error("新增任務(wù)'" + job.getJobName() + "'失敗,目標字符串不允許'ldap(s)'調(diào)用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
{
return error("新增任務(wù)'" + job.getJobName() + "'失敗,目標字符串不允許'http(s)'調(diào)用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR))
{
return error("新增任務(wù)'" + job.getJobName() + "'失敗,目標字符串存在違規(guī)");
}
else if (!ScheduleUtils.whiteList(job.getInvokeTarget()))
{
return error("新增任務(wù)'" + job.getJobName() + "'失敗,目標字符串不在白名單內(nèi)");
}
job.setCreateBy(getUsername());
return toAjax(jobService.insertJob(job));
}
?
/**
* 修改定時任務(wù)
*/
@PutMapping
public AjaxResult edit(@RequestBody SysJob job) throws SchedulerException, TaskException
{
if (!CronUtils.isValid(job.getCronExpression()))
{
return error("修改任務(wù)'" + job.getJobName() + "'失敗,Cron表達式不正確");
}
else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
{
return error("修改任務(wù)'" + job.getJobName() + "'失敗,目標字符串不允許'rmi'調(diào)用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS }))
{
return error("修改任務(wù)'" + job.getJobName() + "'失敗,目標字符串不允許'ldap(s)'調(diào)用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
{
return error("修改任務(wù)'" + job.getJobName() + "'失敗,目標字符串不允許'http(s)'調(diào)用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR))
{
return error("修改任務(wù)'" + job.getJobName() + "'失敗,目標字符串存在違規(guī)");
}
else if (!ScheduleUtils.whiteList(job.getInvokeTarget()))
{
return error("修改任務(wù)'" + job.getJobName() + "'失敗,目標字符串不在白名單內(nèi)");
}
job.setUpdateBy(getUsername());
return toAjax(jobService.updateJob(job));
}
?
/**
* 定時任務(wù)狀態(tài)修改
*/
@PutMapping("/changeStatus")
public AjaxResult changeStatus(@RequestBody SysJob job) throws SchedulerException
{
SysJob newJob = jobService.selectJobById(job.getJobId());
newJob.setStatus(job.getStatus());
return toAjax(jobService.changeStatus(newJob));
}
?
/**
* 定時任務(wù)立即執(zhí)行一次
*/
@PutMapping("/run")
public AjaxResult run(@RequestBody SysJob job) throws SchedulerException
{
boolean result = jobService.run(job);
return result ? success() : error("任務(wù)不存在或已過期!");
}
?
/**
* 刪除定時任務(wù)
*/
@DeleteMapping("/{jobIds}")
public AjaxResult remove(@PathVariable Long[] jobIds) throws SchedulerException, TaskException
{
jobService.deleteJobByIds(jobIds);
return success();
}
復制代碼3、業(yè)務(wù)層:
public interface ISysJobService
{
/**
* 獲取quartz調(diào)度器的計劃任務(wù)
*
* @param job 調(diào)度信息
* @return 調(diào)度任務(wù)集合
*/
public List<SysJob> selectJobList(SysJob job);
?
/**
* 通過調(diào)度任務(wù)ID查詢調(diào)度信息
*
* @param jobId 調(diào)度任務(wù)ID
* @return 調(diào)度任務(wù)對象信息
*/
public SysJob selectJobById(Long jobId);
?
/**
* 暫停任務(wù)
*
* @param job 調(diào)度信息
* @return 結(jié)果
*/
public int pauseJob(SysJob job) throws SchedulerException;
?
/**
* 恢復任務(wù)
*
* @param job 調(diào)度信息
* @return 結(jié)果
*/
public int resumeJob(SysJob job) throws SchedulerException;
?
/**
* 刪除任務(wù)后,所對應(yīng)的trigger也將被刪除
*
* @param job 調(diào)度信息
* @return 結(jié)果
*/
public int deleteJob(SysJob job) throws SchedulerException;
?
/**
* 批量刪除調(diào)度信息
*
* @param jobIds 需要刪除的任務(wù)ID
* @return 結(jié)果
*/
public void deleteJobByIds(Long[] jobIds) throws SchedulerException;
?
/**
* 任務(wù)調(diào)度狀態(tài)修改
*
* @param job 調(diào)度信息
* @return 結(jié)果
*/
public int changeStatus(SysJob job) throws SchedulerException;
?
/**
* 立即運行任務(wù)
*
* @param job 調(diào)度信息
* @return 結(jié)果
*/
public boolean run(SysJob job) throws SchedulerException;
?
/**
* 新增任務(wù)
*
* @param job 調(diào)度信息
* @return 結(jié)果
*/
public int insertJob(SysJob job) throws SchedulerException, TaskException;
?
/**
* 更新任務(wù)
*
* @param job 調(diào)度信息
* @return 結(jié)果
*/
public int updateJob(SysJob job) throws SchedulerException, TaskException;
?
/**
* 校驗cron表達式是否有效
*
* @param cronExpression 表達式
* @return 結(jié)果
*/
public boolean checkCronExpressionIsValid(String cronExpression);
}
復制代碼@Service
public class SysJobServiceImpl implements ISysJobService
{
@Autowired
private Scheduler scheduler;
?
@Autowired
private SysJobMapper jobMapper;
?
/**
* 項目啟動時,初始化定時器 主要是防止手動修改數(shù)據(jù)庫導致未同步到定時任務(wù)處理(注:不能手動修改數(shù)據(jù)庫ID和任務(wù)組名,否則會導致臟數(shù)據(jù))
*/
@PostConstruct
public void init() throws SchedulerException, TaskException
{
scheduler.clear();
List<SysJob> jobList = jobMapper.selectJobAll();
for (SysJob job : jobList)
{
ScheduleUtils.createScheduleJob(scheduler, job);
}
}
?
/**
* 獲取quartz調(diào)度器的計劃任務(wù)列表
*
* @param job 調(diào)度信息
* @return
*/
@Override
public List<SysJob> selectJobList(SysJob job)
{
return jobMapper.selectJobList(job);
}
?
/**
* 通過調(diào)度任務(wù)ID查詢調(diào)度信息
*
* @param jobId 調(diào)度任務(wù)ID
* @return 調(diào)度任務(wù)對象信息
*/
@Override
public SysJob selectJobById(Long jobId)
{
return jobMapper.selectJobById(jobId);
}
?
/**
* 暫停任務(wù)
*
* @param job 調(diào)度信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int pauseJob(SysJob job) throws SchedulerException
{
Long jobId = job.getJobId();
String jobGroup = job.getJobGroup();
job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
int rows = jobMapper.updateJob(job);
if (rows > 0)
{
scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
return rows;
}
?
/**
* 恢復任務(wù)
*
* @param job 調(diào)度信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int resumeJob(SysJob job) throws SchedulerException
{
Long jobId = job.getJobId();
String jobGroup = job.getJobGroup();
job.setStatus(ScheduleConstants.Status.NORMAL.getValue());
int rows = jobMapper.updateJob(job);
if (rows > 0)
{
scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
return rows;
}
?
/**
* 刪除任務(wù)后,所對應(yīng)的trigger也將被刪除
*
* @param job 調(diào)度信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteJob(SysJob job) throws SchedulerException
{
Long jobId = job.getJobId();
String jobGroup = job.getJobGroup();
int rows = jobMapper.deleteJobById(jobId);
if (rows > 0)
{
scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
return rows;
}
?
/**
* 批量刪除調(diào)度信息
*
* @param jobIds 需要刪除的任務(wù)ID
* @return 結(jié)果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteJobByIds(Long[] jobIds) throws SchedulerException
{
for (Long jobId : jobIds)
{
SysJob job = jobMapper.selectJobById(jobId);
deleteJob(job);
}
}
?
/**
* 任務(wù)調(diào)度狀態(tài)修改
*
* @param job 調(diào)度信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int changeStatus(SysJob job) throws SchedulerException
{
int rows = 0;
String status = job.getStatus();
if (ScheduleConstants.Status.NORMAL.getValue().equals(status))
{
rows = resumeJob(job);
}
else if (ScheduleConstants.Status.PAUSE.getValue().equals(status))
{
rows = pauseJob(job);
}
return rows;
}
?
/**
* 立即運行任務(wù)
*
* @param job 調(diào)度信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean run(SysJob job) throws SchedulerException
{
boolean result = false;
Long jobId = job.getJobId();
String jobGroup = job.getJobGroup();
SysJob properties = selectJobById(job.getJobId());
// 參數(shù)
JobDataMap dataMap = new JobDataMap();
dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties);
JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup);
if (scheduler.checkExists(jobKey))
{
result = true;
scheduler.triggerJob(jobKey, dataMap);
}
return result;
}
?
/**
* 新增任務(wù)
*
* @param job 調(diào)度信息 調(diào)度信息
*/
@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;
}
?
/**
* 更新任務(wù)的時間表達式
*
* @param job 調(diào)度信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int updateJob(SysJob job) throws SchedulerException, TaskException
{
SysJob properties = selectJobById(job.getJobId());
int rows = jobMapper.updateJob(job);
if (rows > 0)
{
updateSchedulerJob(job, properties.getJobGroup());
}
return rows;
}
?
/**
* 更新任務(wù)
*
* @param job 任務(wù)對象
* @param jobGroup 任務(wù)組名
*/
public void updateSchedulerJob(SysJob job, String jobGroup) throws SchedulerException, TaskException
{
Long jobId = job.getJobId();
// 判斷是否存在
JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup);
if (scheduler.checkExists(jobKey))
{
// 防止創(chuàng)建時存在數(shù)據(jù)問題 先移除,然后在執(zhí)行創(chuàng)建操作
scheduler.deleteJob(jobKey);
}
ScheduleUtils.createScheduleJob(scheduler, job);
}
?
/**
* 校驗cron表達式是否有效
*
* @param cronExpression 表達式
* @return 結(jié)果
*/
@Override
public boolean checkCronExpressionIsValid(String cronExpression)
{
return CronUtils.isValid(cronExpression);
}
}
復制代碼4、Mapper
public interface SysJobMapper
{
/**
* 查詢調(diào)度任務(wù)日志集合
*
* @param job 調(diào)度信息
* @return 操作日志集合
*/
public List<SysJob> selectJobList(SysJob job);
?
/**
* 查詢所有調(diào)度任務(wù)
*
* @return 調(diào)度任務(wù)列表
*/
public List<SysJob> selectJobAll();
?
/**
* 通過調(diào)度ID查詢調(diào)度任務(wù)信息
*
* @param jobId 調(diào)度ID
* @return 角色對象信息
*/
public SysJob selectJobById(Long jobId);
?
/**
* 通過調(diào)度ID刪除調(diào)度任務(wù)信息
*
* @param jobId 調(diào)度ID
* @return 結(jié)果
*/
public int deleteJobById(Long jobId);
?
/**
* 批量刪除調(diào)度任務(wù)信息
*
* @param ids 需要刪除的數(shù)據(jù)ID
* @return 結(jié)果
*/
public int deleteJobByIds(Long[] ids);
?
/**
* 修改調(diào)度任務(wù)信息
*
* @param job 調(diào)度任務(wù)信息
* @return 結(jié)果
*/
public int updateJob(SysJob job);
?
/**
* 新增調(diào)度任務(wù)信息
*
* @param job 調(diào)度任務(wù)信息
* @return 結(jié)果
*/
public int insertJob(SysJob job);
}
復制代碼<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.job.quartz.mapper.SysJobMapper">
?
<resultMap type="SysJob" id="SysJobResult">
<id property="jobId" column="job_id" />
<result property="jobName" column="job_name" />
<result property="jobGroup" column="job_group" />
<result property="invokeTarget" column="invoke_target" />
<result property="cronExpression" column="cron_expression" />
<result property="misfirePolicy" column="misfire_policy" />
<result property="concurrent" column="concurrent" />
<result property="status" column="status" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>
<sql id="selectJobVo">
select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark
from sys_job
</sql>
<select id="selectJobList" parameterType="SysJob" resultMap="SysJobResult">
<include refid="selectJobVo"/>
<where>
<if test="jobName != null and jobName != ''">
AND job_name like concat('%', #{jobName}, '%')
</if>
<if test="jobGroup != null and jobGroup != ''">
AND job_group = #{jobGroup}
</if>
<if test="status != null and status != ''">
AND status = #{status}
</if>
<if test="invokeTarget != null and invokeTarget != ''">
AND invoke_target like concat('%', #{invokeTarget}, '%')
</if>
</where>
</select>
<select id="selectJobAll" resultMap="SysJobResult">
<include refid="selectJobVo"/>
</select>
<select id="selectJobById" parameterType="Long" resultMap="SysJobResult">
<include refid="selectJobVo"/>
where job_id = #{jobId}
</select>
<delete id="deleteJobById" parameterType="Long">
delete from sys_job where job_id = #{jobId}
</delete>
<delete id="deleteJobByIds" parameterType="Long">
delete from sys_job where job_id in
<foreach collection="array" item="jobId" open="(" separator="," close=")">
#{jobId}
</foreach>
</delete>
<update id="updateJob" parameterType="SysJob">
update sys_job
<set>
<if test="jobName != null and jobName != ''">job_name = #{jobName},</if>
<if test="jobGroup != null and jobGroup != ''">job_group = #{jobGroup},</if>
<if test="invokeTarget != null and invokeTarget != ''">invoke_target = #{invokeTarget},</if>
<if test="cronExpression != null and cronExpression != ''">cron_expression = #{cronExpression},</if>
<if test="misfirePolicy != null and misfirePolicy != ''">misfire_policy = #{misfirePolicy},</if>
<if test="concurrent != null and concurrent != ''">concurrent = #{concurrent},</if>
<if test="status !=null">status = #{status},</if>
<if test="remark != null and remark != ''">remark = #{remark},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time = sysdate()
</set>
where job_id = #{jobId}
</update>
<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>
?
</mapper>
復制代碼5、前端(Vue):
import request from '@/utils/request'
?
// 查詢定時任務(wù)調(diào)度列表
export function listJob(query) {
return request({
url: '/monitor/job/list',
method: 'get',
params: query
})
}
?
// 查詢定時任務(wù)調(diào)度詳細
export function getJob(jobId) {
return request({
url: '/monitor/job/' + jobId,
method: 'get'
})
}
?
// 新增定時任務(wù)調(diào)度
export function addJob(data) {
return request({
url: '/monitor/job',
method: 'post',
data: data
})
}
?
// 修改定時任務(wù)調(diào)度
export function updateJob(data) {
return request({
url: '/monitor/job',
method: 'put',
data: data
})
}
?
// 刪除定時任務(wù)調(diào)度
export function delJob(jobId) {
return request({
url: '/monitor/job/' + jobId,
method: 'delete'
})
}
?
// 任務(wù)狀態(tài)修改
export function changeJobStatus(jobId, status) {
const data = {
jobId,
status
}
return request({
url: '/monitor/job/changeStatus',
method: 'put',
data: data
})
}
?
?
// 定時任務(wù)立即執(zhí)行一次
export function runJob(jobId, jobGroup) {
const data = {
jobId,
jobGroup
}
return request({
url: '/monitor/job/run',
method: 'put',
data: data
})
}
復制代碼import request from '@/utils/request'
?
// 查詢調(diào)度日志列表
export function listJobLog(query) {
return request({
url: '/monitor/jobLog/list',
method: 'get',
params: query
})
}
?
// 刪除調(diào)度日志
export function delJobLog(jobLogId) {
return request({
url: '/monitor/jobLog/' + jobLogId,
method: 'delete'
})
}
?
// 清空調(diào)度日志
export function cleanJobLog() {
return request({
url: '/monitor/jobLog/clean',
method: 'delete'
})
}
?
復制代碼<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="任務(wù)名稱" prop="jobName">
<el-input
v-model="queryParams.jobName"
placeholder="請輸入任務(wù)名稱"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="任務(wù)組名" prop="jobGroup">
<el-select v-model="queryParams.jobGroup" placeholder="請選擇任務(wù)組名" clearable>
<el-option
v-for="dict in dict.type.sys_job_group"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="任務(wù)狀態(tài)" prop="status">
<el-select v-model="queryParams.status" placeholder="請選擇任務(wù)狀態(tài)" clearable>
<el-option
v-for="dict in dict.type.sys_job_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
?
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['monitor:job:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['monitor:job:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['monitor:job:remove']"
>刪除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['monitor:job:export']"
>導出</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="el-icon-s-operation"
size="mini"
@click="handleJobLog"
v-hasPermi="['monitor:job:query']"
>日志</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
?
<el-table v-loading="loading" :data="jobList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="任務(wù)編號" width="100" align="center" prop="jobId" />
<el-table-column label="任務(wù)名稱" align="center" prop="jobName" :show-overflow-tooltip="true" />
<el-table-column label="任務(wù)組名" align="center" prop="jobGroup">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_job_group" :value="scope.row.jobGroup"/>
</template>
</el-table-column>
<el-table-column label="調(diào)用目標字符串" align="center" prop="invokeTarget" :show-overflow-tooltip="true" />
<el-table-column label="cron執(zhí)行表達式" align="center" prop="cronExpression" :show-overflow-tooltip="true" />
<el-table-column label="狀態(tài)" align="center">
<template slot-scope="scope">
<el-switch
v-model="scope.row.status"
active-value="0"
inactive-value="1"
@change="handleStatusChange(scope.row)"
></el-switch>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['monitor:job:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['monitor:job:remove']"
>刪除</el-button>
<el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)" v-hasPermi="['monitor:job:changeStatus', 'monitor:job:query']">
<el-button size="mini" type="text" icon="el-icon-d-arrow-right">更多</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="handleRun" icon="el-icon-caret-right"
v-hasPermi="['monitor:job:changeStatus']">執(zhí)行一次</el-dropdown-item>
<el-dropdown-item command="handleView" icon="el-icon-view"
v-hasPermi="['monitor:job:query']">任務(wù)詳細</el-dropdown-item>
<el-dropdown-item command="handleJobLog" icon="el-icon-s-operation"
v-hasPermi="['monitor:job:query']">調(diào)度日志</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
?
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
?
<!-- 添加或修改定時任務(wù)對話框 -->
<el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<el-row>
<el-col :span="12">
<el-form-item label="任務(wù)名稱" prop="jobName">
<el-input v-model="form.jobName" placeholder="請輸入任務(wù)名稱" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任務(wù)分組" prop="jobGroup">
<el-select v-model="form.jobGroup" placeholder="請選擇任務(wù)分組">
<el-option
v-for="dict in dict.type.sys_job_group"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item prop="invokeTarget">
<span slot="label">
調(diào)用方法
<el-tooltip placement="top">
<div slot="content">
Bean調(diào)用示例:ryTask.ryParams('ry')
<br />Class類調(diào)用示例:com.ruoyi.quartz.task.RyTask.ryParams('ry')
<br />參數(shù)說明:支持字符串,布爾類型,長整型,浮點型,整型
</div>
<i class="el-icon-question"></i>
</el-tooltip>
</span>
<el-input v-model="form.invokeTarget" placeholder="請輸入調(diào)用目標字符串" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="cron表達式" prop="cronExpression">
<el-input v-model="form.cronExpression" placeholder="請輸入cron執(zhí)行表達式">
<template slot="append">
<el-button type="primary" @click="handleShowCron">
生成表達式
<i class="el-icon-time el-icon--right"></i>
</el-button>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="執(zhí)行策略" prop="misfirePolicy">
<el-radio-group v-model="form.misfirePolicy" size="small">
<el-radio-button label="1">立即執(zhí)行</el-radio-button>
<el-radio-button label="2">執(zhí)行一次</el-radio-button>
<el-radio-button label="3">放棄執(zhí)行</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否并發(fā)" prop="concurrent">
<el-radio-group v-model="form.concurrent" size="small">
<el-radio-button label="0">允許</el-radio-button>
<el-radio-button label="1">禁止</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="狀態(tài)">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in dict.type.sys_job_status"
:key="dict.value"
:label="dict.value"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">確 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
?
<el-dialog title="Cron表達式生成器" :visible.sync="openCron" append-to-body destroy-on-close class="scrollbar">
<crontab @hide="openCron=false" @fill="crontabFill" :expression="expression"></crontab>
</el-dialog>
?
<!-- 任務(wù)日志詳細 -->
<el-dialog title="任務(wù)詳細" :visible.sync="openView" width="700px" append-to-body>
<el-form ref="form" :model="form" label-width="120px" size="mini">
<el-row>
<el-col :span="12">
<el-form-item label="任務(wù)編號:">{{ form.jobId }}</el-form-item>
<el-form-item label="任務(wù)名稱:">{{ form.jobName }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任務(wù)分組:">{{ jobGroupFormat(form) }}</el-form-item>
<el-form-item label="創(chuàng)建時間:">{{ form.createTime }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="cron表達式:">{{ form.cronExpression }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="下次執(zhí)行時間:">{{ parseTime(form.nextValidTime) }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="調(diào)用目標方法:">{{ form.invokeTarget }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任務(wù)狀態(tài):">
<div v-if="form.status == 0">正常</div>
<div v-else-if="form.status == 1">失敗</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否并發(fā):">
<div v-if="form.concurrent == 0">允許</div>
<div v-else-if="form.concurrent == 1">禁止</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="執(zhí)行策略:">
<div v-if="form.misfirePolicy == 0">默認策略</div>
<div v-else-if="form.misfirePolicy == 1">立即執(zhí)行</div>
<div v-else-if="form.misfirePolicy == 2">執(zhí)行一次</div>
<div v-else-if="form.misfirePolicy == 3">放棄執(zhí)行</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="openView = false">關(guān) 閉</el-button>
</div>
</el-dialog>
</div>
</template>
?
<script>
import { listJob, getJob, delJob, addJob, updateJob, runJob, changeJobStatus } from "@/api/monitor/job";
import Crontab from '@/components/Crontab'
?
export default {
components: { Crontab },
name: "Job",
dicts: ['sys_job_group', 'sys_job_status'],
data() {
return {
// 遮罩層
loading: true,
// 選中數(shù)組
ids: [],
// 非單個禁用
single: true,
// 非多個禁用
multiple: true,
// 顯示搜索條件
showSearch: true,
// 總條數(shù)
total: 0,
// 定時任務(wù)表格數(shù)據(jù)
jobList: [],
// 彈出層標題
title: "",
// 是否顯示彈出層
open: false,
// 是否顯示詳細彈出層
openView: false,
// 是否顯示Cron表達式彈出層
openCron: false,
// 傳入的表達式
expression: "",
// 查詢參數(shù)
queryParams: {
pageNum: 1,
pageSize: 10,
jobName: undefined,
jobGroup: undefined,
status: undefined
},
// 表單參數(shù)
form: {},
// 表單校驗
rules: {
jobName: [
{ required: true, message: "任務(wù)名稱不能為空", trigger: "blur" }
],
invokeTarget: [
{ required: true, message: "調(diào)用目標字符串不能為空", trigger: "blur" }
],
cronExpression: [
{ required: true, message: "cron執(zhí)行表達式不能為空", trigger: "blur" }
]
}
};
},
created() {
this.getList();
},
methods: {
/** 查詢定時任務(wù)列表 */
getList() {
this.loading = true;
listJob(this.queryParams).then(response => {
this.jobList = response.rows;
this.total = response.total;
this.loading = false;
});
},
// 任務(wù)組名字典翻譯
jobGroupFormat(row, column) {
return this.selectDictLabel(this.dict.type.sys_job_group, row.jobGroup);
},
// 取消按鈕
cancel() {
this.open = false;
this.reset();
},
// 表單重置
reset() {
this.form = {
jobId: undefined,
jobName: undefined,
jobGroup: undefined,
invokeTarget: undefined,
cronExpression: undefined,
misfirePolicy: 1,
concurrent: 1,
status: "0"
};
this.resetForm("form");
},
/** 搜索按鈕操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按鈕操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
// 多選框選中數(shù)據(jù)
handleSelectionChange(selection) {
this.ids = selection.map(item => item.jobId);
this.single = selection.length != 1;
this.multiple = !selection.length;
},
// 更多操作觸發(fā)
handleCommand(command, row) {
switch (command) {
case "handleRun":
this.handleRun(row);
break;
case "handleView":
this.handleView(row);
break;
case "handleJobLog":
this.handleJobLog(row);
break;
default:
break;
}
},
// 任務(wù)狀態(tài)修改
handleStatusChange(row) {
let text = row.status === "0" ? "啟用" : "停用";
this.$modal.confirm('確認要"' + text + '""' + row.jobName + '"任務(wù)嗎?').then(function() {
return changeJobStatus(row.jobId, row.status);
}).then(() => {
this.$modal.msgSuccess(text + "成功");
}).catch(function() {
row.status = row.status === "0" ? "1" : "0";
});
},
/* 立即執(zhí)行一次 */
handleRun(row) {
this.$modal.confirm('確認要立即執(zhí)行一次"' + row.jobName + '"任務(wù)嗎?').then(function() {
return runJob(row.jobId, row.jobGroup);
}).then(() => {
this.$modal.msgSuccess("執(zhí)行成功");
}).catch(() => {});
},
/** 任務(wù)詳細信息 */
handleView(row) {
getJob(row.jobId).then(response => {
this.form = response.data;
this.openView = true;
});
},
/** cron表達式按鈕操作 */
handleShowCron() {
this.expression = this.form.cronExpression;
this.openCron = true;
},
/** 確定后回傳值 */
crontabFill(value) {
this.form.cronExpression = value;
},
/** 任務(wù)日志列表查詢 */
handleJobLog(row) {
const jobId = row.jobId || 0;
this.$router.push({ path: '/monitor/job-log/index', query: { jobId: jobId } })
},
/** 新增按鈕操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加任務(wù)";
},
/** 修改按鈕操作 */
handleUpdate(row) {
this.reset();
const jobId = row.jobId || this.ids;
getJob(jobId).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改任務(wù)";
});
},
/** 提交按鈕 */
submitForm: function() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.jobId != undefined) {
updateJob(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addJob(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 刪除按鈕操作 */
handleDelete(row) {
const jobIds = row.jobId || this.ids;
this.$modal.confirm('是否確認刪除定時任務(wù)編號為"' + jobIds + '"的數(shù)據(jù)項?').then(function() {
return delJob(jobIds);
}).then(() => {
this.getList();
this.$modal.msgSuccess("刪除成功");
}).catch(() => {});
},
/** 導出按鈕操作 */
handleExport() {
this.download('monitor/job/export', {
...this.queryParams
}, `job_${new Date().getTime()}.xlsx`)
}
}
};
</script>
復制代碼<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="任務(wù)名稱" prop="jobName">
<el-input
v-model="queryParams.jobName"
placeholder="請輸入任務(wù)名稱"
clearable
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="任務(wù)組名" prop="jobGroup">
<el-select
v-model="queryParams.jobGroup"
placeholder="請選擇任務(wù)組名"
clearable
style="width: 240px"
>
<el-option
v-for="dict in dict.type.sys_job_group"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="執(zhí)行狀態(tài)" prop="status">
<el-select
v-model="queryParams.status"
placeholder="請選擇執(zhí)行狀態(tài)"
clearable
style="width: 240px"
>
<el-option
v-for="dict in dict.type.sys_common_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="執(zhí)行時間">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="開始日期"
end-placeholder="結(jié)束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
?
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['monitor:job:remove']"
>刪除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
@click="handleClean"
v-hasPermi="['monitor:job:remove']"
>清空</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['monitor:job:export']"
>導出</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-close"
size="mini"
@click="handleClose"
>關(guān)閉</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
?
<el-table v-loading="loading" :data="jobLogList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="日志編號" width="80" align="center" prop="jobLogId" />
<el-table-column label="任務(wù)名稱" align="center" prop="jobName" :show-overflow-tooltip="true" />
<el-table-column label="任務(wù)組名" align="center" prop="jobGroup" :show-overflow-tooltip="true">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_job_group" :value="scope.row.jobGroup"/>
</template>
</el-table-column>
<el-table-column label="調(diào)用目標字符串" align="center" prop="invokeTarget" :show-overflow-tooltip="true" />
<el-table-column label="日志信息" align="center" prop="jobMessage" :show-overflow-tooltip="true" />
<el-table-column label="執(zhí)行狀態(tài)" align="center" prop="status">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_common_status" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="執(zhí)行時間" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleView(scope.row)"
v-hasPermi="['monitor:job:query']"
>詳細</el-button>
</template>
</el-table-column>
</el-table>
?
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
?
<!-- 調(diào)度日志詳細 -->
<el-dialog title="調(diào)度日志詳細" :visible.sync="open" width="700px" append-to-body>
<el-form ref="form" :model="form" label-width="100px" size="mini">
<el-row>
<el-col :span="12">
<el-form-item label="日志序號:">{{ form.jobLogId }}</el-form-item>
<el-form-item label="任務(wù)名稱:">{{ form.jobName }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任務(wù)分組:">{{ form.jobGroup }}</el-form-item>
<el-form-item label="執(zhí)行時間:">{{ form.createTime }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="調(diào)用方法:">{{ form.invokeTarget }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="日志信息:">{{ form.jobMessage }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="執(zhí)行狀態(tài):">
<div v-if="form.status == 0">正常</div>
<div v-else-if="form.status == 1">失敗</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="異常信息:" v-if="form.status == 1">{{ form.exceptionInfo }}</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="open = false">關(guān) 閉</el-button>
</div>
</el-dialog>
</div>
</template>
?
<script>
import { getJob} from "@/api/monitor/job";
import { listJobLog, delJobLog, cleanJobLog } from "@/api/monitor/jobLog";
?
export default {
name: "JobLog",
dicts: ['sys_common_status', 'sys_job_group'],
data() {
return {
// 遮罩層
loading: true,
// 選中數(shù)組
ids: [],
// 非多個禁用
multiple: true,
// 顯示搜索條件
showSearch: true,
// 總條數(shù)
total: 0,
// 調(diào)度日志表格數(shù)據(jù)
jobLogList: [],
// 是否顯示彈出層
open: false,
// 日期范圍
dateRange: [],
// 表單參數(shù)
form: {},
// 查詢參數(shù)
queryParams: {
pageNum: 1,
pageSize: 10,
jobName: undefined,
jobGroup: undefined,
status: undefined
}
};
},
created() {
const jobId = this.$route.query.jobId;
if (jobId !== undefined && jobId != 0) {
getJob(jobId).then(response => {
this.queryParams.jobName = response.data.jobName;
this.queryParams.jobGroup = response.data.jobGroup;
this.getList();
});
} else {
this.getList();
}
},
methods: {
/** 查詢調(diào)度日志列表 */
getList() {
this.loading = true;
listJobLog(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
this.jobLogList = response.rows;
this.total = response.total;
this.loading = false;
}
);
},
// 返回按鈕
handleClose() {
const obj = { path: "/monitor/job" };
this.$tab.closeOpenPage(obj);
},
/** 搜索按鈕操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按鈕操作 */
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
// 多選框選中數(shù)據(jù)
handleSelectionChange(selection) {
this.ids = selection.map(item => item.jobLogId);
this.multiple = !selection.length;
},
/** 詳細按鈕操作 */
handleView(row) {
this.open = true;
this.form = row;
},
/** 刪除按鈕操作 */
handleDelete(row) {
const jobLogIds = this.ids;
this.$modal.confirm('是否確認刪除調(diào)度日志編號為"' + jobLogIds + '"的數(shù)據(jù)項?').then(function() {
return delJobLog(jobLogIds);
}).then(() => {
this.getList();
this.$modal.msgSuccess("刪除成功");
}).catch(() => {});
},
/** 清空按鈕操作 */
handleClean() {
this.$modal.confirm('是否確認清空所有調(diào)度日志數(shù)據(jù)項?').then(function() {
return cleanJobLog();
}).then(() => {
this.getList();
this.$modal.msgSuccess("清空成功");
}).catch(() => {});
},
/** 導出按鈕操作 */
handleExport() {
this.download('/monitor/jobLog/export', {
...this.queryParams
}, `log_${new Date().getTime()}.xlsx`)
}
}
};
</script>以上就是Springboot Vue可配置調(diào)度任務(wù)實現(xiàn)示例詳解的詳細內(nèi)容,更多關(guān)于Springboot Vue配置調(diào)度任務(wù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java并發(fā)工具之Exchanger線程間交換數(shù)據(jù)詳解
這篇文章主要介紹了Java并發(fā)工具之Exchanger線程間交換數(shù)據(jù)詳解,Exchanger是一個用于線程間協(xié)作的工具類,Exchanger用于進行線程間的數(shù)據(jù)交 換,它提供一個同步點,在這個同步點,兩個線程可以交換彼此的數(shù)據(jù),需要的朋友可以參考下2023-12-12
java圖片滑動驗證(登錄驗證)原理與實現(xiàn)方法詳解
這篇文章主要介紹了java圖片滑動驗證(登錄驗證)原理與實現(xiàn)方法,結(jié)合實例形式詳細分析了java圖片滑動登錄驗證的相關(guān)原理、實現(xiàn)方法與操作技巧,需要的朋友可以參考下2019-09-09
springboot整合websocket后啟動報錯(javax.websocket.server.ServerCont
這篇文章主要介紹了springboot整合websocket后啟動報錯(javax.websocket.server.ServerContainer not available),通過分析錯誤信息、排查代碼和配置,找出問題的根源,并給出相應(yīng)的解決方案,感興趣的可以了解一下2024-01-01
MyBatis在insert插入操作時返回主鍵ID的配置(推薦)
這篇文章主要介紹了MyBatis在insert插入操作時返回主鍵ID的配置的相關(guān)資料,需要的朋友可以參考下2017-10-10
關(guān)于Java Guava ImmutableMap不可變集合源碼分析
這篇文章主要介紹Java Guava不可變集合ImmutableMap的源碼分析的相關(guān)資料,需要的朋友可以參考下面具體的文章內(nèi)容2021-09-09

