修改代码问题
This commit is contained in:
@ -35,11 +35,11 @@
|
||||
sp.parent_id,
|
||||
count( sps.menu_id ) istop
|
||||
from
|
||||
eshop_menu sp
|
||||
join eshop_role_menu srp on srp.menu_id = sp.menu_id
|
||||
sys_menu sp
|
||||
join sys_role_menu srp on srp.menu_id = sp.menu_id
|
||||
and sp.delete_flag = 'normal'
|
||||
and sp.menu_status = 'valid'
|
||||
left join eshop_menu sps on sp.parent_id = sps.menu_id
|
||||
left join sys_menu sps on sp.parent_id = sps.menu_id
|
||||
and sps.delete_flag = 'normal'
|
||||
and sps.menu_status = 'valid'
|
||||
where
|
||||
|
@ -22,6 +22,8 @@ public class EshopMenuConverter {
|
||||
private String menuOu;
|
||||
@ApiModelProperty("是否顶级标识:0 是,1 否")
|
||||
private Integer isTop;
|
||||
@ApiModelProperty("父级ID")
|
||||
private String parentId;
|
||||
@ApiModelProperty("菜单图标")
|
||||
private List<EshopMenuConverterChildren> children;
|
||||
@ApiModelProperty("feign调用成功或失败的标识 true:为feign调用成功 down:feign调用失败")
|
||||
|
@ -3,9 +3,13 @@ package com.coscoshipping.ebtp.system.menu.controller;
|
||||
import com.chinaunicom.mall.ebtp.cloud.security.starter.common.Constants;
|
||||
import com.chinaunicom.mall.ebtp.common.base.controller.BaseController;
|
||||
import com.chinaunicom.mall.ebtp.common.base.entity.BaseResponse;
|
||||
import com.chinaunicom.mall.ebtp.common.exception.entity.BusinessException;
|
||||
import com.coscoshipping.ebtp.system.login.entity.EshopMenuConverter;
|
||||
import com.coscoshipping.ebtp.system.login.entity.EshopMenuQuery;
|
||||
import com.coscoshipping.ebtp.system.menu.dto.BaseRoleTabulation;
|
||||
import com.coscoshipping.ebtp.system.menu.entity.SysMenu;
|
||||
import com.coscoshipping.ebtp.system.menu.service.ISysMenuService;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@ -127,4 +131,19 @@ public class SysMenuController extends BaseController {
|
||||
public BaseResponse<List<BaseRoleTabulation>> getBaseRoleTabulation(@RequestParam("userId") String userId) {
|
||||
return BaseResponse.success(menuService.getBaseRoleTabulation(userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据角色查看菜单树
|
||||
* @param body
|
||||
* @return BasePageResponse
|
||||
* @author chentao
|
||||
* @date 2021-1-13
|
||||
* @update [序号][日期YYYY-MM-DD] [更改人姓名][变更描述]
|
||||
*/
|
||||
@ApiOperation(value = "根据角色查看菜单树(吉林用)", notes = "根据角色查看菜单树(吉林用)")
|
||||
@PostMapping("/findMenuList")
|
||||
public BaseResponse<List<EshopMenuConverter>> findMenuList(@RequestBody EshopMenuQuery body) throws BusinessException {
|
||||
return BaseResponse.success(menuService.findMenuList(body));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package com.coscoshipping.ebtp.system.menu.service;
|
||||
|
||||
|
||||
import com.chinaunicom.mall.ebtp.common.exception.entity.BusinessException;
|
||||
import com.coscoshipping.ebtp.system.login.entity.EshopMenuConverter;
|
||||
import com.coscoshipping.ebtp.system.login.entity.EshopMenuQuery;
|
||||
import com.coscoshipping.ebtp.system.menu.dto.BaseRoleTabulation;
|
||||
import com.coscoshipping.ebtp.system.menu.entity.SysMenu;
|
||||
import com.coscoshipping.ebtp.system.menu.entity.TreeSelect;
|
||||
@ -148,4 +151,14 @@ public interface ISysMenuService
|
||||
public boolean checkMenuNameUnique(SysMenu menu);
|
||||
|
||||
List<BaseRoleTabulation> getBaseRoleTabulation(String userId);
|
||||
|
||||
/**
|
||||
* 根据角色查看菜单树
|
||||
* @param body
|
||||
* @return BasePageResponse
|
||||
* @author chentao
|
||||
* @date 2021-1-13
|
||||
* @update [序号][日期YYYY-MM-DD] [更改人姓名][变更描述]
|
||||
*/
|
||||
List<EshopMenuConverter> findMenuList(EshopMenuQuery body) throws BusinessException;
|
||||
}
|
||||
|
@ -4,6 +4,10 @@ import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.lang.Snowflake;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.chinaunicom.mall.ebtp.common.exception.entity.BusinessException;
|
||||
import com.coscoshipping.ebtp.system.login.entity.EshopMenuConverter;
|
||||
import com.coscoshipping.ebtp.system.login.entity.EshopMenuQuery;
|
||||
import com.coscoshipping.ebtp.system.login.dao.EshopMenuMapper;
|
||||
import com.coscoshipping.ebtp.system.menu.dao.SysMenuMapper;
|
||||
import com.coscoshipping.ebtp.system.menu.dto.BaseFuncTabulation;
|
||||
import com.coscoshipping.ebtp.system.menu.dto.BaseRoleTabulation;
|
||||
@ -14,6 +18,8 @@ import com.coscoshipping.ebtp.system.menu.vo.RoleTabulationVo;
|
||||
import com.coscoshipping.ebtp.system.role.dao.SysRoleMapper;
|
||||
import com.coscoshipping.ebtp.system.role.dao.SysRoleMenuMapper;
|
||||
import com.coscoshipping.ebtp.system.role.service.SysRoleService;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -21,6 +27,7 @@ import org.springframework.stereotype.Service;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import com.coscoshipping.ebtp.system.login.entity.EshopMenuConverterChildren;
|
||||
|
||||
/**
|
||||
* 菜单 业务层处理
|
||||
@ -42,6 +49,9 @@ public class SysMenuServiceImpl implements ISysMenuService {
|
||||
@Autowired
|
||||
private SysRoleMenuMapper roleMenuMapper;
|
||||
|
||||
@Autowired
|
||||
private EshopMenuMapper eshopMenuMapper;
|
||||
|
||||
/**
|
||||
* 根据用户查询系统菜单列表
|
||||
*
|
||||
@ -392,4 +402,52 @@ public class SysMenuServiceImpl implements ISysMenuService {
|
||||
return bfList;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据角色查看菜单树
|
||||
* @param body
|
||||
* @return BasePageResponse
|
||||
* @author chentao
|
||||
* @date 2021-1-13
|
||||
* @update [序号][日期YYYY-MM-DD] [更改人姓名][变更描述]
|
||||
*/
|
||||
@Override
|
||||
public List<EshopMenuConverter> findMenuList(EshopMenuQuery body) throws BusinessException {
|
||||
List<String> roleList = body.getRoleIdList();
|
||||
List<EshopMenuConverter> allMenus = new ArrayList<>();
|
||||
Set<String> menuIdSet = new HashSet<>();
|
||||
for (String roleId : roleList) {
|
||||
List<EshopMenuConverter> menuList = eshopMenuMapper.findTopMenuList(roleId);
|
||||
for (EshopMenuConverter menu : menuList) {
|
||||
if (menu != null && menu.getMenuId() != null && !menuIdSet.contains(menu.getMenuId())) {
|
||||
allMenus.add(menu);
|
||||
menuIdSet.add(menu.getMenuId());
|
||||
}
|
||||
}
|
||||
}
|
||||
// 组装树结构
|
||||
Map<String, EshopMenuConverter> menuMap = new HashMap<>();
|
||||
List<EshopMenuConverter> rootMenus = new ArrayList<>();
|
||||
for (EshopMenuConverter menu : allMenus) {
|
||||
menu.setChildren(new ArrayList<>()); // 初始化children
|
||||
menuMap.put(menu.getMenuId(), menu);
|
||||
}
|
||||
for (EshopMenuConverter menu : allMenus) {
|
||||
String parentId = menu.getParentId();
|
||||
if ("0".equals(parentId)) {
|
||||
rootMenus.add(menu);
|
||||
} else if (menuMap.containsKey(parentId)) {
|
||||
// 转换为 EshopMenuConverterChildren
|
||||
EshopMenuConverterChildren child = new EshopMenuConverterChildren();
|
||||
child.setPath(menu.getPath());
|
||||
child.setName(menu.getName());
|
||||
// 这里 frame 字段数据库未查出,若有需要可补充
|
||||
menuMap.get(parentId).getChildren().add(child);
|
||||
} else {
|
||||
rootMenus.add(menu); // 没有父节点的也作为根节点
|
||||
}
|
||||
}
|
||||
// 根节点的 children 字段已初始化为空
|
||||
return rootMenus;
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ public class SysOrgServiceImpl extends BaseServiceImpl<SysOrgMapper, SysOrg> imp
|
||||
// 构建父节点到子节点的映射(key: upOrgId,value: 子节点列表)
|
||||
Map<String, List<SysOrgVO>> parentToChildrenMap = new HashMap<>();
|
||||
for (SysOrgVO orgVO : allOrgVOs) {
|
||||
String parentId = StringUtils.isBlank(orgVO.getUpOrgId()) ? CommonUtil.ROOT_NODE : orgVO.getUpOrgId();
|
||||
String parentId = StringUtils.isBlank(orgVO.getUpOrgId()) ? "0" : orgVO.getUpOrgId();
|
||||
parentToChildrenMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(orgVO);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,57 @@
|
||||
package com.coscoshipping.ebtp.system.scheduled.config;
|
||||
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* 定时任务配置类
|
||||
*/
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class ScheduledTaskConfig {
|
||||
|
||||
/**
|
||||
* 异步任务执行器
|
||||
*/
|
||||
@Bean(name = "taskExecutor")
|
||||
@Order(1)
|
||||
public Executor taskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(5);
|
||||
executor.setMaxPoolSize(20);
|
||||
executor.setQueueCapacity(100);
|
||||
executor.setKeepAliveSeconds(60);
|
||||
executor.setThreadNamePrefix("ScheduledTask-");
|
||||
executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* RestTemplate Bean
|
||||
*/
|
||||
@Bean(name = "scheduledRestTemplate")
|
||||
@LoadBalanced
|
||||
public RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
|
||||
/**
|
||||
* RedisTemplate Bean
|
||||
*/
|
||||
@Bean(name = "scheduledRedisTemplate")
|
||||
public RedisTemplate<String, String> scheduledRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
||||
return redisTemplate;
|
||||
}
|
||||
}
|
@ -0,0 +1,337 @@
|
||||
package com.coscoshipping.ebtp.system.scheduled.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.chinaunicom.mall.ebtp.common.base.entity.BaseResponse;
|
||||
import com.coscoshipping.ebtp.system.scheduled.entity.ScheduledTask;
|
||||
import com.coscoshipping.ebtp.system.scheduled.entity.ScheduledTaskLog;
|
||||
import com.coscoshipping.ebtp.system.scheduled.scheduler.DynamicTaskScheduler;
|
||||
import com.coscoshipping.ebtp.system.scheduled.service.IScheduledTaskLogService;
|
||||
import com.coscoshipping.ebtp.system.scheduled.service.IScheduledTaskService;
|
||||
import com.coscoshipping.ebtp.system.scheduled.util.CronUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 定时任务管理控制器
|
||||
*/
|
||||
@Slf4j
|
||||
@Api(tags = "定时任务管理")
|
||||
@RestController
|
||||
@RequestMapping("/sys/scheduled/task")
|
||||
public class ScheduledTaskController {
|
||||
|
||||
@Autowired
|
||||
private IScheduledTaskService scheduledTaskService;
|
||||
|
||||
@Autowired
|
||||
private IScheduledTaskLogService taskLogService;
|
||||
|
||||
@Autowired
|
||||
private DynamicTaskScheduler dynamicTaskScheduler;
|
||||
|
||||
/**
|
||||
* 分页查询定时任务
|
||||
*/
|
||||
@ApiOperation("分页查询定时任务")
|
||||
@GetMapping("/list")
|
||||
public BaseResponse<IPage<ScheduledTask>> list(
|
||||
@ApiParam("页码") @RequestParam(defaultValue = "1") Integer pageNo,
|
||||
@ApiParam("页大小") @RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@ApiParam("任务名称") @RequestParam(required = false) String taskName,
|
||||
@ApiParam("任务状态") @RequestParam(required = false) Integer status) {
|
||||
|
||||
try {
|
||||
Page<ScheduledTask> page = new Page<>(pageNo, pageSize);
|
||||
QueryWrapper<ScheduledTask> queryWrapper = new QueryWrapper<>();
|
||||
|
||||
if (taskName != null && !taskName.trim().isEmpty()) {
|
||||
queryWrapper.like("task_name", taskName);
|
||||
}
|
||||
|
||||
if (status != null) {
|
||||
queryWrapper.eq("status", status);
|
||||
}
|
||||
|
||||
queryWrapper.orderByDesc("create_time");
|
||||
|
||||
IPage<ScheduledTask> pageResult = scheduledTaskService.page(page, queryWrapper);
|
||||
|
||||
return BaseResponse.success(pageResult);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("分页查询定时任务失败", e);
|
||||
return new BaseResponse<>(500, false, "查询失败:" + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增定时任务
|
||||
*/
|
||||
@ApiOperation("新增定时任务")
|
||||
@PostMapping("/add")
|
||||
public BaseResponse<Boolean> add(@RequestBody ScheduledTask task) {
|
||||
try {
|
||||
// 验证Cron表达式
|
||||
if (!CronUtil.isValidCron(task.getCronExpression())) {
|
||||
return new BaseResponse<>(400, false, "Cron表达式格式不正确", false);
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
task.setStatus(0); // 默认禁用
|
||||
task.setCreateTime(new Date());
|
||||
task.setUpdateTime(new Date());
|
||||
|
||||
// 计算下次执行时间
|
||||
Date nextRunTime = CronUtil.getNextExecutionTime(task.getCronExpression(), new Date());
|
||||
task.setNextRunTime(nextRunTime);
|
||||
|
||||
boolean success = scheduledTaskService.save(task);
|
||||
|
||||
if (success) {
|
||||
return BaseResponse.success(true);
|
||||
} else {
|
||||
return new BaseResponse<>(500, false, "添加失败", false);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("新增定时任务失败", e);
|
||||
return new BaseResponse<>(500, false, "添加失败:" + e.getMessage(), false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改定时任务
|
||||
*/
|
||||
@ApiOperation("修改定时任务")
|
||||
@PostMapping("/edit")
|
||||
public BaseResponse<Boolean> edit(@RequestBody ScheduledTask task) {
|
||||
try {
|
||||
// 验证Cron表达式
|
||||
if (!CronUtil.isValidCron(task.getCronExpression())) {
|
||||
return new BaseResponse<>(400, false, "Cron表达式格式不正确", false);
|
||||
}
|
||||
|
||||
// 设置更新时间
|
||||
task.setUpdateTime(new Date());
|
||||
|
||||
// 计算下次执行时间
|
||||
Date nextRunTime = CronUtil.getNextExecutionTime(task.getCronExpression(), new Date());
|
||||
task.setNextRunTime(nextRunTime);
|
||||
|
||||
boolean success = scheduledTaskService.updateById(task);
|
||||
|
||||
if (success) {
|
||||
return BaseResponse.success(true);
|
||||
} else {
|
||||
return new BaseResponse<>(500, false, "修改失败", false);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("修改定时任务失败", e);
|
||||
return new BaseResponse<>(500, false, "修改失败:" + e.getMessage(), false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除定时任务
|
||||
*/
|
||||
@ApiOperation("删除定时任务")
|
||||
@PostMapping("/delete")
|
||||
public BaseResponse<Boolean> delete(@RequestParam String id) {
|
||||
try {
|
||||
boolean success = scheduledTaskService.removeById(id);
|
||||
|
||||
if (success) {
|
||||
return BaseResponse.success(true);
|
||||
} else {
|
||||
return new BaseResponse<>(500, false, "删除失败", false);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("删除定时任务失败", e);
|
||||
return new BaseResponse<>(500, false, "删除失败:" + e.getMessage(), false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用任务
|
||||
*/
|
||||
@ApiOperation("启用任务")
|
||||
@PostMapping("/enable")
|
||||
public BaseResponse<Boolean> enable(@RequestBody ScheduledTask scheduledTask) {
|
||||
try {
|
||||
boolean success = scheduledTaskService.enableTask(scheduledTask.getId());
|
||||
|
||||
if (success) {
|
||||
return BaseResponse.success(true);
|
||||
} else {
|
||||
return new BaseResponse<>(500, false, "启用失败", false);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("启用定时任务失败", e);
|
||||
return new BaseResponse<>(500, false, "启用失败:" + e.getMessage(), false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用任务
|
||||
*/
|
||||
@ApiOperation("禁用任务")
|
||||
@PostMapping("/disable")
|
||||
public BaseResponse<Boolean> disable(@RequestBody ScheduledTask scheduledTask) {
|
||||
try {
|
||||
boolean success = scheduledTaskService.disableTask(scheduledTask.getId());
|
||||
|
||||
if (success) {
|
||||
return BaseResponse.success(true);
|
||||
} else {
|
||||
return new BaseResponse<>(500, false, "禁用失败", false);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("禁用定时任务失败", e);
|
||||
return new BaseResponse<>(500, false, "禁用失败:" + e.getMessage(), false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即执行任务
|
||||
*/
|
||||
@ApiOperation("立即执行任务")
|
||||
@PostMapping("/execute")
|
||||
public BaseResponse<Boolean> execute(@RequestParam String id) {
|
||||
try {
|
||||
boolean success = scheduledTaskService.executeTaskNow(id);
|
||||
|
||||
if (success) {
|
||||
return BaseResponse.success(true);
|
||||
} else {
|
||||
return new BaseResponse<>(500, false, "执行失败", false);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("立即执行定时任务失败", e);
|
||||
return new BaseResponse<>(500, false, "执行失败:" + e.getMessage(), false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证Cron表达式
|
||||
*/
|
||||
@ApiOperation("验证Cron表达式")
|
||||
@PostMapping("/validateCron")
|
||||
public BaseResponse<Map<String, Object>> validateCron(@RequestParam String cronExpression) {
|
||||
try {
|
||||
boolean valid = CronUtil.isValidCron(cronExpression);
|
||||
|
||||
if (valid) {
|
||||
Date nextTime = CronUtil.getNextExecutionTime(cronExpression, new Date());
|
||||
String description = CronUtil.getCronDescription(cronExpression);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("nextExecutionTime", nextTime);
|
||||
result.put("description", description);
|
||||
|
||||
return BaseResponse.success(result);
|
||||
} else {
|
||||
return new BaseResponse<>(400, false, "Cron表达式无效", null);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("验证Cron表达式失败", e);
|
||||
return new BaseResponse<>(500, false, "验证失败:" + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询任务执行日志
|
||||
*/
|
||||
@ApiOperation("查询任务执行日志")
|
||||
@GetMapping("/logs")
|
||||
public BaseResponse<IPage<ScheduledTaskLog>> getLogs(
|
||||
@ApiParam("页码") @RequestParam(defaultValue = "1") Integer pageNo,
|
||||
@ApiParam("页大小") @RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@ApiParam("任务ID") @RequestParam(required = false) String taskId,
|
||||
@ApiParam("执行状态") @RequestParam(required = false) Integer status) {
|
||||
|
||||
try {
|
||||
Page<ScheduledTaskLog> page = new Page<>(pageNo, pageSize);
|
||||
QueryWrapper<ScheduledTaskLog> queryWrapper = new QueryWrapper<>();
|
||||
|
||||
if (taskId != null && !taskId.trim().isEmpty()) {
|
||||
queryWrapper.eq("task_id", taskId);
|
||||
}
|
||||
|
||||
if (status != null) {
|
||||
queryWrapper.eq("status", status);
|
||||
}
|
||||
|
||||
queryWrapper.orderByDesc("create_time");
|
||||
|
||||
IPage<ScheduledTaskLog> pageResult = taskLogService.page(page, queryWrapper);
|
||||
|
||||
return BaseResponse.success(pageResult);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("查询任务执行日志失败", e);
|
||||
return new BaseResponse<>(500, false, "查询失败:" + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理执行日志
|
||||
*/
|
||||
@ApiOperation("清理执行日志")
|
||||
@PostMapping("/cleanLogs")
|
||||
public BaseResponse<Integer> cleanLogs(@RequestParam(defaultValue = "30") Integer days) {
|
||||
try {
|
||||
int cleanedCount = taskLogService.cleanLogsByDays(days);
|
||||
return BaseResponse.success(cleanedCount);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("清理执行日志失败", e);
|
||||
return new BaseResponse<>(500, false, "清理失败:" + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取调度器状态
|
||||
*/
|
||||
@ApiOperation("获取调度器状态")
|
||||
@GetMapping("/scheduler/status")
|
||||
public BaseResponse<Map<String, Object>> getSchedulerStatus() {
|
||||
try {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("runningTaskCount", dynamicTaskScheduler.getRunningTaskCount());
|
||||
result.put("runningTaskIds", dynamicTaskScheduler.getRunningTaskIds());
|
||||
|
||||
return BaseResponse.success(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取调度器状态失败", e);
|
||||
return new BaseResponse<>(500, false, "查询失败:" + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/test")
|
||||
public BaseResponse<Boolean> test() {
|
||||
System.out.println("test");
|
||||
System.out.println("test");
|
||||
System.out.println("test");
|
||||
System.out.println("test");
|
||||
System.out.println("test");
|
||||
return BaseResponse.success(Boolean.TRUE);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,332 @@
|
||||
package com.coscoshipping.ebtp.system.scheduled.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.chinaunicom.mall.ebtp.common.base.entity.BaseResponse;
|
||||
import com.coscoshipping.ebtp.system.scheduled.entity.ScheduledTask;
|
||||
import com.coscoshipping.ebtp.system.scheduled.entity.ScheduledTaskLog;
|
||||
import com.coscoshipping.ebtp.system.scheduled.scheduler.DynamicTaskScheduler;
|
||||
import com.coscoshipping.ebtp.system.scheduled.service.IScheduledTaskLogService;
|
||||
import com.coscoshipping.ebtp.system.scheduled.service.IScheduledTaskService;
|
||||
import com.coscoshipping.ebtp.system.scheduled.util.CronUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 定时任务测试控制器
|
||||
*/
|
||||
@Slf4j
|
||||
@Api(tags = "定时任务测试接口")
|
||||
@RestController
|
||||
@RequestMapping("/sys/scheduled/test")
|
||||
public class ScheduledTaskTestController {
|
||||
|
||||
@Autowired
|
||||
private IScheduledTaskService scheduledTaskService;
|
||||
|
||||
@Autowired
|
||||
private IScheduledTaskLogService taskLogService;
|
||||
|
||||
@Autowired
|
||||
private DynamicTaskScheduler dynamicTaskScheduler;
|
||||
|
||||
/**
|
||||
* 测试接口 - 创建测试定时任务
|
||||
*/
|
||||
@ApiOperation("创建测试定时任务")
|
||||
@PostMapping("/createTestTask")
|
||||
public BaseResponse<ScheduledTask> createTestTask(
|
||||
@ApiParam("任务名称") @RequestParam(defaultValue = "测试任务") String taskName,
|
||||
@ApiParam("Cron表达式") @RequestParam(defaultValue = "0/30 * * * * ?") String cronExpression,
|
||||
@ApiParam("目标服务") @RequestParam(defaultValue = "localhost:8080") String targetService,
|
||||
@ApiParam("目标API") @RequestParam(defaultValue = "/sys/scheduled/test/echo") String targetApi) {
|
||||
|
||||
try {
|
||||
// 验证Cron表达式
|
||||
if (!CronUtil.isValidCron(cronExpression)) {
|
||||
return new BaseResponse<>(400, false, "Cron表达式格式不正确", null);
|
||||
}
|
||||
|
||||
// 创建测试任务
|
||||
ScheduledTask testTask = new ScheduledTask();
|
||||
testTask.setTaskName(taskName + "_" + System.currentTimeMillis());
|
||||
testTask.setTaskDesc("自动生成的测试任务,用于验证定时任务功能");
|
||||
testTask.setCronExpression(cronExpression);
|
||||
testTask.setTargetService(targetService);
|
||||
testTask.setTargetApi(targetApi);
|
||||
testTask.setRequestMethod("GET");
|
||||
testTask.setRequestParams("{}");
|
||||
testTask.setRequestHeaders("{}");
|
||||
testTask.setStatus(1); // 启用状态
|
||||
testTask.setCreateTime(new Date());
|
||||
testTask.setUpdateTime(new Date());
|
||||
|
||||
// 计算下次执行时间
|
||||
Date nextRunTime = CronUtil.getNextExecutionTime(cronExpression, new Date());
|
||||
testTask.setNextRunTime(nextRunTime);
|
||||
|
||||
boolean success = scheduledTaskService.save(testTask);
|
||||
|
||||
if (success) {
|
||||
log.info("创建测试任务成功,任务ID:{}, 任务名称:{}", testTask.getId(), testTask.getTaskName());
|
||||
return BaseResponse.success(testTask);
|
||||
} else {
|
||||
return new BaseResponse<>(500, false, "创建测试任务失败", null);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("创建测试任务失败", e);
|
||||
return new BaseResponse<>(500, false, "创建测试任务失败:" + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试接口 - Echo测试端点
|
||||
*/
|
||||
@ApiOperation("Echo测试端点")
|
||||
@GetMapping("/echo")
|
||||
public BaseResponse<Map<String, Object>> testEcho(
|
||||
@ApiParam("测试参数") @RequestParam(required = false) String message) {
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("timestamp", new Date());
|
||||
result.put("message", message != null ? message : "Hello from scheduled task test!");
|
||||
result.put("status", "success");
|
||||
result.put("serverInfo", "ScheduledTask Test Echo Service");
|
||||
result.put("threadName", Thread.currentThread().getName());
|
||||
|
||||
log.info("测试Echo接口被调用,参数:{}, 线程:{}", message, Thread.currentThread().getName());
|
||||
|
||||
return BaseResponse.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试接口 - 获取任务执行统计
|
||||
*/
|
||||
@ApiOperation("获取任务执行统计")
|
||||
@GetMapping("/statistics")
|
||||
public BaseResponse<Map<String, Object>> getTaskStatistics(
|
||||
@ApiParam("统计天数") @RequestParam(defaultValue = "7") Integer days) {
|
||||
|
||||
try {
|
||||
Map<String, Object> statistics = new HashMap<>();
|
||||
|
||||
// 获取任务总数
|
||||
long totalTasks = scheduledTaskService.count();
|
||||
|
||||
// 获取启用的任务数
|
||||
QueryWrapper<ScheduledTask> enabledWrapper = new QueryWrapper<>();
|
||||
enabledWrapper.eq("status", 1);
|
||||
long enabledTasks = scheduledTaskService.count(enabledWrapper);
|
||||
|
||||
// 获取最近N天的执行日志统计
|
||||
QueryWrapper<ScheduledTaskLog> logWrapper = new QueryWrapper<>();
|
||||
logWrapper.ge("create_time", new Date(System.currentTimeMillis() - days * 24 * 60 * 60 * 1000L));
|
||||
List<ScheduledTaskLog> recentLogs = taskLogService.list(logWrapper);
|
||||
|
||||
// 统计执行结果
|
||||
long successCount = recentLogs.stream().filter(log -> log.getStatus() == 1).count();
|
||||
long failureCount = recentLogs.stream().filter(log -> log.getStatus() == 0).count();
|
||||
|
||||
// 计算平均执行时间
|
||||
double avgExecutionTime = recentLogs.stream()
|
||||
.filter(log -> log.getExecuteTime() != null)
|
||||
.mapToLong(ScheduledTaskLog::getExecuteTime)
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
|
||||
statistics.put("totalTasks", totalTasks);
|
||||
statistics.put("enabledTasks", enabledTasks);
|
||||
statistics.put("disabledTasks", totalTasks - enabledTasks);
|
||||
statistics.put("recentDays", days);
|
||||
statistics.put("totalExecutions", recentLogs.size());
|
||||
statistics.put("successExecutions", successCount);
|
||||
statistics.put("failureExecutions", failureCount);
|
||||
statistics.put("successRate", recentLogs.size() > 0 ? (double) successCount / recentLogs.size() * 100 : 0);
|
||||
statistics.put("avgExecutionTime", Math.round(avgExecutionTime));
|
||||
statistics.put("runningTasks", dynamicTaskScheduler.getRunningTaskCount());
|
||||
|
||||
return BaseResponse.success(statistics);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取任务统计失败", e);
|
||||
return new BaseResponse<>(500, false, "获取统计失败:" + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试接口 - 批量创建测试任务
|
||||
*/
|
||||
@ApiOperation("批量创建测试任务")
|
||||
@PostMapping("/createBatchTestTasks")
|
||||
public BaseResponse<List<ScheduledTask>> createBatchTestTasks(
|
||||
@ApiParam("任务数量") @RequestParam(defaultValue = "3") Integer count) {
|
||||
|
||||
try {
|
||||
if (count <= 0 || count > 10) {
|
||||
return new BaseResponse<>(400, false, "任务数量必须在1-10之间", null);
|
||||
}
|
||||
|
||||
List<ScheduledTask> createdTasks = new ArrayList<>();
|
||||
String[] cronExpressions = {
|
||||
"0/30 * * * * ?", // 每30秒
|
||||
"0 */2 * * * ?", // 每2分钟
|
||||
"0 */5 * * * ?" // 每5分钟
|
||||
};
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
ScheduledTask task = new ScheduledTask();
|
||||
task.setTaskName("批量测试任务_" + (i + 1) + "_" + System.currentTimeMillis());
|
||||
task.setTaskDesc("批量创建的测试任务 #" + (i + 1));
|
||||
task.setCronExpression(cronExpressions[i % cronExpressions.length]);
|
||||
task.setTargetService("localhost:8080");
|
||||
task.setTargetApi("/sys/scheduled/test/echo?message=batch_task_" + (i + 1));
|
||||
task.setRequestMethod("GET");
|
||||
task.setRequestParams("{}");
|
||||
task.setRequestHeaders("{}");
|
||||
task.setStatus(1); // 启用状态
|
||||
task.setCreateTime(new Date());
|
||||
task.setUpdateTime(new Date());
|
||||
|
||||
// 计算下次执行时间
|
||||
Date nextRunTime = CronUtil.getNextExecutionTime(task.getCronExpression(), new Date());
|
||||
task.setNextRunTime(nextRunTime);
|
||||
|
||||
boolean success = scheduledTaskService.save(task);
|
||||
if (success) {
|
||||
createdTasks.add(task);
|
||||
log.info("批量创建测试任务成功,任务ID:{}, 任务名称:{}", task.getId(), task.getTaskName());
|
||||
}
|
||||
}
|
||||
|
||||
return BaseResponse.success(createdTasks);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("批量创建测试任务失败", e);
|
||||
return new BaseResponse<>(500, false, "批量创建失败:" + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试接口 - 清理测试任务
|
||||
*/
|
||||
@ApiOperation("清理测试任务")
|
||||
@PostMapping("/cleanTestTasks")
|
||||
public BaseResponse<Integer> cleanTestTasks() {
|
||||
try {
|
||||
QueryWrapper<ScheduledTask> wrapper = new QueryWrapper<>();
|
||||
wrapper.like("task_name", "测试任务")
|
||||
.or()
|
||||
.like("task_name", "批量测试任务");
|
||||
|
||||
List<ScheduledTask> testTasks = scheduledTaskService.list(wrapper);
|
||||
int cleanedCount = 0;
|
||||
|
||||
for (ScheduledTask task : testTasks) {
|
||||
boolean removed = scheduledTaskService.removeById(task.getId());
|
||||
if (removed) {
|
||||
cleanedCount++;
|
||||
log.info("清理测试任务:{}", task.getTaskName());
|
||||
}
|
||||
}
|
||||
|
||||
return BaseResponse.success(cleanedCount);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("清理测试任务失败", e);
|
||||
return new BaseResponse<>(500, false, "清理失败:" + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试接口 - 模拟失败的任务
|
||||
*/
|
||||
@ApiOperation("模拟失败的任务端点")
|
||||
@GetMapping("/error")
|
||||
public BaseResponse<Map<String, Object>> testError(
|
||||
@ApiParam("错误类型") @RequestParam(defaultValue = "runtime") String errorType) {
|
||||
|
||||
log.info("测试错误接口被调用,错误类型:{}", errorType);
|
||||
|
||||
switch (errorType) {
|
||||
case "timeout":
|
||||
try {
|
||||
Thread.sleep(35000); // 超过30秒超时
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
break;
|
||||
case "exception":
|
||||
throw new RuntimeException("模拟业务异常");
|
||||
case "http500":
|
||||
return new BaseResponse<>(500, false, "模拟HTTP 500错误", null);
|
||||
default:
|
||||
throw new RuntimeException("模拟运行时异常");
|
||||
}
|
||||
|
||||
return BaseResponse.success(Collections.singletonMap("message", "不应该到达这里"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试接口 - 手动触发任务
|
||||
*/
|
||||
@ApiOperation("手动触发指定任务")
|
||||
@PostMapping("/triggerTask")
|
||||
public BaseResponse<Boolean> triggerTask(@ApiParam("任务ID") @RequestParam String taskId) {
|
||||
try {
|
||||
ScheduledTask task = scheduledTaskService.getById(taskId);
|
||||
if (task == null) {
|
||||
return new BaseResponse<>(404, false, "任务不存在", false);
|
||||
}
|
||||
|
||||
dynamicTaskScheduler.triggerTask(taskId);
|
||||
log.info("手动触发任务成功,任务ID:{}, 任务名称:{}", taskId, task.getTaskName());
|
||||
|
||||
return BaseResponse.success(true);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("手动触发任务失败,任务ID:{}", taskId, e);
|
||||
return new BaseResponse<>(500, false, "触发失败:" + e.getMessage(), false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试接口 - 获取调度器详细状态
|
||||
*/
|
||||
@ApiOperation("获取调度器详细状态")
|
||||
@GetMapping("/schedulerStatus")
|
||||
public BaseResponse<Map<String, Object>> getSchedulerStatus() {
|
||||
try {
|
||||
Map<String, Object> status = new HashMap<>();
|
||||
status.put("runningTaskCount", dynamicTaskScheduler.getRunningTaskCount());
|
||||
status.put("runningTaskIds", dynamicTaskScheduler.getRunningTaskIds());
|
||||
|
||||
// 获取运行中任务的详细信息
|
||||
List<Map<String, Object>> runningTaskDetails = new ArrayList<>();
|
||||
for (String taskId : dynamicTaskScheduler.getRunningTaskIds()) {
|
||||
ScheduledTask task = scheduledTaskService.getById(taskId);
|
||||
if (task != null) {
|
||||
Map<String, Object> taskInfo = new HashMap<>();
|
||||
taskInfo.put("taskId", task.getId());
|
||||
taskInfo.put("taskName", task.getTaskName());
|
||||
taskInfo.put("cronExpression", task.getCronExpression());
|
||||
taskInfo.put("nextRunTime", task.getNextRunTime());
|
||||
taskInfo.put("lastRunTime", task.getLastRunTime());
|
||||
runningTaskDetails.add(taskInfo);
|
||||
}
|
||||
}
|
||||
status.put("runningTaskDetails", runningTaskDetails);
|
||||
|
||||
return BaseResponse.success(status);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取调度器状态失败", e);
|
||||
return new BaseResponse<>(500, false, "获取状态失败:" + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.coscoshipping.ebtp.system.scheduled.dao;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.coscoshipping.ebtp.system.scheduled.entity.ScheduledTaskLog;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 定时任务执行日志Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface ScheduledTaskLogMapper extends BaseMapper<ScheduledTaskLog> {
|
||||
|
||||
/**
|
||||
* 根据任务ID查询执行日志
|
||||
* @param taskId 任务ID
|
||||
* @return 执行日志列表
|
||||
*/
|
||||
List<ScheduledTaskLog> selectLogsByTaskId(@Param("taskId") String taskId);
|
||||
|
||||
/**
|
||||
* 根据时间范围查询执行日志
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @return 执行日志列表
|
||||
*/
|
||||
List<ScheduledTaskLog> selectLogsByTimeRange(@Param("startTime") Date startTime,
|
||||
@Param("endTime") Date endTime);
|
||||
|
||||
/**
|
||||
* 删除指定天数之前的日志
|
||||
* @param days 天数
|
||||
* @return 删除记录数
|
||||
*/
|
||||
int deleteLogsByDays(@Param("days") int days);
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.coscoshipping.ebtp.system.scheduled.dao;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.coscoshipping.ebtp.system.scheduled.entity.ScheduledTask;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 定时任务配置Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface ScheduledTaskMapper extends BaseMapper<ScheduledTask> {
|
||||
|
||||
/**
|
||||
* 查询启用的定时任务列表
|
||||
* @return 启用的定时任务列表
|
||||
*/
|
||||
List<ScheduledTask> selectEnabledTasks();
|
||||
|
||||
/**
|
||||
* 更新任务执行时间
|
||||
* @param taskId 任务ID
|
||||
* @param lastRunTime 上次执行时间
|
||||
* @param nextRunTime 下次执行时间
|
||||
* @return 更新结果
|
||||
*/
|
||||
int updateTaskRunTime(@Param("taskId") String taskId,
|
||||
@Param("lastRunTime") java.util.Date lastRunTime,
|
||||
@Param("nextRunTime") java.util.Date nextRunTime);
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
<?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.coscoshipping.ebtp.system.scheduled.dao.ScheduledTaskLogMapper">
|
||||
|
||||
<!-- 通用查询映射结果 -->
|
||||
<resultMap id="BaseResultMap" type="com.coscoshipping.ebtp.system.scheduled.entity.ScheduledTaskLog">
|
||||
<id column="id" property="id" />
|
||||
<result column="task_id" property="taskId" />
|
||||
<result column="task_name" property="taskName" />
|
||||
<result column="start_time" property="startTime" />
|
||||
<result column="end_time" property="endTime" />
|
||||
<result column="execute_time" property="executeTime" />
|
||||
<result column="status" property="status" />
|
||||
<result column="result" property="result" />
|
||||
<result column="error_msg" property="errorMsg" />
|
||||
<result column="instance_id" property="instanceId" />
|
||||
<result column="target_service" property="targetService" />
|
||||
<result column="target_api" property="targetApi" />
|
||||
<result column="request_params" property="requestParams" />
|
||||
<result column="response_data" property="responseData" />
|
||||
<result column="create_time" property="createTime" />
|
||||
</resultMap>
|
||||
|
||||
<!-- 通用查询结果列 -->
|
||||
<sql id="Base_Column_List">
|
||||
id, task_id, task_name, start_time, end_time, execute_time, status,
|
||||
result, error_msg, instance_id, target_service, target_api,
|
||||
request_params, response_data, create_time
|
||||
</sql>
|
||||
|
||||
<!-- 根据任务ID查询执行日志 -->
|
||||
<select id="selectLogsByTaskId" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List" />
|
||||
FROM scheduled_task_log
|
||||
WHERE task_id = #{taskId}
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据时间范围查询执行日志 -->
|
||||
<select id="selectLogsByTimeRange" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List" />
|
||||
FROM scheduled_task_log
|
||||
WHERE create_time BETWEEN #{startTime} AND #{endTime}
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 删除指定天数之前的日志 -->
|
||||
<delete id="deleteLogsByDays">
|
||||
DELETE FROM scheduled_task_log
|
||||
WHERE create_time < DATE_SUB(NOW(), INTERVAL #{days} DAY)
|
||||
</delete>
|
||||
|
||||
</mapper>
|
@ -0,0 +1,49 @@
|
||||
<?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.coscoshipping.ebtp.system.scheduled.dao.ScheduledTaskMapper">
|
||||
|
||||
<!-- 通用查询映射结果 -->
|
||||
<resultMap id="BaseResultMap" type="com.coscoshipping.ebtp.system.scheduled.entity.ScheduledTask">
|
||||
<id column="id" property="id" />
|
||||
<result column="task_name" property="taskName" />
|
||||
<result column="task_desc" property="taskDesc" />
|
||||
<result column="cron_expression" property="cronExpression" />
|
||||
<result column="target_service" property="targetService" />
|
||||
<result column="target_api" property="targetApi" />
|
||||
<result column="request_method" property="requestMethod" />
|
||||
<result column="request_params" property="requestParams" />
|
||||
<result column="request_headers" property="requestHeaders" />
|
||||
<result column="status" property="status" />
|
||||
<result column="last_run_time" property="lastRunTime" />
|
||||
<result column="next_run_time" property="nextRunTime" />
|
||||
<result column="create_by" property="createBy" />
|
||||
<result column="create_time" property="createTime" />
|
||||
<result column="update_by" property="updateBy" />
|
||||
<result column="update_time" property="updateTime" />
|
||||
<result column="remark" property="remark" />
|
||||
</resultMap>
|
||||
|
||||
<!-- 通用查询结果列 -->
|
||||
<sql id="Base_Column_List">
|
||||
id, task_name, task_desc, cron_expression, target_service, target_api,
|
||||
request_method, request_params, request_headers, status, last_run_time,
|
||||
next_run_time, create_by, create_time, update_by, update_time, remark
|
||||
</sql>
|
||||
|
||||
<!-- 查询启用的定时任务列表 -->
|
||||
<select id="selectEnabledTasks" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List" />
|
||||
FROM scheduled_task
|
||||
WHERE status = 1
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 更新任务执行时间 -->
|
||||
<update id="updateTaskRunTime">
|
||||
UPDATE scheduled_task
|
||||
SET last_run_time = #{lastRunTime},
|
||||
next_run_time = #{nextRunTime},
|
||||
WHERE id = #{taskId}
|
||||
</update>
|
||||
|
||||
</mapper>
|
@ -0,0 +1,119 @@
|
||||
package com.coscoshipping.ebtp.system.scheduled.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 定时任务配置表
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
@TableName("scheduled_task")
|
||||
public class ScheduledTask implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 任务名称
|
||||
*/
|
||||
private String taskName;
|
||||
|
||||
/**
|
||||
* 任务描述
|
||||
*/
|
||||
private String taskDesc;
|
||||
|
||||
/**
|
||||
* cron表达式
|
||||
*/
|
||||
private String cronExpression;
|
||||
|
||||
/**
|
||||
* 目标服务名称
|
||||
*/
|
||||
private String targetService;
|
||||
|
||||
/**
|
||||
* 目标接口路径
|
||||
*/
|
||||
private String targetApi;
|
||||
|
||||
/**
|
||||
* 请求方法类型(GET、POST等)
|
||||
*/
|
||||
private String requestMethod;
|
||||
|
||||
/**
|
||||
* 请求参数(JSON格式)
|
||||
*/
|
||||
private String requestParams;
|
||||
|
||||
/**
|
||||
* 请求头(JSON格式)
|
||||
*/
|
||||
private String requestHeaders;
|
||||
|
||||
/**
|
||||
* 任务状态(0:禁用 1:启用)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 上次执行时间
|
||||
*/
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date lastRunTime;
|
||||
|
||||
/**
|
||||
* 下次执行时间
|
||||
*/
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date nextRunTime;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private String updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package com.coscoshipping.ebtp.system.scheduled.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 定时任务执行日志表
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
@TableName("scheduled_task_log")
|
||||
public class ScheduledTaskLog implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 任务ID
|
||||
*/
|
||||
private String taskId;
|
||||
|
||||
/**
|
||||
* 任务名称
|
||||
*/
|
||||
private String taskName;
|
||||
|
||||
/**
|
||||
* 执行开始时间
|
||||
*/
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date startTime;
|
||||
|
||||
/**
|
||||
* 执行结束时间
|
||||
*/
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date endTime;
|
||||
|
||||
/**
|
||||
* 执行耗时(毫秒)
|
||||
*/
|
||||
private Long executeTime;
|
||||
|
||||
/**
|
||||
* 执行状态(0:失败 1:成功)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 执行结果
|
||||
*/
|
||||
private String result;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String errorMsg;
|
||||
|
||||
/**
|
||||
* 执行实例ID
|
||||
*/
|
||||
private String instanceId;
|
||||
|
||||
/**
|
||||
* 目标服务名称
|
||||
*/
|
||||
private String targetService;
|
||||
|
||||
/**
|
||||
* 目标接口路径
|
||||
*/
|
||||
private String targetApi;
|
||||
|
||||
/**
|
||||
* 请求参数
|
||||
*/
|
||||
private String requestParams;
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
private String responseData;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
}
|
@ -0,0 +1,326 @@
|
||||
package com.coscoshipping.ebtp.system.scheduled.scheduler;
|
||||
|
||||
import com.coscoshipping.ebtp.system.scheduled.entity.ScheduledTask;
|
||||
import com.coscoshipping.ebtp.system.scheduled.service.IScheduledTaskLockService;
|
||||
import com.coscoshipping.ebtp.system.scheduled.service.IScheduledTaskLogService;
|
||||
import com.coscoshipping.ebtp.system.scheduled.service.IScheduledTaskService;
|
||||
import com.coscoshipping.ebtp.system.scheduled.util.CronUtil;
|
||||
import com.coscoshipping.ebtp.system.scheduled.util.TaskExecutorUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.scheduling.support.CronTrigger;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 动态任务调度器 - 基于ThreadPoolTaskScheduler实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DynamicTaskScheduler {
|
||||
|
||||
@Autowired
|
||||
private IScheduledTaskService scheduledTaskService;
|
||||
|
||||
@Autowired
|
||||
private IScheduledTaskLockService taskLockService;
|
||||
|
||||
@Autowired
|
||||
private IScheduledTaskLogService taskLogService;
|
||||
|
||||
@Autowired
|
||||
private TaskExecutorUtil taskExecutorUtil;
|
||||
|
||||
private ThreadPoolTaskScheduler taskScheduler;
|
||||
|
||||
/**
|
||||
* 存储正在运行的任务
|
||||
* Key: 任务ID, Value: ScheduledFuture
|
||||
*/
|
||||
private final Map<String, ScheduledFuture<?>> runningTasks = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 存储任务的最后更新时间,用于检测任务配置变更
|
||||
* Key: 任务ID, Value: 最后更新时间
|
||||
*/
|
||||
private final Map<String, Date> taskUpdateTimes = new ConcurrentHashMap<>();
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// 初始化任务调度器
|
||||
taskScheduler = new ThreadPoolTaskScheduler();
|
||||
taskScheduler.setPoolSize(10);
|
||||
taskScheduler.setThreadNamePrefix("DynamicTask-");
|
||||
taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
|
||||
taskScheduler.setAwaitTerminationSeconds(60);
|
||||
taskScheduler.initialize();
|
||||
|
||||
// 启动任务管理器
|
||||
startTaskManager();
|
||||
|
||||
log.info("动态任务调度器初始化完成");
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
if (taskScheduler != null) {
|
||||
// 停止所有任务
|
||||
runningTasks.values().forEach(future -> future.cancel(false));
|
||||
runningTasks.clear();
|
||||
taskUpdateTimes.clear();
|
||||
|
||||
// 关闭调度器
|
||||
taskScheduler.shutdown();
|
||||
log.info("动态任务调度器已关闭");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动任务管理器 - 定期检查任务配置变更
|
||||
*/
|
||||
private void startTaskManager() {
|
||||
// 初始加载所有启用的任务
|
||||
loadAllEnabledTasks();
|
||||
|
||||
// 每60秒检查一次任务配置变更
|
||||
// taskScheduler.scheduleWithFixedDelay(() -> {
|
||||
// taskChanges();
|
||||
// }, 60000);
|
||||
|
||||
// 每天凌晨清理过期日志
|
||||
taskScheduler.schedule(() -> {
|
||||
try {
|
||||
cleanExpiredLogs();
|
||||
} catch (Exception e) {
|
||||
log.error("清理过期日志失败", e);
|
||||
}
|
||||
}, new CronTrigger("0 0 2 * * ?"));
|
||||
}
|
||||
|
||||
public void taskChanges() {
|
||||
try {
|
||||
log.info("检查任务配置变更...");
|
||||
checkTaskConfigChanges();
|
||||
clearExpiredLocks();
|
||||
} catch (Exception e) {
|
||||
log.error("检查任务配置变更失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载所有启用的任务
|
||||
*/
|
||||
private void loadAllEnabledTasks() {
|
||||
try {
|
||||
List<ScheduledTask> enabledTasks = scheduledTaskService.listEnabledTasks();
|
||||
for (ScheduledTask task : enabledTasks) {
|
||||
scheduleTask(task);
|
||||
}
|
||||
log.info("加载启用任务完成,共{}个任务", enabledTasks.size());
|
||||
} catch (Exception e) {
|
||||
log.error("加载启用任务失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查任务配置变更
|
||||
*/
|
||||
private void checkTaskConfigChanges() {
|
||||
try {
|
||||
List<ScheduledTask> currentTasks = scheduledTaskService.listEnabledTasks();
|
||||
Set<String> currentTaskIds = currentTasks.stream()
|
||||
.map(ScheduledTask::getId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 检查新增或修改的任务
|
||||
for (ScheduledTask task : currentTasks) {
|
||||
Date lastUpdateTime = taskUpdateTimes.get(task.getId());
|
||||
boolean isNewTask = !runningTasks.containsKey(task.getId());
|
||||
boolean isModified = lastUpdateTime == null ||
|
||||
(task.getUpdateTime() != null && task.getUpdateTime().after(lastUpdateTime));
|
||||
|
||||
if (isNewTask || isModified) {
|
||||
log.info("检测到任务变更,重新调度任务:{}", task.getTaskName());
|
||||
rescheduleTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查已删除或禁用的任务
|
||||
runningTasks.keySet().removeIf(taskId -> {
|
||||
if (!currentTaskIds.contains(taskId)) {
|
||||
log.info("检测到任务已删除或禁用,停止调度:{}", taskId);
|
||||
stopTask(taskId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("检查任务配置变更失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调度任务
|
||||
*/
|
||||
public void scheduleTask(ScheduledTask task) {
|
||||
try {
|
||||
if (!CronUtil.isValidCron(task.getCronExpression())) {
|
||||
log.warn("任务Cron表达式无效,跳过调度:{}", task.getTaskName());
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建任务执行器
|
||||
Runnable taskRunner = () -> executeTaskWithLock(task);
|
||||
|
||||
// 使用Cron表达式调度任务
|
||||
CronTrigger cronTrigger = new CronTrigger(task.getCronExpression());
|
||||
ScheduledFuture<?> future = taskScheduler.schedule(taskRunner, cronTrigger);
|
||||
|
||||
// 保存任务信息
|
||||
runningTasks.put(task.getId(), future);
|
||||
taskUpdateTimes.put(task.getId(), task.getUpdateTime() != null ? task.getUpdateTime() : new Date());
|
||||
|
||||
log.info("任务调度成功:{} [{}]", task.getTaskName(), task.getCronExpression());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("调度任务失败:{}", task.getTaskName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新调度任务
|
||||
*/
|
||||
public void rescheduleTask(ScheduledTask task) {
|
||||
// 先停止现有任务
|
||||
stopTask(task.getId());
|
||||
// 重新调度
|
||||
scheduleTask(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止任务
|
||||
*/
|
||||
public void stopTask(String taskId) {
|
||||
ScheduledFuture<?> future = runningTasks.remove(taskId);
|
||||
if (future != null) {
|
||||
future.cancel(false);
|
||||
taskUpdateTimes.remove(taskId);
|
||||
log.info("任务已停止:{}", taskId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带分布式锁的任务执行
|
||||
*/
|
||||
private void executeTaskWithLock(ScheduledTask task) {
|
||||
String instanceId = getInstanceId();
|
||||
|
||||
try {
|
||||
// 尝试获取分布式锁
|
||||
boolean lockAcquired = taskLockService.tryLock(task.getId(), instanceId, 300);
|
||||
if (!lockAcquired) {
|
||||
log.debug("未能获取到分布式锁,跳过任务执行:{}", task.getTaskName());
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("获取到分布式锁,开始执行定时任务:{}", task.getTaskName());
|
||||
|
||||
try {
|
||||
// 更新任务执行时间
|
||||
Date currentTime = new Date();
|
||||
Date nextRunTime = CronUtil.getNextExecutionTime(task.getCronExpression(), currentTime);
|
||||
scheduledTaskService.updateTaskRunTime(task.getId(), currentTime, nextRunTime);
|
||||
|
||||
// 异步执行任务(使用无锁版本,因为锁已经在这里获取了)
|
||||
taskExecutorUtil.executeTaskWithoutLock(task, instanceId);
|
||||
|
||||
} finally {
|
||||
// 释放分布式锁
|
||||
taskLockService.releaseLock(task.getId(), instanceId);
|
||||
log.debug("释放分布式锁:{}", task.getTaskName());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("执行任务失败:{}", task.getTaskName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实例ID
|
||||
*/
|
||||
private String getInstanceId() {
|
||||
try {
|
||||
String hostName = java.net.InetAddress.getLocalHost().getHostName();
|
||||
String hostAddress = java.net.InetAddress.getLocalHost().getHostAddress();
|
||||
return hostName + ":" + hostAddress + ":dynamic";
|
||||
} catch (Exception e) {
|
||||
return "unknown:dynamic";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期锁
|
||||
*/
|
||||
private void clearExpiredLocks() {
|
||||
try {
|
||||
int cleanedCount = taskLockService.clearExpiredLocks();
|
||||
if (cleanedCount > 0) {
|
||||
log.debug("清理过期锁完成,清理数量:{}", cleanedCount);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("清理过期锁失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期日志
|
||||
*/
|
||||
private void cleanExpiredLogs() {
|
||||
try {
|
||||
int cleanedCount = taskLogService.cleanLogsByDays(30);
|
||||
log.info("清理过期日志完成,清理数量:{}", cleanedCount);
|
||||
} catch (Exception e) {
|
||||
log.error("清理过期日志失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发任务
|
||||
*/
|
||||
public void triggerTask(String taskId) {
|
||||
try {
|
||||
ScheduledTask task = scheduledTaskService.getById(taskId);
|
||||
if (task != null) {
|
||||
taskScheduler.execute(() -> executeTaskWithLock(task));
|
||||
log.info("手动触发任务:{}", task.getTaskName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("手动触发任务失败:{}", taskId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取运行中的任务数量
|
||||
*/
|
||||
public int getRunningTaskCount() {
|
||||
return runningTasks.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有运行中的任务ID
|
||||
*/
|
||||
public java.util.Set<String> getRunningTaskIds() {
|
||||
return runningTasks.keySet();
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.coscoshipping.ebtp.system.scheduled.service;
|
||||
|
||||
/**
|
||||
* 定时任务分布式锁服务接口(Redis实现)
|
||||
*/
|
||||
public interface IScheduledTaskLockService {
|
||||
|
||||
/**
|
||||
* 尝试获取分布式锁
|
||||
* @param taskId 任务ID
|
||||
* @param instanceId 实例ID
|
||||
* @param lockTimeoutSeconds 锁超时时间(秒)
|
||||
* @return 是否获取成功
|
||||
*/
|
||||
boolean tryLock(String taskId, String instanceId, int lockTimeoutSeconds);
|
||||
|
||||
/**
|
||||
* 释放分布式锁
|
||||
* @param taskId 任务ID
|
||||
* @param instanceId 实例ID
|
||||
* @return 是否释放成功
|
||||
*/
|
||||
boolean releaseLock(String taskId, String instanceId);
|
||||
|
||||
/**
|
||||
* 清理过期锁(Redis自动过期,通常无需实现)
|
||||
* @return 清理的锁数量
|
||||
*/
|
||||
int clearExpiredLocks();
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package com.coscoshipping.ebtp.system.scheduled.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.coscoshipping.ebtp.system.scheduled.entity.ScheduledTaskLog;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 定时任务执行日志服务接口
|
||||
*/
|
||||
public interface IScheduledTaskLogService extends IService<ScheduledTaskLog> {
|
||||
|
||||
/**
|
||||
* 记录任务执行日志
|
||||
* @param taskId 任务ID
|
||||
* @param taskName 任务名称
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @param status 执行状态
|
||||
* @param result 执行结果
|
||||
* @param errorMsg 错误信息
|
||||
* @param instanceId 实例ID
|
||||
* @param targetService 目标服务
|
||||
* @param targetApi 目标接口
|
||||
* @param requestParams 请求参数
|
||||
* @param responseData 响应数据
|
||||
* @return 保存结果
|
||||
*/
|
||||
boolean saveTaskLog(String taskId, String taskName, Date startTime, Date endTime,
|
||||
Integer status, String result, String errorMsg, String instanceId,
|
||||
String targetService, String targetApi, String requestParams, String responseData);
|
||||
|
||||
/**
|
||||
* 根据任务ID查询执行日志
|
||||
* @param taskId 任务ID
|
||||
* @return 执行日志列表
|
||||
*/
|
||||
List<ScheduledTaskLog> listLogsByTaskId(String taskId);
|
||||
|
||||
/**
|
||||
* 根据时间范围查询执行日志
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @return 执行日志列表
|
||||
*/
|
||||
List<ScheduledTaskLog> listLogsByTimeRange(Date startTime, Date endTime);
|
||||
|
||||
/**
|
||||
* 清理指定天数之前的日志
|
||||
* @param days 天数
|
||||
* @return 删除记录数
|
||||
*/
|
||||
int cleanLogsByDays(int days);
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package com.coscoshipping.ebtp.system.scheduled.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.coscoshipping.ebtp.system.scheduled.entity.ScheduledTask;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 定时任务配置服务接口
|
||||
*/
|
||||
public interface IScheduledTaskService extends IService<ScheduledTask> {
|
||||
|
||||
/**
|
||||
* 查询启用的定时任务列表
|
||||
* @return 启用的定时任务列表
|
||||
*/
|
||||
List<ScheduledTask> listEnabledTasks();
|
||||
|
||||
/**
|
||||
* 更新任务执行时间
|
||||
* @param taskId 任务ID
|
||||
* @param lastRunTime 上次执行时间
|
||||
* @param nextRunTime 下次执行时间
|
||||
* @return 更新结果
|
||||
*/
|
||||
boolean updateTaskRunTime(String taskId, java.util.Date lastRunTime, java.util.Date nextRunTime);
|
||||
|
||||
/**
|
||||
* 启用任务
|
||||
* @param taskId 任务ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
boolean enableTask(String taskId);
|
||||
|
||||
/**
|
||||
* 禁用任务
|
||||
* @param taskId 任务ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
boolean disableTask(String taskId);
|
||||
|
||||
/**
|
||||
* 立即执行任务
|
||||
* @param taskId 任务ID
|
||||
* @return 执行结果
|
||||
*/
|
||||
boolean executeTaskNow(String taskId);
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.coscoshipping.ebtp.system.scheduled.service.impl;
|
||||
|
||||
import com.coscoshipping.ebtp.system.scheduled.service.IScheduledTaskLockService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* 基于Redis的定时任务分布式锁实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ScheduledTaskLockRedisServiceImpl implements IScheduledTaskLockService {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, String> scheduledRedisTemplate;
|
||||
|
||||
@Override
|
||||
public boolean tryLock(String taskId, String instanceId, int lockTimeoutSeconds) {
|
||||
String key = "scheduled:lock:" + taskId;
|
||||
Boolean success = scheduledRedisTemplate.opsForValue().setIfAbsent(key, instanceId, Duration.ofSeconds(lockTimeoutSeconds));
|
||||
if (Boolean.TRUE.equals(success)) {
|
||||
log.debug("成功获取Redis分布式锁,任务ID:{},实例ID:{}", taskId, instanceId);
|
||||
return true;
|
||||
} else {
|
||||
log.debug("获取Redis分布式锁失败,任务ID:{},实例ID:{}", taskId, instanceId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean releaseLock(String taskId, String instanceId) {
|
||||
String key = "scheduled:lock:" + taskId;
|
||||
String value = scheduledRedisTemplate.opsForValue().get(key);
|
||||
if (instanceId.equals(value)) {
|
||||
Boolean deleted = scheduledRedisTemplate.delete(key);
|
||||
log.debug("释放Redis分布式锁,任务ID:{},实例ID:{},结果:{}", taskId, instanceId, deleted);
|
||||
return Boolean.TRUE.equals(deleted);
|
||||
}
|
||||
log.debug("释放Redis分布式锁失败(非本实例持有),任务ID:{},实例ID:{}", taskId, instanceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clearExpiredLocks() {
|
||||
// Redis自动过期,无需手动清理
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.coscoshipping.ebtp.system.scheduled.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.coscoshipping.ebtp.system.scheduled.dao.ScheduledTaskLogMapper;
|
||||
import com.coscoshipping.ebtp.system.scheduled.entity.ScheduledTaskLog;
|
||||
import com.coscoshipping.ebtp.system.scheduled.service.IScheduledTaskLogService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 定时任务执行日志服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ScheduledTaskLogServiceImpl extends ServiceImpl<ScheduledTaskLogMapper, ScheduledTaskLog> implements IScheduledTaskLogService {
|
||||
|
||||
@Override
|
||||
public boolean saveTaskLog(String taskId, String taskName, Date startTime, Date endTime,
|
||||
Integer status, String result, String errorMsg, String instanceId,
|
||||
String targetService, String targetApi, String requestParams, String responseData) {
|
||||
try {
|
||||
ScheduledTaskLog taskLog = new ScheduledTaskLog();
|
||||
taskLog.setTaskId(taskId);
|
||||
taskLog.setTaskName(taskName);
|
||||
taskLog.setStartTime(startTime);
|
||||
taskLog.setEndTime(endTime);
|
||||
taskLog.setExecuteTime(endTime != null && startTime != null ? endTime.getTime() - startTime.getTime() : 0L);
|
||||
taskLog.setStatus(status);
|
||||
taskLog.setResult(result);
|
||||
taskLog.setErrorMsg(errorMsg);
|
||||
taskLog.setInstanceId(instanceId);
|
||||
taskLog.setTargetService(targetService);
|
||||
taskLog.setTargetApi(targetApi);
|
||||
taskLog.setRequestParams(requestParams);
|
||||
taskLog.setResponseData(responseData);
|
||||
taskLog.setCreateTime(new Date());
|
||||
|
||||
return this.save(taskLog);
|
||||
} catch (Exception e) {
|
||||
log.error("保存任务执行日志失败,任务ID:{},错误信息:{}", taskId, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ScheduledTaskLog> listLogsByTaskId(String taskId) {
|
||||
return this.baseMapper.selectLogsByTaskId(taskId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ScheduledTaskLog> listLogsByTimeRange(Date startTime, Date endTime) {
|
||||
return this.baseMapper.selectLogsByTimeRange(startTime, endTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int cleanLogsByDays(int days) {
|
||||
return this.baseMapper.deleteLogsByDays(days);
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package com.coscoshipping.ebtp.system.scheduled.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.coscoshipping.ebtp.system.scheduled.dao.ScheduledTaskMapper;
|
||||
import com.coscoshipping.ebtp.system.scheduled.entity.ScheduledTask;
|
||||
import com.coscoshipping.ebtp.system.scheduled.service.IScheduledTaskService;
|
||||
import com.coscoshipping.ebtp.system.scheduled.scheduler.DynamicTaskScheduler;
|
||||
import com.coscoshipping.ebtp.system.scheduled.util.TaskExecutorUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 定时任务配置服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ScheduledTaskServiceImpl extends ServiceImpl<ScheduledTaskMapper, ScheduledTask> implements IScheduledTaskService {
|
||||
|
||||
@Autowired
|
||||
private DynamicTaskScheduler dynamicTaskScheduler;
|
||||
|
||||
@Override
|
||||
public List<ScheduledTask> listEnabledTasks() {
|
||||
return this.baseMapper.selectEnabledTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateTaskRunTime(String taskId, Date lastRunTime, Date nextRunTime) {
|
||||
int result = this.baseMapper.updateTaskRunTime(taskId, lastRunTime, nextRunTime);
|
||||
return result > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enableTask(String taskId) {
|
||||
UpdateWrapper<ScheduledTask> updateWrapper = new UpdateWrapper<>();
|
||||
updateWrapper.eq("id", taskId);
|
||||
updateWrapper.set("status", 1);
|
||||
// updateWrapper.set("update_time", new Date());
|
||||
boolean result = this.update(updateWrapper);
|
||||
|
||||
// 通知动态调度器重新调度任务
|
||||
if (result) {
|
||||
dynamicTaskScheduler.taskChanges();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disableTask(String taskId) {
|
||||
UpdateWrapper<ScheduledTask> updateWrapper = new UpdateWrapper<>();
|
||||
updateWrapper.eq("id", taskId);
|
||||
updateWrapper.set("status", 0);
|
||||
// updateWrapper.set("update_time", new Date());
|
||||
boolean result = this.update(updateWrapper);
|
||||
|
||||
// 通知动态调度器重新调度任务
|
||||
if (result) {
|
||||
dynamicTaskScheduler.taskChanges();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean executeTaskNow(String taskId) {
|
||||
try {
|
||||
ScheduledTask task = this.getById(taskId);
|
||||
if (task == null) {
|
||||
log.error("任务不存在,任务ID:{}", taskId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用动态调度器手动触发任务
|
||||
dynamicTaskScheduler.triggerTask(taskId);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("立即执行任务失败,任务ID:{},错误信息:{}", taskId, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
-- 定时任务管理相关表结构
|
||||
|
||||
-- 1. 定时任务配置表
|
||||
CREATE TABLE `scheduled_task` (
|
||||
`id` varchar(32) NOT NULL COMMENT '主键ID',
|
||||
`task_name` varchar(100) NOT NULL COMMENT '任务名称',
|
||||
`task_desc` varchar(500) DEFAULT NULL COMMENT '任务描述',
|
||||
`cron_expression` varchar(100) NOT NULL COMMENT 'cron表达式',
|
||||
`target_service` varchar(100) NOT NULL COMMENT '目标服务名称',
|
||||
`target_api` varchar(200) NOT NULL COMMENT '目标接口路径',
|
||||
`request_method` varchar(10) DEFAULT 'POST' COMMENT '请求方法类型(GET、POST等)',
|
||||
`request_params` text COMMENT '请求参数(JSON格式)',
|
||||
`request_headers` text COMMENT '请求头(JSON格式)',
|
||||
`status` tinyint(1) DEFAULT '0' COMMENT '任务状态(0:禁用 1:启用)',
|
||||
`last_run_time` datetime DEFAULT NULL COMMENT '上次执行时间',
|
||||
`next_run_time` datetime DEFAULT NULL COMMENT '下次执行时间',
|
||||
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
|
||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
|
||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_target_service` (`target_service`),
|
||||
KEY `idx_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='定时任务配置表';
|
||||
|
||||
-- 2. 定时任务执行日志表
|
||||
CREATE TABLE `scheduled_task_log` (
|
||||
`id` varchar(32) NOT NULL COMMENT '主键ID',
|
||||
`task_id` varchar(32) NOT NULL COMMENT '任务ID',
|
||||
`task_name` varchar(100) NOT NULL COMMENT '任务名称',
|
||||
`start_time` datetime NOT NULL COMMENT '执行开始时间',
|
||||
`end_time` datetime DEFAULT NULL COMMENT '执行结束时间',
|
||||
`execute_time` bigint(20) DEFAULT '0' COMMENT '执行耗时(毫秒)',
|
||||
`status` tinyint(1) DEFAULT '0' COMMENT '执行状态(0:失败 1:成功)',
|
||||
`result` varchar(200) DEFAULT NULL COMMENT '执行结果',
|
||||
`error_msg` text COMMENT '错误信息',
|
||||
`instance_id` varchar(100) DEFAULT NULL COMMENT '执行实例ID',
|
||||
`target_service` varchar(100) DEFAULT NULL COMMENT '目标服务名称',
|
||||
`target_api` varchar(200) DEFAULT NULL COMMENT '目标接口路径',
|
||||
`request_params` text COMMENT '请求参数',
|
||||
`response_data` text COMMENT '响应数据',
|
||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_task_id` (`task_id`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_start_time` (`start_time`),
|
||||
KEY `idx_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='定时任务执行日志表';
|
||||
|
||||
-- 3. 定时任务分布式锁表
|
||||
CREATE TABLE `scheduled_task_lock` (
|
||||
`task_id` varchar(32) NOT NULL COMMENT '任务ID',
|
||||
`lock_instance` varchar(100) DEFAULT NULL COMMENT '锁定实例ID',
|
||||
`lock_time` datetime DEFAULT NULL COMMENT '锁定时间',
|
||||
`expire_time` datetime DEFAULT NULL COMMENT '锁定到期时间',
|
||||
`status` tinyint(1) DEFAULT '0' COMMENT '锁定状态(0:未锁定 1:已锁定)',
|
||||
PRIMARY KEY (`task_id`),
|
||||
KEY `idx_expire_time` (`expire_time`),
|
||||
KEY `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='定时任务分布式锁表';
|
||||
|
||||
-- 插入示例数据
|
||||
INSERT INTO `scheduled_task` (`id`, `task_name`, `task_desc`, `cron_expression`, `target_service`, `target_api`, `request_method`, `request_params`, `request_headers`, `status`, `create_by`, `remark`) VALUES
|
||||
('1', '测试任务', '这是一个测试任务', '0 */5 * * * ?', 'test-service', '/api/test', 'POST', '{"param1": "value1"}', '{"Content-Type": "application/json"}', 0, 'admin', '每5分钟执行一次的测试任务');
|
||||
|
||||
-- 创建索引优化查询性能
|
||||
CREATE INDEX idx_scheduled_task_next_run_time ON scheduled_task(next_run_time);
|
||||
CREATE INDEX idx_scheduled_task_log_task_time ON scheduled_task_log(task_id, start_time);
|
||||
CREATE INDEX idx_scheduled_task_lock_instance ON scheduled_task_lock(lock_instance);
|
@ -0,0 +1,97 @@
|
||||
package com.coscoshipping.ebtp.system.scheduled.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.support.CronSequenceGenerator;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Cron表达式工具类
|
||||
*/
|
||||
@Slf4j
|
||||
public class CronUtil {
|
||||
|
||||
/**
|
||||
* 验证Cron表达式是否有效
|
||||
* @param cronExpression cron表达式
|
||||
* @return 是否有效
|
||||
*/
|
||||
public static boolean isValidCron(String cronExpression) {
|
||||
try {
|
||||
new CronSequenceGenerator(cronExpression);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.warn("无效的Cron表达式:{}", cronExpression, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下次执行时间
|
||||
* @param cronExpression cron表达式
|
||||
* @param currentTime 当前时间
|
||||
* @return 下次执行时间
|
||||
*/
|
||||
public static Date getNextExecutionTime(String cronExpression, Date currentTime) {
|
||||
try {
|
||||
CronSequenceGenerator cron = new CronSequenceGenerator(cronExpression);
|
||||
return cron.next(currentTime);
|
||||
} catch (Exception e) {
|
||||
log.error("计算下次执行时间失败,Cron表达式:{},当前时间:{}", cronExpression, currentTime, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前时间是否应该执行任务
|
||||
* @param cronExpression cron表达式
|
||||
* @param currentTime 当前时间
|
||||
* @param lastExecutionTime 上次执行时间
|
||||
* @return 是否应该执行
|
||||
*/
|
||||
public static boolean shouldExecute(String cronExpression, Date currentTime, Date lastExecutionTime) {
|
||||
try {
|
||||
CronSequenceGenerator cron = new CronSequenceGenerator(cronExpression);
|
||||
|
||||
// 如果从未执行过,检查当前时间是否匹配
|
||||
if (lastExecutionTime == null) {
|
||||
// 获取一分钟前的时间点的下次执行时间
|
||||
Date oneMinuteAgo = new Date(currentTime.getTime() - 60000);
|
||||
Date nextExecution = cron.next(oneMinuteAgo);
|
||||
return nextExecution != null && nextExecution.getTime() <= currentTime.getTime();
|
||||
}
|
||||
|
||||
// 获取上次执行时间之后的下一次执行时间
|
||||
Date nextExecution = cron.next(lastExecutionTime);
|
||||
|
||||
// 如果下次执行时间在当前时间之前或等于当前时间,则应该执行
|
||||
return nextExecution != null && nextExecution.getTime() <= currentTime.getTime();
|
||||
} catch (Exception e) {
|
||||
log.error("判断任务是否应该执行失败,Cron表达式:{},当前时间:{},上次执行时间:{}",
|
||||
cronExpression, currentTime, lastExecutionTime, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Cron表达式描述
|
||||
* @param cronExpression cron表达式
|
||||
* @return 描述信息
|
||||
*/
|
||||
public static String getCronDescription(String cronExpression) {
|
||||
try {
|
||||
// 这里可以集成第三方库来生成更友好的描述
|
||||
// 简单实现,返回基本信息
|
||||
String[] parts = cronExpression.split("\\s+");
|
||||
if (parts.length >= 6) {
|
||||
return String.format("秒:%s 分:%s 时:%s 日:%s 月:%s 周:%s",
|
||||
parts[0], parts[1], parts[2], parts[3], parts[4], parts[5]);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("获取Cron表达式描述失败:{}", cronExpression, e);
|
||||
}
|
||||
return cronExpression;
|
||||
}
|
||||
}
|
@ -0,0 +1,154 @@
|
||||
package com.coscoshipping.ebtp.system.scheduled.util;
|
||||
|
||||
import com.coscoshipping.ebtp.system.scheduled.entity.ScheduledTask;
|
||||
import com.coscoshipping.ebtp.system.scheduled.service.IScheduledTaskLogService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 任务执行工具类
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class TaskExecutorUtil {
|
||||
|
||||
@Autowired
|
||||
private IScheduledTaskLogService taskLogService;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("scheduledRestTemplate")
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* 异步执行任务(无锁版本,用于已经获取锁的场景)
|
||||
*/
|
||||
@Async
|
||||
public void executeTaskWithoutLock(ScheduledTask task, String instanceId) {
|
||||
Date startTime = new Date();
|
||||
String result = null;
|
||||
String errorMsg = null;
|
||||
String responseData = null;
|
||||
Integer status = 1; // 1:成功 0:失败
|
||||
|
||||
try {
|
||||
log.info("开始执行定时任务,任务ID:{},任务名称:{},实例ID:{}", task.getId(), task.getTaskName(), instanceId);
|
||||
|
||||
// 执行任务
|
||||
responseData = executeTaskInternal(task);
|
||||
result = "执行成功";
|
||||
|
||||
log.info("定时任务执行完成,任务ID:{},任务名称:{}", task.getId(), task.getTaskName());
|
||||
|
||||
} catch (Exception e) {
|
||||
status = 0;
|
||||
errorMsg = e.getMessage();
|
||||
result = "执行失败";
|
||||
log.error("定时任务执行失败,任务ID:{},任务名称:{},错误信息:{}", task.getId(), task.getTaskName(), e.getMessage(), e);
|
||||
} finally {
|
||||
Date endTime = new Date();
|
||||
|
||||
// 记录执行日志
|
||||
taskLogService.saveTaskLog(
|
||||
task.getId(),
|
||||
task.getTaskName(),
|
||||
startTime,
|
||||
endTime,
|
||||
status,
|
||||
result,
|
||||
errorMsg,
|
||||
instanceId,
|
||||
task.getTargetService(),
|
||||
task.getTargetApi(),
|
||||
task.getRequestParams(),
|
||||
responseData
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行任务内部方法
|
||||
*/
|
||||
private String executeTaskInternal(ScheduledTask task) throws Exception {
|
||||
String targetService = task.getTargetService();
|
||||
String targetApi = task.getTargetApi();
|
||||
String requestMethod = task.getRequestMethod();
|
||||
String requestParams = task.getRequestParams();
|
||||
String requestHeaders = task.getRequestHeaders();
|
||||
|
||||
// 解析请求参数
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
if (requestParams != null && !requestParams.trim().isEmpty()) {
|
||||
try {
|
||||
params = objectMapper.readValue(requestParams, Map.class);
|
||||
} catch (Exception e) {
|
||||
log.warn("解析请求参数失败,使用空参数,任务ID:{},参数:{}", task.getId(), requestParams, e);
|
||||
}
|
||||
}
|
||||
|
||||
// 解析请求头
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
if (requestHeaders != null && !requestHeaders.trim().isEmpty()) {
|
||||
try {
|
||||
headers = objectMapper.readValue(requestHeaders, Map.class);
|
||||
} catch (Exception e) {
|
||||
log.warn("解析请求头失败,使用空请求头,任务ID:{},请求头:{}", task.getId(), requestHeaders, e);
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试HTTP调用
|
||||
try {
|
||||
return callByHttp(targetService, targetApi, requestMethod, params, headers);
|
||||
} catch (Exception e) {
|
||||
log.warn("通过HTTP调用失败,任务ID:{},错误:{}", task.getId(), e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过HTTP调用
|
||||
*/
|
||||
private String callByHttp(String targetService, String targetApi, String requestMethod,
|
||||
Map<String, Object> params, Map<String, String> headers) throws Exception {
|
||||
// 构建请求URL
|
||||
String url = "http://" + targetService + targetApi;
|
||||
|
||||
// 设置请求头
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
httpHeaders.add(header.getKey(), header.getValue());
|
||||
}
|
||||
|
||||
// 构建请求体
|
||||
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(params, httpHeaders);
|
||||
|
||||
// 发送请求
|
||||
ResponseEntity<String> response;
|
||||
HttpMethod httpMethod = HttpMethod.valueOf(requestMethod.toUpperCase());
|
||||
|
||||
try {
|
||||
response = restTemplate.exchange(url, httpMethod, requestEntity, String.class);
|
||||
} catch (Exception e) {
|
||||
throw new Exception("HTTP调用失败,详细信息:" + e.getMessage(), e);
|
||||
}
|
||||
|
||||
if (response.getStatusCode().is2xxSuccessful()) {
|
||||
return response.getBody();
|
||||
} else {
|
||||
throw new Exception("HTTP调用失败,状态码:" + response.getStatusCodeValue() + ",响应:" + response.getBody());
|
||||
}
|
||||
}
|
||||
}
|
@ -10,16 +10,18 @@ import com.coscoshipping.ebtp.system.faceCompare.feign.AssessjFeignClient;
|
||||
import com.coscoshipping.ebtp.system.faceCompare.feign.ExtendFeignClient;
|
||||
import com.coscoshipping.ebtp.system.faceCompare.feign.RsmsFeignClient;
|
||||
import com.coscoshipping.ebtp.system.login.dao.BaseUserMapper;
|
||||
import com.coscoshipping.ebtp.system.test.RSA;
|
||||
import com.coscoshipping.ebtp.system.userinfo.dao.BaseRoleMapper;
|
||||
import com.coscoshipping.ebtp.system.userinfo.entity.*;
|
||||
import com.coscoshipping.ebtp.system.userinfo.service.EbtpUserInfoService;
|
||||
import com.coscoshipping.ebtp.system.userinfo.service.UnicomOAuthClient;
|
||||
import com.coscoshipping.ebtp.system.userinfo.utils.HttpsUtils;
|
||||
import com.coscoshipping.ebtp.system.userinfo.utils.UserinfoConstants;
|
||||
import com.coscoshipping.ebtp.system.role.dao.SysRoleMapper;
|
||||
import com.coscoshipping.ebtp.system.role.entity.SysRole;
|
||||
import com.coscoshipping.ebtp.system.test.RSA;
|
||||
import com.coscoshipping.ebtp.system.user.entity.SysUser;
|
||||
import com.coscoshipping.ebtp.system.userinfo.dao.BaseRoleMapper;
|
||||
import com.coscoshipping.ebtp.system.userinfo.entity.AuthCodeVo;
|
||||
import com.coscoshipping.ebtp.system.userinfo.entity.AuthLogoutResponse;
|
||||
import com.coscoshipping.ebtp.system.userinfo.entity.ExpertLoginUser;
|
||||
import com.coscoshipping.ebtp.system.userinfo.entity.ExpertUpdateUser;
|
||||
import com.coscoshipping.ebtp.system.userinfo.service.EbtpUserInfoService;
|
||||
import com.coscoshipping.ebtp.system.userinfo.utils.HttpsUtils;
|
||||
import com.coscoshipping.ebtp.system.userinfo.utils.UserinfoConstants;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@ -34,8 +36,8 @@ import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
import static com.chinaunicom.mall.ebtp.cloud.security.starter.common.Constants.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class EbtpUserInfoServiceImpl implements EbtpUserInfoService {
|
||||
@ -51,9 +53,6 @@ public class EbtpUserInfoServiceImpl implements EbtpUserInfoService {
|
||||
@Qualifier("userinfoRedisTemplate")
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Resource
|
||||
private UnicomOAuthClient unicomOAuthClient;
|
||||
|
||||
@Resource
|
||||
private RsmsFeignClient rsmsFeignClient;
|
||||
|
||||
@ -71,6 +70,7 @@ public class EbtpUserInfoServiceImpl implements EbtpUserInfoService {
|
||||
|
||||
@Value("${auth.oauth.expert_update_url}")
|
||||
private String expert_update_url;
|
||||
|
||||
/**
|
||||
* 删除token缓存
|
||||
*
|
||||
@ -93,24 +93,19 @@ public class EbtpUserInfoServiceImpl implements EbtpUserInfoService {
|
||||
|
||||
/**
|
||||
* 系统登出
|
||||
*
|
||||
* @param token
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public AuthLogoutResponse logout(String token){
|
||||
AuthLogoutResponse response;
|
||||
try{
|
||||
//调用认证中心登出
|
||||
response = unicomOAuthClient.logout(token);
|
||||
public AuthLogoutResponse logout(String token) {
|
||||
AuthLogoutResponse response = new AuthLogoutResponse();
|
||||
try {
|
||||
//如果注销成功,删除redis
|
||||
try{
|
||||
if(response.isSuccess() && response.getData().equals("success")) {
|
||||
redisTemplate.delete(REDIS_USER_KEY + token);
|
||||
}
|
||||
}finally {
|
||||
return response;
|
||||
}
|
||||
}catch(Exception e){
|
||||
redisTemplate.delete(REDIS_USER_KEY + token);
|
||||
response.setCode("0").setSuccess(true).setMessage("注销成功").setData("success");
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
response = new AuthLogoutResponse();
|
||||
response.setCode("1").setSuccess(false).setMessage("注销失败").setData("error");
|
||||
return response;
|
||||
@ -118,70 +113,70 @@ public class EbtpUserInfoServiceImpl implements EbtpUserInfoService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String,Object> expertLoginCheck(ExpertLoginUser user){
|
||||
public Map<String, Object> expertLoginCheck(ExpertLoginUser user) {
|
||||
Boolean b = false;
|
||||
//先校验认证中心
|
||||
log.info("身份证解密前:"+user.getIdentityCard());
|
||||
String identityCard = RSA.decrypt(user.getIdentityCard(),privateKey);
|
||||
log.info("身份证解密结果:"+identityCard);
|
||||
log.info("身份证解密前:" + user.getIdentityCard());
|
||||
String identityCard = RSA.decrypt(user.getIdentityCard(), privateKey);
|
||||
log.info("身份证解密结果:" + identityCard);
|
||||
user.setIdentityCard(identityCard);
|
||||
|
||||
Map<String,Object> map = JSON.parseObject(JSON.toJSONString(user), Map.class);
|
||||
String json ="";
|
||||
for(String key :map.keySet()){
|
||||
if(!"".equals(json)){
|
||||
json+="&";
|
||||
Map<String, Object> map = JSON.parseObject(JSON.toJSONString(user), Map.class);
|
||||
String json = "";
|
||||
for (String key : map.keySet()) {
|
||||
if (!"".equals(json)) {
|
||||
json += "&";
|
||||
}
|
||||
json+=key+"="+map.get(key);
|
||||
json += key + "=" + map.get(key);
|
||||
}
|
||||
String rJson = HttpsUtils.expertHttpPost(expert_url, json, user.getClientId());
|
||||
Map<String, Object> rmap = JSON.parseObject(rJson, Map.class);
|
||||
b = UserinfoConstants.okCode.equals(rmap.get(UserinfoConstants.responseCode));
|
||||
|
||||
if(b) {
|
||||
if (b) {
|
||||
//手机验证码
|
||||
if(user.getPcaptcha()!=null&&!"".equals(user.getPcaptcha())&&user.getTel()!=null&&!"".equals(user.getTel())){
|
||||
if (user.getPcaptcha() != null && !"".equals(user.getPcaptcha()) && user.getTel() != null && !"".equals(user.getTel())) {
|
||||
|
||||
/**
|
||||
* extend会解密 这里只是展示
|
||||
*/
|
||||
String cTel = RSA.decrypt(user.getTel(),privateKey);
|
||||
log.info("解密后手机号:"+cTel);
|
||||
String cTel = RSA.decrypt(user.getTel(), privateKey);
|
||||
log.info("解密后手机号:" + cTel);
|
||||
|
||||
AuthCodeVo vo = new AuthCodeVo();
|
||||
vo.setMobile(user.getTel());
|
||||
vo.setAuthCode(user.getPcaptcha());
|
||||
|
||||
Boolean telCheck = extendFeignClient.authCodeCheck(vo).getData();
|
||||
if(telCheck!=null&&telCheck){
|
||||
if (telCheck != null && telCheck) {
|
||||
//b = true;
|
||||
}else{
|
||||
CommonExceptionEnum.FRAME_EXCEPTION_COMMON_DATA_OTHER_ERROR.customValidName("短信验证码已失效",true);
|
||||
} else {
|
||||
CommonExceptionEnum.FRAME_EXCEPTION_COMMON_DATA_OTHER_ERROR.customValidName("短信验证码已失效", true);
|
||||
}
|
||||
}else if(user.getVerificationCode()!=null&&!"".equals(user.getVerificationCode())){//评审室口令
|
||||
} else if (user.getVerificationCode() != null && !"".equals(user.getVerificationCode())) {//评审室口令
|
||||
//获取当前登录专家对应评审室
|
||||
List<String> roomIds = rsmsFeignClient.getRoomByCertificate(user.getIdentityCard()).getData();
|
||||
log.info("roomIds:"+roomIds);
|
||||
log.info("roomIds:" + roomIds);
|
||||
//无参与评标
|
||||
CommonExceptionEnum.FRAME_EXCEPTION_COMMON_DATA_OTHER_ERROR.assertListNotNullByName("短信验证码或口令错误0021",roomIds);
|
||||
CommonExceptionEnum.FRAME_EXCEPTION_COMMON_DATA_OTHER_ERROR.assertListNotNullByName("短信验证码或口令错误0021", roomIds);
|
||||
|
||||
List<BizAssessRoom> assessRoomList = assessjFeignClient.getByIds(roomIds).getData();
|
||||
List<BizAssessRoom> haveList = assessRoomList.stream().filter(a->"2".equals(String.valueOf(a.getStatus()))
|
||||
&&user.getVerificationCode().equals(String.valueOf(a.getVerificationCode()))).distinct().collect(Collectors.toList());
|
||||
log.info("-------haveList-------:"+haveList);
|
||||
if(haveList!=null&&haveList.size()>0){
|
||||
// b = true;
|
||||
}else{
|
||||
CommonExceptionEnum.FRAME_EXCEPTION_COMMON_DATA_OTHER_ERROR.customValidName("验证码或口令错误0022",true);
|
||||
List<BizAssessRoom> haveList = assessRoomList.stream().filter(a -> "2".equals(String.valueOf(a.getStatus()))
|
||||
&& user.getVerificationCode().equals(String.valueOf(a.getVerificationCode()))).distinct().collect(Collectors.toList());
|
||||
log.info("-------haveList-------:" + haveList);
|
||||
if (haveList != null && haveList.size() > 0) {
|
||||
// b = true;
|
||||
} else {
|
||||
CommonExceptionEnum.FRAME_EXCEPTION_COMMON_DATA_OTHER_ERROR.customValidName("验证码或口令错误0022", true);
|
||||
}
|
||||
|
||||
}else{
|
||||
CommonExceptionEnum.FRAME_EXCEPTION_COMMON_DATA_OTHER_ERROR.customValidName("验证码或口令错误0000",true);
|
||||
} else {
|
||||
CommonExceptionEnum.FRAME_EXCEPTION_COMMON_DATA_OTHER_ERROR.customValidName("验证码或口令错误0000", true);
|
||||
}
|
||||
|
||||
log.info("----------rmap------" + rmap);
|
||||
return rmap;
|
||||
}else{
|
||||
} else {
|
||||
return rmap;
|
||||
}
|
||||
}
|
||||
@ -189,28 +184,29 @@ public class EbtpUserInfoServiceImpl implements EbtpUserInfoService {
|
||||
|
||||
/**
|
||||
* 专家面修改
|
||||
*
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Map<String,Object> reSetPassExpeBaseInfo(ExpertUpdateUser user){
|
||||
log.info("身份证解密前:"+user.getIdentityCard());
|
||||
String identityCard = RSA.decrypt(user.getIdentityCard(),privateKey);
|
||||
log.info("身份证解密结果:"+identityCard);
|
||||
log.info("新密码解密前:"+user.getNewPassword());
|
||||
String newPassword = RSA.decrypt(user.getNewPassword(),privateKey);
|
||||
log.info("新密码解密结果:"+newPassword);
|
||||
log.info("旧密码解密前:"+user.getOldPassword());
|
||||
String oldPassword = RSA.decrypt(user.getOldPassword(),privateKey);
|
||||
log.info("新密码解密结果:"+oldPassword);
|
||||
String url = expert_update_url+"?identityCard="+identityCard+"&oldPassword="
|
||||
+oldPassword+"&newPassword="+newPassword+"&newPassword1="+newPassword+"&token="+user.getToken()+"&code="+user.getCode();
|
||||
public Map<String, Object> reSetPassExpeBaseInfo(ExpertUpdateUser user) {
|
||||
log.info("身份证解密前:" + user.getIdentityCard());
|
||||
String identityCard = RSA.decrypt(user.getIdentityCard(), privateKey);
|
||||
log.info("身份证解密结果:" + identityCard);
|
||||
log.info("新密码解密前:" + user.getNewPassword());
|
||||
String newPassword = RSA.decrypt(user.getNewPassword(), privateKey);
|
||||
log.info("新密码解密结果:" + newPassword);
|
||||
log.info("旧密码解密前:" + user.getOldPassword());
|
||||
String oldPassword = RSA.decrypt(user.getOldPassword(), privateKey);
|
||||
log.info("新密码解密结果:" + oldPassword);
|
||||
String url = expert_update_url + "?identityCard=" + identityCard + "&oldPassword="
|
||||
+ oldPassword + "&newPassword=" + newPassword + "&newPassword1=" + newPassword + "&token=" + user.getToken() + "&code=" + user.getCode();
|
||||
String rJson = HttpsUtils.authorizationCodeHttpPost(url);
|
||||
Map<String, Object> rmap = JSON.parseObject(rJson, Map.class);
|
||||
return rmap;
|
||||
}
|
||||
|
||||
public static void main(String args[]){
|
||||
public static void main(String args[]) {
|
||||
Integer s = 2;
|
||||
System.out.println(s.equals(2));
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ server:
|
||||
context-path: /
|
||||
|
||||
spring:
|
||||
main:
|
||||
allow-bean-definition-overriding: true
|
||||
cloud:
|
||||
feign:
|
||||
client:
|
||||
@ -13,7 +15,7 @@ spring:
|
||||
writeTimeout: 35000
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: 127.0.0.1:8848 #192.168.110.109:8848
|
||||
server-addr: 192.168.110.231:8848
|
||||
aop:
|
||||
auto: true #开启spring的aop配置
|
||||
proxy-target-class: true
|
||||
@ -80,7 +82,7 @@ spring:
|
||||
redis:
|
||||
sentinel:
|
||||
master: mymaster
|
||||
nodes: 192.168.110.231:26379 #10.60.161.59:26379, 10.60.161.59:26380, 10.60.161.59:26381
|
||||
nodes: 192.168.110.231:26379
|
||||
password: pass
|
||||
database:
|
||||
sharding: 1
|
||||
@ -149,6 +151,8 @@ hystrix:
|
||||
forceClosed: true
|
||||
|
||||
ribbon:
|
||||
eureka:
|
||||
enabled: false
|
||||
ReadTimeout: 200000 #请求处理的超时时间
|
||||
ConnectTimeout: 200000 #请求连接超时时间
|
||||
MaxAutoRetries: 1 #对当前实例的重试次数
|
||||
|
Reference in New Issue
Block a user