Commit 03e6690a by zhangxingmin

xxl-job

parent ed904f15
package com.yd.email.api.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
\ No newline at end of file
package com.yd.email.api.config;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
@ConditionalOnClass(XxlJobSpringExecutor.class)
public class XxlJobConfig {
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.executor.appname}")
private String appname;
@Value("${xxl.job.executor.port:9999}")
private int port;
@Value("${xxl.job.accessToken:}")
private String accessToken;
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
log.info(">>>>>>>>>>> xxl-job config init. appname: {}, port: {}, accessToken: {}",
appname, port, StringUtils.isNotBlank(accessToken) ? "已配置" : "未配置");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppname(appname);
xxlJobSpringExecutor.setPort(port);
// 设置accessToken
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogRetentionDays(30);
return xxlJobSpringExecutor;
}
}
\ No newline at end of file
...@@ -27,55 +27,4 @@ public class ApiEmailSendController implements ApiEmailSendFeignClient { ...@@ -27,55 +27,4 @@ public class ApiEmailSendController implements ApiEmailSendFeignClient {
public Result<ApiSendEmailResponse> sendEmail(ApiSendEmailRequest request) { public Result<ApiSendEmailResponse> sendEmail(ApiSendEmailRequest request) {
return apiEmailSendService.sendEmail(request); return apiEmailSendService.sendEmail(request);
} }
// @Autowired
// private MailTaskMapper mailTaskMapper;
//
// @Autowired
// private MailRecipientMapper mailRecipientMapper;
//
// @Autowired
// private XxlJobService xxlJobService;
//
// @PostMapping("/send")
// public ResponseResult sendMail(@RequestBody MailSendRequest request) {
// try {
// // 1. 保存邮件任务到数据库
// MailTask mailTask = new MailTask();
// mailTask.setFromAddress(request.getFrom());
// mailTask.setSubject(request.getSubject());
// mailTask.setContent(request.getContent());
// mailTask.setAttachmentPath(request.getAttachmentPath());
// mailTask.setSendTime(request.getSendTime());
// mailTask.setStatus(0);
// mailTaskMapper.insert(mailTask);
//
// Long taskId = mailTask.getId();
//
// // 2. 保存收件人信息
// for (MailSendRequest.Recipient recipient : request.getRecipients()) {
// MailRecipient mailRecipient = new MailRecipient();
// mailRecipient.setTaskId(taskId);
// mailRecipient.setToAddress(recipient.getTo());
// if (recipient.getCc() != null && !recipient.getCc().isEmpty()) {
// mailRecipient.setCcAddresses(String.join(",", recipient.getCc()));
// }
// mailRecipient.setSendStatus(0);
// mailRecipientMapper.insert(mailRecipient);
// }
//
// // 3. 创建XXL-Job定时任务
// String jobId = xxlJobService.addScheduleJob(taskId, request.getSendTime());
//
// return ResponseResult.success("邮件任务创建成功", Map.of(
// "taskId", taskId,
// "jobId", jobId,
// "scheduleTime", request.getSendTime()
// ));
//
// } catch (Exception e) {
// log.error("创建邮件发送任务失败", e);
// return ResponseResult.error("创建任务失败: " + e.getMessage());
// }
// }
} }
\ No newline at end of file
package com.yd.email.api.service.impl; package com.yd.email.api.service.impl;
import com.yd.common.enums.CommonEnum;
import com.yd.common.exception.BusinessException;
import com.yd.common.result.Result; import com.yd.common.result.Result;
import com.yd.common.utils.DateUtil;
import com.yd.common.utils.RandomStringGenerator;
import com.yd.email.api.service.ApiEmailSendService; import com.yd.email.api.service.ApiEmailSendService;
import com.yd.email.api.service.XxlJobService; import com.yd.email.api.service.XxlJobService;
import com.yd.email.feign.enums.EmailTaskStatusEnum;
import com.yd.email.feign.request.ApiSendEmailRequest; import com.yd.email.feign.request.ApiSendEmailRequest;
import com.yd.email.feign.response.ApiSendEmailResponse; import com.yd.email.feign.response.ApiSendEmailResponse;
import com.yd.email.service.model.EmailTask;
import com.yd.email.service.model.EmailTaskRecipients;
import com.yd.email.service.service.IEmailTaskService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/** /**
* 邮件发送实现类 * 邮件发送实现类
...@@ -19,6 +31,9 @@ public class ApiEmailSendServiceImpl implements ApiEmailSendService { ...@@ -19,6 +31,9 @@ public class ApiEmailSendServiceImpl implements ApiEmailSendService {
@Autowired @Autowired
private XxlJobService xxlJobService; private XxlJobService xxlJobService;
@Autowired
private IEmailTaskService iEmailTaskService;
/** /**
* 发送邮件 * 发送邮件
* @param request * @param request
...@@ -26,44 +41,63 @@ public class ApiEmailSendServiceImpl implements ApiEmailSendService { ...@@ -26,44 +41,63 @@ public class ApiEmailSendServiceImpl implements ApiEmailSendService {
*/ */
@Override @Override
public Result<ApiSendEmailResponse> sendEmail(ApiSendEmailRequest request) { public Result<ApiSendEmailResponse> sendEmail(ApiSendEmailRequest request) {
// try { ApiSendEmailResponse response = new ApiSendEmailResponse();
// // 1. 保存邮件任务到数据库 try {
// MailTask mailTask = new MailTask(); //保存邮件任务到数据库
// mailTask.setFromAddress(request.getFrom()); EmailTask mailTask = new EmailTask();
// mailTask.setSubject(request.getSubject()); //邮件任务唯一业务ID
// mailTask.setContent(request.getContent()); mailTask.setTaskBizId(RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_EMAIL_TASK.getCode()));
// mailTask.setAttachmentPath(request.getAttachmentPath()); //任务名称:邮件主题 + 邮件发送任务
// mailTask.setSendTime(request.getSendTime()); mailTask.setTaskName(request.getSubject() + "邮件发送任务");
// mailTask.setStatus(0); //关联发件人唯一业务ID
// mailTaskMapper.insert(mailTask); mailTask.setSenderBizId(request.getSenderBizId());
// //发件人邮箱
// Long taskId = mailTask.getId(); mailTask.setSendEmail(request.getSendEmail());
// //邮件主题
// // 2. 保存收件人信息 mailTask.setSubject(request.getSubject());
// for (MailSendRequest.Recipient recipient : request.getRecipients()) { //邮件内容
// MailRecipient mailRecipient = new MailRecipient(); mailTask.setContent(request.getContent());
// mailRecipient.setTaskId(taskId); //发送邮件的附件路径(多个用分号分隔)
// mailRecipient.setToAddress(recipient.getTo()); mailTask.setAttachmentPath(request.getAttachmentPath());
// if (recipient.getCc() != null && !recipient.getCc().isEmpty()) { //计划发送时间(为空表示立即发送,不为空表示定时发送)
// mailRecipient.setCcAddresses(String.join(",", recipient.getCc())); mailTask.setScheduleTime(request.getScheduleTime());
// } //任务状态:计划发送时间(为空表示立即发送状态为发送中,不为空表示发送状态为定时发送)
// mailRecipient.setSendStatus(0); String taskStatus = !Objects.isNull(request.getScheduleTime()) ? EmailTaskStatusEnum.SCHEDULED.getItemValue() : EmailTaskStatusEnum.SENDING.getItemValue();
// mailRecipientMapper.insert(mailRecipient); mailTask.setStatus(taskStatus);
// } iEmailTaskService.saveOrUpdate(mailTask);
//
// // 3. 创建XXL-Job定时任务 //邮件任务唯一业务ID
// String jobId = xxlJobService.addScheduleJob(taskId, request.getSendTime()); String taskBizId = mailTask.getTaskBizId();
//
// return ResponseResult.success("邮件任务创建成功", Map.of( // 保存收件人信息
// "taskId", taskId, if (!CollectionUtils.isEmpty(request.getRecipientsDtoList())) {
// "jobId", jobId, List<EmailTaskRecipients> recipientsList = request.getRecipientsDtoList()
// "scheduleTime", request.getSendTime() .stream().map(dto -> {
// )); EmailTaskRecipients mailRecipient = new EmailTaskRecipients();
// //邮件任务唯一业务ID
// } catch (Exception e) { mailRecipient.setTaskBizId(taskBizId);
// log.error("创建邮件发送任务失败", e); mailRecipient.setStatus(taskStatus);
// return ResponseResult.error("创建任务失败: " + e.getMessage()); mailRecipient.setContactBizId(dto.getContactBizId());
// } mailRecipient.setReceiveEmail(dto.getReceiveEmail());
return null; //抄送人邮箱(多个用分号分隔)
mailRecipient.setCcEmail(!CollectionUtils.isEmpty(dto.getCcEmailList()) ? String.join(";",dto.getCcEmailList()) : "");
return mailRecipient;
}).collect(Collectors.toList());
}
//计划发送时间(为空表示立即发送,不为空表示定时发送)
String jobId = "";
if (!Objects.isNull(request.getScheduleTime())) {
//创建XXL-Job定时任务
jobId = xxlJobService.addScheduleJob(taskBizId, DateUtil.convertDateByLocalDateTime(request.getScheduleTime()));
}
response.setJobId(jobId);
response.setScheduleTime(request.getScheduleTime());
response.setTaskBizId(taskBizId);
return Result.success(response);
} catch (Exception e) {
log.error("创建邮件发送任务失败", e);
throw new BusinessException("创建邮件发送任务失败");
}
} }
} }
package com.yd.email.api.service.impl; package com.yd.email.api.service.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yd.email.api.service.XxlJobService; import com.yd.email.api.service.XxlJobService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity; import org.springframework.http.*;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
...@@ -16,121 +19,299 @@ import java.util.Date; ...@@ -16,121 +19,299 @@ import java.util.Date;
@Slf4j @Slf4j
public class XxlJobServiceImpl implements XxlJobService { public class XxlJobServiceImpl implements XxlJobService {
// 从配置文件中注入XXL-Job管理端的地址
@Value("${xxl.job.admin.addresses}") @Value("${xxl.job.admin.addresses}")
private String adminAddresses; private String adminAddresses;
// 从配置文件中注入执行器的应用名称
@Value("${xxl.job.executor.appname}") @Value("${xxl.job.executor.appname}")
private String appName; private String appName;
// 自动注入Spring的RestTemplate用于HTTP请求 @Value("${xxl.job.admin.username:admin}")
private String adminUsername;
@Value("${xxl.job.admin.password:123456}")
private String adminPassword;
@Autowired @Autowired
private RestTemplate restTemplate; private RestTemplate restTemplate;
// 添加认证Cookie存储
private String authCookie;
// 添加API路径常量
private static final String API_LOGIN = "/login";
private static final String API_JOB_GROUP_LIST = "/jobgroup/pageList"; // 可能是这个路径
private static final String API_JOB_ADD = "/jobinfo/add";
private static final String API_JOB_START = "/jobinfo/start";
/** /**
* 添加定时任务 * 登录XXL-Job获取认证Cookie
* @param taskBizId 邮件任务ID
* @param scheduleTime 计划执行时间
* @return 返回创建的jobId
*/ */
private boolean loginXxlJob() {
try {
String loginUrl = adminAddresses + "/login";
MultiValueMap<String, String> loginParams = new LinkedMultiValueMap<>();
loginParams.add("userName", adminUsername);
loginParams.add("password", adminPassword);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> loginEntity = new HttpEntity<>(loginParams, headers);
ResponseEntity<String> loginResponse = restTemplate.postForEntity(loginUrl, loginEntity, String.class);
if (loginResponse.getStatusCode().is2xxSuccessful()) {
// 从响应头中获取Cookie
HttpHeaders responseHeaders = loginResponse.getHeaders();
if (responseHeaders.containsKey("Set-Cookie")) {
authCookie = responseHeaders.getFirst("Set-Cookie");
log.info("XXL-Job登录成功");
return true;
}
}
log.error("XXL-Job登录失败: {}", loginResponse.getStatusCode());
return false;
} catch (Exception e) {
log.error("XXL-Job登录异常", e);
return false;
}
}
@Override @Override
public String addScheduleJob(String taskBizId, Date scheduleTime) { public String addScheduleJob(String taskBizId, Date scheduleTime) {
// 使用try-catch捕获可能出现的异常
try { try {
// 将Date时间转换为Cron表达式(在指定时间执行一次) // 登录认证
if (authCookie == null && !loginXxlJob()) {
throw new RuntimeException("XXL-Job认证失败");
}
// 先获取执行器ID
Integer jobGroupId = getExecutorGroupId(appName);
if (jobGroupId == null) {
throw new RuntimeException("获取执行器ID失败,执行器可能未注册: " + appName);
}
String cronExpression = convertDateToCron(scheduleTime); String cronExpression = convertDateToCron(scheduleTime);
// 创建MultiValueMap用于存储HTTP请求参数
MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
// 设置执行器组ID,默认为1 params.add("jobGroup", jobGroupId.toString()); // 使用动态获取的执行器ID
params.add("jobGroup", "1");
// 设置任务描述,包含任务ID用于标识
params.add("jobDesc", "邮件发送任务-" + taskBizId); params.add("jobDesc", "邮件发送任务-" + taskBizId);
// 设置任务作者
params.add("author", "system"); params.add("author", "system");
// 设置调度类型为CRON表达式
params.add("scheduleType", "CRON"); params.add("scheduleType", "CRON");
// 设置CRON表达式
params.add("scheduleConf", cronExpression); params.add("scheduleConf", cronExpression);
// 设置任务模式为BEAN模式
params.add("glueType", "BEAN"); params.add("glueType", "BEAN");
// 设置执行器处理器名称(对应@XxlJob注解的值)
params.add("executorHandler", "mailSendJobHandler"); params.add("executorHandler", "mailSendJobHandler");
// 设置任务参数(传递邮件任务ID)
params.add("executorParam", taskBizId); params.add("executorParam", taskBizId);
// 设置路由策略为第一个
params.add("executorRouteStrategy", "FIRST"); params.add("executorRouteStrategy", "FIRST");
// 设置调度过期策略为忽略
params.add("misfireStrategy", "DO_NOTHING"); params.add("misfireStrategy", "DO_NOTHING");
// 设置阻塞处理策略为串行执行
params.add("executorBlockStrategy", "SERIAL_EXECUTION"); params.add("executorBlockStrategy", "SERIAL_EXECUTION");
// 构建完整的API请求URL
String url = adminAddresses + "/jobinfo/add"; String url = adminAddresses + "/jobinfo/add";
// 发送POST请求到XXL-Job管理端创建任务
ResponseEntity<String> response = restTemplate.postForEntity(url, params, String.class);
// 检查HTTP响应状态码是否为2xx成功 HttpHeaders headers = new HttpHeaders();
headers.add("Cookie", authCookie);
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
if (response.getStatusCode().is2xxSuccessful()) { if (response.getStatusCode().is2xxSuccessful()) {
// 记录成功日志 String jobId = extractJobId(response.getBody());
log.info("创建XXL-Job任务成功, taskId: {}", taskBizId); log.info("创建XXL-Job任务成功, taskId: {}, jobId: {}", taskBizId, jobId);
// 从响应体中提取jobId
return extractJobId(response.getBody()); // 创建成功后自动启动任务
if (startJob(jobId)) {
log.info("自动启动XXL-Job任务成功, jobId: {}", jobId);
} else {
log.warn("自动启动XXL-Job任务失败, jobId: {}", jobId);
}
return jobId;
} else { } else {
// 如果HTTP请求失败,抛出运行时异常
throw new RuntimeException("XXL-Job API调用失败: " + response.getStatusCode()); throw new RuntimeException("XXL-Job API调用失败: " + response.getStatusCode());
} }
} catch (Exception e) { } catch (Exception e) {
// 捕获所有异常并记录错误日志
log.error("创建XXL-Job任务失败", e); log.error("创建XXL-Job任务失败", e);
// 抛出包装后的运行时异常
throw new RuntimeException("创建定时任务失败: " + e.getMessage()); throw new RuntimeException("创建定时任务失败: " + e.getMessage());
} }
} }
// 添加版本检测方法
private void checkApiPaths() {
log.info("正在检测XXL-Job Admin API路径...");
// 可以添加API路径检测逻辑
}
/** /**
* 将Date转换为Cron表达式(只执行一次) * 根据执行器AppName获取执行器ID - 修复版本
* @param date 需要转换的日期时间
* @return 返回对应的Cron表达式
*/ */
private Integer getExecutorGroupId(String appName) {
try {
// 尝试不同的API路径
String[] possiblePaths = {
"/jobgroup/pageList", // XXL-Job 2.3.0+
"/jobgroup/list", // 旧版本
"/jobgroup/listByApp" // 其他可能路径
};
for (String path : possiblePaths) {
Integer groupId = tryGetExecutorGroupId(appName, path);
if (groupId != null) {
log.info("成功获取执行器ID: {},使用的API路径: {}", groupId, path);
return groupId;
}
}
log.error("所有API路径尝试均失败,无法获取执行器ID");
return null;
} catch (Exception e) {
log.error("获取执行器ID异常", e);
return null;
}
}
private Integer tryGetExecutorGroupId(String appName, String apiPath) {
try {
String url = adminAddresses + apiPath;
log.info("尝试API路径: {}", url);
HttpHeaders headers = new HttpHeaders();
headers.add("Cookie", authCookie);
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// 对于分页接口可能需要添加参数
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
if (apiPath.equals("/jobgroup/pageList")) {
params.add("start", "0");
params.add("length", "100");
params.add("appname", appName);
}
HttpEntity<?> entity = params.isEmpty() ?
new HttpEntity<>(headers) :
new HttpEntity<>(params, headers);
ResponseEntity<String> response = restTemplate.exchange(
url, HttpMethod.POST, entity, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(response.getBody());
log.info("API响应: {}", response.getBody());
// 解析响应,根据不同的API路径处理
if (jsonNode.has("data")) {
JsonNode dataNode = jsonNode.get("data");
if (dataNode.isArray()) {
// 数组格式的响应
for (JsonNode item : dataNode) {
if (item.has("appname") && appName.equals(item.get("appname").asText())) {
return item.get("id").asInt();
}
}
} else if (dataNode.has("data")) {
// 分页格式的响应
JsonNode dataArray = dataNode.get("data");
if (dataArray.isArray()) {
for (JsonNode item : dataArray) {
if (item.has("appname") && appName.equals(item.get("appname").asText())) {
return item.get("id").asInt();
}
}
}
}
}
}
return null;
} catch (Exception e) {
log.warn("API路径 {} 尝试失败: {}", apiPath, e.getMessage());
return null;
}
}
/**
* 启动XXL-Job任务
*/
private boolean startJob(String jobId) {
try {
String startUrl = adminAddresses + "/jobinfo/start";
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("id", jobId);
HttpHeaders headers = new HttpHeaders();
headers.add("Cookie", authCookie);
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);
ResponseEntity<String> response = restTemplate.exchange(startUrl, HttpMethod.POST, entity, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
log.info("启动任务成功, jobId: {}", jobId);
return true;
} else {
log.error("启动任务失败, jobId: {}, 状态码: {}, 响应: {}", jobId, response.getStatusCode(), response.getBody());
return false;
}
} catch (Exception e) {
log.error("启动任务异常, jobId: {}", jobId, e);
return false;
}
}
private String convertDateToCron(Date date) { private String convertDateToCron(Date date) {
// 创建Calendar实例用于时间解析
Calendar calendar = Calendar.getInstance(); Calendar calendar = Calendar.getInstance();
// 设置Calendar的时间为传入的Date
calendar.setTime(date); calendar.setTime(date);
// 获取秒数(0-59)
int second = calendar.get(Calendar.SECOND); int second = calendar.get(Calendar.SECOND);
// 获取分钟数(0-59)
int minute = calendar.get(Calendar.MINUTE); int minute = calendar.get(Calendar.MINUTE);
// 获取小时数(24小时制,0-23)
int hour = calendar.get(Calendar.HOUR_OF_DAY); int hour = calendar.get(Calendar.HOUR_OF_DAY);
// 获取日期(1-31)
int day = calendar.get(Calendar.DAY_OF_MONTH); int day = calendar.get(Calendar.DAY_OF_MONTH);
// 获取月份(Calendar的月份从0开始,所以需要+1)
int month = calendar.get(Calendar.MONTH) + 1; int month = calendar.get(Calendar.MONTH) + 1;
// 获取年份
int year = calendar.get(Calendar.YEAR); int year = calendar.get(Calendar.YEAR);
// 格式化Cron表达式:秒 分 时 日 月 周 年
// 注意:Cron表达式通常不包含年份,但这里为了单次执行包含了年份
return String.format("%d %d %d %d %d ? %d", second, minute, hour, day, month, year); return String.format("%d %d %d %d %d ? %d", second, minute, hour, day, month, year);
} }
/** /**
* 从XXL-Job的响应体中提取jobId * 从XXL-Job响应中提取真实的jobId
* @param responseBody HTTP响应体内容 * 响应格式: {"code":200,"msg":null,"content":"6"}
* @return 返回提取的jobId */
/**
* 从XXL-Job响应中提取真实的jobId
* 响应格式: {"code":200,"msg":null,"content":"6"}
*/ */
private String extractJobId(String responseBody) { private String extractJobId(String responseBody) {
// 注意:这里需要根据XXL-Job API的实际返回格式进行解析 try {
// 目前使用时间戳生成临时jobId,实际应该解析JSON响应 log.info("XXL-Job响应: {}", responseBody);
// XXL-Job通常返回JSON格式:{"code":200,"msg":null,"content":123}
// 其中content就是jobId if (responseBody == null || responseBody.trim().isEmpty()) {
log.warn("响应体为空");
return String.valueOf(System.currentTimeMillis());
}
// 使用JSON解析器正确解析响应
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(responseBody);
// 临时方案:生成基于时间戳的jobId if (jsonNode.has("code") && jsonNode.get("code").asInt() == 200) {
return "job_" + System.currentTimeMillis(); if (jsonNode.has("content")) {
String jobId = jsonNode.get("content").asText();
log.info("提取到真实jobId: {}", jobId);
return jobId;
}
}
log.warn("无法从响应中解析jobId,响应: {}", responseBody);
return String.valueOf(System.currentTimeMillis());
} catch (Exception e) {
log.error("解析jobId异常", e);
return String.valueOf(System.currentTimeMillis());
}
} }
} }
\ No newline at end of file
...@@ -44,7 +44,7 @@ spring: ...@@ -44,7 +44,7 @@ spring:
# 配置中心 # 配置中心
config: config:
# 命名空间id(此处不用public,因public初始化的空间, id为空) # 命名空间id(此处不用public,因public初始化的空间, id为空)
namespace: b3b01715-eb85-4242-992a-5aff03d864d4 namespace: 8fbea9a4-b626-46de-a4e6-9d23f6609318
# nacos的ip地址和端口 # nacos的ip地址和端口
server-addr: 139.224.145.34:8848 server-addr: 139.224.145.34:8848
# 这个就表示 在我们nacos命名空间id为 dev中 有一个data-id 为 demo-service.yml 的配置文件 读取这个里面的配置 # 这个就表示 在我们nacos命名空间id为 dev中 有一个data-id 为 demo-service.yml 的配置文件 读取这个里面的配置
......
package com.yd.email.feign.dto;
import lombok.Data;
import java.util.List;
@Data
public class ApiEmailTaskRecipientsDto {
/**
* 收件人唯一业务ID(联系人唯一业务ID)有就传值,没有就不传值
*/
private String contactBizId;
/**
* 收件人邮箱(单个)
*/
private String receiveEmail;
/**
* 抄送人邮箱(数组)
*/
private List<String> ccEmailList;
}
package com.yd.email.feign.request; package com.yd.email.feign.request;
import com.yd.email.feign.dto.ApiEmailTaskRecipientsDto;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime;
import java.util.List;
@Data @Data
public class ApiSendEmailRequest { public class ApiSendEmailRequest {
/** /**
* 发件人唯一业务ID
*/
private String senderBizId;
/**
* 发件人邮箱(单个) * 发件人邮箱(单个)
*/ */
@NotBlank(message = "发件人邮箱不能为空") @NotBlank(message = "发件人邮箱不能为空")
...@@ -18,4 +25,25 @@ public class ApiSendEmailRequest { ...@@ -18,4 +25,25 @@ public class ApiSendEmailRequest {
*/ */
@NotBlank(message = "邮件主题不能为空") @NotBlank(message = "邮件主题不能为空")
private String subject; private String subject;
/**
* 邮件内容
*/
@NotBlank(message = "邮件内容不能为空")
private String content;
/**
* 计划发送时间(为空表示立即发送,不为空表示定时发送)
*/
private LocalDateTime scheduleTime;
/**
* 发送邮件的附件路径(多个用分号分隔)
*/
private String attachmentPath;
/**
* 收件人列表信息
*/
private List<ApiEmailTaskRecipientsDto> recipientsDtoList;
} }
...@@ -2,6 +2,17 @@ package com.yd.email.feign.response; ...@@ -2,6 +2,17 @@ package com.yd.email.feign.response;
import lombok.Data; import lombok.Data;
import java.time.LocalDateTime;
@Data @Data
public class ApiSendEmailResponse { public class ApiSendEmailResponse {
private String jobId;
private String taskBizId;
/**
* 计划发送时间(为空表示立即发送,不为空表示定时发送)
*/
private LocalDateTime scheduleTime;
} }
...@@ -79,13 +79,13 @@ public class EmailTask implements Serializable { ...@@ -79,13 +79,13 @@ public class EmailTask implements Serializable {
private String status; private String status;
/** /**
* 计划发送时间(为空表示立即发送) * 计划发送时间(为空表示立即发送,不为空表示定时发送
*/ */
@TableField("schedule_time") @TableField("schedule_time")
private LocalDateTime scheduleTime; private LocalDateTime scheduleTime;
/** /**
* 实际发送时间 * 实际发送时间(发送成功的时间)
*/ */
@TableField("send_time") @TableField("send_time")
private LocalDateTime sendTime; private LocalDateTime sendTime;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment