Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yd-notice
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-notice
Commits
156bacf5
Commit
156bacf5
authored
Apr 03, 2026
by
zhangxingmin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
通知
parent
d1bf002c
Hide whitespace changes
Inline
Side-by-side
Showing
45 changed files
with
1853 additions
and
1 deletions
+1853
-1
.idea/compiler.xml
+1
-0
yd-notice-api/Dockerfile
+12
-0
yd-notice-api/pom.xml
+1
-1
yd-notice-api/src/main/java/com/yd/notice/api/controller/ApiNotificationTaskController.java
+45
-0
yd-notice-api/src/main/java/com/yd/notice/api/controller/ChannelConfigController.java
+18
-0
yd-notice-api/src/main/java/com/yd/notice/api/controller/NotificationRecordController.java
+18
-0
yd-notice-api/src/main/java/com/yd/notice/api/controller/NotificationTemplateController.java
+18
-0
yd-notice-api/src/main/java/com/yd/notice/api/service/ApiNotificationTaskService.java
+9
-0
yd-notice-api/src/main/java/com/yd/notice/api/service/impl/ApiNotificationTaskServiceImpl.java
+40
-0
yd-notice-api/src/main/resources/banner.txt
+11
-0
yd-notice-api/src/main/resources/bootstrap.yml
+106
-0
yd-notice-api/src/main/resources/spy.properties
+24
-0
yd-notice-feign/src/main/java/com/yd/notice/feign/client/ApiNotificationTaskFeignClient.java
+35
-0
yd-notice-feign/src/main/java/com/yd/notice/feign/fallback/ApiNotificationTaskFeignFallbackFactory.java
+27
-0
yd-notice-feign/src/main/java/com/yd/notice/feign/request/ApiSendRequest.java
+35
-0
yd-notice-feign/src/main/java/com/yd/notice/feign/response/ApiSendResponse.java
+12
-0
yd-notice-service/pom.xml
+22
-0
yd-notice-service/src/main/java/com/yd/notice/service/config/AsyncConfig.java
+25
-0
yd-notice-service/src/main/java/com/yd/notice/service/config/WecomConfig.java
+27
-0
yd-notice-service/src/main/java/com/yd/notice/service/config/WecomProperties.java
+15
-0
yd-notice-service/src/main/java/com/yd/notice/service/dao/ChannelConfigMapper.java
+16
-0
yd-notice-service/src/main/java/com/yd/notice/service/dao/NotificationRecordMapper.java
+16
-0
yd-notice-service/src/main/java/com/yd/notice/service/dao/NotificationTaskMapper.java
+16
-0
yd-notice-service/src/main/java/com/yd/notice/service/dao/NotificationTemplateMapper.java
+16
-0
yd-notice-service/src/main/java/com/yd/notice/service/model/ChannelConfig.java
+116
-0
yd-notice-service/src/main/java/com/yd/notice/service/model/NotificationRecord.java
+164
-0
yd-notice-service/src/main/java/com/yd/notice/service/model/NotificationTask.java
+170
-0
yd-notice-service/src/main/java/com/yd/notice/service/model/NotificationTemplate.java
+134
-0
yd-notice-service/src/main/java/com/yd/notice/service/schedule/MessageSendScheduler.java
+98
-0
yd-notice-service/src/main/java/com/yd/notice/service/send/WecomMessageSender.java
+78
-0
yd-notice-service/src/main/java/com/yd/notice/service/service/IChannelConfigService.java
+16
-0
yd-notice-service/src/main/java/com/yd/notice/service/service/INotificationRecordService.java
+16
-0
yd-notice-service/src/main/java/com/yd/notice/service/service/INotificationTaskService.java
+20
-0
yd-notice-service/src/main/java/com/yd/notice/service/service/INotificationTemplateService.java
+16
-0
yd-notice-service/src/main/java/com/yd/notice/service/service/impl/ChannelConfigServiceImpl.java
+20
-0
yd-notice-service/src/main/java/com/yd/notice/service/service/impl/NotificationRecordServiceImpl.java
+20
-0
yd-notice-service/src/main/java/com/yd/notice/service/service/impl/NotificationTaskServiceImpl.java
+237
-0
yd-notice-service/src/main/java/com/yd/notice/service/service/impl/NotificationTemplateServiceImpl.java
+20
-0
yd-notice-service/src/main/java/com/yd/notice/service/utils/AesEncryptUtil.java
+46
-0
yd-notice-service/src/main/java/com/yd/notice/service/utils/MyBatisPlusCodeGenerator.java
+36
-0
yd-notice-service/src/main/java/com/yd/notice/service/utils/TemplateRenderUtil.java
+61
-0
yd-notice-service/src/main/resources/mappers/ChannelConfigMapper.xml
+5
-0
yd-notice-service/src/main/resources/mappers/NotificationRecordMapper.xml
+5
-0
yd-notice-service/src/main/resources/mappers/NotificationTaskMapper.xml
+5
-0
yd-notice-service/src/main/resources/mappers/NotificationTemplateMapper.xml
+5
-0
No files found.
.idea/compiler.xml
View file @
156bacf5
...
...
@@ -2,6 +2,7 @@
<project
version=
"4"
>
<component
name=
"CompilerConfiguration"
>
<annotationProcessing>
<profile
default=
"true"
name=
"Default"
enabled=
"true"
/>
<profile
name=
"Maven default annotation processors profile"
enabled=
"true"
>
<sourceOutputDir
name=
"target/generated-sources/annotations"
/>
<sourceTestOutputDir
name=
"target/generated-test-sources/test-annotations"
/>
...
...
yd-notice-api/Dockerfile
0 → 100644
View file @
156bacf5
# 基础镜像
FROM
openjdk:8
# 维护人
LABEL
maintainer="zxm<2060197959@qq.com>"
# 创建目录
RUN
mkdir
-p
/home/app
# 拷贝项目jar - 使用可执行的 fat JAR
COPY
target/yd-notice-api-1.0-SNAPSHOT-exec.jar /home/app/yd-notice-api.jar
# 执行命令启动jar,并设置JVM内存参数
ENTRYPOINT
["java","-Duser.timezone=Asia/Shanghai", "-Xmx256m", "-Xms128m", "-jar", "/home/app/yd-notice-api.jar"]
# 暴露端口
EXPOSE
9490
yd-notice-api/pom.xml
View file @
156bacf5
...
...
@@ -92,7 +92,7 @@
</goals>
<configuration>
<classifier>
exec
</classifier>
<mainClass>
com.yd.
ai
.api.NoticeApiApplication
</mainClass>
<mainClass>
com.yd.
notice
.api.NoticeApiApplication
</mainClass>
</configuration>
</execution>
</executions>
...
...
yd-notice-api/src/main/java/com/yd/notice/api/controller/ApiNotificationTaskController.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
api
.
controller
;
import
com.yd.common.result.Result
;
import
com.yd.notice.api.service.ApiNotificationTaskService
;
import
com.yd.notice.feign.client.ApiNotificationTaskFeignClient
;
import
com.yd.notice.feign.request.ApiSendRequest
;
import
com.yd.notice.feign.response.ApiSendResponse
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.validation.annotation.Validated
;
import
org.springframework.web.bind.annotation.*
;
/**
* 消息任务信息
*
* @author zxm
* @since 2026-04-02
*/
@RestController
@RequestMapping
(
"/notificationTask"
)
@Validated
public
class
ApiNotificationTaskController
implements
ApiNotificationTaskFeignClient
{
@Autowired
private
ApiNotificationTaskService
apiNotificationTaskService
;
/**
* 发送企业微信消息
* @param request
* @return
*/
@Override
public
Result
<
ApiSendResponse
>
sendWecomMessage
(
ApiSendRequest
request
)
{
return
apiNotificationTaskService
.
sendWecomMessage
(
request
);
}
// /**
// * 查询消息任务状态
// * @param taskBizId
// * @return
// */
// @GetMapping("/task/{taskBizId}")
// public NotificationTask getTaskStatus(@PathVariable String taskBizId) {
// return taskService.getByBizId(taskBizId);
// }
}
yd-notice-api/src/main/java/com/yd/notice/api/controller/ChannelConfigController.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
api
.
controller
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RestController
;
/**
* <p>
* 渠道配置表 前端控制器
* </p>
*
* @author zxm
* @since 2026-04-02
*/
@RestController
@RequestMapping
(
"/channelConfig"
)
public
class
ChannelConfigController
{
}
yd-notice-api/src/main/java/com/yd/notice/api/controller/NotificationRecordController.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
api
.
controller
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RestController
;
/**
* <p>
* 发送记录表 前端控制器
* </p>
*
* @author zxm
* @since 2026-04-02
*/
@RestController
@RequestMapping
(
"/notificationRecord"
)
public
class
NotificationRecordController
{
}
yd-notice-api/src/main/java/com/yd/notice/api/controller/NotificationTemplateController.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
api
.
controller
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RestController
;
/**
* <p>
* 消息模板表 前端控制器
* </p>
*
* @author zxm
* @since 2026-04-02
*/
@RestController
@RequestMapping
(
"/notificationTemplate"
)
public
class
NotificationTemplateController
{
}
yd-notice-api/src/main/java/com/yd/notice/api/service/ApiNotificationTaskService.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
api
.
service
;
import
com.yd.common.result.Result
;
import
com.yd.notice.feign.request.ApiSendRequest
;
import
com.yd.notice.feign.response.ApiSendResponse
;
public
interface
ApiNotificationTaskService
{
Result
<
ApiSendResponse
>
sendWecomMessage
(
ApiSendRequest
request
);
}
yd-notice-api/src/main/java/com/yd/notice/api/service/impl/ApiNotificationTaskServiceImpl.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
api
.
service
.
impl
;
import
com.yd.common.result.Result
;
import
com.yd.notice.api.service.ApiNotificationTaskService
;
import
com.yd.notice.feign.request.ApiSendRequest
;
import
com.yd.notice.feign.response.ApiSendResponse
;
import
com.yd.notice.service.service.INotificationTaskService
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Transactional
;
@Slf4j
@Service
public
class
ApiNotificationTaskServiceImpl
implements
ApiNotificationTaskService
{
@Autowired
private
INotificationTaskService
iNotificationTaskService
;
/**
* 发送企业微信消息
* @param request
* @return
*/
@Override
@Transactional
(
rollbackFor
=
Exception
.
class
)
public
Result
<
ApiSendResponse
>
sendWecomMessage
(
ApiSendRequest
request
)
{
String
taskBizId
=
iNotificationTaskService
.
createAndSendTask
(
request
.
getChannelBizId
(),
request
.
getTemplateBizId
(),
request
.
getReceiver
(),
request
.
getParams
()
);
ApiSendResponse
response
=
new
ApiSendResponse
();
response
.
setTaskBizId
(
taskBizId
);
return
Result
.
success
(
response
);
}
}
yd-notice-api/src/main/resources/banner.txt
0 → 100644
View file @
156bacf5
${AnsiColor.GREEN}
_ _ _ __ __ ____ _ _ _ _
| | (_)_ __ | | _\ \ / /__ / ___| |__ __ _| |_ / \ _ __ (_)
| | | | '_ \| |/ /\ \ /\ / / _ \ | | '_ \ / _` | __| / _ \ | '_ \| |
| |___| | | | | < \ V V / __/ |___| | | | (_| | |_ / ___ \| |_) | |
|_____|_|_| |_|_|\_\ \_/\_/ \___|\____|_| |_|\__,_|\__/_/ \_\ .__/|_|
|_|
${AnsiColor.BRIGHT_WHITE}
Spring Boot Version: ${spring-boot.version}
\ No newline at end of file
yd-notice-api/src/main/resources/bootstrap.yml
0 → 100644
View file @
156bacf5
spring
:
profiles
:
active
:
test
# active: '@spring.profiles.active@'
---
spring
:
application
:
name
:
yd-notice-api
profiles
:
dev
main
:
allow-bean-definition-overriding
:
true
allow-circular-references
:
true
cloud
:
nacos
:
# 配置中心
config
:
# 命名空间id(此处不用public,因public初始化的空间, id为空) 4e237601-cea8-414d-b7b9-d7adc8cbcf95
namespace
:
22f9d61e-9011-4d45-88cb-24f9857e3eec
# nacos的ip地址和端口 120.79.64.17:10848
server-addr
:
127.0.0.1:8848
# 这个就表示 在我们nacos命名空间id为 dev中 有一个data-id 为 demo-service.yml 的配置文件 读取这个里面的配置
file-extension
:
yml
config-retry-time
:
300000
# 共享配置, 可以把公共配置放在同个命名空间下,然后创建一个 common.yml 文件 ,里面可以放共用的配置
shared-configs[0]
:
dataId
:
linkwe-common.yml
refresh
:
true
# 发布到注册中心 (如果没有使用可以不配)
discovery
:
# 命名空间id(此处不用public,因public初始化的空间, id为空)
namespace
:
${spring.cloud.nacos.config.namespace}
# nacos的ip地址和端口
server-addr
:
${spring.cloud.nacos.config.server-addr}
---
spring
:
application
:
name
:
yd-notice-api
profiles
:
test
main
:
allow-bean-definition-overriding
:
true
allow-circular-references
:
true
cloud
:
nacos
:
# 配置中心
config
:
# 命名空间id(此处不用public,因public初始化的空间, id为空)
namespace
:
b3b01715-eb85-4242-992a-5aff03d864d4
# nacos的ip地址和端口
server-addr
:
139.224.145.34:8848
# 这个就表示 在我们nacos命名空间id为 dev中 有一个data-id 为 demo-service.yml 的配置文件 读取这个里面的配置
file-extension
:
yml
config-retry-time
:
300000
# 共享配置, 可以把公共配置放在同个命名空间下,然后创建一个 common.yml 文件 ,里面可以放共用的配置
shared-configs[0]
:
dataId
:
yd-common.yml
group
:
YD_GROUP
refresh
:
true
extension-configs
:
# 扩展配置
-
data-id
:
yd-notice-api.yml
group
:
YD_GROUP
refresh
:
true
# 发布到注册中心 (如果没有使用可以不配)
discovery
:
# 命名空间id(此处不用public,因public初始化的空间, id为空)
namespace
:
${spring.cloud.nacos.config.namespace}
# nacos的ip地址和端口
server-addr
:
${spring.cloud.nacos.config.server-addr}
group
:
YD_GROUP
---
spring
:
profiles
:
prod
application
:
name
:
yd-notice-api
server
:
port
:
9490
main
:
allow-bean-definition-overriding
:
true
allow-circular-references
:
true
cloud
:
nacos
:
# 配置中心
config
:
# 命名空间id(此处不用public,因public初始化的空间, id为空)
namespace
:
cb587d6d-d3b2-45ca-a3ef-5b5c80ece5b3
# nacos的ip地址和端口
server-addr
:
139.224.150.79:8848
# 这个就表示 在我们nacos命名空间id为 dev中 有一个data-id 为 demo-service.yml 的配置文件 读取这个里面的配置
file-extension
:
yml
config-retry-time
:
300000
# 共享配置, 可以把公共配置放在同个命名空间下,然后创建一个 common.yml 文件 ,里面可以放共用的配置
shared-configs[0]
:
dataId
:
yd-common.yml
group
:
YD_GROUP
refresh
:
true
extension-configs
:
# 扩展配置
-
data-id
:
yd-notice-api.yml
group
:
YD_GROUP
refresh
:
true
# 发布到注册中心 (如果没有使用可以不配)
discovery
:
# 命名空间id(此处不用public,因public初始化的空间, id为空)
namespace
:
${spring.cloud.nacos.config.namespace}
# nacos的ip地址和端口
server-addr
:
${spring.cloud.nacos.config.server-addr}
group
:
YD_GROUP
yd-notice-api/src/main/resources/spy.properties
0 → 100644
View file @
156bacf5
#3.2.1\u4EE5\u4E0A\u4F7F\u7528
modulelist
=
com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1\u4EE5\u4E0B\u4F7F\u7528\u6216\u8005\u4E0D\u914D\u7F6E
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# \u81EA\u5B9A\u4E49\u65E5\u5FD7\u6253\u5370
logMessageFormat
=
com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#\u65E5\u5FD7\u8F93\u51FA\u5230\u63A7\u5236\u53F0
appender
=
com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# \u4F7F\u7528\u65E5\u5FD7\u7CFB\u7EDF\u8BB0\u5F55 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# \u8BBE\u7F6E p6spy driver \u4EE3\u7406
deregisterdrivers
=
true
# \u53D6\u6D88JDBC URL\u524D\u7F00
useprefix
=
true
# \u914D\u7F6E\u8BB0\u5F55 Log \u4F8B\u5916,\u53EF\u53BB\u6389\u7684\u7ED3\u679C\u96C6\u6709error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories
=
info,debug,result,commit,resultset
# \u65E5\u671F\u683C\u5F0F
dateformat
=
yyyy-MM-dd HH:mm:ss
# \u5B9E\u9645\u9A71\u52A8\u53EF\u591A\u4E2A
#driverlist=org.h2.Driver
# \u662F\u5426\u5F00\u542F\u6162SQL\u8BB0\u5F55
outagedetection
=
true
# \u6162SQL\u8BB0\u5F55\u6807\u51C6 2 \u79D2
outagedetectioninterval
=
2
yd-notice-feign/src/main/java/com/yd/notice/feign/client/ApiNotificationTaskFeignClient.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
feign
.
client
;
import
com.yd.common.result.Result
;
import
com.yd.notice.feign.fallback.ApiNotificationTaskFeignFallbackFactory
;
import
com.yd.notice.feign.request.ApiSendRequest
;
import
com.yd.notice.feign.response.ApiSendResponse
;
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-notice-api"
,
path
=
"/notice/api/notificationTask"
,
fallbackFactory
=
ApiNotificationTaskFeignFallbackFactory
.
class
)
public
interface
ApiNotificationTaskFeignClient
{
/**
* 发送企业微信消息
* @param request
* @return
*/
@PostMapping
(
"/send/wecom"
)
Result
<
ApiSendResponse
>
sendWecomMessage
(
@Validated
@RequestBody
ApiSendRequest
request
);
// /**
// * 查询消息任务状态
// * @param taskBizId
// * @return
// */
// @GetMapping("/task/{taskBizId}")
// NotificationTask getTaskStatus(@PathVariable String taskBizId);
}
yd-notice-feign/src/main/java/com/yd/notice/feign/fallback/ApiNotificationTaskFeignFallbackFactory.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
feign
.
fallback
;
import
com.yd.common.result.Result
;
import
com.yd.notice.feign.client.ApiNotificationTaskFeignClient
;
import
com.yd.notice.feign.request.ApiSendRequest
;
import
com.yd.notice.feign.response.ApiSendResponse
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.cloud.openfeign.FallbackFactory
;
import
org.springframework.stereotype.Component
;
/**
* 通知服务-消息任务信息Feign降级处理
*/
@Slf4j
@Component
public
class
ApiNotificationTaskFeignFallbackFactory
implements
FallbackFactory
<
ApiNotificationTaskFeignClient
>
{
@Override
public
ApiNotificationTaskFeignClient
create
(
Throwable
cause
)
{
return
new
ApiNotificationTaskFeignClient
()
{
@Override
public
Result
<
ApiSendResponse
>
sendWecomMessage
(
ApiSendRequest
request
)
{
return
null
;
}
};
}
}
yd-notice-feign/src/main/java/com/yd/notice/feign/request/ApiSendRequest.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
feign
.
request
;
import
lombok.Data
;
@Data
public
class
ApiSendRequest
{
/**
* 渠道配置表唯一业务ID(对应 channel_config 表的 channel_biz_id)
* 用于标识使用哪个渠道(如 wecom、wechat、sms 等)发送消息
*/
private
String
channelBizId
;
/**
* 消息模板表唯一业务ID(对应 notification_template 表的 template_biz_id)
* 模板中定义了标题模板、内容模板以及占位符规则
*/
private
String
templateBizId
;
/**
* 消息接收人标识
* 具体格式由渠道和接收人类型决定:
* - 企业微信:userid(多个用 | 分隔,@all 表示全员)
* - 短信:手机号
* - 邮件:邮箱地址
*/
private
String
receiver
;
/**
* 模板参数,JSON 字符串格式,用于替换模板中的占位符
* 示例:{"name":"张三","orderId":"123456"}
* 占位符格式:{{name}}、{{orderId}}
*/
private
String
params
;
}
\ No newline at end of file
yd-notice-feign/src/main/java/com/yd/notice/feign/response/ApiSendResponse.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
feign
.
response
;
import
lombok.Data
;
@Data
public
class
ApiSendResponse
{
/**
* 消息任务唯一业务ID
*/
private
String
taskBizId
;
}
yd-notice-service/pom.xml
View file @
156bacf5
...
...
@@ -82,5 +82,26 @@
<artifactId>
yd-framework
</artifactId>
<version>
${project.version}
</version>
</dependency>
<!-- 企业微信 Java SDK(推荐 binarywang 版本) -->
<dependency>
<groupId>
com.github.binarywang
</groupId>
<artifactId>
weixin-java-cp
</artifactId>
<version>
4.6.0
</version>
</dependency>
<!-- Redis 依赖(用于 AccessToken 缓存) -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-data-redis</artifactId>-->
<!-- </dependency>-->
<!-- Hutool 工具类(可选) -->
<dependency>
<groupId>
cn.hutool
</groupId>
<artifactId>
hutool-all
</artifactId>
<version>
5.8.25
</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
yd-notice-service/src/main/java/com/yd/notice/service/config/AsyncConfig.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
config
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
;
import
java.util.concurrent.Executor
;
import
java.util.concurrent.ThreadPoolExecutor
;
@Configuration
public
class
AsyncConfig
{
@Bean
(
"noticeExecutor"
)
public
Executor
noticeExecutor
()
{
ThreadPoolTaskExecutor
executor
=
new
ThreadPoolTaskExecutor
();
executor
.
setCorePoolSize
(
5
);
executor
.
setMaxPoolSize
(
20
);
executor
.
setQueueCapacity
(
1000
);
executor
.
setThreadNamePrefix
(
"notice-"
);
executor
.
setRejectedExecutionHandler
(
new
ThreadPoolExecutor
.
CallerRunsPolicy
());
executor
.
initialize
();
return
executor
;
}
}
\ No newline at end of file
yd-notice-service/src/main/java/com/yd/notice/service/config/WecomConfig.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
config
;
import
lombok.RequiredArgsConstructor
;
import
me.chanjar.weixin.cp.api.WxCpService
;
import
me.chanjar.weixin.cp.api.impl.WxCpServiceImpl
;
import
me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
@Configuration
@RequiredArgsConstructor
public
class
WecomConfig
{
private
final
WecomProperties
wecomProperties
;
@Bean
public
WxCpService
wxCpService
()
{
WxCpDefaultConfigImpl
config
=
new
WxCpDefaultConfigImpl
();
config
.
setCorpId
(
wecomProperties
.
getCorpId
());
config
.
setAgentId
(
wecomProperties
.
getAgentId
());
config
.
setCorpSecret
(
wecomProperties
.
getSecret
());
WxCpServiceImpl
wxCpService
=
new
WxCpServiceImpl
();
wxCpService
.
setWxCpConfigStorage
(
config
);
return
wxCpService
;
}
}
\ No newline at end of file
yd-notice-service/src/main/java/com/yd/notice/service/config/WecomProperties.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
config
;
import
lombok.Data
;
import
org.springframework.boot.context.properties.ConfigurationProperties
;
import
org.springframework.context.annotation.Configuration
;
@Data
@Configuration
@ConfigurationProperties
(
prefix
=
"wecom"
)
public
class
WecomProperties
{
private
String
corpId
;
private
Integer
agentId
;
private
String
secret
;
}
\ No newline at end of file
yd-notice-service/src/main/java/com/yd/notice/service/dao/ChannelConfigMapper.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
dao
;
import
com.yd.notice.service.model.ChannelConfig
;
import
com.baomidou.mybatisplus.core.mapper.BaseMapper
;
/**
* <p>
* 渠道配置表 Mapper 接口
* </p>
*
* @author zxm
* @since 2026-04-02
*/
public
interface
ChannelConfigMapper
extends
BaseMapper
<
ChannelConfig
>
{
}
yd-notice-service/src/main/java/com/yd/notice/service/dao/NotificationRecordMapper.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
dao
;
import
com.yd.notice.service.model.NotificationRecord
;
import
com.baomidou.mybatisplus.core.mapper.BaseMapper
;
/**
* <p>
* 发送记录表 Mapper 接口
* </p>
*
* @author zxm
* @since 2026-04-02
*/
public
interface
NotificationRecordMapper
extends
BaseMapper
<
NotificationRecord
>
{
}
yd-notice-service/src/main/java/com/yd/notice/service/dao/NotificationTaskMapper.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
dao
;
import
com.yd.notice.service.model.NotificationTask
;
import
com.baomidou.mybatisplus.core.mapper.BaseMapper
;
/**
* <p>
* 消息任务表 Mapper 接口
* </p>
*
* @author zxm
* @since 2026-04-02
*/
public
interface
NotificationTaskMapper
extends
BaseMapper
<
NotificationTask
>
{
}
yd-notice-service/src/main/java/com/yd/notice/service/dao/NotificationTemplateMapper.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
dao
;
import
com.yd.notice.service.model.NotificationTemplate
;
import
com.baomidou.mybatisplus.core.mapper.BaseMapper
;
/**
* <p>
* 消息模板表 Mapper 接口
* </p>
*
* @author zxm
* @since 2026-04-02
*/
public
interface
NotificationTemplateMapper
extends
BaseMapper
<
NotificationTemplate
>
{
}
yd-notice-service/src/main/java/com/yd/notice/service/model/ChannelConfig.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
model
;
import
com.baomidou.mybatisplus.annotation.IdType
;
import
com.baomidou.mybatisplus.annotation.TableField
;
import
com.baomidou.mybatisplus.annotation.TableId
;
import
com.baomidou.mybatisplus.annotation.TableName
;
import
java.io.Serializable
;
import
java.time.LocalDateTime
;
import
lombok.Getter
;
import
lombok.Setter
;
/**
* <p>
* 渠道配置表
* </p>
*
* @author zxm
* @since 2026-04-02
*/
@Getter
@Setter
@TableName
(
"channel_config"
)
public
class
ChannelConfig
implements
Serializable
{
private
static
final
long
serialVersionUID
=
1L
;
/**
* 主键ID
*/
@TableId
(
value
=
"id"
,
type
=
IdType
.
AUTO
)
private
Long
id
;
/**
* 渠道配置表唯一业务ID
*/
@TableField
(
"channel_biz_id"
)
private
String
channelBizId
;
/**
* 渠道类型(wecom/wechat/miniprogram/app/sms/email)
*/
@TableField
(
"channel_type"
)
private
String
channelType
;
/**
* 渠道名称(企业微信/微信/小程序/APP/短信/邮件)
*/
@TableField
(
"channel_name"
)
private
String
channelName
;
/**
* 配置键(渠道商的KEY)
*/
@TableField
(
"config_key"
)
private
String
configKey
;
/**
* 配置值(敏感信息加密存储)(渠道商的密钥)
*/
@TableField
(
"config_value"
)
private
String
configValue
;
/**
* 配置说明
*/
@TableField
(
"description"
)
private
String
description
;
/**
* 状态:0禁用,1启用
*/
@TableField
(
"status"
)
private
Integer
status
;
/**
* 排序
*/
@TableField
(
"sort"
)
private
Integer
sort
;
/**
* 通用备注
*/
@TableField
(
"remark"
)
private
String
remark
;
/**
* 删除标识: 0-正常, 1-删除
*/
@TableField
(
"is_deleted"
)
private
Integer
isDeleted
;
/**
* 创建人ID
*/
@TableField
(
"creator_id"
)
private
String
creatorId
;
/**
* 更新人ID
*/
@TableField
(
"updater_id"
)
private
String
updaterId
;
/**
* 创建时间
*/
@TableField
(
"create_time"
)
private
LocalDateTime
createTime
;
/**
* 更新时间
*/
@TableField
(
"update_time"
)
private
LocalDateTime
updateTime
;
}
yd-notice-service/src/main/java/com/yd/notice/service/model/NotificationRecord.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
model
;
import
com.baomidou.mybatisplus.annotation.IdType
;
import
com.baomidou.mybatisplus.annotation.TableField
;
import
com.baomidou.mybatisplus.annotation.TableId
;
import
com.baomidou.mybatisplus.annotation.TableName
;
import
java.io.Serializable
;
import
java.time.LocalDateTime
;
import
lombok.Getter
;
import
lombok.Setter
;
/**
* <p>
* 发送记录表
* </p>
*
* @author zxm
* @since 2026-04-02
*/
@Getter
@Setter
@TableName
(
"notification_record"
)
public
class
NotificationRecord
implements
Serializable
{
private
static
final
long
serialVersionUID
=
1L
;
/**
* 主键ID
*/
@TableId
(
value
=
"id"
,
type
=
IdType
.
AUTO
)
private
Long
id
;
/**
* 关联消息任务表唯一业务ID
*/
@TableField
(
"task_biz_id"
)
private
String
taskBizId
;
/**
* 通知渠道(关联渠道配置表唯一业务ID)
*/
@TableField
(
"channel_biz_id"
)
private
String
channelBizId
;
/**
* 消息模板表唯一业务ID
*/
@TableField
(
"template_biz_id"
)
private
String
templateBizId
;
/**
* 发送人
*/
@TableField
(
"sender"
)
private
String
sender
;
/**
* 发送人类型:sys/userid/mobile/email/openid
*/
@TableField
(
"sender_type"
)
private
String
senderType
;
/**
* 接收人
*/
@TableField
(
"receiver"
)
private
String
receiver
;
/**
* 接收人类型:sys/userid/mobile/email/openid
*/
@TableField
(
"receiver_type"
)
private
String
receiverType
;
/**
* 发送标题
*/
@TableField
(
"title"
)
private
String
title
;
/**
* 发送内容
*/
@TableField
(
"content"
)
private
String
content
;
/**
* 状态:0待发送,1发送中,2成功,3失败
*/
@TableField
(
"status"
)
private
Integer
status
;
/**
* 错误码
*/
@TableField
(
"error_code"
)
private
String
errorCode
;
/**
* 错误信息
*/
@TableField
(
"error_msg"
)
private
String
errorMsg
;
/**
* 耗时(毫秒)
*/
@TableField
(
"cost_time"
)
private
Integer
costTime
;
/**
* 发送时间
*/
@TableField
(
"send_time"
)
private
LocalDateTime
sendTime
;
/**
* 回调数据
*/
@TableField
(
"callback_data"
)
private
String
callbackData
;
/**
* 排序
*/
@TableField
(
"sort"
)
private
Integer
sort
;
/**
* 通用备注
*/
@TableField
(
"remark"
)
private
String
remark
;
/**
* 删除标识: 0-正常, 1-删除
*/
@TableField
(
"is_deleted"
)
private
Integer
isDeleted
;
/**
* 创建人ID
*/
@TableField
(
"creator_id"
)
private
String
creatorId
;
/**
* 更新人ID
*/
@TableField
(
"updater_id"
)
private
String
updaterId
;
/**
* 创建时间
*/
@TableField
(
"create_time"
)
private
LocalDateTime
createTime
;
/**
* 更新时间
*/
@TableField
(
"update_time"
)
private
LocalDateTime
updateTime
;
}
yd-notice-service/src/main/java/com/yd/notice/service/model/NotificationTask.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
model
;
import
com.baomidou.mybatisplus.annotation.IdType
;
import
com.baomidou.mybatisplus.annotation.TableField
;
import
com.baomidou.mybatisplus.annotation.TableId
;
import
com.baomidou.mybatisplus.annotation.TableName
;
import
java.io.Serializable
;
import
java.time.LocalDateTime
;
import
lombok.Getter
;
import
lombok.Setter
;
/**
* <p>
* 消息任务表
* </p>
*
* @author zxm
* @since 2026-04-02
*/
@Getter
@Setter
@TableName
(
"notification_task"
)
public
class
NotificationTask
implements
Serializable
{
private
static
final
long
serialVersionUID
=
1L
;
/**
* 主键ID
*/
@TableId
(
value
=
"id"
,
type
=
IdType
.
AUTO
)
private
Long
id
;
/**
* 消息任务表唯一业务ID
*/
@TableField
(
"task_biz_id"
)
private
String
taskBizId
;
/**
* 消息模板表唯一业务ID
*/
@TableField
(
"template_biz_id"
)
private
String
templateBizId
;
/**
* 消息标题
*/
@TableField
(
"title"
)
private
String
title
;
/**
* 消息内容
*/
@TableField
(
"content"
)
private
String
content
;
/**
* 通知渠道(关联渠道配置表唯一业务ID)
*/
@TableField
(
"channel_biz_id"
)
private
String
channelBizId
;
/**
* 发送人
*/
@TableField
(
"sender"
)
private
String
sender
;
/**
* 发送人类型:sys/userid/mobile/email/openid
*/
@TableField
(
"sender_type"
)
private
String
senderType
;
/**
* 接收人
*/
@TableField
(
"receiver"
)
private
String
receiver
;
/**
* 接收人类型:sys/userid/mobile/email/openid
*/
@TableField
(
"receiver_type"
)
private
String
receiverType
;
/**
* 扩展参数(各渠道特有参数)
*/
@TableField
(
"extra_params"
)
private
String
extraParams
;
/**
* 优先级:1-10,数字越小优先级越高
*/
@TableField
(
"priority"
)
private
Integer
priority
;
/**
* 已重试次数
*/
@TableField
(
"retry_count"
)
private
Integer
retryCount
;
/**
* 最大重试次数
*/
@TableField
(
"max_retry"
)
private
Integer
maxRetry
;
/**
* 是否定时 0-否 1-是
*/
@TableField
(
"is_timing"
)
private
Integer
isTiming
;
/**
* 定时时间
*/
@TableField
(
"timing_time"
)
private
LocalDateTime
timingTime
;
/**
* 状态:0待发送,1发送中,2成功,3失败,4已取消
*/
@TableField
(
"status"
)
private
Integer
status
;
/**
* 排序
*/
@TableField
(
"sort"
)
private
Integer
sort
;
/**
* 通用备注
*/
@TableField
(
"remark"
)
private
String
remark
;
/**
* 删除标识: 0-正常, 1-删除
*/
@TableField
(
"is_deleted"
)
private
Integer
isDeleted
;
/**
* 创建人ID
*/
@TableField
(
"creator_id"
)
private
String
creatorId
;
/**
* 更新人ID
*/
@TableField
(
"updater_id"
)
private
String
updaterId
;
/**
* 创建时间
*/
@TableField
(
"create_time"
)
private
LocalDateTime
createTime
;
/**
* 更新时间
*/
@TableField
(
"update_time"
)
private
LocalDateTime
updateTime
;
}
yd-notice-service/src/main/java/com/yd/notice/service/model/NotificationTemplate.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
model
;
import
com.baomidou.mybatisplus.annotation.IdType
;
import
com.baomidou.mybatisplus.annotation.TableField
;
import
com.baomidou.mybatisplus.annotation.TableId
;
import
com.baomidou.mybatisplus.annotation.TableName
;
import
java.io.Serializable
;
import
java.time.LocalDateTime
;
import
lombok.Getter
;
import
lombok.Setter
;
/**
* <p>
* 消息模板表
* </p>
*
* @author zxm
* @since 2026-04-02
*/
@Getter
@Setter
@TableName
(
"notification_template"
)
public
class
NotificationTemplate
implements
Serializable
{
private
static
final
long
serialVersionUID
=
1L
;
/**
* 主键ID
*/
@TableId
(
value
=
"id"
,
type
=
IdType
.
AUTO
)
private
Long
id
;
/**
* 消息模板表唯一业务ID
*/
@TableField
(
"template_biz_id"
)
private
String
templateBizId
;
/**
* 适用渠道(关联渠道配置表唯一业务ID)
*/
@TableField
(
"channel_biz_id"
)
private
String
channelBizId
;
/**
* 模板编码(唯一)
*/
@TableField
(
"template_code"
)
private
String
templateCode
;
/**
* 模板名称
*/
@TableField
(
"template_name"
)
private
String
templateName
;
/**
* 模板类型:text/markdown/html
*/
@TableField
(
"template_type"
)
private
String
templateType
;
/**
* 标题模板(支持占位符)
*/
@TableField
(
"title_template"
)
private
String
titleTemplate
;
/**
* 内容模板(支持占位符,如{{name}})
*/
@TableField
(
"content_template"
)
private
String
contentTemplate
;
/**
* 扩展参数模板
*/
@TableField
(
"extra_template"
)
private
String
extraTemplate
;
/**
* 模板描述
*/
@TableField
(
"description"
)
private
String
description
;
/**
* 状态:0禁用,1启用
*/
@TableField
(
"status"
)
private
Integer
status
;
/**
* 排序
*/
@TableField
(
"sort"
)
private
Integer
sort
;
/**
* 通用备注
*/
@TableField
(
"remark"
)
private
String
remark
;
/**
* 删除标识: 0-正常, 1-删除
*/
@TableField
(
"is_deleted"
)
private
Integer
isDeleted
;
/**
* 创建人ID
*/
@TableField
(
"creator_id"
)
private
String
creatorId
;
/**
* 更新人ID
*/
@TableField
(
"updater_id"
)
private
String
updaterId
;
/**
* 创建时间
*/
@TableField
(
"create_time"
)
private
LocalDateTime
createTime
;
/**
* 更新时间
*/
@TableField
(
"update_time"
)
private
LocalDateTime
updateTime
;
}
yd-notice-service/src/main/java/com/yd/notice/service/schedule/MessageSendScheduler.java
0 → 100644
View file @
156bacf5
//package com.yd.notice.service.schedule;
//
//import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
//import com.yd.notice.service.dao.ChannelConfigMapper;
//import com.yd.notice.service.dao.NotificationTaskMapper;
//import com.yd.notice.service.model.ChannelConfig;
//import com.yd.notice.service.model.NotificationTask;
//import com.yd.notice.service.send.WecomMessageSender;
//import com.yd.notice.service.utils.AesEncryptUtil;
//import lombok.RequiredArgsConstructor;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.scheduling.annotation.Scheduled;
//import org.springframework.stereotype.Component;
//import java.time.LocalDateTime;
//import java.util.List;
//
//@Slf4j
//@Component
//@RequiredArgsConstructor
//public class MessageSendScheduler {
// private final NotificationTaskMapper taskMapper;
// private final ChannelConfigMapper channelConfigMapper;
// private final WecomMessageSender wecomMessageSender;
// private final AesEncryptUtil aesEncryptUtil;
//
// /**
// * 每10秒执行一次,捞取待发送的消息任务
// */
// @Scheduled(fixedDelay = 10000)
// public void processPendingTasks() {
// // 1. 捞取待发送的任务(status=0,且未删除)
// List<NotificationTask> tasks = taskMapper.selectList(
// new LambdaQueryWrapper<NotificationTask>()
// .eq(NotificationTask::getStatus, 0)
// .eq(NotificationTask::getIsDeleted, 0)
// .last("limit 100")
// );
//
// if (tasks.isEmpty()) {
// return;
// }
//
// // 2. 获取企业微信渠道配置
// ChannelConfig wecomConfig = channelConfigMapper.selectOne(
// new LambdaQueryWrapper<ChannelConfig>()
// .eq(ChannelConfig::getChannelType, "wecom")
// .eq(ChannelConfig::getStatus, 1)
// .eq(ChannelConfig::getIsDeleted, 0)
// );
//
// if (wecomConfig == null) {
// log.warn("企业微信渠道未配置或已禁用");
// return;
// }
//
// // 3. 解密配置密钥(如果需要手动配置SDK,这里可提取config_value)
// String decryptedSecret = aesEncryptUtil.decrypt(wecomConfig.getConfigValue());
//
// // 4. 逐条发送消息
// for (NotificationTask task : tasks) {
// try {
// // 更新状态为“发送中”
// task.setStatus(1);
// task.setUpdateTime(LocalDateTime.now());
// taskMapper.updateById(task);
//
// // 调用发送服务
// boolean success = wecomMessageSender.sendTextMessage(task);
//
// if (success) {
// task.setStatus(2); // 成功
// task.setUpdateTime(LocalDateTime.now());
// } else {
// // 失败处理:增加重试次数
// int retryCount = task.getRetryCount() + 1;
// task.setRetryCount(retryCount);
// if (retryCount >= task.getMaxRetry()) {
// task.setStatus(3); // 最终失败
// log.error("消息任务最终失败,taskBizId: {}, 已重试{}次",
// task.getTaskBizId(), retryCount);
// } else {
// task.setStatus(0); // 保留待发送状态,等待下次重试
// }
// task.setUpdateTime(LocalDateTime.now());
// }
// taskMapper.updateById(task);
//
// } catch (Exception e) {
// log.error("处理消息任务异常,taskBizId: {}", task.getTaskBizId(), e);
// // 异常情况,保持待发送状态
// task.setStatus(0);
// task.setUpdateTime(LocalDateTime.now());
// taskMapper.updateById(task);
// }
// }
// }
//}
\ No newline at end of file
yd-notice-service/src/main/java/com/yd/notice/service/send/WecomMessageSender.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
send
;
import
me.chanjar.weixin.cp.bean.article.NewArticle
;
import
com.yd.notice.service.model.NotificationTask
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
me.chanjar.weixin.cp.api.WxCpService
;
import
me.chanjar.weixin.cp.bean.message.WxCpMessage
;
import
org.springframework.stereotype.Service
;
@Slf4j
@Service
@RequiredArgsConstructor
public
class
WecomMessageSender
{
private
final
WxCpService
wxCpService
;
/**
* 发送文本消息
* @param task 消息任务实体
* @return true-发送成功,false-发送失败
*/
public
boolean
sendTextMessage
(
NotificationTask
task
)
{
try
{
// 构建消息体
WxCpMessage
message
=
WxCpMessage
.
TEXT
()
.
agentId
(
wxCpService
.
getWxCpConfigStorage
().
getAgentId
())
.
toUser
(
task
.
getReceiver
())
// 接收人UserID,多个用"|"分隔,或使用"@all"
.
content
(
task
.
getContent
())
// 消息内容
.
build
();
// 发送消息
wxCpService
.
getMessageService
().
send
(
message
);
log
.
info
(
"企业微信消息发送成功,taskBizId: {}, receiver: {}"
,
task
.
getTaskBizId
(),
task
.
getReceiver
());
return
true
;
}
catch
(
Exception
e
)
{
log
.
error
(
"企业微信消息发送失败,taskBizId: {}, error: {}"
,
task
.
getTaskBizId
(),
e
.
getMessage
(),
e
);
return
false
;
}
}
/**
* 发送图文卡片消息(带跳转链接)
*/
public
boolean
sendNewsMessage
(
NotificationTask
task
,
String
url
,
String
description
)
{
try
{
// 使用 builder() 构建 NewArticle 对象
NewArticle
article
=
NewArticle
.
builder
()
.
title
(
task
.
getTitle
())
// 消息标题
.
description
(
description
)
// 消息描述
.
url
(
url
)
// 点击后跳转的链接
// .picUrl("https://your-domain.com/cover.jpg") // 可选:封面图片
// .btnText("查看详情") // 可选:按钮文字
.
build
();
// 构建 WxCpMessage 消息体
WxCpMessage
message
=
WxCpMessage
.
NEWS
()
.
agentId
(
wxCpService
.
getWxCpConfigStorage
().
getAgentId
())
.
toUser
(
task
.
getReceiver
())
.
addArticle
(
article
)
// 使用NewArticle对象
.
build
();
// 发送消息
wxCpService
.
getMessageService
().
send
(
message
);
log
.
info
(
"企业微信图文消息发送成功,taskBizId: {}"
,
task
.
getTaskBizId
());
return
true
;
}
catch
(
Exception
e
)
{
log
.
error
(
"企业微信图文消息发送失败,taskBizId: {}"
,
task
.
getTaskBizId
(),
e
);
return
false
;
}
}
}
\ No newline at end of file
yd-notice-service/src/main/java/com/yd/notice/service/service/IChannelConfigService.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
service
;
import
com.yd.notice.service.model.ChannelConfig
;
import
com.baomidou.mybatisplus.extension.service.IService
;
/**
* <p>
* 渠道配置表 服务类
* </p>
*
* @author zxm
* @since 2026-04-02
*/
public
interface
IChannelConfigService
extends
IService
<
ChannelConfig
>
{
}
yd-notice-service/src/main/java/com/yd/notice/service/service/INotificationRecordService.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
service
;
import
com.yd.notice.service.model.NotificationRecord
;
import
com.baomidou.mybatisplus.extension.service.IService
;
/**
* <p>
* 发送记录表 服务类
* </p>
*
* @author zxm
* @since 2026-04-02
*/
public
interface
INotificationRecordService
extends
IService
<
NotificationRecord
>
{
}
yd-notice-service/src/main/java/com/yd/notice/service/service/INotificationTaskService.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
service
;
import
com.yd.notice.service.model.NotificationTask
;
import
com.baomidou.mybatisplus.extension.service.IService
;
import
org.springframework.transaction.annotation.Transactional
;
/**
* <p>
* 消息任务表 服务类
* </p>
*
* @author zxm
* @since 2026-04-02
*/
public
interface
INotificationTaskService
extends
IService
<
NotificationTask
>
{
@Transactional
(
rollbackFor
=
Exception
.
class
)
String
createAndSendTask
(
String
channelBizId
,
String
templateBizId
,
String
receiver
,
String
params
);
}
yd-notice-service/src/main/java/com/yd/notice/service/service/INotificationTemplateService.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
service
;
import
com.yd.notice.service.model.NotificationTemplate
;
import
com.baomidou.mybatisplus.extension.service.IService
;
/**
* <p>
* 消息模板表 服务类
* </p>
*
* @author zxm
* @since 2026-04-02
*/
public
interface
INotificationTemplateService
extends
IService
<
NotificationTemplate
>
{
}
yd-notice-service/src/main/java/com/yd/notice/service/service/impl/ChannelConfigServiceImpl.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
service
.
impl
;
import
com.yd.notice.service.model.ChannelConfig
;
import
com.yd.notice.service.dao.ChannelConfigMapper
;
import
com.yd.notice.service.service.IChannelConfigService
;
import
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
;
import
org.springframework.stereotype.Service
;
/**
* <p>
* 渠道配置表 服务实现类
* </p>
*
* @author zxm
* @since 2026-04-02
*/
@Service
public
class
ChannelConfigServiceImpl
extends
ServiceImpl
<
ChannelConfigMapper
,
ChannelConfig
>
implements
IChannelConfigService
{
}
yd-notice-service/src/main/java/com/yd/notice/service/service/impl/NotificationRecordServiceImpl.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
service
.
impl
;
import
com.yd.notice.service.model.NotificationRecord
;
import
com.yd.notice.service.dao.NotificationRecordMapper
;
import
com.yd.notice.service.service.INotificationRecordService
;
import
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
;
import
org.springframework.stereotype.Service
;
/**
* <p>
* 发送记录表 服务实现类
* </p>
*
* @author zxm
* @since 2026-04-02
*/
@Service
public
class
NotificationRecordServiceImpl
extends
ServiceImpl
<
NotificationRecordMapper
,
NotificationRecord
>
implements
INotificationRecordService
{
}
yd-notice-service/src/main/java/com/yd/notice/service/service/impl/NotificationTaskServiceImpl.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
service
.
impl
;
import
cn.hutool.core.date.LocalDateTimeUtil
;
import
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
;
import
com.yd.common.exception.BusinessException
;
import
com.yd.notice.service.model.NotificationRecord
;
import
com.yd.notice.service.model.NotificationTask
;
import
com.yd.notice.service.dao.NotificationTaskMapper
;
import
com.yd.notice.service.model.NotificationTemplate
;
import
com.yd.notice.service.send.WecomMessageSender
;
import
com.yd.notice.service.service.INotificationRecordService
;
import
com.yd.notice.service.service.INotificationTaskService
;
import
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
;
import
com.yd.notice.service.service.INotificationTemplateService
;
import
com.yd.notice.service.utils.TemplateRenderUtil
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Qualifier
;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Transactional
;
import
java.time.LocalDateTime
;
import
java.util.UUID
;
import
java.util.concurrent.Executor
;
/**
* <p>
* 消息任务表 服务实现类
* </p>
*
* @author zxm
* @since 2026-04-02
*/
@Slf4j
@Service
@RequiredArgsConstructor
public
class
NotificationTaskServiceImpl
extends
ServiceImpl
<
NotificationTaskMapper
,
NotificationTask
>
implements
INotificationTaskService
{
private
final
INotificationTemplateService
templateService
;
private
final
TemplateRenderUtil
templateRenderUtil
;
private
final
WecomMessageSender
wecomMessageSender
;
private
final
NotificationTaskMapper
taskMapper
;
private
final
INotificationRecordService
recordService
;
@Qualifier
(
"noticeExecutor"
)
private
final
Executor
noticeExecutor
;
/**
* 创建消息任务并立即异步发送
*/
@Transactional
(
rollbackFor
=
Exception
.
class
)
public
String
createAndSendTask
(
String
channelBizId
,
String
templateBizId
,
String
receiver
,
String
params
)
{
// 获取模板并渲染内容
NotificationTemplate
template
=
templateService
.
getOne
(
new
LambdaQueryWrapper
<
NotificationTemplate
>()
.
eq
(
NotificationTemplate:
:
getTemplateBizId
,
templateBizId
)
.
eq
(
NotificationTemplate:
:
getStatus
,
1
)
);
if
(
template
==
null
)
{
throw
new
BusinessException
(
"模板不存在或已禁用"
);
}
String
title
=
templateRenderUtil
.
render
(
template
.
getTitleTemplate
(),
params
);
String
content
=
templateRenderUtil
.
render
(
template
.
getContentTemplate
(),
params
);
// 创建任务记录(状态为待发送)
NotificationTask
task
=
new
NotificationTask
();
String
taskBizId
=
UUID
.
randomUUID
().
toString
();
task
.
setTaskBizId
(
taskBizId
);
task
.
setTemplateBizId
(
templateBizId
);
task
.
setChannelBizId
(
channelBizId
);
task
.
setTitle
(
title
);
task
.
setContent
(
content
);
task
.
setReceiver
(
receiver
);
task
.
setReceiverType
(
"userid"
);
task
.
setStatus
(
0
);
// 待发送
task
.
setRetryCount
(
0
);
task
.
setMaxRetry
(
3
);
task
.
setIsTiming
(
0
);
task
.
setCreateTime
(
LocalDateTime
.
now
());
task
.
setUpdateTime
(
LocalDateTime
.
now
());
save
(
task
);
log
.
info
(
"创建消息任务成功,taskBizId: {},准备异步发送"
,
taskBizId
);
// 3. 异步发送(不阻塞接口响应)
noticeExecutor
.
execute
(()
->
doSendTask
(
task
));
return
taskBizId
;
}
/**
* 实际发送逻辑(支持重试,每次尝试都会记录发送记录)
*/
private
void
doSendTask
(
NotificationTask
task
)
{
// NotificationTask task = taskMapper.selectOne(
// new LambdaQueryWrapper<NotificationTask>()
// .eq(NotificationTask::getTaskBizId, taskBizId)
// );
String
taskBizId
=
task
.
getTaskBizId
();
if
(
task
==
null
)
{
log
.
error
(
"任务不存在: {}"
,
taskBizId
);
return
;
}
// 防止并发重复发送(状态不是待发送则跳过)
if
(
task
.
getStatus
()
!=
0
)
{
log
.
warn
(
"任务状态不是待发送,跳过执行: {}"
,
taskBizId
);
return
;
}
// 更新任务状态为“发送中”
task
.
setStatus
(
1
);
task
.
setUpdateTime
(
LocalDateTime
.
now
());
taskMapper
.
updateById
(
task
);
// 带重试的发送(每次尝试都会产生一条发送记录)
boolean
success
=
sendWithRetryAndRecord
(
task
);
// 最终结果处理
if
(
success
)
{
task
.
setStatus
(
2
);
// 成功
log
.
info
(
"消息发送成功,taskBizId: {}"
,
taskBizId
);
}
else
{
task
.
setStatus
(
3
);
// 失败(已达最大重试次数)
log
.
error
(
"消息发送最终失败,taskBizId: {}"
,
taskBizId
);
}
task
.
setUpdateTime
(
LocalDateTime
.
now
());
taskMapper
.
updateById
(
task
);
}
/**
* 带重试机制的发送,每次尝试都记录 NotificationRecord
* @param task 任务实体
* @return 是否最终成功
*/
private
boolean
sendWithRetryAndRecord
(
NotificationTask
task
)
{
int
maxRetry
=
task
.
getMaxRetry
();
int
currentRetry
=
0
;
long
waitMillis
=
1000
;
// 初始等待1秒
while
(
currentRetry
<=
maxRetry
)
{
// 创建一条发送记录(状态为待发送)
NotificationRecord
record
=
buildInitialRecord
(
task
,
currentRetry
);
recordService
.
save
(
record
);
Long
recordId
=
record
.
getId
();
// 获取自增ID,便于后续更新
LocalDateTime
startTime
=
LocalDateTime
.
now
();
boolean
success
=
false
;
String
errorCode
=
null
;
String
errorMsg
=
null
;
Integer
costTime
=
null
;
try
{
// 更新记录状态为“发送中”
record
.
setStatus
(
1
);
record
.
setUpdateTime
(
LocalDateTime
.
now
());
recordService
.
updateById
(
record
);
// 实际调用企业微信API
success
=
wecomMessageSender
.
sendTextMessage
(
task
);
if
(
success
)
{
errorCode
=
null
;
errorMsg
=
null
;
}
}
catch
(
Exception
e
)
{
log
.
error
(
"发送异常,taskBizId: {}, retry: {}"
,
task
.
getTaskBizId
(),
currentRetry
,
e
);
errorCode
=
"SEND_EXCEPTION"
;
errorMsg
=
e
.
getMessage
();
success
=
false
;
}
finally
{
// 计算耗时
costTime
=
(
int
)
(
LocalDateTimeUtil
.
between
(
startTime
,
LocalDateTime
.
now
()).
toMillis
());
}
// 更新记录最终状态
record
.
setStatus
(
success
?
2
:
3
);
record
.
setErrorCode
(
errorCode
);
record
.
setErrorMsg
(
errorMsg
);
record
.
setCostTime
(
costTime
);
record
.
setSendTime
(
LocalDateTime
.
now
());
record
.
setUpdateTime
(
LocalDateTime
.
now
());
recordService
.
updateById
(
record
);
if
(
success
)
{
return
true
;
}
// 失败处理:增加任务的重试计数
currentRetry
++;
task
.
setRetryCount
(
currentRetry
);
task
.
setUpdateTime
(
LocalDateTime
.
now
());
taskMapper
.
updateById
(
task
);
if
(
currentRetry
<=
maxRetry
)
{
log
.
warn
(
"发送失败,{}秒后进行第{}次重试,taskBizId: {}"
,
waitMillis
/
1000
,
currentRetry
,
task
.
getTaskBizId
());
try
{
Thread
.
sleep
(
waitMillis
);
waitMillis
=
Math
.
min
(
waitMillis
*
2
,
30000
);
// 退避,最大30秒
}
catch
(
InterruptedException
e
)
{
Thread
.
currentThread
().
interrupt
();
break
;
}
}
}
return
false
;
}
/**
* 构建初始的发送记录(状态为待发送)
* @param task 任务实体
* @param retryIndex 第几次尝试(0表示首次)
* @return 初始记录
*/
private
NotificationRecord
buildInitialRecord
(
NotificationTask
task
,
int
retryIndex
)
{
NotificationRecord
record
=
new
NotificationRecord
();
record
.
setTaskBizId
(
task
.
getTaskBizId
());
record
.
setChannelBizId
(
task
.
getChannelBizId
());
record
.
setTemplateBizId
(
task
.
getTemplateBizId
());
// 发送人信息:企业微信场景下,发送人通常是应用名称,可以从配置获取,这里先固定
record
.
setSender
(
"企业微信应用"
);
record
.
setSenderType
(
"sys"
);
record
.
setReceiver
(
task
.
getReceiver
());
record
.
setReceiverType
(
task
.
getReceiverType
());
record
.
setTitle
(
task
.
getTitle
());
record
.
setContent
(
task
.
getContent
());
record
.
setStatus
(
0
);
// 待发送
record
.
setCreateTime
(
LocalDateTime
.
now
());
record
.
setUpdateTime
(
LocalDateTime
.
now
());
// 可选:备注重试次数
record
.
setRemark
(
"retry_"
+
retryIndex
);
return
record
;
}
}
yd-notice-service/src/main/java/com/yd/notice/service/service/impl/NotificationTemplateServiceImpl.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
service
.
impl
;
import
com.yd.notice.service.model.NotificationTemplate
;
import
com.yd.notice.service.dao.NotificationTemplateMapper
;
import
com.yd.notice.service.service.INotificationTemplateService
;
import
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
;
import
org.springframework.stereotype.Service
;
/**
* <p>
* 消息模板表 服务实现类
* </p>
*
* @author zxm
* @since 2026-04-02
*/
@Service
public
class
NotificationTemplateServiceImpl
extends
ServiceImpl
<
NotificationTemplateMapper
,
NotificationTemplate
>
implements
INotificationTemplateService
{
}
yd-notice-service/src/main/java/com/yd/notice/service/utils/AesEncryptUtil.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
utils
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.stereotype.Component
;
import
javax.crypto.Cipher
;
import
javax.crypto.spec.SecretKeySpec
;
import
java.util.Base64
;
@Slf4j
@Component
public
class
AesEncryptUtil
{
private
static
final
String
ALGORITHM
=
"AES"
;
private
static
final
String
TRANSFORMATION
=
"AES/ECB/PKCS5Padding"
;
@Value
(
"${notice.aes.secret-key}"
)
private
String
secretKey
;
// 从配置文件注入,建议使用16/24/32字节的密钥
public
String
encrypt
(
String
plainText
)
{
try
{
SecretKeySpec
keySpec
=
new
SecretKeySpec
(
secretKey
.
getBytes
(),
ALGORITHM
);
Cipher
cipher
=
Cipher
.
getInstance
(
TRANSFORMATION
);
cipher
.
init
(
Cipher
.
ENCRYPT_MODE
,
keySpec
);
byte
[]
encrypted
=
cipher
.
doFinal
(
plainText
.
getBytes
());
return
Base64
.
getEncoder
().
encodeToString
(
encrypted
);
}
catch
(
Exception
e
)
{
log
.
error
(
"加密失败"
,
e
);
throw
new
RuntimeException
(
"加密失败"
,
e
);
}
}
public
String
decrypt
(
String
cipherText
)
{
try
{
SecretKeySpec
keySpec
=
new
SecretKeySpec
(
secretKey
.
getBytes
(),
ALGORITHM
);
Cipher
cipher
=
Cipher
.
getInstance
(
TRANSFORMATION
);
cipher
.
init
(
Cipher
.
DECRYPT_MODE
,
keySpec
);
byte
[]
decoded
=
Base64
.
getDecoder
().
decode
(
cipherText
);
byte
[]
decrypted
=
cipher
.
doFinal
(
decoded
);
return
new
String
(
decrypted
);
}
catch
(
Exception
e
)
{
log
.
error
(
"解密失败"
,
e
);
throw
new
RuntimeException
(
"解密失败"
,
e
);
}
}
}
\ No newline at end of file
yd-notice-service/src/main/java/com/yd/notice/service/utils/MyBatisPlusCodeGenerator.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
utils
;
import
com.baomidou.mybatisplus.generator.FastAutoGenerator
;
import
com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine
;
public
class
MyBatisPlusCodeGenerator
{
public
static
void
main
(
String
[]
args
)
{
FastAutoGenerator
.
create
(
"jdbc:mysql://139.224.145.34:3308/yd_notice?serverTimezone=GMT%2B8"
,
"root"
,
"Zxm7320017"
)
.
globalConfig
(
builder
->
{
builder
.
author
(
"zxm"
)
// .outputDir("src/main/java/com/yd/ai/service");
.
outputDir
(
"D:/soft/ideaproject/v2/yd-notice/yd-notice-service/src/main/java"
);
})
.
packageConfig
(
builder
->
{
builder
.
parent
(
"com.yd.notice.service"
)
.
entity
(
"model"
)
.
mapper
(
"dao"
)
.
service
(
"service"
)
.
serviceImpl
(
"service.impl"
)
.
xml
(
"mappers"
);
})
.
strategyConfig
(
builder
->
{
builder
.
addInclude
(
"channel_config"
,
"notification_record"
,
"notification_task"
,
"notification_template"
)
.
entityBuilder
()
.
enableLombok
()
.
enableTableFieldAnnotation
()
.
controllerBuilder
()
.
enableRestStyle
();
})
.
templateEngine
(
new
FreemarkerTemplateEngine
())
.
execute
();
}
}
yd-notice-service/src/main/java/com/yd/notice/service/utils/TemplateRenderUtil.java
0 → 100644
View file @
156bacf5
package
com
.
yd
.
notice
.
service
.
utils
;
import
cn.hutool.core.map.MapUtil
;
import
cn.hutool.core.util.StrUtil
;
import
com.fasterxml.jackson.core.JsonProcessingException
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.stereotype.Component
;
import
java.util.Map
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
@Slf4j
@Component
@RequiredArgsConstructor
public
class
TemplateRenderUtil
{
private
static
final
Pattern
PLACEHOLDER_PATTERN
=
Pattern
.
compile
(
"\\{\\{(\\w+)\\}\\}"
);
private
final
ObjectMapper
objectMapper
;
/**
* 渲染模板内容
* @param template 模板字符串,如 "Hello {{name}}"
* @param params 参数JSON字符串,如 "{\"name\": \"张三\"}"
* @return 渲染后的字符串
*/
public
String
render
(
String
template
,
String
params
)
{
if
(
StrUtil
.
isBlank
(
template
))
{
return
""
;
}
Map
<
String
,
Object
>
paramMap
=
parseParams
(
params
);
if
(
MapUtil
.
isEmpty
(
paramMap
))
{
return
template
;
}
StringBuffer
result
=
new
StringBuffer
();
Matcher
matcher
=
PLACEHOLDER_PATTERN
.
matcher
(
template
);
while
(
matcher
.
find
())
{
String
key
=
matcher
.
group
(
1
);
String
value
=
paramMap
.
containsKey
(
key
)
?
String
.
valueOf
(
paramMap
.
get
(
key
))
:
""
;
matcher
.
appendReplacement
(
result
,
Matcher
.
quoteReplacement
(
value
));
}
matcher
.
appendTail
(
result
);
return
result
.
toString
();
}
@SuppressWarnings
(
"unchecked"
)
private
Map
<
String
,
Object
>
parseParams
(
String
params
)
{
if
(
StrUtil
.
isBlank
(
params
))
{
return
MapUtil
.
empty
();
}
try
{
return
objectMapper
.
readValue
(
params
,
Map
.
class
);
}
catch
(
JsonProcessingException
e
)
{
log
.
warn
(
"解析模板参数失败: {}"
,
params
,
e
);
return
MapUtil
.
empty
();
}
}
}
\ No newline at end of file
yd-notice-service/src/main/resources/mappers/ChannelConfigMapper.xml
0 → 100644
View file @
156bacf5
<?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.notice.service.dao.ChannelConfigMapper"
>
</mapper>
yd-notice-service/src/main/resources/mappers/NotificationRecordMapper.xml
0 → 100644
View file @
156bacf5
<?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.notice.service.dao.NotificationRecordMapper"
>
</mapper>
yd-notice-service/src/main/resources/mappers/NotificationTaskMapper.xml
0 → 100644
View file @
156bacf5
<?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.notice.service.dao.NotificationTaskMapper"
>
</mapper>
yd-notice-service/src/main/resources/mappers/NotificationTemplateMapper.xml
0 → 100644
View file @
156bacf5
<?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.notice.service.dao.NotificationTemplateMapper"
>
</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