Commit a3153b21 by zhangxingmin

Merge remote-tracking branch 'origin/dev_zxm' into test

parents fd1733e7 03e6690a
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
package com.yd.email.api.controller;
import com.yd.common.result.Result;
import com.yd.email.api.service.ApiEmailSendService;
import com.yd.email.feign.client.ApiEmailSendFeignClient;
import com.yd.email.feign.request.ApiSendEmailRequest;
import com.yd.email.feign.response.ApiSendEmailResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/email")
@Slf4j
public class ApiEmailSendController implements ApiEmailSendFeignClient {
@Autowired
private ApiEmailSendService apiEmailSendService;
/**
* 发送邮件
* @param request
* @return
*/
@Override
public Result<ApiSendEmailResponse> sendEmail(ApiSendEmailRequest request) {
return apiEmailSendService.sendEmail(request);
}
}
\ No newline at end of file
package com.yd.email.api.handler;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.yd.email.api.service.ApiEmailService;
import com.yd.email.feign.enums.EmailTaskStatusEnum;
import com.yd.email.service.model.EmailTask;
import com.yd.email.service.model.EmailTaskRecipients;
import com.yd.email.service.service.IEmailTaskRecipientsService;
import com.yd.email.service.service.IEmailTaskService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* 邮件发送任务处理器 - XXL-Job定时任务执行器
* 使用@XxlJob注解方式
*/
@Component
@Slf4j
public class MailSendJobHandler {
@Autowired
private IEmailTaskService iEmailTaskService;
@Autowired
private IEmailTaskRecipientsService iEmailTaskRecipientsService;
@Autowired
private ApiEmailService apiEmailService;
/**
* XXL-Job任务执行入口方法
*/
@XxlJob("mailSendJobHandler")
public void execute() throws Exception {
// 从XXL-Job参数中获取任务ID
String param = XxlJobHelper.getJobParam();
// 记录任务开始日志
log.info("开始执行邮件发送任务,参数: {}", param);
// 解析任务参数(参数为邮件任务业务ID)
String taskBizId = param;
// 根据邮件任务业务ID查询邮件任务信息
EmailTask emailTask = iEmailTaskService.queryOne(taskBizId);
// 检查邮件任务是否存在
if (Objects.isNull(emailTask)) {
// 记录错误并返回
XxlJobHelper.log("邮件任务不存在: " + taskBizId);
XxlJobHelper.handleFail("邮件任务不存在");
return;
}
// 更新任务状态为发送中
emailTask.setStatus(EmailTaskStatusEnum.SENDING.getItemValue());
iEmailTaskService.saveOrUpdate(emailTask);
try {
// 查询该任务下的所有收件人信息
List<EmailTaskRecipients> recipients = iEmailTaskRecipientsService.queryList(taskBizId);
// 初始化成功和失败计数器
int successCount = 0;
int failCount = 0;
// 遍历所有收件人,逐个发送邮件
for (EmailTaskRecipients recipient : recipients) {
try {
// 处理抄送人列表:将数据库中的逗号分隔字符串转换为List
List<String> ccList = StringUtils.isNotBlank(recipient.getCcEmail()) ?
Arrays.asList(recipient.getCcEmail().split(",")) :
new ArrayList<>();
// 调用邮件服务发送邮件
apiEmailService.sendMail(
emailTask.getSendEmail(), // 发件人地址
recipient.getReceiveEmail(), // 收件人地址
ccList, // 抄送人列表
emailTask.getSubject(), // 邮件主题
emailTask.getContent(), // 邮件内容
emailTask.getAttachmentPath() // 附件路径
);
// 发送成功:更新收件人状态为成功
recipient.setStatus(EmailTaskStatusEnum.SUCCESSFUL.getItemValue());
recipient.setSendTime(LocalDateTime.now()); // 记录实际发送时间
iEmailTaskRecipientsService.saveOrUpdate(recipient);
successCount++; // 成功计数加1
} catch (Exception e) {
// 发送失败:记录错误日志
log.error("发送邮件失败: {}", recipient.getReceiveEmail(), e);
// 更新收件人状态为失败
recipient.setStatus(EmailTaskStatusEnum.FAILED.getItemValue());
recipient.setErrorMsg(e.getMessage()); // 保存错误信息
iEmailTaskRecipientsService.saveOrUpdate(recipient);
failCount++; // 失败计数加1
}
}
// 根据发送结果更新邮件任务状态
emailTask.setStatus(failCount == 0 ?
EmailTaskStatusEnum.ALL_SUCCESSFUL.getItemValue() :
EmailTaskStatusEnum.PARTIAL_FAILURE.getItemValue());
iEmailTaskService.saveOrUpdate(emailTask);
// 记录任务完成日志
log.info("邮件发送任务完成: 成功{}个, 失败{}个", successCount, failCount);
// 设置任务执行结果
if (failCount == 0) {
XxlJobHelper.handleSuccess("发送完成: 成功" + successCount + "个");
} else {
XxlJobHelper.handleSuccess("发送完成: 成功" + successCount + "个, 失败" + failCount + "个");
}
} catch (Exception e) {
// 任务执行过程中发生异常
log.error("邮件发送任务执行异常", e);
// 更新任务状态为全部发送失败
emailTask.setStatus(EmailTaskStatusEnum.ALL_FAILED.getItemValue());
iEmailTaskService.saveOrUpdate(emailTask);
// 返回任务执行异常信息
XxlJobHelper.handleFail("任务执行异常: " + e.getMessage());
}
}
}
\ No newline at end of file
package com.yd.email.api.service;
import com.yd.common.result.Result;
import com.yd.email.feign.request.ApiSendEmailRequest;
import com.yd.email.feign.response.ApiSendEmailResponse;
public interface ApiEmailSendService {
Result<ApiSendEmailResponse> sendEmail(ApiSendEmailRequest request);
}
package com.yd.email.api.service;
import java.util.List;
public interface ApiEmailService {
// 发送邮件的主要方法
void sendMail(String from, String to, List<String> cc, String subject,
String content, String attachmentPath) throws Exception;
}
package com.yd.email.api.service;
import java.util.Date;
public interface XxlJobService {
String addScheduleJob(String taskBizId, Date scheduleTime);
}
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.utils.DateUtil;
import com.yd.common.utils.RandomStringGenerator;
import com.yd.email.api.service.ApiEmailSendService;
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.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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 邮件发送实现类
*/
@Service
@Slf4j
public class ApiEmailSendServiceImpl implements ApiEmailSendService {
@Autowired
private XxlJobService xxlJobService;
@Autowired
private IEmailTaskService iEmailTaskService;
/**
* 发送邮件
* @param request
* @return
*/
@Override
public Result<ApiSendEmailResponse> sendEmail(ApiSendEmailRequest request) {
ApiSendEmailResponse response = new ApiSendEmailResponse();
try {
//保存邮件任务到数据库
EmailTask mailTask = new EmailTask();
//邮件任务唯一业务ID
mailTask.setTaskBizId(RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_EMAIL_TASK.getCode()));
//任务名称:邮件主题 + 邮件发送任务
mailTask.setTaskName(request.getSubject() + "邮件发送任务");
//关联发件人唯一业务ID
mailTask.setSenderBizId(request.getSenderBizId());
//发件人邮箱
mailTask.setSendEmail(request.getSendEmail());
//邮件主题
mailTask.setSubject(request.getSubject());
//邮件内容
mailTask.setContent(request.getContent());
//发送邮件的附件路径(多个用分号分隔)
mailTask.setAttachmentPath(request.getAttachmentPath());
//计划发送时间(为空表示立即发送,不为空表示定时发送)
mailTask.setScheduleTime(request.getScheduleTime());
//任务状态:计划发送时间(为空表示立即发送状态为发送中,不为空表示发送状态为定时发送)
String taskStatus = !Objects.isNull(request.getScheduleTime()) ? EmailTaskStatusEnum.SCHEDULED.getItemValue() : EmailTaskStatusEnum.SENDING.getItemValue();
mailTask.setStatus(taskStatus);
iEmailTaskService.saveOrUpdate(mailTask);
//邮件任务唯一业务ID
String taskBizId = mailTask.getTaskBizId();
// 保存收件人信息
if (!CollectionUtils.isEmpty(request.getRecipientsDtoList())) {
List<EmailTaskRecipients> recipientsList = request.getRecipientsDtoList()
.stream().map(dto -> {
EmailTaskRecipients mailRecipient = new EmailTaskRecipients();
//邮件任务唯一业务ID
mailRecipient.setTaskBizId(taskBizId);
mailRecipient.setStatus(taskStatus);
mailRecipient.setContactBizId(dto.getContactBizId());
mailRecipient.setReceiveEmail(dto.getReceiveEmail());
//抄送人邮箱(多个用分号分隔)
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;
import com.yd.email.api.service.ApiEmailService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.io.File;
import java.util.Date;
import java.util.List;
import java.util.Properties;
/**
* 邮件服务实现类
*/
@Service
@Slf4j
public class ApiEmailServiceImpl implements ApiEmailService {
@Value("${spring.mail.host}") // 从配置文件中注入邮件服务器主机地址
private String host;
@Value("${spring.mail.username}") // 从配置文件中注入发件人用户名
private String username;
@Value("${spring.mail.password}") // 从配置文件中注入发件人密码
private String password;
// @Autowired // 自动注入邮件任务Mapper(数据库操作接口)
// private MailTaskMapper mailTaskMapper;
//
// @Autowired // 自动注入邮件收件人Mapper(数据库操作接口)
// private MailRecipientMapper mailRecipientMapper;
// 发送邮件的主要方法
@Override
public void sendMail(String from, String to, List<String> cc, String subject,
String content, String attachmentPath) throws Exception {
// 创建邮件配置属性对象
Properties props = new Properties();
// 设置SMTP需要身份验证
props.put("mail.smtp.auth", "true");
// 启用TLS加密传输
props.put("mail.smtp.starttls.enable", "true");
// 设置邮件服务器主机名
props.put("mail.smtp.host", host);
// 设置邮件服务器端口号(587是TLS标准端口)
props.put("mail.smtp.port", "587");
// 创建邮件会话对象,传入配置和认证器
Session session = Session.getInstance(props, new Authenticator() {
// 重写获取密码认证的方法
protected PasswordAuthentication getPasswordAuthentication() {
// 返回用户名密码认证对象
return new PasswordAuthentication(username, password);
}
});
// 使用try-catch块处理邮件发送异常
try {
// 创建MIME类型邮件消息对象
Message message = new MimeMessage(session);
// 设置发件人地址
message.setFrom(new InternetAddress(from));
// 设置收件人地址(支持多个收件人,用逗号分隔)
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
// 判断是否有抄送人
if (cc != null && !cc.isEmpty()) {
// 将抄送人列表转换为逗号分隔的字符串
String ccAddresses = String.join(",", cc);
// 设置抄送人地址
message.setRecipients(Message.RecipientType.CC, InternetAddress.parse(ccAddresses));
}
// 设置邮件主题
message.setSubject(subject);
// 设置邮件发送时间(当前时间)
message.setSentDate(new Date());
// 创建邮件正文部分
MimeBodyPart messageBodyPart = new MimeBodyPart();
// 设置正文内容,指定HTML格式和UTF-8编码
messageBodyPart.setContent(content, "text/html;charset=utf-8");
// 创建多部分内容容器(用于组合正文和附件)
Multipart multipart = new MimeMultipart();
// 将正文部分添加到多部分容器中
multipart.addBodyPart(messageBodyPart);
// 判断是否有附件路径
if (StringUtils.isNotBlank(attachmentPath)) {
// 创建附件部分
MimeBodyPart attachmentPart = new MimeBodyPart();
// 创建文件数据源
DataSource source = new FileDataSource(attachmentPath);
// 设置附件的数据处理器
attachmentPart.setDataHandler(new DataHandler(source));
// 设置附件文件名(使用原文件名)
attachmentPart.setFileName(new File(attachmentPath).getName());
// 将附件部分添加到多部分容器中
multipart.addBodyPart(attachmentPart);
}
// 将多部分内容设置为邮件的完整内容
message.setContent(multipart);
// 发送邮件
Transport.send(message);
// 记录成功日志
log.info("邮件发送成功: {} -> {}", from, to);
} catch (MessagingException e) {
// 记录失败日志,包含异常信息
log.error("邮件发送失败: {} -> {}", from, to, e);
// 抛出运行时异常
throw new RuntimeException("邮件发送失败: " + e.getMessage());
}
}
}
\ No newline at end of file
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 lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.util.Calendar;
import java.util.Date;
@Service
@Slf4j
public class XxlJobServiceImpl implements XxlJobService {
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.executor.appname}")
private String appName;
@Value("${xxl.job.admin.username:admin}")
private String adminUsername;
@Value("${xxl.job.admin.password:123456}")
private String adminPassword;
@Autowired
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
*/
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
public String addScheduleJob(String taskBizId, Date scheduleTime) {
try {
// 登录认证
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);
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("jobGroup", jobGroupId.toString()); // 使用动态获取的执行器ID
params.add("jobDesc", "邮件发送任务-" + taskBizId);
params.add("author", "system");
params.add("scheduleType", "CRON");
params.add("scheduleConf", cronExpression);
params.add("glueType", "BEAN");
params.add("executorHandler", "mailSendJobHandler");
params.add("executorParam", taskBizId);
params.add("executorRouteStrategy", "FIRST");
params.add("misfireStrategy", "DO_NOTHING");
params.add("executorBlockStrategy", "SERIAL_EXECUTION");
String url = adminAddresses + "/jobinfo/add";
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()) {
String jobId = extractJobId(response.getBody());
log.info("创建XXL-Job任务成功, taskId: {}, jobId: {}", taskBizId, jobId);
// 创建成功后自动启动任务
if (startJob(jobId)) {
log.info("自动启动XXL-Job任务成功, jobId: {}", jobId);
} else {
log.warn("自动启动XXL-Job任务失败, jobId: {}", jobId);
}
return jobId;
} else {
throw new RuntimeException("XXL-Job API调用失败: " + response.getStatusCode());
}
} catch (Exception e) {
log.error("创建XXL-Job任务失败", e);
throw new RuntimeException("创建定时任务失败: " + e.getMessage());
}
}
// 添加版本检测方法
private void checkApiPaths() {
log.info("正在检测XXL-Job Admin API路径...");
// 可以添加API路径检测逻辑
}
/**
* 根据执行器AppName获取执行器ID - 修复版本
*/
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) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
int second = calendar.get(Calendar.SECOND);
int minute = calendar.get(Calendar.MINUTE);
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int day = calendar.get(Calendar.DAY_OF_MONTH);
int month = calendar.get(Calendar.MONTH) + 1;
int year = calendar.get(Calendar.YEAR);
return String.format("%d %d %d %d %d ? %d", second, minute, hour, day, month, year);
}
/**
* 从XXL-Job响应中提取真实的jobId
* 响应格式: {"code":200,"msg":null,"content":"6"}
*/
/**
* 从XXL-Job响应中提取真实的jobId
* 响应格式: {"code":200,"msg":null,"content":"6"}
*/
private String extractJobId(String responseBody) {
try {
log.info("XXL-Job响应: {}", responseBody);
if (responseBody == null || responseBody.trim().isEmpty()) {
log.warn("响应体为空");
return String.valueOf(System.currentTimeMillis());
}
// 使用JSON解析器正确解析响应
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(responseBody);
if (jsonNode.has("code") && jsonNode.get("code").asInt() == 200) {
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:
# 配置中心
config:
# 命名空间id(此处不用public,因public初始化的空间, id为空)
namespace: b3b01715-eb85-4242-992a-5aff03d864d4
namespace: 8fbea9a4-b626-46de-a4e6-9d23f6609318
# nacos的ip地址和端口
server-addr: 139.224.145.34:8848
# 这个就表示 在我们nacos命名空间id为 dev中 有一个data-id 为 demo-service.yml 的配置文件 读取这个里面的配置
......
......@@ -27,11 +27,19 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- 邮箱依赖 -->
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>${email.version}</version>
</dependency>
<!-- XXL-Job 核心依赖 -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
</dependency>
</dependencies>
</project>
package com.yd.email.feign.client;
import com.yd.common.result.Result;
import com.yd.email.feign.fallback.ApiEmailSendFeignFallbackFactory;
import com.yd.email.feign.request.ApiSendEmailRequest;
import com.yd.email.feign.response.ApiSendEmailResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* 邮箱服务-邮件发送Feign客户端
*/
@FeignClient(name = "yd-email-api", fallbackFactory = ApiEmailSendFeignFallbackFactory.class)
public interface ApiEmailSendFeignClient {
/**
* 发送邮件
* @param request
* @return
*/
@PostMapping("/send")
Result<ApiSendEmailResponse> sendEmail(@Validated @RequestBody ApiSendEmailRequest request);
}
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.enums;
/**
* 邮件任务状态枚举
*/
public enum EmailTaskStatusEnum {
PENDING("待发送","PENDING"),
SENDING("发送中","SENDING"),
SUCCESSFUL("发送成功","SUCCESSFUL"),
FAILED("发送失败","FAILED"),
CANCELLED("已取消","CANCELLED"),
SCHEDULED("已定时","SCHEDULED"),
ALL_SUCCESSFUL("全部发送成功","ALL_SUCCESSFUL"),
ALL_FAILED("全部发送失败","ALL_FAILED"),
PARTIALLY_SUCCESSFUL("部分发送成功","PARTIALLY_SUCCESSFUL"),
PARTIAL_FAILURE("部分发送失败","PARTIAL_FAILURE"),
;
//字典项标签(名称)
private String itemLabel;
//字典项值
private String itemValue;
//构造函数
EmailTaskStatusEnum(String itemLabel, String itemValue) {
this.itemLabel = itemLabel;
this.itemValue = itemValue;
}
public String getItemLabel() {
return itemLabel;
}
public String getItemValue() {
return itemValue;
}
}
package com.yd.email.feign.fallback;
import com.yd.common.result.Result;
import com.yd.email.feign.client.ApiEmailSendFeignClient;
import com.yd.email.feign.request.ApiSendEmailRequest;
import com.yd.email.feign.response.ApiSendEmailResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* 邮箱服务-邮件发送Feign降级处理
*/
@Slf4j
@Component
public class ApiEmailSendFeignFallbackFactory implements FallbackFactory<ApiEmailSendFeignClient> {
@Override
public ApiEmailSendFeignClient create(Throwable cause) {
return new ApiEmailSendFeignClient() {
@Override
public Result<ApiSendEmailResponse> sendEmail(ApiSendEmailRequest request) {
return null;
}
};
}
}
package com.yd.email.feign.request;
import com.yd.email.feign.dto.ApiEmailTaskRecipientsDto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class ApiSendEmailRequest {
/**
* 发件人唯一业务ID
*/
private String senderBizId;
/**
* 发件人邮箱(单个)
*/
@NotBlank(message = "发件人邮箱不能为空")
private String sendEmail;
/**
* 邮件主题
*/
@NotBlank(message = "邮件主题不能为空")
private String subject;
/**
* 邮件内容
*/
@NotBlank(message = "邮件内容不能为空")
private String content;
/**
* 计划发送时间(为空表示立即发送,不为空表示定时发送)
*/
private LocalDateTime scheduleTime;
/**
* 发送邮件的附件路径(多个用分号分隔)
*/
private String attachmentPath;
/**
* 收件人列表信息
*/
private List<ApiEmailTaskRecipientsDto> recipientsDtoList;
}
package com.yd.email.feign.response;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class ApiSendEmailResponse {
private String jobId;
private String taskBizId;
/**
* 计划发送时间(为空表示立即发送,不为空表示定时发送)
*/
private LocalDateTime scheduleTime;
}
......@@ -49,6 +49,12 @@ public class EmailTask implements Serializable {
private String senderBizId;
/**
* 发件人邮箱(单个)
*/
@TableField("send_email")
private String sendEmail;
/**
* 邮件主题
*/
@TableField("subject")
......@@ -73,18 +79,24 @@ public class EmailTask implements Serializable {
private String status;
/**
* 计划发送时间(为空表示立即发送)
* 计划发送时间(为空表示立即发送,不为空表示定时发送
*/
@TableField("schedule_time")
private LocalDateTime scheduleTime;
/**
* 实际发送时间
* 实际发送时间(发送成功的时间)
*/
@TableField("send_time")
private LocalDateTime sendTime;
/**
* 发送邮件的附件路径(多个用分号分隔)
*/
@TableField("attachment_path")
private String attachmentPath;
/**
* 通用备注
*/
@TableField("remark")
......
......@@ -43,16 +43,40 @@ public class EmailTaskRecipients implements Serializable {
private String contactBizId;
/**
* 收件人类型:TO-收件人, CC-抄送, BCC-密送
* 收件人邮箱(单个)
*/
@TableField("recipient_type")
private String recipientType;
@TableField("receive_email")
private String receiveEmail;
/**
* 邮箱(根据收件人类型显示
* 抄送人邮箱(多个用分号分隔
*/
@TableField("email")
private String email;
@TableField("cc_email")
private String ccEmail;
/**
* 密送人邮箱(多个用分号分隔)
*/
@TableField("bcc_email")
private String bccEmail;
/**
* 收件人邮件状态:PENDING-待发送, SENDING-发送中, COMPLETED-已完成, FAILED-发送失败, CANCELLED-已取消
*/
@TableField("status")
private String status;
/**
* 实际发送时间
*/
@TableField("send_time")
private LocalDateTime sendTime;
/**
* 发送失败的错误信息
*/
@TableField("error_msg")
private String errorMsg;
/**
* 通用备注
......
......@@ -3,6 +3,8 @@ package com.yd.email.service.service;
import com.yd.email.service.model.EmailTaskRecipients;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* <p>
* 邮件任务收件人关联表 服务类
......@@ -13,4 +15,5 @@ import com.baomidou.mybatisplus.extension.service.IService;
*/
public interface IEmailTaskRecipientsService extends IService<EmailTaskRecipients> {
List<EmailTaskRecipients> queryList(String taskBizId);
}
......@@ -13,4 +13,5 @@ import com.baomidou.mybatisplus.extension.service.IService;
*/
public interface IEmailTaskService extends IService<EmailTask> {
EmailTask queryOne(String taskBizId);
}
package com.yd.email.service.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yd.email.service.model.EmailContactImport;
import com.yd.email.service.model.EmailTaskRecipients;
import com.yd.email.service.dao.EmailTaskRecipientsMapper;
import com.yd.email.service.service.IEmailTaskRecipientsService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* <p>
* 邮件任务收件人关联表 服务实现类
......@@ -17,4 +22,17 @@ import org.springframework.stereotype.Service;
@Service
public class EmailTaskRecipientsServiceImpl extends ServiceImpl<EmailTaskRecipientsMapper, EmailTaskRecipients> implements IEmailTaskRecipientsService {
/**
* 根据任务业务id查询邮件任务收件人关联列表
* @param taskBizId
* @return
*/
@Override
public List<EmailTaskRecipients> queryList(String taskBizId) {
List<EmailTaskRecipients> list = baseMapper.selectList(new LambdaQueryWrapper<EmailTaskRecipients>()
.eq(StringUtils.isNotBlank(taskBizId),EmailTaskRecipients::getTaskBizId,taskBizId)
);
return list;
}
}
package com.yd.email.service.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yd.email.service.model.EmailTask;
import com.yd.email.service.dao.EmailTaskMapper;
import com.yd.email.service.service.IEmailTaskService;
......@@ -17,4 +18,14 @@ import org.springframework.stereotype.Service;
@Service
public class EmailTaskServiceImpl extends ServiceImpl<EmailTaskMapper, EmailTask> implements IEmailTaskService {
/**
* 查询单个对象
* @param taskBizId
* @return
*/
@Override
public EmailTask queryOne(String taskBizId) {
return this.getOne(new LambdaQueryWrapper<EmailTask>().eq(EmailTask::getTaskBizId,taskBizId));
}
}
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