Commit 156bacf5 by zhangxingmin

通知

parent d1bf002c
......@@ -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" />
......
# 基础镜像
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
......@@ -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>
......
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);
// }
}
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 {
}
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 {
}
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 {
}
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);
}
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);
}
}
${AnsiColor.GREEN}
_ _ _ __ __ ____ _ _ _ _
| | (_)_ __ | | _\ \ / /__ / ___| |__ __ _| |_ / \ _ __ (_)
| | | | '_ \| |/ /\ \ /\ / / _ \ | | '_ \ / _` | __| / _ \ | '_ \| |
| |___| | | | | < \ V V / __/ |___| | | | (_| | |_ / ___ \| |_) | |
|_____|_|_| |_|_|\_\ \_/\_/ \___|\____|_| |_|\__,_|\__/_/ \_\ .__/|_|
|_|
${AnsiColor.BRIGHT_WHITE}
Spring Boot Version: ${spring-boot.version}
\ No newline at end of file
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
#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
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);
}
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;
}
};
}
}
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
package com.yd.notice.feign.response;
import lombok.Data;
@Data
public class ApiSendResponse {
/**
* 消息任务唯一业务ID
*/
private String taskBizId;
}
......@@ -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
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
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
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
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> {
}
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> {
}
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> {
}
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> {
}
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;
}
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;
}
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;
}
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;
}
//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
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
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> {
}
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> {
}
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);
}
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> {
}
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 {
}
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 {
}
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;
}
}
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 {
}
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
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();
}
}
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
<?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>
<?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>
<?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>
<?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>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment