Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yd-csf
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-csf
Commits
68cda857
Commit
68cda857
authored
Sep 14, 2026
by
jianan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
保费到期提醒1
parent
dd86ab31
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
400 additions
and
0 deletions
+400
-0
yd-csf-api/pom.xml
+16
-0
yd-csf-api/src/main/java/com/yd/csf/api/handler/RenewalRemindJobHandler.java
+28
-0
yd-csf-api/src/main/java/com/yd/csf/api/service/RenewalDispatchService.java
+14
-0
yd-csf-api/src/main/java/com/yd/csf/api/service/RenewalMailService.java
+22
-0
yd-csf-api/src/main/java/com/yd/csf/api/service/impl/RenewalDispatchServiceImpl.java
+181
-0
yd-csf-api/src/main/java/com/yd/csf/api/service/impl/RenewalMailServiceImpl.java
+67
-0
yd-csf-service/src/main/java/com/yd/csf/service/dao/RenewalRemindBatchMapper.java
+10
-0
yd-csf-service/src/main/java/com/yd/csf/service/service/IRenewalRemindBatchService.java
+18
-0
yd-csf-service/src/main/java/com/yd/csf/service/utils/SimpleDateUtils.java
+14
-0
yd-csf-service/src/main/resources/mappers/RenewalRemindBatchMapper.xml
+30
-0
No files found.
yd-csf-api/pom.xml
View file @
68cda857
...
@@ -80,6 +80,12 @@
...
@@ -80,6 +80,12 @@
<artifactId>
hutool-all
</artifactId>
<artifactId>
hutool-all
</artifactId>
</dependency>
</dependency>
<!-- 邮件发送:续期提醒等内部通知,直连 SMTP -->
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-mail
</artifactId>
</dependency>
<dependency>
<dependency>
<groupId>
com.yd
</groupId>
<groupId>
com.yd
</groupId>
<artifactId>
yd-question-feign
</artifactId>
<artifactId>
yd-question-feign
</artifactId>
...
@@ -103,6 +109,16 @@
...
@@ -103,6 +109,16 @@
<groupId>
com.belerweb
</groupId>
<groupId>
com.belerweb
</groupId>
<artifactId>
pinyin4j
</artifactId>
<artifactId>
pinyin4j
</artifactId>
</dependency>
</dependency>
<dependency>
<groupId>
junit
</groupId>
<artifactId>
junit
</artifactId>
<scope>
test
</scope>
</dependency>
<dependency>
<groupId>
org.junit.jupiter
</groupId>
<artifactId>
junit-jupiter
</artifactId>
<scope>
test
</scope>
</dependency>
</dependencies>
</dependencies>
<build>
<build>
...
...
yd-csf-api/src/main/java/com/yd/csf/api/handler/RenewalRemindJobHandler.java
0 → 100644
View file @
68cda857
package
com
.
yd
.
csf
.
api
.
handler
;
import
com.xxl.job.core.handler.annotation.XxlJob
;
import
com.yd.csf.api.service.RenewalDispatchService
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.stereotype.Component
;
import
javax.annotation.Resource
;
/**
* 保单续期提醒月度任务处理器。
* 默认每月 1 号由 XXL-Job Admin 触发(cron 在 Admin 维护);
* 支持手动传参 bizMonth=yyyy-MM 补发指定月份。
*/
@Component
@Slf4j
public
class
RenewalRemindJobHandler
{
@Resource
private
RenewalDispatchService
renewalDispatchService
;
@XxlJob
(
"renewalRemindJobHandler"
)
public
void
execute
()
{
String
param
=
com
.
xxl
.
job
.
core
.
context
.
XxlJobHelper
.
getJobParam
();
log
.
info
(
"开始保单续期提醒任务,参数: {}"
,
param
);
renewalDispatchService
.
dispatch
(
param
);
}
}
yd-csf-api/src/main/java/com/yd/csf/api/service/RenewalDispatchService.java
0 → 100644
View file @
68cda857
package
com
.
yd
.
csf
.
api
.
service
;
/**
* 续期提醒月度跑批编排:筛选 → 生成清单 → 发邮件 → 批次落库
*/
public
interface
RenewalDispatchService
{
/**
* 执行一次续期提醒跑批
*
* @param bizMonth 执行月参数(yyyy-MM 或 bizMonth=yyyy-MM),为空时取当前月
*/
void
dispatch
(
String
bizMonth
);
}
yd-csf-api/src/main/java/com/yd/csf/api/service/RenewalMailService.java
0 → 100644
View file @
68cda857
package
com
.
yd
.
csf
.
api
.
service
;
import
java.util.List
;
/**
* 续期提醒邮件发送。
* 一期为占位实现,后续替换为公司统一邮件服务(yd-email-api)。
*/
public
interface
RenewalMailService
{
/**
* 发送带 Excel 附件的邮件
*
* @param recipients 收件人邮箱列表
* @param subject 邮件主题
* @param content 邮件正文
* @param fileName 附件文件名
* @param attachment 附件字节内容
*/
void
sendWithAttachment
(
List
<
String
>
recipients
,
String
subject
,
String
content
,
String
fileName
,
byte
[]
attachment
);
}
yd-csf-api/src/main/java/com/yd/csf/api/service/impl/RenewalDispatchServiceImpl.java
0 → 100644
View file @
68cda857
package
com
.
yd
.
csf
.
api
.
service
.
impl
;
import
com.yd.common.exception.BusinessException
;
import
com.yd.common.utils.RandomStringGenerator
;
import
com.yd.csf.api.service.RenewalDispatchService
;
import
com.yd.csf.api.service.RenewalMailService
;
import
com.yd.csf.service.component.RenewalPolicyReportService
;
import
com.yd.csf.service.dto.RenewalReminderData
;
import
com.yd.csf.service.enums.RenewalRemindStatusEnum
;
import
com.yd.csf.service.model.RenewalRemindBatch
;
import
com.yd.csf.service.service.IRenewalRemindBatchService
;
import
com.yd.csf.service.service.RenewalReminderQueryService
;
import
com.yd.framework.config.LockExecutor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.commons.collections4.CollectionUtils
;
import
org.apache.commons.lang3.StringUtils
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.stereotype.Service
;
import
javax.annotation.Resource
;
import
java.time.LocalDate
;
import
java.time.LocalDateTime
;
import
java.time.YearMonth
;
import
java.time.format.DateTimeParseException
;
import
java.util.Arrays
;
import
java.util.List
;
import
java.util.stream.Collectors
;
/**
* 续期提醒月度跑批编排实现
*/
@Service
@Slf4j
public
class
RenewalDispatchServiceImpl
implements
RenewalDispatchService
{
private
static
final
String
LOCK_KEY_PREFIX
=
"renewal:remind:"
;
private
static
final
String
BATCH_BIZ_ID_PREFIX
=
"RR"
;
private
static
final
int
ERROR_MSG_MAX_LEN
=
1900
;
@Value
(
"${renewal.reminder.recipients:}"
)
private
String
recipientsConfig
;
@Resource
private
LockExecutor
lockExecutor
;
@Resource
private
RenewalReminderQueryService
renewalReminderQueryService
;
@Resource
private
RenewalPolicyReportService
renewalPolicyReportService
;
@Resource
private
RenewalMailService
renewalMailService
;
@Resource
private
IRenewalRemindBatchService
renewalRemindBatchService
;
@Override
public
void
dispatch
(
String
bizMonthParam
)
{
YearMonth
execMonth
=
parseExecMonth
(
bizMonthParam
);
String
bizMonth
=
execMonth
.
toString
();
lockExecutor
.
executeWithLock
(
LOCK_KEY_PREFIX
+
bizMonth
,
()
->
doDispatch
(
execMonth
));
}
private
void
doDispatch
(
YearMonth
execMonth
)
{
String
bizMonth
=
execMonth
.
toString
();
RenewalRemindBatch
existBatch
=
renewalRemindBatchService
.
queryByBizMonth
(
bizMonth
);
if
(
existBatch
!=
null
&&
RenewalRemindStatusEnum
.
SUCCESS
.
getItemValue
().
equals
(
existBatch
.
getStatus
()))
{
log
.
info
(
"续期提醒当月已成功发送,跳过。bizMonth={}"
,
bizMonth
);
return
;
}
RenewalReminderData
data
=
null
;
String
fileName
=
null
;
String
recipientsSnapshot
=
null
;
try
{
LocalDate
today
=
LocalDate
.
now
();
data
=
renewalReminderQueryService
.
build
(
execMonth
,
today
);
// 窗口内无到期保单:不发邮件,记 EMPTY
if
(
CollectionUtils
.
isEmpty
(
data
.
getDuePolicies
()))
{
saveBatch
(
existBatch
,
execMonth
,
data
,
null
,
RenewalRemindStatusEnum
.
EMPTY
,
null
,
null
);
log
.
info
(
"续期提醒窗口内无到期保单,已记 EMPTY。bizMonth={}"
,
bizMonth
);
return
;
}
List
<
String
>
recipients
=
parseRecipients
();
recipientsSnapshot
=
String
.
join
(
","
,
recipients
);
fileName
=
renewalPolicyReportService
.
buildFileName
(
data
,
today
);
byte
[]
attachment
=
renewalPolicyReportService
.
generate
(
data
);
String
subject
=
buildSubject
(
data
);
String
content
=
buildContent
(
data
);
renewalMailService
.
sendWithAttachment
(
recipients
,
subject
,
content
,
fileName
,
attachment
);
saveBatch
(
existBatch
,
execMonth
,
data
,
fileName
,
RenewalRemindStatusEnum
.
SUCCESS
,
null
,
recipientsSnapshot
);
log
.
info
(
"续期提醒发送完成。bizMonth={}, 保单数={}"
,
bizMonth
,
data
.
getDuePolicies
().
size
());
}
catch
(
Exception
e
)
{
String
errorMsg
=
StringUtils
.
substring
(
e
.
getMessage
(),
0
,
ERROR_MSG_MAX_LEN
);
saveBatch
(
existBatch
,
execMonth
,
data
,
fileName
,
RenewalRemindStatusEnum
.
FAIL
,
errorMsg
,
recipientsSnapshot
);
log
.
error
(
"续期提醒跑批失败。bizMonth={}"
,
bizMonth
,
e
);
throw
new
BusinessException
(
"续期提醒跑批失败: "
+
e
.
getMessage
());
}
}
private
void
saveBatch
(
RenewalRemindBatch
exist
,
YearMonth
execMonth
,
RenewalReminderData
data
,
String
fileName
,
RenewalRemindStatusEnum
status
,
String
errorMsg
,
String
recipients
)
{
RenewalRemindBatch
batch
=
exist
==
null
?
new
RenewalRemindBatch
()
:
exist
;
if
(
exist
==
null
)
{
batch
.
setBatchBizId
(
RandomStringGenerator
.
generateBizId16
(
BATCH_BIZ_ID_PREFIX
));
batch
.
setBizMonth
(
execMonth
.
toString
());
batch
.
setCreateTime
(
LocalDateTime
.
now
());
}
// 查询阶段就失败时 data 可能为空,窗口由执行月推导
batch
.
setWindowStart
(
data
!=
null
?
data
.
getWindowStart
()
:
execMonth
.
plusMonths
(
1
).
atDay
(
1
));
batch
.
setWindowEnd
(
data
!=
null
?
data
.
getWindowEnd
()
:
execMonth
.
plusMonths
(
2
).
atEndOfMonth
());
batch
.
setPolicyCount
(
data
!=
null
?
data
.
getDuePolicies
().
size
()
:
0
);
batch
.
setSkippedCount
(
data
!=
null
&&
data
.
getSkipped
()
!=
null
?
data
.
getSkipped
().
size
()
:
0
);
batch
.
setRecipients
(
recipients
);
batch
.
setFileName
(
fileName
);
batch
.
setStatus
(
status
.
getItemValue
());
batch
.
setErrorMsg
(
errorMsg
);
if
(
status
!=
RenewalRemindStatusEnum
.
FAIL
)
{
batch
.
setSendTime
(
LocalDateTime
.
now
());
}
batch
.
setUpdateTime
(
LocalDateTime
.
now
());
renewalRemindBatchService
.
saveOrUpdate
(
batch
);
}
private
List
<
String
>
parseRecipients
()
{
List
<
String
>
recipients
=
Arrays
.
stream
(
StringUtils
.
defaultString
(
recipientsConfig
).
split
(
","
))
.
map
(
String:
:
trim
)
.
filter
(
StringUtils:
:
isNotBlank
)
.
collect
(
Collectors
.
toList
());
if
(
CollectionUtils
.
isEmpty
(
recipients
))
{
throw
new
BusinessException
(
"续期提醒收件人未配置,请在配置中设置 renewal.reminder.recipients"
);
}
return
recipients
;
}
private
String
buildSubject
(
RenewalReminderData
data
)
{
return
String
.
format
(
"【续期提醒】%d年%d-%d月到期保单清单(共%d张)"
,
data
.
getWindowStart
().
getYear
(),
data
.
getWindowStart
().
getMonthValue
(),
data
.
getWindowEnd
().
getMonthValue
(),
data
.
getDuePolicies
().
size
());
}
private
String
buildContent
(
RenewalReminderData
data
)
{
StringBuilder
sb
=
new
StringBuilder
();
sb
.
append
(
String
.
format
(
"以下保单将于 %s 至 %s 到期应缴续期保费,共 %d 张,详见附件。"
,
data
.
getWindowStart
(),
data
.
getWindowEnd
(),
data
.
getDuePolicies
().
size
()));
if
(
data
.
getSkipped
()
!=
null
&&
!
data
.
getSkipped
().
isEmpty
())
{
sb
.
append
(
String
.
format
(
" 另有 %d 张保单因数据异常未纳入,请核对源数据(详见跑批日志)。"
,
data
.
getSkipped
().
size
()));
}
return
sb
.
toString
();
}
/**
* 支持入参为空(当前月)、"yyyy-MM"、"bizMonth=yyyy-MM"
*/
private
YearMonth
parseExecMonth
(
String
param
)
{
String
value
=
StringUtils
.
trimToNull
(
param
);
if
(
value
==
null
)
{
return
YearMonth
.
now
();
}
if
(
value
.
startsWith
(
"bizMonth="
))
{
value
=
StringUtils
.
trimToNull
(
value
.
substring
(
"bizMonth="
.
length
()));
}
if
(
value
==
null
)
{
return
YearMonth
.
now
();
}
try
{
return
YearMonth
.
parse
(
value
);
}
catch
(
DateTimeParseException
e
)
{
throw
new
BusinessException
(
"续期提醒任务参数格式错误,应为 yyyy-MM,实际: "
+
param
);
}
}
}
yd-csf-api/src/main/java/com/yd/csf/api/service/impl/RenewalMailServiceImpl.java
0 → 100644
View file @
68cda857
package
com
.
yd
.
csf
.
api
.
service
.
impl
;
import
com.yd.csf.api.service.RenewalMailService
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.commons.lang3.StringUtils
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.core.io.ByteArrayResource
;
import
org.springframework.mail.javamail.JavaMailSender
;
import
org.springframework.mail.javamail.MimeMessageHelper
;
import
org.springframework.stereotype.Service
;
import
javax.annotation.Resource
;
import
javax.mail.internet.MimeMessage
;
import
java.io.UnsupportedEncodingException
;
import
java.nio.charset.StandardCharsets
;
import
java.util.List
;
/**
* 续期提醒邮件发送:直连 SMTP。
* 配置见 Nacos:spring.mail.* 与 renewal.reminder.mail-from。
*/
@Service
@Slf4j
public
class
RenewalMailServiceImpl
implements
RenewalMailService
{
@Resource
private
JavaMailSender
javaMailSender
;
/**
* 发件人地址;不配置时取 spring.mail.username
*/
@Value
(
"${renewal.reminder.mail-from:}"
)
private
String
mailFrom
;
@Value
(
"${spring.mail.username:}"
)
private
String
mailUsername
;
@Override
public
void
sendWithAttachment
(
List
<
String
>
recipients
,
String
subject
,
String
content
,
String
fileName
,
byte
[]
attachment
)
{
try
{
MimeMessage
message
=
javaMailSender
.
createMimeMessage
();
MimeMessageHelper
helper
=
new
MimeMessageHelper
(
message
,
true
,
StandardCharsets
.
UTF_8
.
name
());
helper
.
setFrom
(
resolveFrom
());
helper
.
setTo
(
recipients
.
toArray
(
new
String
[
0
]));
helper
.
setSubject
(
subject
);
helper
.
setText
(
content
,
false
);
helper
.
addAttachment
(
fileName
,
new
ByteArrayResource
(
attachment
));
javaMailSender
.
send
(
message
);
log
.
info
(
"[续期提醒-邮件] 发送成功,收件人={}, 主题={}, 附件={}"
,
recipients
,
subject
,
fileName
);
}
catch
(
Exception
e
)
{
log
.
error
(
"[续期提醒-邮件] 发送失败,收件人={}, 主题={}"
,
recipients
,
subject
,
e
);
throw
new
RuntimeException
(
"续期提醒邮件发送失败: "
+
e
.
getMessage
(),
e
);
}
}
private
String
resolveFrom
()
throws
UnsupportedEncodingException
{
String
from
=
StringUtils
.
defaultIfBlank
(
mailFrom
,
mailUsername
);
if
(
StringUtils
.
isBlank
(
from
))
{
throw
new
IllegalStateException
(
"未配置发件人:renewal.reminder.mail-from 或 spring.mail.username"
);
}
return
from
;
}
}
yd-csf-service/src/main/java/com/yd/csf/service/dao/RenewalRemindBatchMapper.java
0 → 100644
View file @
68cda857
package
com
.
yd
.
csf
.
service
.
dao
;
import
com.baomidou.mybatisplus.core.mapper.BaseMapper
;
import
com.yd.csf.service.model.RenewalRemindBatch
;
/**
* 针对表【renewal_remind_batch(保单续期提醒发送批次表)】的数据库操作 Mapper
*/
public
interface
RenewalRemindBatchMapper
extends
BaseMapper
<
RenewalRemindBatch
>
{
}
yd-csf-service/src/main/java/com/yd/csf/service/service/IRenewalRemindBatchService.java
0 → 100644
View file @
68cda857
package
com
.
yd
.
csf
.
service
.
service
;
import
com.baomidou.mybatisplus.extension.service.IService
;
import
com.yd.csf.service.model.RenewalRemindBatch
;
/**
* 保单续期提醒发送批次 服务类
*/
public
interface
IRenewalRemindBatchService
extends
IService
<
RenewalRemindBatch
>
{
/**
* 按执行月查询批次
*
* @param bizMonth 执行月 yyyy-MM
* @return 批次记录,不存在返回 null
*/
RenewalRemindBatch
queryByBizMonth
(
String
bizMonth
);
}
yd-csf-service/src/main/java/com/yd/csf/service/utils/SimpleDateUtils.java
0 → 100644
View file @
68cda857
package
com
.
yd
.
csf
.
service
.
utils
;
import
java.time.LocalDate
;
import
java.time.ZoneId
;
import
java.util.Date
;
public
class
SimpleDateUtils
{
private
static
final
ZoneId
ZONE
=
ZoneId
.
systemDefault
();
public
static
LocalDate
toLocalDate
(
Date
date
)
{
return
date
!=
null
?
date
.
toInstant
().
atZone
(
ZONE
).
toLocalDate
()
:
null
;
}
}
\ No newline at end of file
yd-csf-service/src/main/resources/mappers/RenewalRemindBatchMapper.xml
0 → 100644
View file @
68cda857
<?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.yd.csf.service.dao.RenewalRemindBatchMapper"
>
<resultMap
id=
"BaseResultMap"
type=
"com.yd.csf.service.model.RenewalRemindBatch"
>
<id
property=
"id"
column=
"id"
/>
<result
property=
"batchBizId"
column=
"batch_biz_id"
/>
<result
property=
"bizMonth"
column=
"biz_month"
/>
<result
property=
"windowStart"
column=
"window_start"
/>
<result
property=
"windowEnd"
column=
"window_end"
/>
<result
property=
"policyCount"
column=
"policy_count"
/>
<result
property=
"skippedCount"
column=
"skipped_count"
/>
<result
property=
"recipients"
column=
"recipients"
/>
<result
property=
"fileName"
column=
"file_name"
/>
<result
property=
"status"
column=
"status"
/>
<result
property=
"errorMsg"
column=
"error_msg"
/>
<result
property=
"sendTime"
column=
"send_time"
/>
<result
property=
"createTime"
column=
"create_time"
/>
<result
property=
"updateTime"
column=
"update_time"
/>
</resultMap>
<sql
id=
"Base_Column_List"
>
id
,batch_biz_id,biz_month,window_start,window_end,policy_count,
skipped_count,recipients,file_name,status,error_msg,
send_time,create_time,update_time
</sql>
</mapper>
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