微调
This commit is contained in:
@@ -6,6 +6,7 @@ import org.springframework.core.Ordered;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 自定义的 URL 的安全配置
|
||||
|
||||
@@ -38,7 +38,7 @@ public class MsgpushSecurityAutoConfiguration {
|
||||
private SecurityProperties securityProperties;
|
||||
|
||||
/**
|
||||
* 认证失败处理类 Bean
|
||||
* 身份认证失败处理类 Bean
|
||||
*/
|
||||
@Bean
|
||||
public AuthenticationEntryPoint authenticationEntryPoint() {
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.google.common.collect.Multimap;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.springframework.util.Assert;
|
||||
public class TransmittableThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
|
||||
|
||||
/**
|
||||
* 使用 TransmittableThreadLocal 作为上下文
|
||||
* 使用 TransmittableThreadLocal 实现线程之间上下文的传递。
|
||||
*/
|
||||
private static final ThreadLocal<SecurityContext> CONTEXT_HOLDER = new TransmittableThreadLocal<>();
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
com.njcn.msgpush.framework.security.config.MsgpushSecurityRpcAutoConfiguration
|
||||
com.njcn.msgpush.framework.security.config.MsgpushSecurityAutoConfiguration
|
||||
com.njcn.msgpush.framework.security.config.MsgpushWebSecurityConfigurerAdapter
|
||||
com.njcn.msgpush.framework.operatelog.config.MsgpushOperateLogConfiguration
|
||||
com.njcn.msgpush.framework.operatelog.config.MsgpushOperateLogRpcAutoConfiguration
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.njcn.msgpush.module.push.annoation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-10
|
||||
* @description 接口幂等性检查注解
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface CheckIdmpotent {
|
||||
}
|
||||
@@ -105,6 +105,20 @@
|
||||
<artifactId>hutool-extra</artifactId> <!-- 邮件 -->
|
||||
</dependency>
|
||||
|
||||
<!-- 阿里云 Direct Mail (邮箱服务) -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>dm20151123</artifactId>
|
||||
<version>1.9.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 阿里云短信服务 -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>dysmsapi20170525</artifactId>
|
||||
<version>4.2.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -2,9 +2,11 @@ package com.njcn.msgpush.module.push;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
||||
/**
|
||||
* 项目的启动类
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@SpringBootApplication
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.njcn.msgpush.module.push.checker;
|
||||
|
||||
import com.njcn.msgpush.module.push.controller.admin.message.vo.MessageRecordSendReqVO;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-27
|
||||
* @description 检查器接口
|
||||
*/
|
||||
public interface IChecker {
|
||||
|
||||
boolean check(MessageRecordSendReqVO reqVO);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.njcn.msgpush.module.push.checker;
|
||||
|
||||
import com.njcn.msgpush.module.push.checker.impl.BlacklistChecker;
|
||||
import com.njcn.msgpush.module.push.checker.impl.IdempotencyChecker;
|
||||
import com.njcn.msgpush.module.push.checker.impl.QuotaChecker;
|
||||
import com.njcn.msgpush.module.push.checker.impl.RateLimitChecker;
|
||||
import com.njcn.msgpush.module.push.controller.admin.message.vo.MessageRecordSendReqVO;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-28
|
||||
* @description 检查链
|
||||
*/
|
||||
public class MsgPushGuardChain {
|
||||
private final List<IChecker> checkers;
|
||||
|
||||
public MsgPushGuardChain() {
|
||||
this.checkers = new ArrayList<>();
|
||||
this.checkers.add(new IdempotencyChecker());
|
||||
this.checkers.add(new BlacklistChecker());
|
||||
this.checkers.add(new QuotaChecker());
|
||||
this.checkers.add(new RateLimitChecker());
|
||||
}
|
||||
|
||||
public boolean checkAll(MessageRecordSendReqVO reqVO) {
|
||||
for (IChecker checker : checkers) {
|
||||
boolean result = checker.check(reqVO);
|
||||
if (!result) {
|
||||
// 任何一层检查失败,立即返回拒绝
|
||||
logRejection(reqVO);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void logRejection(MessageRecordSendReqVO reqVO) {
|
||||
// 记录拒绝日志,用于监控和分析
|
||||
System.out.printf("消息请求被拒绝: receiver=%s, messageId=%s, reason=%s%n", reqVO.getReceiver(), reqVO.getMessageId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.njcn.msgpush.module.push.checker.impl;
|
||||
|
||||
import com.njcn.msgpush.module.push.checker.IChecker;
|
||||
import com.njcn.msgpush.module.push.controller.admin.message.vo.MessageRecordSendReqVO;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-27
|
||||
* @description 黑名单检查器
|
||||
*/
|
||||
public class BlacklistChecker implements IChecker {
|
||||
@Override
|
||||
public boolean check(MessageRecordSendReqVO reqVO) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.njcn.msgpush.module.push.checker.impl;
|
||||
|
||||
import com.njcn.msgpush.module.push.checker.IChecker;
|
||||
import com.njcn.msgpush.module.push.controller.admin.message.vo.MessageRecordSendReqVO;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-27
|
||||
* @description 接口幂等性检查器
|
||||
*/
|
||||
public class IdempotencyChecker implements IChecker {
|
||||
@Override
|
||||
public boolean check(MessageRecordSendReqVO reqVO) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.njcn.msgpush.module.push.checker.impl;
|
||||
|
||||
import com.njcn.msgpush.module.push.checker.IChecker;
|
||||
import com.njcn.msgpush.module.push.controller.admin.message.vo.MessageRecordSendReqVO;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-27
|
||||
* @description 系统配额检查器
|
||||
*/
|
||||
public class QuotaChecker implements IChecker {
|
||||
@Override
|
||||
public boolean check(MessageRecordSendReqVO reqVO) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.njcn.msgpush.module.push.checker.impl;
|
||||
|
||||
|
||||
import com.njcn.msgpush.module.push.checker.IChecker;
|
||||
import com.njcn.msgpush.module.push.controller.admin.message.vo.MessageRecordSendReqVO;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-27
|
||||
* @description 接收者频率检查器
|
||||
*/
|
||||
public class RateLimitChecker implements IChecker {
|
||||
@Override
|
||||
public boolean check(MessageRecordSendReqVO reqVO) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.njcn.msgpush.module.push.client.channel.appPush;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-11
|
||||
*/
|
||||
public interface AppPushClient {
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.njcn.msgpush.module.push.client.channel.appPush.factory;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-11
|
||||
*/
|
||||
public class AppPushFactory {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.njcn.msgpush.module.push.client.channel.mail;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-11
|
||||
*/
|
||||
public interface MailClient {
|
||||
|
||||
void sendMail(String accountName, Integer addressType, Boolean replyToAddress, String toAddress, String subject, String htmlBody);
|
||||
|
||||
void queryMailAddressByParam();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.njcn.msgpush.module.push.client.channel.mail.factory;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.msgpush.module.push.client.channel.mail.MailClient;
|
||||
import com.njcn.msgpush.module.push.client.channel.mail.impl.AliYunMailClient;
|
||||
import com.njcn.msgpush.module.push.client.constant.ClientConstant;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-11
|
||||
*/
|
||||
@Component
|
||||
public class MailFactory {
|
||||
|
||||
@Autowired
|
||||
private AliYunMailClient aliYunMailClient;
|
||||
|
||||
public MailClient getClient(String mailProviderTypeName) throws RuntimeException {
|
||||
if (StrUtil.equals(ClientConstant.ALI_YUN, mailProviderTypeName)) {
|
||||
return aliYunMailClient;
|
||||
} else {
|
||||
throw new RuntimeException("暂时不提供" + mailProviderTypeName + "邮件服务提供商");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.njcn.msgpush.module.push.client.channel.mail.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.aliyun.dm20151123.Client;
|
||||
import com.aliyun.dm20151123.models.QueryMailAddressByParamRequest;
|
||||
import com.aliyun.dm20151123.models.QueryMailAddressByParamResponse;
|
||||
import com.aliyun.dm20151123.models.SingleSendMailRequest;
|
||||
import com.aliyun.teaopenapi.models.Config;
|
||||
import com.aliyun.teautil.models.RuntimeOptions;
|
||||
import com.njcn.msgpush.module.push.client.channel.mail.MailClient;
|
||||
import com.njcn.msgpush.module.push.client.setting.mail.AliYunMailSetting;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-11
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AliYunMailClient implements MailClient {
|
||||
private static final String ACCOUNT_NAME = "accountName";
|
||||
private static final Integer ADDRESS_TYPE = 0;
|
||||
private static final String REPLY_TO_ADDRESS = "replyToAddress";
|
||||
private static final String HTML_BODY = "htmlBody";
|
||||
private static final String TEXT_BODY = "textBody";
|
||||
private static final String TO_ADDRESS = "toAddress";
|
||||
private static final String SUBJECT = "subject";
|
||||
private static final String CLICK_TRACE = "1";
|
||||
|
||||
@Autowired
|
||||
private AliYunMailSetting aliYunMailSetting;
|
||||
|
||||
private Client client;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
Config config = new Config()
|
||||
.setAccessKeyId(aliYunMailSetting.getAccessKeyId())
|
||||
.setAccessKeySecret(aliYunMailSetting.getAccessKeySecret())
|
||||
.setRegionId(aliYunMailSetting.getRegionId())
|
||||
.setEndpoint(aliYunMailSetting.getEndpoint());
|
||||
|
||||
try {
|
||||
this.client = new Client(config);
|
||||
} catch (Exception e) {
|
||||
log.error("阿里云-邮件服务初始化失败,请检查配置信息");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMail(String accountName, Integer addressType, Boolean replyToAddress, String toAddress, String subject, String htmlBody) {
|
||||
RuntimeOptions runtimeOptions = new RuntimeOptions();
|
||||
// 设置自动重试,默认是不开启的。重试次数默认是3次
|
||||
runtimeOptions.autoretry = true;
|
||||
SingleSendMailRequest request = new SingleSendMailRequest()
|
||||
.setAccountName(accountName)
|
||||
.setAddressType(addressType)
|
||||
.setReplyToAddress(replyToAddress)
|
||||
.setToAddress(toAddress)
|
||||
.setSubject(subject)
|
||||
.setHtmlBody(htmlBody);
|
||||
try {
|
||||
client.singleSendMailWithOptions(request, runtimeOptions);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("阿里云-邮件服务发送失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queryMailAddressByParam() {
|
||||
QueryMailAddressByParamRequest queryMailAddressByParamRequest = null;
|
||||
try {
|
||||
queryMailAddressByParamRequest = QueryMailAddressByParamRequest.build(new HashMap<>());
|
||||
QueryMailAddressByParamResponse response = client.queryMailAddressByParam(queryMailAddressByParamRequest);
|
||||
System.out.println(JSON.toJSON(response));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.njcn.msgpush.module.push.client.channel.sms.Impl;
|
||||
|
||||
import com.aliyun.dysmsapi20170525.Client;
|
||||
import com.aliyun.dysmsapi20170525.models.*;
|
||||
import com.aliyun.teaopenapi.models.Config;
|
||||
import com.aliyun.teautil.models.RuntimeOptions;
|
||||
import com.njcn.msgpush.module.push.client.channel.sms.SmsClient;
|
||||
import com.njcn.msgpush.module.push.client.setting.sms.AliYunSmsSetting;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.aliyun.teautil.Common.toJSONString;
|
||||
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-11
|
||||
* @description 阿里云短信服务实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AliYunSmsClient implements SmsClient {
|
||||
|
||||
public static final String SIGN_NAME = "signName";
|
||||
public static final String TEMPLATE_CODE = "templateCode";
|
||||
public static final String TEMPLATE_PARAM = "templateParam";
|
||||
public static final String OK = "OK";
|
||||
|
||||
|
||||
@Autowired
|
||||
private AliYunSmsSetting aliYunSmsSetting;
|
||||
private Client client;
|
||||
|
||||
private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
|
||||
5,
|
||||
5,
|
||||
1000,
|
||||
TimeUnit.MILLISECONDS,
|
||||
new java.util.concurrent.ArrayBlockingQueue<>(1000),
|
||||
r -> {
|
||||
Thread thread = new Thread(r);
|
||||
thread.setName("AliYunSmsClient-Pool-" + thread.getId());
|
||||
thread.setDaemon(false);
|
||||
return thread;
|
||||
},
|
||||
new ThreadPoolExecutor.CallerRunsPolicy()
|
||||
);
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
Config config = new Config()
|
||||
.setAccessKeyId(aliYunSmsSetting.getAccessKeyId())
|
||||
.setAccessKeySecret(aliYunSmsSetting.getAccessKeySecret())
|
||||
.setRegionId(aliYunSmsSetting.getRegionId())
|
||||
.setEndpoint(aliYunSmsSetting.getEndpoint());
|
||||
|
||||
try {
|
||||
this.client = new Client(config);
|
||||
} catch (Exception e) {
|
||||
log.error("阿里云-短信服务初始化失败,请检查配置信息");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendSms(Map<String, Object> params, String phoneNumber) throws Exception {
|
||||
Future<Boolean> future = THREAD_POOL_EXECUTOR.submit(() -> {
|
||||
// todo 修改消息的状态为 sending
|
||||
|
||||
RuntimeOptions runtimeOptions = new RuntimeOptions();
|
||||
// 设置自动重试,默认是不开启的。重试次数默认是3次
|
||||
runtimeOptions.autoretry = true;
|
||||
SendSmsRequest request = new SendSmsRequest()
|
||||
.setPhoneNumbers(phoneNumber)
|
||||
.setSignName(params.get(SIGN_NAME).toString())
|
||||
.setTemplateCode(params.get(TEMPLATE_CODE).toString())
|
||||
.setTemplateParam(params.get(TEMPLATE_PARAM).toString());
|
||||
try {
|
||||
SendSmsResponse response = this.client.sendSmsWithOptions(request, runtimeOptions);
|
||||
System.out.println(toJSONString(response));
|
||||
if (OK.equals(response.body.code)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("阿里云-短信服务发送失败");
|
||||
throw new Exception(e);
|
||||
}
|
||||
});
|
||||
Boolean b = future.get(3, TimeUnit.SECONDS);
|
||||
if (b) {
|
||||
// todo 修改消息的状态为 success
|
||||
} else {
|
||||
// todo 修改消息的状态为 failed
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendBatchSms(Map<String, Object> params, List<String> phoneNumbers) {
|
||||
RuntimeOptions runtimeOptions = new RuntimeOptions();
|
||||
// 设置自动重试,默认是不开启的。重试次数默认是3次
|
||||
runtimeOptions.autoretry = true;
|
||||
SendBatchSmsRequest request = new SendBatchSmsRequest()
|
||||
.setPhoneNumberJson(toJSONString(phoneNumbers))
|
||||
.setSignNameJson(toJSONString(params.get(SIGN_NAME)))
|
||||
.setTemplateCode(params.get(TEMPLATE_CODE).toString())
|
||||
.setTemplateParamJson(toJSONString(params.get(TEMPLATE_PARAM)));
|
||||
try {
|
||||
SendBatchSmsResponse response = this.client.sendBatchSmsWithOptions(request, runtimeOptions);
|
||||
System.out.println(toJSONString(response));
|
||||
if (OK.equals(response.body.code)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("阿里云-短信服务发送失败");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void querySmsTemplateList() {
|
||||
QuerySmsTemplateListRequest request = new QuerySmsTemplateListRequest();
|
||||
request.setPageIndex(1);
|
||||
request.setPageSize(10);
|
||||
try {
|
||||
QuerySmsTemplateListResponse querySmsTemplateListResponse = this.client.querySmsTemplateList(request);
|
||||
System.out.println(toJSONString(querySmsTemplateListResponse));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.njcn.msgpush.module.push.client.channel.sms.Impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.msgpush.module.push.client.channel.sms.SmsClient;
|
||||
import com.njcn.msgpush.module.push.client.setting.sms.TelecomSmsSetting;
|
||||
import com.njcn.msgpush.module.push.util.RestTemplateUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-11
|
||||
* @description 电信e企云短信服务实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TelecomSmsClient implements SmsClient {
|
||||
|
||||
/**
|
||||
* 短信接口地址
|
||||
*/
|
||||
private static final String API_URL = "https://sms.ymeeting.cn/smsv2";
|
||||
/**
|
||||
* 虚拟接入码
|
||||
*/
|
||||
private static final String ACCESS_CODE = "106905631";
|
||||
/**
|
||||
* 短信接口内容类型
|
||||
*/
|
||||
private static final String CONTENT_TYPE = "application/json;charset=utf-8";
|
||||
|
||||
// public static final String ACCOUNT = "account";
|
||||
// public static final String PASSWORD = "password";
|
||||
public static final String CONTENT = "content";
|
||||
|
||||
@Autowired
|
||||
private TelecomSmsSetting telecomSmsSetting;
|
||||
|
||||
@Autowired
|
||||
private RestTemplateUtil restTemplateUtil;
|
||||
|
||||
@Override
|
||||
public boolean sendSms(Map<String, Object> params, String phoneNumber) throws Exception {
|
||||
return this.sendBatchSms(params, List.of(phoneNumber));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendBatchSms(Map<String, Object> params, List<String> phoneNumbers) {
|
||||
// 构建请求参数
|
||||
Map<String, Object> request = new HashMap<>();
|
||||
request.put("action", "send");
|
||||
request.put("account", telecomSmsSetting.getAccount());
|
||||
request.put("password", telecomSmsSetting.getPassword());
|
||||
request.put("mobile", StrUtil.join(StrUtil.COMMA, phoneNumbers));
|
||||
request.put("content", params.get(CONTENT).toString());
|
||||
request.put("extno", ACCESS_CODE);
|
||||
|
||||
// 设置请求头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Content-Type", CONTENT_TYPE);
|
||||
|
||||
// 发送请求
|
||||
ResponseEntity<String> response = restTemplateUtil.post(
|
||||
API_URL,
|
||||
request,
|
||||
headers,
|
||||
String.class
|
||||
);
|
||||
String body = response.getBody();
|
||||
|
||||
if (body.contains("\"status\": 0")) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void querySmsTemplateList() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.njcn.msgpush.module.push.client.channel.sms;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-10
|
||||
*/
|
||||
public interface SmsClient {
|
||||
|
||||
/**
|
||||
* 向单个手机号发送短信
|
||||
*
|
||||
* @param phoneNumber 手机号
|
||||
* @param params 参数
|
||||
* @return 发送结果
|
||||
*/
|
||||
boolean sendSms(Map<String, Object> params, String phoneNumber) throws Exception;
|
||||
|
||||
/**
|
||||
* 向多个手机号发送短信
|
||||
*
|
||||
* @param phoneNumbers 手机号集合
|
||||
* @param params 参数
|
||||
* @return 发送结果
|
||||
*/
|
||||
boolean sendBatchSms(Map<String, Object> params, List<String> phoneNumbers);
|
||||
|
||||
void querySmsTemplateList();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.njcn.msgpush.module.push.client.channel.sms.factory;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.msgpush.module.push.client.channel.sms.Impl.AliYunSmsClient;
|
||||
import com.njcn.msgpush.module.push.client.channel.sms.Impl.TelecomSmsClient;
|
||||
import com.njcn.msgpush.module.push.client.channel.sms.SmsClient;
|
||||
import com.njcn.msgpush.module.push.client.constant.ClientConstant;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-11
|
||||
*/
|
||||
@Component
|
||||
public class SmsFactory {
|
||||
|
||||
@Autowired
|
||||
private AliYunSmsClient aliYunSmsClient;
|
||||
|
||||
@Autowired
|
||||
private TelecomSmsClient telecomSmsClient;
|
||||
|
||||
public SmsClient getClient(String smsProviderTypeName) throws RuntimeException {
|
||||
if (StrUtil.equals(ClientConstant.ALI_YUN, smsProviderTypeName)) {
|
||||
return aliYunSmsClient;
|
||||
} else if (StrUtil.equals(ClientConstant.TELECOM, smsProviderTypeName)) {
|
||||
return telecomSmsClient;
|
||||
} else {
|
||||
throw new RuntimeException("暂时不提供" + smsProviderTypeName + "短信服务提供商");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.njcn.msgpush.module.push.client.constant;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-10
|
||||
*/
|
||||
public interface ClientConstant {
|
||||
String ALI_YUN = "阿里云";
|
||||
|
||||
String TELECOM = "电信";
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.njcn.msgpush.module.push.client.setting;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-09
|
||||
* @description 各个推送渠道通用的配置
|
||||
*/
|
||||
public abstract class BaseChannelSetting {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.njcn.msgpush.module.push.client.setting.appPush;
|
||||
|
||||
import com.njcn.msgpush.module.push.client.setting.BaseChannelSetting;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-09
|
||||
* @description App推送配置
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public abstract class AppPushSetting extends BaseChannelSetting {
|
||||
private String appKey; //示例
|
||||
private String secret; //示例
|
||||
private String key; //示例
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.njcn.msgpush.module.push.client.setting.appPush;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-09
|
||||
* @description UniPush应用推送配置
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class UniPushAppPushSetting extends AppPushSetting {
|
||||
private String accessKeyId;
|
||||
private String accessKeySecret;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.njcn.msgpush.module.push.client.setting.mail;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-09
|
||||
* @description 阿里云邮件配置
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "aliyun.mail")
|
||||
public class AliYunMailSetting extends MailSetting {
|
||||
private String accessKeyId;
|
||||
private String accessKeySecret;
|
||||
private String regionId;
|
||||
private String endpoint;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.njcn.msgpush.module.push.client.setting.mail;
|
||||
|
||||
import com.njcn.msgpush.module.push.client.setting.BaseChannelSetting;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-09
|
||||
* @description 邮箱配置
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public abstract class MailSetting extends BaseChannelSetting {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.njcn.msgpush.module.push.client.setting.sms;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-09
|
||||
* @description 阿里云短信应用配置
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "aliyun.sms")
|
||||
public class AliYunSmsSetting extends SmsSetting {
|
||||
private String accessKeyId;
|
||||
private String accessKeySecret;
|
||||
private String regionId;
|
||||
private String endpoint;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.njcn.msgpush.module.push.client.setting.sms;
|
||||
|
||||
import com.njcn.msgpush.module.push.client.setting.BaseChannelSetting;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-09
|
||||
* @description 短信配置抽象类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public abstract class SmsSetting extends BaseChannelSetting {
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.njcn.msgpush.module.push.client.setting.sms;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-10
|
||||
* @description 电信e企云短信服务应用配置
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "telecom.sms")
|
||||
public class TelecomSmsSetting extends SmsSetting {
|
||||
private String account;
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.njcn.msgpush.module.push.controller.admin.channel;
|
||||
|
||||
import com.njcn.msgpush.module.push.service.channel.ChannelProviderConfigService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "管理后台 - 渠道服务商")
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/push/channel")
|
||||
public class ChannelProviderConfigController {
|
||||
|
||||
@Autowired
|
||||
private ChannelProviderConfigService channelProviderConfigService;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.njcn.msgpush.module.push.controller.admin.channel.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "管理后台 - 渠道服务商配置 Request VO")
|
||||
public class ChannelProviderConfigReqVO {
|
||||
@Schema(description = "渠道类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "sms/email/app_push")
|
||||
private String channel;
|
||||
|
||||
@Schema(description = "服务商名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "中国电信/阿里云/UniPush")
|
||||
private String providerName;
|
||||
|
||||
@Schema(description = "服务商类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "telecom/cmcc/aliyun/twilio/unipush")
|
||||
private String providerType;
|
||||
|
||||
@Schema(description = "API地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://api.example.com")
|
||||
private String apiUrl;
|
||||
|
||||
@Schema(description = "AppKey", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
|
||||
private String appKey;
|
||||
|
||||
@Schema(description = "AppSecret", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
|
||||
private String appSecret;
|
||||
|
||||
@Schema(description = "额外配置(JSON格式)", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}")
|
||||
private String extraConfig;
|
||||
|
||||
@Schema(description = "优先级(数字越小优先级越高)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer priority;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.njcn.msgpush.module.push.controller.admin.message;
|
||||
|
||||
import com.njcn.msgpush.framework.common.pojo.CommonResult;
|
||||
import com.njcn.msgpush.module.push.controller.admin.message.vo.MessageRecordSendReqVO;
|
||||
import com.njcn.msgpush.module.push.service.message.MessageRecordService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "管理后台 - 消息")
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/push/message")
|
||||
public class MessageRecordController {
|
||||
|
||||
@Autowired
|
||||
private MessageRecordService messageRecordService;
|
||||
|
||||
@PostMapping("send")
|
||||
@PermitAll
|
||||
@Operation(summary = "使用账号密码登录")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
public CommonResult<Boolean> send(MessageRecordSendReqVO messageRecordSendReqVO) {
|
||||
Boolean result = messageRecordService.send(messageRecordSendReqVO);
|
||||
return CommonResult.success(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.njcn.msgpush.module.push.controller.admin.message.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "管理后台 - 消息记录发送 Request VO")
|
||||
public class MessageRecordSendReqVO {
|
||||
|
||||
@Schema(description = "消息唯一ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
|
||||
private String messageId;
|
||||
|
||||
@Schema(description = "应用名称/来源系统标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "NPQS-9500")
|
||||
private String appName;
|
||||
|
||||
@Schema(description = "渠道类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "sms/email/app_push")
|
||||
private String channel;
|
||||
|
||||
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "verify_code/order_notify/marketing/system_notify")
|
||||
private String messageType;
|
||||
|
||||
@Schema(description = "接收者", requiredMode = Schema.RequiredMode.REQUIRED, example = "15601691300")
|
||||
private String receiver;
|
||||
|
||||
@Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String title;
|
||||
|
||||
@Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String content;
|
||||
|
||||
@Schema(description = "模板编码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String templateCode;
|
||||
|
||||
@Schema(description = "模板参数")
|
||||
private String templateParams;
|
||||
|
||||
@Schema(description = "服务商类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "telecom/cmcc/aliyun/twilio")
|
||||
private String providerType;
|
||||
|
||||
@Schema(description = "第三方消息ID")
|
||||
private String thirdPartyId;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.njcn.msgpush.module.push.dal.dataobject.channel;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-06
|
||||
* @description 渠道服务商配置表对应的数据对象
|
||||
*/
|
||||
@Data
|
||||
@TableName("push_channel_provider_config")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ChannelProviderConfigDO extends BaseDO {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 渠道类型:sms/email/app_push
|
||||
*/
|
||||
private String channel;
|
||||
|
||||
/**
|
||||
* 服务商名称:中国电信/阿里云/UniPush
|
||||
*/
|
||||
private String providerName;
|
||||
|
||||
/**
|
||||
* 服务商类型:telecom/cmcc/aliyun/twilio/unipush
|
||||
*/
|
||||
private String providerType;
|
||||
|
||||
/**
|
||||
* API地址
|
||||
*/
|
||||
private String apiUrl;
|
||||
|
||||
/**
|
||||
* AppKey
|
||||
*/
|
||||
private String appKey;
|
||||
|
||||
/**
|
||||
* AppSecret
|
||||
*/
|
||||
private String appSecret;
|
||||
|
||||
/**
|
||||
* 额外配置(JSON格式)
|
||||
*/
|
||||
private String extraConfig;
|
||||
|
||||
/**
|
||||
* 优先级(数字越小优先级越高)
|
||||
*/
|
||||
private Integer priority;
|
||||
|
||||
/**
|
||||
* 是否启用:0-禁用 1-启用(手动控制)
|
||||
*/
|
||||
private Integer enabled;
|
||||
|
||||
/**
|
||||
* 健康状态:0-异常 1-正常(自动检测)
|
||||
*/
|
||||
private Integer healthStatus;
|
||||
|
||||
/**
|
||||
* 连续失败次数(自动统计)
|
||||
*/
|
||||
private Integer failureCount;
|
||||
|
||||
/**
|
||||
* 最后失败时间(自动记录)
|
||||
*/
|
||||
private LocalDateTime lastFailureTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.njcn.msgpush.module.push.dal.dataobject.message;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-06
|
||||
* @description 消息记录表对应的数据对象
|
||||
*/
|
||||
@Data
|
||||
@TableName("push_message_record")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class MessageRecordDO extends BaseDO {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 消息唯一ID
|
||||
*/
|
||||
private String messageId;
|
||||
|
||||
/**
|
||||
* 应用名称/来源系统标识
|
||||
*/
|
||||
private String appName;
|
||||
|
||||
/**
|
||||
* 渠道类型:sms/email/app_push
|
||||
*/
|
||||
private String channel;
|
||||
|
||||
/**
|
||||
* 消息类型:verify_code/order_notify/marketing/system_notify
|
||||
*/
|
||||
private String messageType;
|
||||
|
||||
/**
|
||||
* 接收者
|
||||
*/
|
||||
private String receiver;
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 模板编码
|
||||
*/
|
||||
private String templateCode;
|
||||
|
||||
/**
|
||||
* 模板参数
|
||||
*/
|
||||
private String templateParams;
|
||||
|
||||
/**
|
||||
* 状态:pending/sending/success/failed/final_failed/blacklisted/quota_exceeded/rate_limited/abandoned
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 发送时间
|
||||
*/
|
||||
private LocalDateTime sendTime;
|
||||
|
||||
/**
|
||||
* 发送耗时(毫秒)
|
||||
*/
|
||||
private Integer costTime;
|
||||
|
||||
/**
|
||||
* 已重试次数
|
||||
*/
|
||||
private Integer retryCount;
|
||||
|
||||
/**
|
||||
* 最后重试时间
|
||||
*/
|
||||
private LocalDateTime lastRetryTime;
|
||||
|
||||
/**
|
||||
* 下次重试时间
|
||||
*/
|
||||
private LocalDateTime nextRetryTime;
|
||||
|
||||
/**
|
||||
* 服务商类型:telecom/cmcc/aliyun/twilio
|
||||
*/
|
||||
private String providerType;
|
||||
|
||||
/**
|
||||
* 第三方消息ID
|
||||
*/
|
||||
private String thirdPartyId;
|
||||
|
||||
/**
|
||||
* 统一错误码
|
||||
*/
|
||||
private String errorCode;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String errorMsg;
|
||||
|
||||
/**
|
||||
* 过期时间
|
||||
*/
|
||||
private LocalDateTime expireTime;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.njcn.msgpush.module.push.dal.mysql.channel;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.msgpush.module.push.dal.dataobject.channel.ChannelProviderConfigDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ChannelProviderConfigMapper extends BaseMapperX<ChannelProviderConfigDO> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.njcn.msgpush.module.push.dal.mysql.message;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.msgpush.module.push.dal.dataobject.message.MessageRecordDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface MessageRecordMapper extends BaseMapperX<MessageRecordDO> {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.njcn.msgpush.module.push.service.channel;
|
||||
|
||||
public interface ChannelProviderConfigService {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.njcn.msgpush.module.push.service.channel;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class ChannelProviderConfigServiceImpl implements ChannelProviderConfigService {
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.njcn.msgpush.module.push.service.message;
|
||||
|
||||
import com.njcn.msgpush.module.push.controller.admin.message.vo.MessageRecordSendReqVO;
|
||||
|
||||
public interface MessageRecordService {
|
||||
|
||||
/**
|
||||
* 发送消息(包括email、sms、app_push)
|
||||
*
|
||||
* @param messageRecordSendReqVO
|
||||
* @return 发送是否成功的结果
|
||||
*/
|
||||
Boolean send(MessageRecordSendReqVO messageRecordSendReqVO);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.njcn.msgpush.module.push.service.message;
|
||||
|
||||
|
||||
import com.njcn.msgpush.module.push.controller.admin.message.vo.MessageRecordSendReqVO;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class MessageRecordServiceImpl implements MessageRecordService{
|
||||
@Override
|
||||
public Boolean send(MessageRecordSendReqVO messageRecordSendReqVO) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.njcn.msgpush.module.push.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-10
|
||||
* @description restTemplate工具类
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RestTemplateUtil {
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
public RestTemplateUtil() {
|
||||
this.restTemplate = new RestTemplate();
|
||||
}
|
||||
|
||||
public <T> ResponseEntity<T> get(String url, Class<T> responseType) {
|
||||
return get(url, null, responseType);
|
||||
}
|
||||
|
||||
public <T> ResponseEntity<T> get(String url, HttpHeaders headers, Class<T> responseType) {
|
||||
try {
|
||||
HttpEntity<String> requestEntity = new HttpEntity<>(headers);
|
||||
log.info("发送GET请求到: {}", url);
|
||||
ResponseEntity<T> response = restTemplate.exchange(url, HttpMethod.GET, requestEntity, responseType);
|
||||
log.info("GET请求响应状态: {}", response.getStatusCode());
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
log.error("GET请求异常: {}", e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> ResponseEntity<T> post(String url, Object requestBody, Class<T> responseType) {
|
||||
return post(url, requestBody, null, responseType);
|
||||
}
|
||||
|
||||
public <T> ResponseEntity<T> post(String url, Object requestBody, HttpHeaders headers, Class<T> responseType) {
|
||||
try {
|
||||
if (headers == null) {
|
||||
headers = new HttpHeaders();
|
||||
}
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
HttpEntity<Object> request = new HttpEntity<>(requestBody, headers);
|
||||
log.info("发送POST请求到: {}", url);
|
||||
ResponseEntity<T> response = restTemplate.postForEntity(url, request, responseType);
|
||||
log.info("POST请求响应状态: {}", response.getStatusCode());
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
log.error("POST请求异常: {}", e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> ResponseEntity<T> postForm(String url, Map<String, String> formData, Class<T> responseType) {
|
||||
return postForm(url, formData, null, responseType);
|
||||
}
|
||||
|
||||
public <T> ResponseEntity<T> postForm(String url, Map<String, String> formData, HttpHeaders headers, Class<T> responseType) {
|
||||
try {
|
||||
if (headers == null) {
|
||||
headers = new HttpHeaders();
|
||||
}
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
|
||||
HttpEntity<Map<String, String>> request = new HttpEntity<>(formData, headers);
|
||||
log.info("发送POST表单请求到: {}", url);
|
||||
ResponseEntity<T> response = restTemplate.postForEntity(url, request, responseType);
|
||||
log.info("POST表单请求响应状态: {}", response.getStatusCode());
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
log.error("POST表单请求异常: {}", e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T getForObject(String url, Class<T> responseType) {
|
||||
try {
|
||||
log.info("发送GET请求获取对象到: {}", url);
|
||||
T result = restTemplate.getForObject(url, responseType);
|
||||
log.info("GET请求成功获取对象");
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("GET请求获取对象异常: {}", e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T postForObject(String url, Object requestBody, Class<T> responseType) {
|
||||
try {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<Object> request = new HttpEntity<>(requestBody, headers);
|
||||
|
||||
log.info("发送POST请求获取对象到: {}", url);
|
||||
T result = restTemplate.postForObject(url, request, responseType);
|
||||
log.info("POST请求成功获取对象");
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("POST请求获取对象异常: {}", e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public String getString(String url) {
|
||||
return getForObject(url, String.class);
|
||||
}
|
||||
|
||||
public String postString(String url, Object requestBody) {
|
||||
return postForObject(url, requestBody, String.class);
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,13 @@ spring:
|
||||
username: # Nacos 账号
|
||||
password: # Nacos 密码
|
||||
discovery: # 【配置中心】配置项
|
||||
namespace: dev # 命名空间。这里使用 dev 开发环境
|
||||
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
|
||||
namespace: msgCenter # 命名空间。这里使用 dev 开发环境
|
||||
group: DEV # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
|
||||
metadata:
|
||||
version: 1.0.0 # 服务实例的版本号,可用于灰度发布
|
||||
config: # 【注册中心】配置项
|
||||
namespace: dev # 命名空间。这里使用 dev 开发环境
|
||||
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
|
||||
namespace: msgCenter # 命名空间。这里使用 dev 开发环境
|
||||
group: DEV # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
|
||||
|
||||
--- #################### 数据库相关配置 ####################
|
||||
spring:
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
--- #################### 注册中心 + 配置中心相关配置 ####################
|
||||
|
||||
#spring:
|
||||
# cloud:
|
||||
# nacos:
|
||||
# server-addr: 192.168.1.103:18848 # Nacos 服务器地址
|
||||
# username: # Nacos 账号
|
||||
# password: # Nacos 密码
|
||||
# discovery: # 【配置中心】配置项
|
||||
# namespace: msgCenter # 命名空间。这里使用 dev 开发环境
|
||||
# group: DEV # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
|
||||
# metadata:
|
||||
# version: 1.0.0 # 服务实例的版本号,可用于灰度发布
|
||||
# config: # 【注册中心】配置项
|
||||
# namespace: msgCenter # 命名空间。这里使用 dev 开发环境
|
||||
# group: DEV # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
|
||||
spring:
|
||||
cloud:
|
||||
nacos:
|
||||
server-addr: 192.168.1.103:18848 # Nacos 服务器地址
|
||||
username: # Nacos 账号
|
||||
password: # Nacos 密码
|
||||
discovery: # 【配置中心】配置项
|
||||
namespace: dev # 命名空间。这里使用 dev 开发环境
|
||||
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
|
||||
metadata:
|
||||
version: 1.0.0 # 服务实例的版本号,可用于灰度发布
|
||||
config: # 【注册中心】配置项
|
||||
namespace: dev # 命名空间。这里使用 dev 开发环境
|
||||
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
|
||||
discovery:
|
||||
enabled: false
|
||||
config:
|
||||
enabled: false
|
||||
|
||||
|
||||
--- #################### 数据库相关配置 ####################
|
||||
spring:
|
||||
|
||||
# 数据源配置项
|
||||
autoconfigure:
|
||||
exclude:
|
||||
|
||||
@@ -106,3 +106,20 @@ msgpush:
|
||||
|
||||
|
||||
debug: false
|
||||
aliyun:
|
||||
sms:
|
||||
access-key-id: LTAI4FxsR76x2dq3w9c5puUe
|
||||
access-key-secret: GxkTR8fsrvHtixTlD9UPmOGli35tZs
|
||||
regionId: cn-hangzhou
|
||||
endpoint: dysmsapi.aliyuncs.com
|
||||
mail:
|
||||
access-key-id: LTAI4FxsR76x2dq3w9c5puUe
|
||||
access-key-secret: GxkTR8fsrvHtixTlD9UPmOGli35tZs
|
||||
regionId: cn-hangzhou
|
||||
endpoint: dm.aliyuncs.com
|
||||
|
||||
telecom:
|
||||
sms:
|
||||
account: 925631
|
||||
password: AMW2pOVrdky
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.njcn.msgpush.module.push;
|
||||
|
||||
|
||||
import com.njcn.msgpush.module.push.client.channel.mail.impl.AliYunMailClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
public class AliYunMailClientTest {
|
||||
@Autowired
|
||||
private AliYunMailClient aliYunMailClient;
|
||||
|
||||
@Test
|
||||
public void testQueryMailAddressByParam(){
|
||||
aliYunMailClient.queryMailAddressByParam();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.njcn.msgpush.module.push.sms;
|
||||
|
||||
import com.njcn.msgpush.module.push.client.channel.sms.Impl.AliYunSmsClient;
|
||||
import com.njcn.msgpush.module.push.client.channel.sms.SmsClient;
|
||||
import com.njcn.msgpush.module.push.client.channel.sms.factory.SmsFactory;
|
||||
import com.njcn.msgpush.module.push.client.constant.ClientConstant;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-06
|
||||
*/
|
||||
@SpringBootTest
|
||||
public class AliYumSmsClientTest {
|
||||
|
||||
@Autowired
|
||||
private SmsFactory smsFactory;
|
||||
|
||||
@Test
|
||||
public void testSendSms() throws Exception {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put(AliYunSmsClient.SIGN_NAME, "灿能云");
|
||||
params.put(AliYunSmsClient.TEMPLATE_CODE, "SMS_481710295");
|
||||
params.put(AliYunSmsClient.TEMPLATE_PARAM, "{\"code\":\"123456\"}");
|
||||
boolean b = smsFactory.getClient("阿里云").sendSms(params, "18839431215");
|
||||
System.out.println(System.currentTimeMillis() + " " + b);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendBatchSms() {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put(AliYunSmsClient.SIGN_NAME, List.of("灿能云"));
|
||||
params.put(AliYunSmsClient.TEMPLATE_CODE, "SMS_481710295");
|
||||
params.put(AliYunSmsClient.TEMPLATE_PARAM, List.of("{\"code\":\"123456\"}"));
|
||||
smsFactory.getClient("阿里云").sendBatchSms(params, List.of("18839431215"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQuerySmsTemplateListRequest() {
|
||||
SmsClient client = smsFactory.getClient(ClientConstant.ALI_YUN);
|
||||
client.querySmsTemplateList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.njcn.msgpush.module.push.sms;
|
||||
|
||||
import com.njcn.msgpush.module.push.client.channel.sms.Impl.TelecomSmsClient;
|
||||
import com.njcn.msgpush.module.push.client.channel.sms.factory.SmsFactory;
|
||||
import com.njcn.msgpush.module.push.client.constant.ClientConstant;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2026-02-10
|
||||
*/
|
||||
@SpringBootTest
|
||||
public class TelecomSmsClientTest {
|
||||
|
||||
@Autowired
|
||||
private SmsFactory smsFactory;
|
||||
|
||||
@Test
|
||||
public void testSendSms() throws Exception {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
// params.put(TelecomSmsClient.ACCOUNT, "925631");
|
||||
// params.put(TelecomSmsClient.PASSWORD, "AMW2pOVrdky");
|
||||
params.put(TelecomSmsClient.CONTENT, "【南京灿能电力】这是JUnit群发短信测试,请忽略。时间:" + LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
|
||||
smsFactory.getClient(ClientConstant.TELECOM).sendSms(params, "18839431215");
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
* @author hongawen
|
||||
*/
|
||||
@SuppressWarnings("SpringComponentScan") // 忽略 IDEA 无法识别 ${msgpush.info.base-package}
|
||||
@SpringBootApplication(scanBasePackages = {"${msgpush.info.base-package}.server", "${msgpush.info.base-package}.module"},
|
||||
excludeName = {
|
||||
})
|
||||
@SpringBootApplication(scanBasePackages = {"${msgpush.info.base-package}.server", "${msgpush.info.base-package}.module"}, excludeName = {})
|
||||
public class MsgpushServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
Reference in New Issue
Block a user