Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yd-email
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
xingmin
yd-email
Commits
03e6690a
Commit
03e6690a
authored
Sep 24, 2025
by
zhangxingmin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
xxl-job
parent
ed904f15
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
436 additions
and
151 deletions
+436
-151
yd-email-api/src/main/java/com/yd/email/api/config/RestTemplateConfig.java
+15
-0
yd-email-api/src/main/java/com/yd/email/api/config/XxlJobConfig.java
+44
-0
yd-email-api/src/main/java/com/yd/email/api/controller/ApiEmailSendController.java
+0
-52
yd-email-api/src/main/java/com/yd/email/api/service/impl/ApiEmailSendServiceImpl.java
+73
-39
yd-email-api/src/main/java/com/yd/email/api/service/impl/XxlJobServiceImpl.java
+238
-56
yd-email-api/src/main/resources/bootstrap.yml
+1
-1
yd-email-feign/src/main/java/com/yd/email/feign/dto/ApiEmailTaskRecipientsDto.java
+23
-0
yd-email-feign/src/main/java/com/yd/email/feign/request/ApiSendEmailRequest.java
+29
-1
yd-email-feign/src/main/java/com/yd/email/feign/response/ApiSendEmailResponse.java
+11
-0
yd-email-service/src/main/java/com/yd/email/service/model/EmailTask.java
+2
-2
No files found.
yd-email-api/src/main/java/com/yd/email/api/config/RestTemplateConfig.java
0 → 100644
View file @
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
yd-email-api/src/main/java/com/yd/email/api/config/XxlJobConfig.java
0 → 100644
View file @
03e6690a
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
yd-email-api/src/main/java/com/yd/email/api/controller/ApiEmailSendController.java
View file @
03e6690a
...
@@ -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
yd-email-api/src/main/java/com/yd/email/api/service/impl/ApiEmailSendServiceImpl.java
View file @
03e6690a
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
(
"创建邮件发送任务失败"
);
}
}
}
}
}
yd-email-api/src/main/java/com/yd/email/api/service/impl/XxlJobServiceImpl.java
View file @
03e6690a
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
yd-email-api/src/main/resources/bootstrap.yml
View file @
03e6690a
...
@@ -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 的配置文件 读取这个里面的配置
...
...
yd-email-feign/src/main/java/com/yd/email/feign/dto/ApiEmailTaskRecipientsDto.java
0 → 100644
View file @
03e6690a
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
;
}
yd-email-feign/src/main/java/com/yd/email/feign/request/ApiSendEmailRequest.java
View file @
03e6690a
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
;
}
}
yd-email-feign/src/main/java/com/yd/email/feign/response/ApiSendEmailResponse.java
View file @
03e6690a
...
@@ -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
;
}
}
yd-email-service/src/main/java/com/yd/email/service/model/EmailTask.java
View file @
03e6690a
...
@@ -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
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment