From 8f31ee92f62021540480f83f029e67cf6a75fc3a Mon Sep 17 00:00:00 2001 From: 3y Date: Wed, 24 Aug 2022 20:30:33 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E8=AF=BB=E5=8F=96=E6=95=B0=E6=8D=AE=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/constant/SendAccountConstant.java | 1 - .../austin/common/enums/SmsSupplier.java | 37 +++++++++ .../RefreshDingDingAccessTokenHandler.java | 19 +++-- .../RefreshGeTuiAccessTokenHandler.java | 25 +++--- .../austin/handler/domain/sms/SmsParam.java | 6 ++ .../handler/handler/impl/SmsHandler.java | 2 + .../handler/receipt/MessageReceipt.java | 28 +------ ...entSmsReceipt.java => SmsPullReceipt.java} | 78 ++++++++++++------- .../handler/receipt/YunPianSmsReceipt.java | 25 ------ .../handler/script/impl/TencentSmsScript.java | 2 +- .../handler/script/impl/YunPianSmsScript.java | 2 +- .../austin/support/utils/AccountUtils.java | 32 +++++++- .../src/main/resources/application.properties | 70 +++++++++-------- austin-web/src/main/resources/logback.xml | 2 +- 14 files changed, 192 insertions(+), 137 deletions(-) create mode 100644 austin-common/src/main/java/com/java3y/austin/common/enums/SmsSupplier.java rename austin-handler/src/main/java/com/java3y/austin/handler/receipt/{TencentSmsReceipt.java => SmsPullReceipt.java} (53%) delete mode 100644 austin-handler/src/main/java/com/java3y/austin/handler/receipt/YunPianSmsReceipt.java diff --git a/austin-common/src/main/java/com/java3y/austin/common/constant/SendAccountConstant.java b/austin-common/src/main/java/com/java3y/austin/common/constant/SendAccountConstant.java index baedd24..3f90cc2 100644 --- a/austin-common/src/main/java/com/java3y/austin/common/constant/SendAccountConstant.java +++ b/austin-common/src/main/java/com/java3y/austin/common/constant/SendAccountConstant.java @@ -88,7 +88,6 @@ public class SendAccountConstant { /** * 短信账号code */ - public static final Integer TENCENT_SMS_CODE = 10; public static final Integer YUN_PIAN_SMS_CODE = 20; diff --git a/austin-common/src/main/java/com/java3y/austin/common/enums/SmsSupplier.java b/austin-common/src/main/java/com/java3y/austin/common/enums/SmsSupplier.java new file mode 100644 index 0000000..818acba --- /dev/null +++ b/austin-common/src/main/java/com/java3y/austin/common/enums/SmsSupplier.java @@ -0,0 +1,37 @@ +package com.java3y.austin.common.enums; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +/** + * 短信渠道商 + * @author 3y + */ +@Getter +@ToString +@AllArgsConstructor +public enum SmsSupplier { + + + TENCENT(10,"腾讯渠道商"), + YUN_PAIN(20,"云片渠道商"); + private Integer code; + private String description; + + + /** + * 根据状态获取描述信息 + * @param code + * @return + */ + public static String getDescriptionByStatus(Integer code) { + for (SmsStatus value : SmsStatus.values()) { + if (value.getCode().equals(code)) { + return value.getDescription(); + } + } + return ""; + } +} diff --git a/austin-cron/src/main/java/com/java3y/austin/cron/handler/RefreshDingDingAccessTokenHandler.java b/austin-cron/src/main/java/com/java3y/austin/cron/handler/RefreshDingDingAccessTokenHandler.java index 25f378e..5bb1478 100644 --- a/austin-cron/src/main/java/com/java3y/austin/cron/handler/RefreshDingDingAccessTokenHandler.java +++ b/austin-cron/src/main/java/com/java3y/austin/cron/handler/RefreshDingDingAccessTokenHandler.java @@ -1,6 +1,7 @@ package com.java3y.austin.cron.handler; import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; import com.dingtalk.api.DefaultDingTalkClient; import com.dingtalk.api.DingTalkClient; import com.dingtalk.api.request.OapiGettokenRequest; @@ -9,7 +10,10 @@ import com.google.common.base.Throwables; import com.java3y.austin.common.constant.AustinConstant; import com.java3y.austin.common.constant.SendAccountConstant; import com.java3y.austin.common.dto.account.DingDingWorkNoticeAccount; +import com.java3y.austin.common.enums.ChannelType; import com.java3y.austin.support.config.SupportThreadPoolConfig; +import com.java3y.austin.support.dao.ChannelAccountDao; +import com.java3y.austin.support.domain.ChannelAccount; import com.java3y.austin.support.utils.AccountUtils; import com.xxl.job.core.handler.annotation.XxlJob; import lombok.extern.slf4j.Slf4j; @@ -17,6 +21,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; +import java.util.List; + /** * 刷新钉钉的access_token @@ -36,7 +42,8 @@ public class RefreshDingDingAccessTokenHandler { private StringRedisTemplate redisTemplate; @Autowired - private AccountUtils accountUtils; + private ChannelAccountDao channelAccountDao; + /** * 每小时请求一次接口刷新(以防失效) @@ -45,14 +52,12 @@ public class RefreshDingDingAccessTokenHandler { public void execute() { log.info("refreshAccessTokenJob#execute!"); SupportThreadPoolConfig.getPendingSingleThreadPool().execute(() -> { - for (int index = SendAccountConstant.START; true; index = index + SendAccountConstant.STEP) { - DingDingWorkNoticeAccount account = accountUtils.getAccount(index, SendAccountConstant.DING_DING_WORK_NOTICE_ACCOUNT_KEY, SendAccountConstant.DING_DING_WORK_NOTICE_PREFIX, DingDingWorkNoticeAccount.class); - if (account == null) { - break; - } + List accountList = channelAccountDao.findAllByIsDeletedEqualsAndSendChannelEquals(AustinConstant.FALSE, ChannelType.DING_DING_WORK_NOTICE.getCode()); + for (ChannelAccount channelAccount : accountList) { + DingDingWorkNoticeAccount account = JSON.parseObject(channelAccount.getAccountConfig(), DingDingWorkNoticeAccount.class); String accessToken = getAccessToken(account); if (StrUtil.isNotBlank(accessToken)) { - redisTemplate.opsForValue().set(SendAccountConstant.DING_DING_ACCESS_TOKEN_PREFIX + index, accessToken); + redisTemplate.opsForValue().set(SendAccountConstant.DING_DING_ACCESS_TOKEN_PREFIX + channelAccount.getId(), accessToken); } } }); diff --git a/austin-cron/src/main/java/com/java3y/austin/cron/handler/RefreshGeTuiAccessTokenHandler.java b/austin-cron/src/main/java/com/java3y/austin/cron/handler/RefreshGeTuiAccessTokenHandler.java index b000df9..a978e52 100644 --- a/austin-cron/src/main/java/com/java3y/austin/cron/handler/RefreshGeTuiAccessTokenHandler.java +++ b/austin-cron/src/main/java/com/java3y/austin/cron/handler/RefreshGeTuiAccessTokenHandler.java @@ -7,11 +7,15 @@ import cn.hutool.http.Header; import cn.hutool.http.HttpRequest; import com.alibaba.fastjson.JSON; import com.google.common.base.Throwables; +import com.java3y.austin.common.constant.AustinConstant; import com.java3y.austin.common.constant.SendAccountConstant; import com.java3y.austin.common.dto.account.GeTuiAccount; -import com.java3y.austin.cron.dto.getui.QueryTokenParamDTO; +import com.java3y.austin.common.enums.ChannelType; import com.java3y.austin.cron.dto.getui.GeTuiTokenResultDTO; +import com.java3y.austin.cron.dto.getui.QueryTokenParamDTO; import com.java3y.austin.support.config.SupportThreadPoolConfig; +import com.java3y.austin.support.dao.ChannelAccountDao; +import com.java3y.austin.support.domain.ChannelAccount; import com.java3y.austin.support.utils.AccountUtils; import com.xxl.job.core.handler.annotation.XxlJob; import lombok.extern.slf4j.Slf4j; @@ -19,6 +23,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; +import java.util.List; + /** * 刷新个推的token @@ -35,7 +41,8 @@ public class RefreshGeTuiAccessTokenHandler { private StringRedisTemplate redisTemplate; @Autowired - private AccountUtils accountUtils; + private ChannelAccountDao channelAccountDao; + /** * 每小时请求一次接口刷新(以防失效) @@ -44,14 +51,14 @@ public class RefreshGeTuiAccessTokenHandler { public void execute() { log.info("refreshGeTuiAccessTokenJob#execute!"); SupportThreadPoolConfig.getPendingSingleThreadPool().execute(() -> { + List accountList = channelAccountDao.findAllByIsDeletedEqualsAndSendChannelEquals(AustinConstant.FALSE, ChannelType.PUSH.getCode()); for (int index = SendAccountConstant.START; true; index = index + SendAccountConstant.STEP) { - GeTuiAccount account = accountUtils.getAccount(index, SendAccountConstant.GE_TUI_ACCOUNT_KEY, SendAccountConstant.GE_TUI_ACCOUNT_PREFIX, GeTuiAccount.class); - if (account == null) { - break; - } - String accessToken = getAccessToken(account); - if (StrUtil.isNotBlank(accessToken)) { - redisTemplate.opsForValue().set(SendAccountConstant.GE_TUI_ACCESS_TOKEN_PREFIX + index, accessToken); + for (ChannelAccount channelAccount : accountList) { + GeTuiAccount account = JSON.parseObject(channelAccount.getAccountConfig(), GeTuiAccount.class); + String accessToken = getAccessToken(account); + if (StrUtil.isNotBlank(accessToken)) { + redisTemplate.opsForValue().set(SendAccountConstant.GE_TUI_ACCESS_TOKEN_PREFIX + index, accessToken); + } } } }); diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/domain/sms/SmsParam.java b/austin-handler/src/main/java/com/java3y/austin/handler/domain/sms/SmsParam.java index 74eee4f..96a5881 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/domain/sms/SmsParam.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/domain/sms/SmsParam.java @@ -24,6 +24,12 @@ public class SmsParam { */ private Set phones; + /** + * 发送渠道账号的Id + */ + private Integer sendAccountId; + + /** * 发送文案 */ diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/SmsHandler.java b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/SmsHandler.java index d8cd98f..4db4632 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/SmsHandler.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/SmsHandler.java @@ -56,7 +56,9 @@ public class SmsHandler extends BaseHandler implements Handler { .phones(taskInfo.getReceiver()) .content(getSmsContent(taskInfo)) .messageTemplateId(taskInfo.getMessageTemplateId()) + .sendAccountId(taskInfo.getSendAccount()) .build(); + try { /** * 1、动态配置做流量负载 diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/MessageReceipt.java b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/MessageReceipt.java index 00f955f..1ad7ce3 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/MessageReceipt.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/MessageReceipt.java @@ -1,31 +1,15 @@ package com.java3y.austin.handler.receipt; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DatePattern; -import cn.hutool.core.date.DateUtil; -import com.alibaba.fastjson.JSON; -import com.google.common.base.Throwables; -import com.java3y.austin.common.constant.SendAccountConstant; -import com.java3y.austin.common.dto.account.TencentSmsAccount; -import com.java3y.austin.common.enums.SmsStatus; import com.java3y.austin.support.config.SupportThreadPoolConfig; -import com.java3y.austin.support.domain.SmsRecord; -import com.tencentcloudapi.sms.v20210111.SmsClient; -import com.tencentcloudapi.sms.v20210111.models.PullSmsSendStatus; -import com.tencentcloudapi.sms.v20210111.models.PullSmsSendStatusRequest; -import com.tencentcloudapi.sms.v20210111.models.PullSmsSendStatusResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; /** - * 拉取短信回执信息 + * 拉取回执信息 * * @author 3y */ @@ -34,22 +18,18 @@ import java.util.List; public class MessageReceipt { @Autowired - private TencentSmsReceipt tencentSmsReceipt; - - @Autowired - private YunPianSmsReceipt yunPianSmsReceipt; + private SmsPullReceipt smsPullReceipt; @PostConstruct private void init() { SupportThreadPoolConfig.getPendingSingleThreadPool().execute(() -> { while (true) { - // TODO 回执这里自行打开(免得报错) -// tencentSmsReceipt.pull(); -// yunPianSmsReceipt.pull(); + // smsPullReceipt.pull(); try { Thread.sleep(200); } catch (InterruptedException e) { + e.printStackTrace(); } } }); diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/TencentSmsReceipt.java b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/SmsPullReceipt.java similarity index 53% rename from austin-handler/src/main/java/com/java3y/austin/handler/receipt/TencentSmsReceipt.java rename to austin-handler/src/main/java/com/java3y/austin/handler/receipt/SmsPullReceipt.java index 704422e..bb7dd2c 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/TencentSmsReceipt.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/SmsPullReceipt.java @@ -5,64 +5,95 @@ import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import com.alibaba.fastjson.JSON; import com.google.common.base.Throwables; -import com.java3y.austin.common.constant.SendAccountConstant; +import com.java3y.austin.common.constant.AustinConstant; import com.java3y.austin.common.dto.account.TencentSmsAccount; +import com.java3y.austin.common.dto.account.YunPianSmsAccount; +import com.java3y.austin.common.enums.ChannelType; import com.java3y.austin.common.enums.SmsStatus; -import com.java3y.austin.support.config.SupportThreadPoolConfig; +import com.java3y.austin.common.enums.SmsSupplier; +import com.java3y.austin.support.dao.ChannelAccountDao; import com.java3y.austin.support.dao.SmsRecordDao; +import com.java3y.austin.support.domain.ChannelAccount; import com.java3y.austin.support.domain.SmsRecord; -import com.java3y.austin.support.utils.AccountUtils; import com.tencentcloudapi.common.Credential; import com.tencentcloudapi.common.profile.ClientProfile; import com.tencentcloudapi.common.profile.HttpProfile; import com.tencentcloudapi.sms.v20210111.SmsClient; -import com.tencentcloudapi.sms.v20210111.models.*; +import com.tencentcloudapi.sms.v20210111.models.PullSmsSendStatus; +import com.tencentcloudapi.sms.v20210111.models.PullSmsSendStatusRequest; +import com.tencentcloudapi.sms.v20210111.models.PullSmsSendStatusResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.Date; import java.util.List; /** - * 拉取腾讯云短信回执信息 + * 拉取短信回执信息 * * @author 3y */ @Component @Slf4j -public class TencentSmsReceipt { - +public class SmsPullReceipt { @Autowired - private AccountUtils accountUtils; + private ChannelAccountDao channelAccountDao; @Autowired private SmsRecordDao smsRecordDao; - /** * 拉取消息并入库 + *

+ * eg accountList:[{"sms_10":{"url":"sms.tencentcloudapi.com","region":"ap-guangzhou","secretId":"234234","secretKey":"234324324","smsSdkAppId":"2343242","templateId":"234234","signName":"Java3y公众号","supplierId":10,"supplierName":"腾讯云"}},{"sms_20":{"url":"https://sms.yunpian.com/v2/sms/tpl_batch_send.json","apikey":"23423432","tpl_id":"23423432","supplierId":20,"supplierName":"云片"}}] */ public void pull() { + List channelAccountList = channelAccountDao.findAllByIsDeletedEqualsAndSendChannelEquals(AustinConstant.FALSE, ChannelType.SMS.getCode()); + for (ChannelAccount channelAccount : channelAccountList) { + Integer supplierId = JSON.parseObject(channelAccount.getAccountConfig()).getInteger("supplierId"); + if (SmsSupplier.TENCENT.getCode().equals(supplierId)) { + TencentSmsAccount tencentSmsAccount = JSON.parseObject(channelAccount.getAccountConfig(), TencentSmsAccount.class); + pullTencent(tencentSmsAccount); + } else if (SmsSupplier.YUN_PAIN.getCode().equals(supplierId)) { + YunPianSmsAccount yunPianSmsAccount = JSON.parseObject(channelAccount.getAccountConfig(), YunPianSmsAccount.class); + pullYunPain(yunPianSmsAccount); + } + } + } - // 获取腾讯云账号信息 - TencentSmsAccount account = accountUtils.getAccount(10, SendAccountConstant.SMS_ACCOUNT_KEY, SendAccountConstant.SMS_PREFIX, TencentSmsAccount.class); + /** + * 拉取腾讯的回执 + * + * @param account + */ + private void pullTencent(TencentSmsAccount account) { try { - SmsClient client = getSmsClient(account); + /** + * 初始化客户端 + */ + HttpProfile httpProfile = new HttpProfile(); + httpProfile.setEndpoint(account.getUrl()); + ClientProfile clientProfile = new ClientProfile(); + clientProfile.setHttpProfile(httpProfile); + SmsClient client = new SmsClient(new Credential(account.getSecretId(), account.getSecretKey()), account.getRegion(), clientProfile); - // 每次拉取10条 + /** + * 每次拉取10条 + */ PullSmsSendStatusRequest req = new PullSmsSendStatusRequest(); req.setLimit(10L); req.setSmsSdkAppId(account.getSmsSdkAppId()); + /** + * 拉取回执后入库 + */ PullSmsSendStatusResponse resp = client.PullSmsSendStatus(req); List smsRecordList = new ArrayList<>(); if (resp != null && resp.getPullSmsSendStatusSet() != null && resp.getPullSmsSendStatusSet().length > 0) { - log.debug("receipt sms:{}", JSON.toJSONString(resp.getPullSmsSendStatusSet())); for (PullSmsSendStatus pullSmsSendStatus : resp.getPullSmsSendStatusSet()) { SmsRecord smsRecord = SmsRecord.builder() .sendDate(Integer.valueOf(DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN))) @@ -89,20 +120,7 @@ public class TencentSmsReceipt { } } - /** - * 构造smsClient - * - * @param account - * @return - */ - private SmsClient getSmsClient(TencentSmsAccount account) { - Credential cred = new Credential(account.getSecretId(), account.getSecretKey()); - HttpProfile httpProfile = new HttpProfile(); - httpProfile.setEndpoint(account.getUrl()); - ClientProfile clientProfile = new ClientProfile(); - clientProfile.setHttpProfile(httpProfile); - return new SmsClient(cred, account.getRegion(), clientProfile); - } - + private void pullYunPain(YunPianSmsAccount yunPianSmsAccount) { + } } diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/YunPianSmsReceipt.java b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/YunPianSmsReceipt.java deleted file mode 100644 index 1e6442d..0000000 --- a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/YunPianSmsReceipt.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.java3y.austin.handler.receipt; - - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - - - -/** - * 拉取云片网短信回执信息 - * - * @author 3y - */ -@Component -@Slf4j -public class YunPianSmsReceipt { - /** - * 拉取消息并入库 - */ - public void pull() { - - } - -} diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/TencentSmsScript.java b/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/TencentSmsScript.java index 3412560..8df4d12 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/TencentSmsScript.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/TencentSmsScript.java @@ -49,7 +49,7 @@ public class TencentSmsScript extends BaseSmsScript implements SmsScript { @Override public List send(SmsParam smsParam) { try { - TencentSmsAccount tencentSmsAccount = accountUtils.getAccount(SendAccountConstant.TENCENT_SMS_CODE, SendAccountConstant.SMS_ACCOUNT_KEY, SendAccountConstant.SMS_PREFIX, TencentSmsAccount.class); + TencentSmsAccount tencentSmsAccount = accountUtils.getAccount(smsParam.getSendAccountId(), SendAccountConstant.SMS_ACCOUNT_KEY, SendAccountConstant.SMS_PREFIX, TencentSmsAccount.class); SmsClient client = init(tencentSmsAccount); SendSmsRequest request = assembleReq(smsParam, tencentSmsAccount); SendSmsResponse response = client.SendSms(request); diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/YunPianSmsScript.java b/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/YunPianSmsScript.java index c4ac233..19f6f9e 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/YunPianSmsScript.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/YunPianSmsScript.java @@ -39,7 +39,7 @@ public class YunPianSmsScript extends BaseSmsScript implements SmsScript { public List send(SmsParam smsParam) { try { - YunPianSmsAccount account = accountUtils.getAccount(SendAccountConstant.YUN_PIAN_SMS_CODE, SendAccountConstant.SMS_ACCOUNT_KEY, SendAccountConstant.SMS_PREFIX, YunPianSmsAccount.class); + YunPianSmsAccount account = accountUtils.getAccount(smsParam.getSendAccountId(), SendAccountConstant.SMS_ACCOUNT_KEY, SendAccountConstant.SMS_PREFIX, YunPianSmsAccount.class); Map params = assembleParam(smsParam, account); String result = HttpRequest.post(account.getUrl()) diff --git a/austin-support/src/main/java/com/java3y/austin/support/utils/AccountUtils.java b/austin-support/src/main/java/com/java3y/austin/support/utils/AccountUtils.java index 7b82971..37ce656 100644 --- a/austin-support/src/main/java/com/java3y/austin/support/utils/AccountUtils.java +++ b/austin-support/src/main/java/com/java3y/austin/support/utils/AccountUtils.java @@ -3,24 +3,33 @@ package com.java3y.austin.support.utils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; -import com.ctrip.framework.apollo.Config; -import com.ctrip.framework.apollo.spring.annotation.ApolloConfig; +import com.google.common.base.Throwables; import com.java3y.austin.common.constant.AustinConstant; +import com.java3y.austin.support.dao.ChannelAccountDao; +import com.java3y.austin.support.domain.ChannelAccount; import com.java3y.austin.support.service.ConfigService; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.util.Optional; + /** * 获取账号信息工具类 * * @author 3y */ @Component +@Slf4j public class AccountUtils { @Autowired private ConfigService config; + @Autowired + private ChannelAccountDao channelAccountDao; + + /** * (key:smsAccount)短信参数示例:[{"sms_10":{"url":"sms.tencentcloudapi.com","region":"ap-guangzhou","secretId":"AKIDhDxxxxxxxx1WljQq","secretKey":"B4hwww39yxxxrrrrgxyi","smsSdkAppId":"1423123125","templateId":"1182097","signName":"Java3y公众号","supplierId":10,"supplierName":"腾讯云"}},{"sms_20":{"url":"https://sms.yunpian.com/v2/sms/tpl_batch_send.json","apikey":"caffff8234234231b5cd7","tpl_id":"523333332","supplierId":20,"supplierName":"云片"}}] * (key:emailAccount)邮件参数示例:[{"email_10":{"host":"smtp.qq.com","port":465,"user":"23423423@qq.com","pass":"23423432432423423","from":"234@qq.com","starttlsEnable":true,"auth":true,"sslEnable":true}},{"email_20":{"host":"smtp.163.com","port":465,"user":"22222@163.com","pass":"23432423","from":"234324324234@163.com","starttlsEnable":false,"auth":true,"sslEnable":true}}] @@ -30,12 +39,27 @@ public class AccountUtils { * (key:officialAccount) 微信服务号模板消息参数示例:[{"official_10":{"appId":"wxecb4693d2eef1ea7","secret":"624asdfsa1640d769ba20120821","templateId":"JHUk6eE9T5Ts7asdfsadfiKNDQsk-Q","url":"http://weixin.qq.com/download","miniProgramId":"xiaochengxuappid12345","path":"index?foo=bar"}}] * (key:miniProgramAccount) 微信小程序订阅消息参数示例:[{"mini_program_10":{"appId":"wxecb4693d2eef1ea7","appSecret":"6240870f4d91701640d769ba20120821","templateId":"JHUk6eE9T5TasdfCrQsk-Q","grantType":"client_credential","miniProgramState":"trial","page":"index?foo=bar"}}] */ - public T getAccount(Integer sendAccount, String apolloKey, String prefix, Class clazz) { + public T getAccount(Integer sendAccountId, String apolloKey, String prefix, Class clazz) { + + /** + * 优先读数据库的,数据库没有才读配置 + */ + try { + Optional optionalChannelAccount = channelAccountDao.findById(Long.valueOf(sendAccountId)); + if (optionalChannelAccount.isPresent()) { + ChannelAccount channelAccount = optionalChannelAccount.get(); + return JSON.parseObject(channelAccount.getAccountConfig(), clazz); + } + } catch (Exception e) { + log.warn("AccountUtils#getAccount not found:{}", Throwables.getStackTraceAsString(e)); + } + + String accountValues = config.getProperty(apolloKey, AustinConstant.APOLLO_DEFAULT_VALUE_JSON_ARRAY); JSONArray jsonArray = JSON.parseArray(accountValues); for (int i = 0; i < jsonArray.size(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); - T object = jsonObject.getObject(prefix + sendAccount, clazz); + T object = jsonObject.getObject(prefix + sendAccountId, clazz); if (object != null) { return object; } diff --git a/austin-web/src/main/resources/application.properties b/austin-web/src/main/resources/application.properties index 2de4250..2b31cf3 100644 --- a/austin-web/src/main/resources/application.properties +++ b/austin-web/src/main/resources/application.properties @@ -40,18 +40,18 @@ austin.nacos.enabled=false # todo [grayLog] ip 【optional】 austin.grayLog.ip=austin.graylog -##################### system properties ##################### -server.shutdown=graceful - +# TODO if windows os and need upload file to send message ,replace path !【optional】 +austin.business.upload.crowd.path=/Users/3y/temp -##################### database properties ##################### +########################################## database start ########################################## # notice:mysql version 5.7x !!! spring.datasource.url=jdbc:mysql://${austin.database.ip}:${austin.database.port}/austin?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull spring.datasource.username=${austin.database.username} spring.datasource.password=${austin.database.password} spring.datasource.driver-class-name=com.mysql.jdbc.Driver +########################################## database end ########################################## -##################### kafka properties ##################### +########################################## kafka start ########################################## spring.kafka.bootstrap-servers=${austin.kafka.ip}:${austin.kafka.port} spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer @@ -61,55 +61,47 @@ spring.kafka.consumer.auto.offset.reset=earliest spring.kafka.consumer.auto-commit-interval=1000 spring.kafka.consumer.enable-auto-commit=true -##################### rocketmq properties ##################### +### +austin.business.topic.name=austinBusiness +austin.business.recall.topic.name=austinRecall +austin.business.recall.group.name=recallGroupId +austin.business.log.topic.name=austinTraceLog +### TODO kafka tag filter,if you need, replace tagIdValue ,eg:com.java3y.austin.yyy +austin.business.tagId.key=kafka_tag_id +austin.business.tagId.value=com.java3y.austin.3y +########################################## kafka end ########################################## + +########################################## rocketMq start ########################################## rocketmq.name-server=${austin.rocketmq.nameserver.ip}:${austin.rocketmq.nameserver.port} rocketmq.producer.group=unique-producer-group austin.rocketmq.biz.consumer.group=unique-biz-consumer-group austin.rocketmq.recall.consumer.group=unique-recall-consumer-group +########################################## rocketMq end ########################################## - -##################### Rabbit properties ##################### -#RabbitMq所在服务器IP +########################################## RabbitMq start ########################################## spring.rabbitmq.host=${austin.rabbitmq.ip} -#连接端口号 spring.rabbitmq.port=${austin.rabbitmq.port} - server.port=8080 spring.application.name=cl -#用户名 spring.rabbitmq.username=root -#用户密码 spring.rabbitmq.password=123456 -# 开启发送确认 spring.rabbitmq.publisher-confirm-type=correlated -# 开启发送失败退回 spring.rabbitmq.publisher-returns=true spring.rabbitmq.virtual-host=/ austin.rabbitmq.topic.name=austinRabbit austin.rabbitmq.exchange.name=austin.point +########################################## RabbitMq end ########################################## -##################### redis properties ##################### +########################################## redis start ########################################## spring.redis.host=${austin.redis.ip} spring.redis.port=${austin.redis.port} spring.redis.password=${austin.redis.password} +########################################## redis end ########################################## -##################### business properties ##################### -austin.business.topic.name=austinBusiness -austin.business.recall.topic.name=austinRecall -austin.business.recall.group.name=recallGroupId -austin.business.log.topic.name=austinTraceLog -austin.business.graylog.ip=${austin.grayLog.ip} - -# TODO kafka tag filter,if you need, replace tagIdValue ,eg:com.java3y.austin.yyy -austin.business.tagId.key=kafka_tag_id -austin.business.tagId.value=com.java3y.austin.3y - -# TODO if windows os and need upload file to send message ,replace path ! -austin.business.upload.crowd.path=/Users/3y/temp -##################### xxl properties ##################### +########################################## xxl start ########################################## xxl.job.admin.addresses=http://${austin.xxl.job.ip}:${austin.xxl.job.port}/xxl-job-admin xxl.job.admin.username=admin xxl.job.admin.password=123456 @@ -120,30 +112,40 @@ xxl.job.executor.port=6666 xxl.job.executor.logpath=logs/xxl xxl.job.executor.logretentiondays=30 xxl.job.accessToken= +########################################## xxl end ########################################## -##################### apollo ##################### +########################################## apollo start ########################################## app.id=austin apollo.bootstrap.enabled=${austin.apollo.enabled} apollo.bootstrap.namespaces=boss.austin,dynamic-tp-apollo-dtp.yml +########################################## apollo end ########################################## -##################### nacos ##################### +########################################## nacos start ########################################## austin.nacos.server= austin.nacos.dataId=austin austin.nacos.group=DEFAULT_GROUP austin.nacos.namespace=9537c674-f3a6-4203-b286-ef0c36bfacb2 nacos.config.enabled=${austin.nacos.enabled} +########################################## nacos end ########################################## -##################### httpUtils properties ##################### +########################################## httpUtils start ########################################## ok.http.connect-timeout=30 ok.http.keep-alive-duration=300 ok.http.max-idle-connections=200 ok.http.read-timeout=30 ok.http.write-timeout=30 +########################################## httpUtils end ########################################## -##################### monitor properties ##################### +########################################## monitor start ########################################## management.endpoint.health.show-details=always management.endpoint.metrics.enabled=true management.endpoint.prometheus.enabled=true management.endpoints.web.exposure.include=* management.metrics.export.prometheus.enabled=true management.health.rabbit.enabled=false +########################################## monitor end ########################################## + +########################################## system start ########################################## +server.shutdown=graceful +########################################## system end ########################################## + diff --git a/austin-web/src/main/resources/logback.xml b/austin-web/src/main/resources/logback.xml index 824f367..838e611 100644 --- a/austin-web/src/main/resources/logback.xml +++ b/austin-web/src/main/resources/logback.xml @@ -7,7 +7,7 @@ - + From 5cf63b7661cb468f2af3073ab9630f4782553fc1 Mon Sep 17 00:00:00 2001 From: 3y Date: Fri, 25 Nov 2022 16:55:40 +0800 Subject: [PATCH 2/5] =?UTF-8?q?1=E3=80=81=E6=B8=A0=E9=81=93=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E8=B5=B0=E6=95=B0=E6=8D=AE=E5=BA=93=EF=BC=8C=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E8=B5=B0=E9=85=8D=E7=BD=AE=202=E3=80=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E9=83=A8=E5=88=86=E4=BB=A3=E7=A0=81=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../common/constant/AustinConstant.java | 42 +- .../common/constant/CommonConstant.java | 47 +- .../common/constant/SendAccountConstant.java | 73 -- .../dto/account/AlipayMiniProgramAccount.java | 42 + .../common/dto/account/sms/SmsAccount.java | 28 + .../dto/account/sms/TencentSmsAccount.java | 35 + .../dto/account/sms/YunPianSmsAccount.java | 35 + .../model/AlipayMiniProgramContentModel.java | 25 + .../austin/common/enums/ChannelType.java | 1 + .../RefreshDingDingAccessTokenHandler.java | 5 +- .../RefreshGeTuiAccessTokenHandler.java | 15 +- .../cron/pending/CrowdBatchTaskPending.java | 5 +- .../austin/cron/xxl/utils/XxlJobUtils.java | 9 +- austin-handler/pom.xml | 5 + .../AlipayMiniProgramAccountService.java | 18 + .../AlipayMiniProgramAccountServiceImpl.java | 86 ++ .../DeduplicationRuleService.java | 3 +- .../limit/SimpleLimitService.java | 3 +- .../discard/DiscardMessageService.java | 4 +- .../domain/alipay/AlipayMiniProgramParam.java | 43 + .../austin/handler/domain/sms/SmsParam.java | 4 +- .../flowcontrol/FlowControlFactory.java | 3 +- .../impl/AlipayMiniProgramAccountHandler.java | 67 ++ .../handler/impl/DingDingRobotHandler.java | 11 +- .../impl/DingDingWorkNoticeHandler.java | 4 +- .../handler/handler/impl/EmailHandler.java | 2 +- .../handler/impl/EnterpriseWeChatHandler.java | 11 +- .../impl/EnterpriseWeChatRobotHandler.java | 2 +- .../handler/impl/FeiShuRobotHandler.java | 2 +- .../handler/handler/impl/PushHandler.java | 2 +- .../handler/handler/impl/SmsHandler.java | 30 +- .../handler/receipt/DingDingWorkReceipt.java | 2 +- .../handler/receipt/SmsPullReceipt.java | 8 +- .../handler/script/impl/TencentSmsScript.java | 12 +- .../handler/script/impl/YunPianSmsScript.java | 15 +- .../impl/MiniProgramAccountServiceImpl.java | 4 +- .../impl/OfficialAccountServiceImpl.java | 2 +- .../api/impl/action/AssembleAction.java | 9 +- .../api/impl/action/PreParamCheckAction.java | 11 +- .../austin/support/dao/ChannelAccountDao.java | 1 + .../support/dao/MessageTemplateDao.java | 2 +- .../support/pending/AbstractLazyPending.java | 2 +- .../austin/support/utils/AccountUtils.java | 47 +- .../austin/support/utils/NacosUtils.java | 6 + .../austin/support/utils/RedisUtils.java | 3 +- .../austin/support/utils/TaskInfoUtils.java | 10 +- .../web/config/SwaggerConfiguration.java | 2 + .../controller/ChannelAccountController.java | 17 +- .../austin/web/controller/DataController.java | 3 +- .../web/controller/MaterialController.java | 6 +- .../controller/MessageTemplateController.java | 6 +- .../impl/ChannelAccountServiceImpl.java | 5 +- .../impl/MessageTemplateServiceImpl.java | 9 +- .../austin/web/vo/MessageTemplateParam.java | 5 + .../src/main/resources/application.properties | 2 + .../src/main/resources/local.properties | 9 +- ...cker-compose-rabbitmq-3.7.8-management.yml | 18 + .../docker-compose-rabbitmq-cluster.yml | 47 + docker/rabbitmq/docker-compose-rabbitmq.yml | 21 + .../rabbitmq/rabbitmq-cluster/.erlang.cookie | 1 + .../rabbitmq-cluster/init-rabbitmq.sh | 44 + ...rabbitmq_delayed_message_exchange-3.9.0.ez | Bin 0 -> 36358 bytes .../rabbitmq-1/config/rabbitmq.conf | 8 + .../rabbitmq-2/config/rabbitmq.conf | 8 + .../config/10-default-guest-user.conf | 8 + docker/rabbitmq/rabbitmq/config/rabbitmq.conf | 8 + .../rabbitmq/config/rabbitmq_bak.conf | 1060 +++++++++++++++++ .../rabbitmq/config/rabbitmq_bak.config | 925 ++++++++++++++ ...rabbitmq_delayed_message_exchange-3.9.0.ez | Bin 0 -> 36358 bytes docker/rocketmq/docker-compose-rocketmq.yml | 59 + .../rocketmq/rocketmq_broker/conf/broker.conf | 96 ++ pom.xml | 7 + 73 files changed, 2928 insertions(+), 244 deletions(-) create mode 100644 austin-common/src/main/java/com/java3y/austin/common/dto/account/AlipayMiniProgramAccount.java create mode 100644 austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/SmsAccount.java create mode 100644 austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/TencentSmsAccount.java create mode 100644 austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/YunPianSmsAccount.java create mode 100644 austin-common/src/main/java/com/java3y/austin/common/dto/model/AlipayMiniProgramContentModel.java create mode 100644 austin-handler/src/main/java/com/java3y/austin/handler/alipay/AlipayMiniProgramAccountService.java create mode 100644 austin-handler/src/main/java/com/java3y/austin/handler/alipay/impl/AlipayMiniProgramAccountServiceImpl.java create mode 100644 austin-handler/src/main/java/com/java3y/austin/handler/domain/alipay/AlipayMiniProgramParam.java create mode 100644 austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/AlipayMiniProgramAccountHandler.java create mode 100644 docker/rabbitmq/docker-compose-rabbitmq-3.7.8-management.yml create mode 100644 docker/rabbitmq/docker-compose-rabbitmq-cluster.yml create mode 100644 docker/rabbitmq/docker-compose-rabbitmq.yml create mode 100644 docker/rabbitmq/rabbitmq-cluster/.erlang.cookie create mode 100644 docker/rabbitmq/rabbitmq-cluster/init-rabbitmq.sh create mode 100644 docker/rabbitmq/rabbitmq-cluster/plugins/rabbitmq_delayed_message_exchange-3.9.0.ez create mode 100644 docker/rabbitmq/rabbitmq-cluster/rabbitmq-1/config/rabbitmq.conf create mode 100644 docker/rabbitmq/rabbitmq-cluster/rabbitmq-2/config/rabbitmq.conf create mode 100644 docker/rabbitmq/rabbitmq/config/10-default-guest-user.conf create mode 100644 docker/rabbitmq/rabbitmq/config/rabbitmq.conf create mode 100644 docker/rabbitmq/rabbitmq/config/rabbitmq_bak.conf create mode 100644 docker/rabbitmq/rabbitmq/config/rabbitmq_bak.config create mode 100644 docker/rabbitmq/rabbitmq/plugins/rabbitmq_delayed_message_exchange-3.9.0.ez create mode 100644 docker/rocketmq/docker-compose-rocketmq.yml create mode 100644 docker/rocketmq/rocketmq/rocketmq_broker/conf/broker.conf diff --git a/README.md b/README.md index 18b2d62..bf70fd4 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ curl -XPOST "127.0.0.1:8080/send" -H 'Content-Type: application/json' -d '{"co 5、除了项目,还可以问我些学习经验、学习路线、简历编写、面试经验等等问题,技术和学习上的知识**知无不言** -详情可以看戳:[我开通了付费渠道](https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247505577&idx=1&sn=5114f8f583755899c2946fbea0b22e4b&chksm=ebd497a8dca31ebe8f98344483a00c860863dfc3586e51eed95b25988151427fee8101311f4f&token=319992632&lang=zh_CN#rd) +详情可以看戳:[我开通了付费渠道](https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247507166&idx=1&sn=d4437089c2db18b90a6d3ec742380554&chksm=ebd49ddfdca314c94d49a02da2ecb1358ac08d86616b6f1fce34720cc96e81d3006a51e86beb&token=28465847&lang=zh_CN#rd) ## 项目交流 diff --git a/austin-common/src/main/java/com/java3y/austin/common/constant/AustinConstant.java b/austin-common/src/main/java/com/java3y/austin/common/constant/AustinConstant.java index cb23a55..026887a 100644 --- a/austin-common/src/main/java/com/java3y/austin/common/constant/AustinConstant.java +++ b/austin-common/src/main/java/com/java3y/austin/common/constant/AustinConstant.java @@ -2,30 +2,16 @@ package com.java3y.austin.common.constant; /** - * 基础的常量信息 + * Austin常量信息 * * @author 3y */ public class AustinConstant { /** - * boolean转换 + * 跨域地址端口 */ - public final static Integer TRUE = 1; - public final static Integer FALSE = 0; - - /** - * cron时间格式 - */ - public final static String CRON_FORMAT = "ss mm HH dd MM ? yyyy-yyyy"; - - - /** - * apollo默认的值 - */ - public final static String APOLLO_DEFAULT_VALUE_JSON_OBJECT = "{}"; - public final static String APOLLO_DEFAULT_VALUE_JSON_ARRAY = "[]"; - + public static final String ORIGIN_VALUE = "http://localhost:3000"; /** * businessId默认的长度 @@ -33,6 +19,10 @@ public class AustinConstant { */ public final static Integer BUSINESS_ID_LENGTH = 16; + /** + * 接口限制 最多的人数 + */ + public static final Integer BATCH_RECEIVER_SIZE = 100; /** * 消息发送给全部人的标识 @@ -43,23 +33,5 @@ public class AustinConstant { public static final String SEND_ALL = "@all"; - /** - * 加密算法 - */ - public static final String HMAC_SHA256_ENCRYPTION_ALGO = "HmacSHA256"; - - /** - * 编码格式 - */ - public static final String CHARSET_NAME = "UTF-8"; - - - /** - * HTTP 请求方法 - */ - public static final String REQUEST_METHOD_GET = "GET"; - public static final String REQUEST_METHOD_POST = "POST"; - - } diff --git a/austin-common/src/main/java/com/java3y/austin/common/constant/CommonConstant.java b/austin-common/src/main/java/com/java3y/austin/common/constant/CommonConstant.java index ffc9088..8f41ae7 100644 --- a/austin-common/src/main/java/com/java3y/austin/common/constant/CommonConstant.java +++ b/austin-common/src/main/java/com/java3y/austin/common/constant/CommonConstant.java @@ -1,6 +1,7 @@ package com.java3y.austin.common.constant; public class CommonConstant { + public final static String PERIOD = "."; public final static String COMMA = ","; public final static String COLON = ":"; @@ -9,10 +10,54 @@ public class CommonConstant { public final static String SLASH = "/"; public final static String BACKSLASH = "\\"; public final static String EMPTY_STRING = ""; - // + public final static String RADICAL = "|"; + + public final static String QM_STRING = "?"; + public final static String EQUAL_STRING = "="; + public final static String AND_STRING = "&"; + + public final static String ONE = "1"; public final static String ZERO = "0"; public final static String MINUS_ONE = "-1"; public final static String YES = "Y"; public final static String NO = "N"; + + + public final static char QM = '?'; + + /** + * boolean转换 + */ + public final static Integer TRUE = 1; + public final static Integer FALSE = 0; + + + /** + * 加密算法 + */ + public static final String HMAC_SHA256_ENCRYPTION_ALGO = "HmacSHA256"; + + /** + * 编码格式 + */ + public static final String CHARSET_NAME = "UTF-8"; + + /** + * HTTP 请求方法 + */ + public static final String REQUEST_METHOD_GET = "GET"; + public static final String REQUEST_METHOD_POST = "POST"; + + /** + * JSON默认值 + */ + public final static String EMPTY_JSON_OBJECT = "{}"; + public final static String EMPTY_VALUE_JSON_ARRAY = "[]"; + + /** + * cron时间格式 + */ + public final static String CRON_FORMAT = "ss mm HH dd MM ? yyyy-yyyy"; + } \ No newline at end of file diff --git a/austin-common/src/main/java/com/java3y/austin/common/constant/SendAccountConstant.java b/austin-common/src/main/java/com/java3y/austin/common/constant/SendAccountConstant.java index 3f90cc2..67b5df8 100644 --- a/austin-common/src/main/java/com/java3y/austin/common/constant/SendAccountConstant.java +++ b/austin-common/src/main/java/com/java3y/austin/common/constant/SendAccountConstant.java @@ -3,92 +3,19 @@ package com.java3y.austin.common.constant; /** * 发送账号的常量信息汇总 - *

- * (读取apollo的key和前缀) - *

- * 约定:所有的账号都从10开始,步长为10 - * * @author 3y */ public class SendAccountConstant { - /** - * 账号约定:所有的账号都从10开始,步长为10 - */ - public static final Integer START = 10; - public static final Integer STEP = 10; - - /** * 钉钉 工作应用消息 账号 */ - public static final String DING_DING_WORK_NOTICE_ACCOUNT_KEY = "dingDingWorkNoticeAccount"; - public static final String DING_DING_WORK_NOTICE_PREFIX = "ding_ding_work_notice_"; public static final String DING_DING_ACCESS_TOKEN_PREFIX = "ding_ding_access_token_"; /** * 个推PUSH 消息账号 */ - public static final String GE_TUI_ACCOUNT_KEY = "geTuiAccount"; - public static final String GE_TUI_ACCOUNT_PREFIX = "ge_tui_account_"; public static final String GE_TUI_ACCESS_TOKEN_PREFIX = "ge_tui_access_token_"; - - /** - * 邮件 账号 - */ - public static final String EMAIL_ACCOUNT_KEY = "emailAccount"; - public static final String EMAIL_ACCOUNT_PREFIX = "email_"; - - - /** - * 钉钉群自定义机器人 账号 - */ - public static final String DING_DING_ROBOT_ACCOUNT_KEY = "dingDingRobotAccount"; - public static final String DING_DING_ROBOT_PREFIX = "ding_ding_robot_"; - - /** - * 企业微信群机器人 账号 - */ - public static final String ENTERPRISE_WECHAT_ROBOT_ACCOUNT_KEY = "enterpriseWechatAccountRobot"; - public static final String ENTERPRISE_WECHAT_ROBOT_PREFIX = "enterprise_wechat_robot_"; - - /** - * 飞书群机器人 账号 - */ - public static final String FEI_SHU_ROBOT_ACCOUNT_KEY = "feiShuAccountRobot"; - public static final String FEI_SHU_ROBOT_PREFIX = "fei_shu_robot_"; - - /** - * 企业微信 应用消息 账号 - */ - public static final String ENTERPRISE_WECHAT_ACCOUNT_KEY = "enterpriseWechatAccount"; - public static final String ENTERPRISE_WECHAT_PREFIX = "enterprise_wechat_"; - - /** - * 微信服务号 应用消息 账号 - */ - public static final String WECHAT_OFFICIAL_ACCOUNT_KEY = "officialAccount"; - public static final String WECHAT_OFFICIAL__PREFIX = "official_"; - - /** - * 微信小程序 应用消息 账号 - */ - public static final String WECHAT_MINI_PROGRAM_ACCOUNT_KEY = "miniProgramAccount"; - public static final String WECHAT_MINI_PROGRAM_PREFIX = "mini_program_"; - - /** - * 短信 账号 - */ - public static final String SMS_ACCOUNT_KEY = "smsAccount"; - public static final String SMS_PREFIX = "sms_"; - - - /** - * 短信账号code - */ - public static final Integer YUN_PIAN_SMS_CODE = 20; - - } diff --git a/austin-common/src/main/java/com/java3y/austin/common/dto/account/AlipayMiniProgramAccount.java b/austin-common/src/main/java/com/java3y/austin/common/dto/account/AlipayMiniProgramAccount.java new file mode 100644 index 0000000..9dfa247 --- /dev/null +++ b/austin-common/src/main/java/com/java3y/austin/common/dto/account/AlipayMiniProgramAccount.java @@ -0,0 +1,42 @@ +package com.java3y.austin.common.dto.account; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author jwq + * 支付宝小程序订阅消息账号配置 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class AlipayMiniProgramAccount { + + /** + * 应用私钥 + */ + private String privateKey; + + /** + * 支付宝公钥 + */ + private String alipayPublicKey; + + /** + * 支付宝小程序的AppID + */ + private String appId; + + /** + * 订阅模版Id + */ + private String userTemplateId; + + /** + * 点击跳转到的小程序页面 + */ + private String page; +} diff --git a/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/SmsAccount.java b/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/SmsAccount.java new file mode 100644 index 0000000..31a5215 --- /dev/null +++ b/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/SmsAccount.java @@ -0,0 +1,28 @@ +package com.java3y.austin.common.dto.account.sms; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SmsAccount { + /** + * 标识渠道商Id + */ + protected Integer supplierId; + + /** + * 标识渠道商名字 + */ + protected String supplierName; + + /** + * 【重要】类名,定位到具体的处理下发和回执逻辑 + * + * 依据scriptName对应具体的某一个短信账号 + */ + protected String scriptName; +} diff --git a/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/TencentSmsAccount.java b/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/TencentSmsAccount.java new file mode 100644 index 0000000..1f28379 --- /dev/null +++ b/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/TencentSmsAccount.java @@ -0,0 +1,35 @@ +package com.java3y.austin.common.dto.account.sms; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 腾讯短信参数 + * + * 账号参数示例: + * {"url":"sms.tencentcloudapi.com","region":"ap-guangzhou","secretId":"AKIDhDxxxxxxxx1WljQq","secretKey":"B4hwww39yxxxrrrrgxyi","smsSdkAppId":"1423123125","templateId":"1182097","signName":"Java3y公众号","supplierId":10,"supplierName":"腾讯云","scriptName":"TencentSmsScript"} + * + * @author 3y + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class TencentSmsAccount extends SmsAccount { + + /** + * api相关 + */ + private String url; + private String region ; + + /** + * 账号相关 + */ + private String secretId; + private String secretKey; + private String smsSdkAppId; + private String templateId; + private String signName; +} diff --git a/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/YunPianSmsAccount.java b/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/YunPianSmsAccount.java new file mode 100644 index 0000000..1421569 --- /dev/null +++ b/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/YunPianSmsAccount.java @@ -0,0 +1,35 @@ +package com.java3y.austin.common.dto.account.sms; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 云片账号信息 + * + * 账号参数示例: + * {"url":"https://sms.yunpian.com/v2/sms/tpl_batch_send.json","apikey":"caffff8234234231b5cd7","tpl_id":"523333332","supplierId":20,"supplierName":"云片","scriptName":"YunPianSmsScript"} + * + * @author 3y + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class YunPianSmsAccount extends SmsAccount { + + /** + * apikey + */ + private String apikey; + /** + * tplId + */ + private String tplId; + + /** + * api相关 + */ + private String url; + +} diff --git a/austin-common/src/main/java/com/java3y/austin/common/dto/model/AlipayMiniProgramContentModel.java b/austin-common/src/main/java/com/java3y/austin/common/dto/model/AlipayMiniProgramContentModel.java new file mode 100644 index 0000000..5ee780f --- /dev/null +++ b/austin-common/src/main/java/com/java3y/austin/common/dto/model/AlipayMiniProgramContentModel.java @@ -0,0 +1,25 @@ +package com.java3y.austin.common.dto.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +/** + * @author jwq + * 支付宝小程序订阅消息内容 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class AlipayMiniProgramContentModel extends ContentModel{ + + /** + * 模板消息发送的数据 + */ + Map map; + +} diff --git a/austin-common/src/main/java/com/java3y/austin/common/enums/ChannelType.java b/austin-common/src/main/java/com/java3y/austin/common/enums/ChannelType.java index 965b857..c81882d 100644 --- a/austin-common/src/main/java/com/java3y/austin/common/enums/ChannelType.java +++ b/austin-common/src/main/java/com/java3y/austin/common/enums/ChannelType.java @@ -28,6 +28,7 @@ public enum ChannelType { DING_DING_WORK_NOTICE(90, "dingDingWorkNotice(钉钉工作通知)", DingDingWorkContentModel.class, "ding_ding_work_notice"), ENTERPRISE_WE_CHAT_ROBOT(100, "enterpriseWeChat(企业微信机器人)", EnterpriseWeChatRobotContentModel.class, "enterprise_we_chat_robot"), FEI_SHU_ROBOT(110, "feiShuRoot(飞书机器人)", FeiShuRobotContentModel.class, "fei_shu_robot"), + ALIPAY_MINI_PROGRAM(120,"alipayMiniProgram(支付宝小程序)",AlipayMiniProgramContentModel.class,"alipay_mini_program"), ; /** diff --git a/austin-cron/src/main/java/com/java3y/austin/cron/handler/RefreshDingDingAccessTokenHandler.java b/austin-cron/src/main/java/com/java3y/austin/cron/handler/RefreshDingDingAccessTokenHandler.java index 5bb1478..84b5c59 100644 --- a/austin-cron/src/main/java/com/java3y/austin/cron/handler/RefreshDingDingAccessTokenHandler.java +++ b/austin-cron/src/main/java/com/java3y/austin/cron/handler/RefreshDingDingAccessTokenHandler.java @@ -8,6 +8,7 @@ import com.dingtalk.api.request.OapiGettokenRequest; import com.dingtalk.api.response.OapiGettokenResponse; import com.google.common.base.Throwables; import com.java3y.austin.common.constant.AustinConstant; +import com.java3y.austin.common.constant.CommonConstant; import com.java3y.austin.common.constant.SendAccountConstant; import com.java3y.austin.common.dto.account.DingDingWorkNoticeAccount; import com.java3y.austin.common.enums.ChannelType; @@ -52,7 +53,7 @@ public class RefreshDingDingAccessTokenHandler { public void execute() { log.info("refreshAccessTokenJob#execute!"); SupportThreadPoolConfig.getPendingSingleThreadPool().execute(() -> { - List accountList = channelAccountDao.findAllByIsDeletedEqualsAndSendChannelEquals(AustinConstant.FALSE, ChannelType.DING_DING_WORK_NOTICE.getCode()); + List accountList = channelAccountDao.findAllByIsDeletedEqualsAndSendChannelEquals(CommonConstant.FALSE, ChannelType.DING_DING_WORK_NOTICE.getCode()); for (ChannelAccount channelAccount : accountList) { DingDingWorkNoticeAccount account = JSON.parseObject(channelAccount.getAccountConfig(), DingDingWorkNoticeAccount.class); String accessToken = getAccessToken(account); @@ -76,7 +77,7 @@ public class RefreshDingDingAccessTokenHandler { OapiGettokenRequest req = new OapiGettokenRequest(); req.setAppkey(account.getAppKey()); req.setAppsecret(account.getAppSecret()); - req.setHttpMethod(AustinConstant.REQUEST_METHOD_GET); + req.setHttpMethod(CommonConstant.REQUEST_METHOD_GET); OapiGettokenResponse rsp = client.execute(req); accessToken = rsp.getAccessToken(); } catch (Exception e) { diff --git a/austin-cron/src/main/java/com/java3y/austin/cron/handler/RefreshGeTuiAccessTokenHandler.java b/austin-cron/src/main/java/com/java3y/austin/cron/handler/RefreshGeTuiAccessTokenHandler.java index a978e52..94977fe 100644 --- a/austin-cron/src/main/java/com/java3y/austin/cron/handler/RefreshGeTuiAccessTokenHandler.java +++ b/austin-cron/src/main/java/com/java3y/austin/cron/handler/RefreshGeTuiAccessTokenHandler.java @@ -8,6 +8,7 @@ import cn.hutool.http.HttpRequest; import com.alibaba.fastjson.JSON; import com.google.common.base.Throwables; import com.java3y.austin.common.constant.AustinConstant; +import com.java3y.austin.common.constant.CommonConstant; import com.java3y.austin.common.constant.SendAccountConstant; import com.java3y.austin.common.dto.account.GeTuiAccount; import com.java3y.austin.common.enums.ChannelType; @@ -51,14 +52,12 @@ public class RefreshGeTuiAccessTokenHandler { public void execute() { log.info("refreshGeTuiAccessTokenJob#execute!"); SupportThreadPoolConfig.getPendingSingleThreadPool().execute(() -> { - List accountList = channelAccountDao.findAllByIsDeletedEqualsAndSendChannelEquals(AustinConstant.FALSE, ChannelType.PUSH.getCode()); - for (int index = SendAccountConstant.START; true; index = index + SendAccountConstant.STEP) { - for (ChannelAccount channelAccount : accountList) { - GeTuiAccount account = JSON.parseObject(channelAccount.getAccountConfig(), GeTuiAccount.class); - String accessToken = getAccessToken(account); - if (StrUtil.isNotBlank(accessToken)) { - redisTemplate.opsForValue().set(SendAccountConstant.GE_TUI_ACCESS_TOKEN_PREFIX + index, accessToken); - } + List accountList = channelAccountDao.findAllByIsDeletedEqualsAndSendChannelEquals(CommonConstant.FALSE, ChannelType.PUSH.getCode()); + for (ChannelAccount channelAccount : accountList) { + GeTuiAccount account = JSON.parseObject(channelAccount.getAccountConfig(), GeTuiAccount.class); + String accessToken = getAccessToken(account); + if (StrUtil.isNotBlank(accessToken)) { + redisTemplate.opsForValue().set(SendAccountConstant.GE_TUI_ACCESS_TOKEN_PREFIX + channelAccount.getId(), accessToken); } } }); diff --git a/austin-cron/src/main/java/com/java3y/austin/cron/pending/CrowdBatchTaskPending.java b/austin-cron/src/main/java/com/java3y/austin/cron/pending/CrowdBatchTaskPending.java index 50339ab..7b420df 100644 --- a/austin-cron/src/main/java/com/java3y/austin/cron/pending/CrowdBatchTaskPending.java +++ b/austin-cron/src/main/java/com/java3y/austin/cron/pending/CrowdBatchTaskPending.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import com.google.common.collect.Lists; +import com.java3y.austin.common.constant.AustinConstant; import com.java3y.austin.cron.config.CronAsyncThreadPoolConfig; import com.java3y.austin.cron.constants.PendingConstant; import com.java3y.austin.cron.vo.CrowdInfoVo; @@ -40,9 +41,9 @@ public class CrowdBatchTaskPending extends AbstractLazyPending { public CrowdBatchTaskPending() { PendingParam pendingParam = new PendingParam<>(); - pendingParam.setNumThreshold(PendingConstant.NUM_THRESHOLD) - .setQueue(new LinkedBlockingQueue(PendingConstant.QUEUE_SIZE)) + pendingParam.setQueue(new LinkedBlockingQueue(PendingConstant.QUEUE_SIZE)) .setTimeThreshold(PendingConstant.TIME_THRESHOLD) + .setNumThreshold(AustinConstant.BATCH_RECEIVER_SIZE) .setExecutorService(CronAsyncThreadPoolConfig.getConsumePendingThreadPool()); this.pendingParam = pendingParam; } diff --git a/austin-cron/src/main/java/com/java3y/austin/cron/xxl/utils/XxlJobUtils.java b/austin-cron/src/main/java/com/java3y/austin/cron/xxl/utils/XxlJobUtils.java index cb6de84..4df140c 100644 --- a/austin-cron/src/main/java/com/java3y/austin/cron/xxl/utils/XxlJobUtils.java +++ b/austin-cron/src/main/java/com/java3y/austin/cron/xxl/utils/XxlJobUtils.java @@ -3,6 +3,7 @@ package com.java3y.austin.cron.xxl.utils; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import com.java3y.austin.common.constant.AustinConstant; +import com.java3y.austin.common.constant.CommonConstant; import com.java3y.austin.common.enums.RespStatusEnum; import com.java3y.austin.common.vo.BasicResultVO; import com.java3y.austin.cron.xxl.constants.XxlJobConstant; @@ -44,8 +45,8 @@ public class XxlJobUtils { String scheduleConf = messageTemplate.getExpectPushTime(); // 如果没有指定cron表达式,说明立即执行(给到xxl-job延迟5秒的cron表达式) - if (messageTemplate.getExpectPushTime().equals(String.valueOf(AustinConstant.FALSE))) { - scheduleConf = DateUtil.format(DateUtil.offsetSecond(new Date(), XxlJobConstant.DELAY_TIME), AustinConstant.CRON_FORMAT); + if (messageTemplate.getExpectPushTime().equals(String.valueOf(CommonConstant.FALSE))) { + scheduleConf = DateUtil.format(DateUtil.offsetSecond(new Date(), XxlJobConstant.DELAY_TIME), CommonConstant.CRON_FORMAT); } XxlJobInfo xxlJobInfo = XxlJobInfo.builder() @@ -61,7 +62,7 @@ public class XxlJobUtils { .executorTimeout(XxlJobConstant.TIME_OUT) .executorFailRetryCount(XxlJobConstant.RETRY_COUNT) .glueType(GlueTypeEnum.BEAN.name()) - .triggerStatus(AustinConstant.FALSE) + .triggerStatus(CommonConstant.FALSE) .glueRemark(StrUtil.EMPTY) .glueSource(StrUtil.EMPTY) .alarmEmail(StrUtil.EMPTY) @@ -80,7 +81,7 @@ public class XxlJobUtils { private Integer queryJobGroupId() { BasicResultVO basicResultVO = cronTaskService.getGroupId(appName, jobHandlerName); if (basicResultVO.getData() == null) { - XxlJobGroup xxlJobGroup = XxlJobGroup.builder().appname(appName).title(jobHandlerName).addressType(AustinConstant.FALSE).build(); + XxlJobGroup xxlJobGroup = XxlJobGroup.builder().appname(appName).title(jobHandlerName).addressType(CommonConstant.FALSE).build(); if (RespStatusEnum.SUCCESS.getCode().equals(cronTaskService.createGroup(xxlJobGroup).getStatus())) { return (int) cronTaskService.getGroupId(appName, jobHandlerName).getData(); } diff --git a/austin-handler/pom.xml b/austin-handler/pom.xml index 7f542c6..eb6ef31 100644 --- a/austin-handler/pom.xml +++ b/austin-handler/pom.xml @@ -57,6 +57,11 @@ com.github.binarywang weixin-java-cp + + + com.alipay.sdk + alipay-sdk-java + diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/alipay/AlipayMiniProgramAccountService.java b/austin-handler/src/main/java/com/java3y/austin/handler/alipay/AlipayMiniProgramAccountService.java new file mode 100644 index 0000000..6b5c957 --- /dev/null +++ b/austin-handler/src/main/java/com/java3y/austin/handler/alipay/AlipayMiniProgramAccountService.java @@ -0,0 +1,18 @@ +package com.java3y.austin.handler.alipay; + +import com.alipay.api.AlipayApiException; +import com.java3y.austin.handler.domain.alipay.AlipayMiniProgramParam; + +/** + * @author jwq + * 支付宝小程序发送订阅消息接口 + */ +public interface AlipayMiniProgramAccountService { + /** + * 发送订阅消息 + * + * @param miniProgramParam 订阅消息参数 + * @throws AlipayApiException alipay异常 + */ + void send(AlipayMiniProgramParam miniProgramParam) throws AlipayApiException; +} diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/alipay/impl/AlipayMiniProgramAccountServiceImpl.java b/austin-handler/src/main/java/com/java3y/austin/handler/alipay/impl/AlipayMiniProgramAccountServiceImpl.java new file mode 100644 index 0000000..4fa1d56 --- /dev/null +++ b/austin-handler/src/main/java/com/java3y/austin/handler/alipay/impl/AlipayMiniProgramAccountServiceImpl.java @@ -0,0 +1,86 @@ +package com.java3y.austin.handler.alipay.impl; + +import com.alipay.api.AlipayApiException; +import com.alipay.api.AlipayClient; +import com.alipay.api.AlipayConfig; +import com.alipay.api.DefaultAlipayClient; +import com.alipay.api.domain.AlipayOpenAppMiniTemplatemessageSendModel; +import com.alipay.api.request.AlipayOpenAppMiniTemplatemessageSendRequest; +import com.java3y.austin.common.constant.SendAccountConstant; +import com.java3y.austin.common.dto.account.AlipayMiniProgramAccount; +import com.java3y.austin.handler.alipay.AlipayMiniProgramAccountService; +import com.java3y.austin.handler.domain.alipay.AlipayMiniProgramParam; +import com.java3y.austin.support.utils.AccountUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * @author jwq + * 支付宝小程序发送订阅消息实现 + */ +@Service +@Slf4j +public class AlipayMiniProgramAccountServiceImpl implements AlipayMiniProgramAccountService { + + @Autowired + private AccountUtils accountUtils; + + /** + * 发送订阅消息 + * + * @param miniProgramParam 订阅消息参数 + * @throws AlipayApiException alipay异常 + */ + @Override + public void send(AlipayMiniProgramParam miniProgramParam) throws AlipayApiException { + AlipayMiniProgramAccount miniProgramAccount = accountUtils.getAccountById(miniProgramParam.getSendAccount(), AlipayMiniProgramAccount.class); + + AlipayClient client = initService(miniProgramAccount); + List request = assembleReq(miniProgramParam, miniProgramAccount); + for(AlipayOpenAppMiniTemplatemessageSendRequest req : request){ + client.execute(req); + } + } + + /** + * 组装模板消息的参数 + */ + private List assembleReq(AlipayMiniProgramParam alipayMiniProgramParam, AlipayMiniProgramAccount alipayMiniProgramAccount){ + Set receiver = alipayMiniProgramParam.getToUserId(); + List requestList = new ArrayList<>(receiver.size()); + + for(String toUserId : receiver){ + AlipayOpenAppMiniTemplatemessageSendRequest request = new AlipayOpenAppMiniTemplatemessageSendRequest(); + AlipayOpenAppMiniTemplatemessageSendModel model = new AlipayOpenAppMiniTemplatemessageSendModel(); + model.setToUserId(toUserId); + model.setUserTemplateId(alipayMiniProgramAccount.getUserTemplateId()); + model.setPage(alipayMiniProgramAccount.getPage()); + model.setData(alipayMiniProgramParam.getData().toString()); + request.setBizModel(model); + requestList.add(request); + } + return requestList; + } + + /** + * 初始化支付宝小程序 + */ + private AlipayClient initService(AlipayMiniProgramAccount alipayMiniProgramAccount) throws AlipayApiException { + AlipayConfig alipayConfig = new AlipayConfig(); + alipayConfig.setServerUrl("https://openapi.alipaydev.com/gateway.do"); + alipayConfig.setAppId(alipayMiniProgramAccount.getAppId()); + alipayConfig.setPrivateKey(alipayMiniProgramAccount.getPrivateKey()); + alipayConfig.setFormat("json"); + alipayConfig.setAlipayPublicKey(alipayMiniProgramAccount.getAlipayPublicKey()); + alipayConfig.setCharset("utf-8"); + alipayConfig.setSignType("RSA2"); + return new DefaultAlipayClient(alipayConfig); + } + + +} diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/deduplication/DeduplicationRuleService.java b/austin-handler/src/main/java/com/java3y/austin/handler/deduplication/DeduplicationRuleService.java index 1fbb90b..bf254b2 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/deduplication/DeduplicationRuleService.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/deduplication/DeduplicationRuleService.java @@ -1,6 +1,7 @@ package com.java3y.austin.handler.deduplication; import com.java3y.austin.common.constant.AustinConstant; +import com.java3y.austin.common.constant.CommonConstant; import com.java3y.austin.common.domain.TaskInfo; import com.java3y.austin.common.enums.DeduplicationType; import com.java3y.austin.support.service.ConfigService; @@ -27,7 +28,7 @@ public class DeduplicationRuleService { public void duplication(TaskInfo taskInfo) { // 配置样例:{"deduplication_10":{"num":1,"time":300},"deduplication_20":{"num":5}} - String deduplicationConfig = config.getProperty(DEDUPLICATION_RULE_KEY, AustinConstant.APOLLO_DEFAULT_VALUE_JSON_OBJECT); + String deduplicationConfig = config.getProperty(DEDUPLICATION_RULE_KEY, CommonConstant.EMPTY_JSON_OBJECT); // 去重 List deduplicationList = DeduplicationType.getDeduplicationList(); diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/deduplication/limit/SimpleLimitService.java b/austin-handler/src/main/java/com/java3y/austin/handler/deduplication/limit/SimpleLimitService.java index c543a5f..569188b 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/deduplication/limit/SimpleLimitService.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/deduplication/limit/SimpleLimitService.java @@ -2,6 +2,7 @@ package com.java3y.austin.handler.deduplication.limit; import cn.hutool.core.collection.CollUtil; import com.java3y.austin.common.constant.AustinConstant; +import com.java3y.austin.common.constant.CommonConstant; import com.java3y.austin.common.domain.TaskInfo; import com.java3y.austin.handler.deduplication.DeduplicationParam; import com.java3y.austin.handler.deduplication.service.AbstractDeduplicationService; @@ -66,7 +67,7 @@ public class SimpleLimitService extends AbstractLimitService { if (inRedisValue.get(key) != null) { keyValues.put(key, String.valueOf(Integer.valueOf(inRedisValue.get(key)) + 1)); } else { - keyValues.put(key, String.valueOf(AustinConstant.TRUE)); + keyValues.put(key, String.valueOf(CommonConstant.TRUE)); } } if (CollUtil.isNotEmpty(keyValues)) { diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/discard/DiscardMessageService.java b/austin-handler/src/main/java/com/java3y/austin/handler/discard/DiscardMessageService.java index 49850b2..e056ed6 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/discard/DiscardMessageService.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/discard/DiscardMessageService.java @@ -3,6 +3,7 @@ package com.java3y.austin.handler.discard; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.java3y.austin.common.constant.AustinConstant; +import com.java3y.austin.common.constant.CommonConstant; import com.java3y.austin.common.domain.AnchorInfo; import com.java3y.austin.common.domain.TaskInfo; import com.java3y.austin.common.enums.AnchorState; @@ -33,8 +34,7 @@ public class DiscardMessageService { */ public boolean isDiscard(TaskInfo taskInfo) { // 配置示例: ["1","2"] - JSONArray array = JSON.parseArray(config.getProperty(DISCARD_MESSAGE_KEY, - AustinConstant.APOLLO_DEFAULT_VALUE_JSON_ARRAY)); + JSONArray array = JSON.parseArray(config.getProperty(DISCARD_MESSAGE_KEY, CommonConstant.EMPTY_VALUE_JSON_ARRAY)); if (array.contains(String.valueOf(taskInfo.getMessageTemplateId()))) { logUtils.print(AnchorInfo.builder().businessId(taskInfo.getBusinessId()).ids(taskInfo.getReceiver()).state(AnchorState.DISCARD.getCode()).build()); diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/domain/alipay/AlipayMiniProgramParam.java b/austin-handler/src/main/java/com/java3y/austin/handler/domain/alipay/AlipayMiniProgramParam.java new file mode 100644 index 0000000..469641c --- /dev/null +++ b/austin-handler/src/main/java/com/java3y/austin/handler/domain/alipay/AlipayMiniProgramParam.java @@ -0,0 +1,43 @@ +package com.java3y.austin.handler.domain.alipay; + +import lombok.Builder; +import lombok.Data; + +import java.util.Map; +import java.util.Set; + +/** + * @author jwq + * 支付宝小程序参数 + */ +@Data +@Builder +public class AlipayMiniProgramParam { + /** + * 业务Id + */ + private Long messageTemplateId; + + /** + * 发送账号 + */ + private Integer sendAccount; + + /** + * 接收者(用户)的 UserId + */ + private Set toUserId; + + /** + * 模板内容,格式形如 { "key1": { "value": any }, "key2": { "value": any } } + */ + private Map data; + +// /** +// * 支付消息模板:需传入用户发生的交易行为的支付宝交易号 trade_no; +// * 表单提交模板:需传入用户在小程序触发表单提交事件获得的表单号; +// * 刷脸消息模板:需传入在IOT刷脸后得到的ftoken等,用于信息发送的校验。 +// * 说明:订阅消息模板无需传入本参数。 +// */ +// private String formId; +} diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/domain/sms/SmsParam.java b/austin-handler/src/main/java/com/java3y/austin/handler/domain/sms/SmsParam.java index 96a5881..f9cf01a 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/domain/sms/SmsParam.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/domain/sms/SmsParam.java @@ -25,9 +25,9 @@ public class SmsParam { private Set phones; /** - * 发送渠道账号的Id + * 发送渠道账号的脚本名标识 */ - private Integer sendAccountId; + private String scriptName; /** diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/flowcontrol/FlowControlFactory.java b/austin-handler/src/main/java/com/java3y/austin/handler/flowcontrol/FlowControlFactory.java index 92b22da..217b790 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/flowcontrol/FlowControlFactory.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/flowcontrol/FlowControlFactory.java @@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.google.common.util.concurrent.RateLimiter; import com.java3y.austin.common.constant.AustinConstant; +import com.java3y.austin.common.constant.CommonConstant; import com.java3y.austin.common.domain.TaskInfo; import com.java3y.austin.common.enums.ChannelType; import com.java3y.austin.handler.enums.RateLimitStrategy; @@ -76,7 +77,7 @@ public class FlowControlFactory implements ApplicationContextAware { * @param channelCode */ private Double getRateLimitConfig(Integer channelCode) { - String flowControlConfig = config.getProperty(FLOW_CONTROL_KEY, AustinConstant.APOLLO_DEFAULT_VALUE_JSON_OBJECT); + String flowControlConfig = config.getProperty(FLOW_CONTROL_KEY, CommonConstant.EMPTY_JSON_OBJECT); JSONObject jsonObject = JSON.parseObject(flowControlConfig); if (jsonObject.getDouble(FLOW_CONTROL_PREFIX + channelCode) == null) { return null; diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/AlipayMiniProgramAccountHandler.java b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/AlipayMiniProgramAccountHandler.java new file mode 100644 index 0000000..6b90bae --- /dev/null +++ b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/AlipayMiniProgramAccountHandler.java @@ -0,0 +1,67 @@ +package com.java3y.austin.handler.handler.impl; + +import com.alibaba.fastjson.JSON; +import com.google.common.base.Throwables; +import com.java3y.austin.common.domain.TaskInfo; +import com.java3y.austin.common.dto.model.AlipayMiniProgramContentModel; +import com.java3y.austin.common.enums.ChannelType; +import com.java3y.austin.handler.alipay.AlipayMiniProgramAccountService; +import com.java3y.austin.handler.domain.alipay.AlipayMiniProgramParam; +import com.java3y.austin.handler.handler.BaseHandler; +import com.java3y.austin.handler.handler.Handler; +import com.java3y.austin.support.domain.MessageTemplate; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @author jwq + * 支付宝小程序发送订阅消息 + */ +@Component +@Slf4j +public class AlipayMiniProgramAccountHandler extends BaseHandler implements Handler { + + @Autowired + private AlipayMiniProgramAccountService alipayMiniProgramAccountService; + + public AlipayMiniProgramAccountHandler() { + channelCode = ChannelType.ALIPAY_MINI_PROGRAM.getCode(); + } + + @Override + public boolean handler(TaskInfo taskInfo) { + AlipayMiniProgramParam miniProgramParam = buildMiniProgramParam(taskInfo); + try { + alipayMiniProgramAccountService.send(miniProgramParam); + }catch (Exception e) { + log.error("AlipayMiniProgramAccountHandler#handler fail:{},params:{}", + Throwables.getStackTraceAsString(e), JSON.toJSONString(taskInfo)); + return false; + } + return true; + } + + /** + * 通过taskInfo构建小程序订阅消息 + * + * @param taskInfo 任务信息 + * @return AlipayMiniProgramParam + */ + private AlipayMiniProgramParam buildMiniProgramParam(TaskInfo taskInfo){ + AlipayMiniProgramParam param = AlipayMiniProgramParam.builder() + .toUserId(taskInfo.getReceiver()) + .messageTemplateId(taskInfo.getMessageTemplateId()) + .sendAccount(taskInfo.getSendAccount()) + .build(); + + AlipayMiniProgramContentModel contentModel = (AlipayMiniProgramContentModel) taskInfo.getContentModel(); + param.setData(contentModel.getMap()); + return param; + } + + @Override + public void recall(MessageTemplate messageTemplate) { + + } +} diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/DingDingRobotHandler.java b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/DingDingRobotHandler.java index e378215..997db58 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/DingDingRobotHandler.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/DingDingRobotHandler.java @@ -6,6 +6,7 @@ import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSON; import com.google.common.base.Throwables; import com.java3y.austin.common.constant.AustinConstant; +import com.java3y.austin.common.constant.CommonConstant; import com.java3y.austin.common.constant.SendAccountConstant; import com.java3y.austin.common.domain.TaskInfo; import com.java3y.austin.common.dto.account.DingDingRobotAccount; @@ -48,7 +49,7 @@ public class DingDingRobotHandler extends BaseHandler implements Handler { @Override public boolean handler(TaskInfo taskInfo) { try { - DingDingRobotAccount account = accountUtils.getAccount(taskInfo.getSendAccount(), SendAccountConstant.DING_DING_ROBOT_ACCOUNT_KEY, SendAccountConstant.DING_DING_ROBOT_PREFIX, DingDingRobotAccount.class); + DingDingRobotAccount account = accountUtils.getAccountById(taskInfo.getSendAccount(), DingDingRobotAccount.class); DingDingRobotParam dingDingRobotParam = assembleParam(taskInfo); String httpResult = HttpUtil.post(assembleParamUrl(account), JSON.toJSONString(dingDingRobotParam)); DingDingRobotResult dingDingRobotResult = JSON.parseObject(httpResult, DingDingRobotResult.class); @@ -126,10 +127,10 @@ public class DingDingRobotHandler extends BaseHandler implements Handler { String sign = ""; try { String stringToSign = currentTimeMillis + String.valueOf(StrUtil.C_LF) + secret; - Mac mac = Mac.getInstance(AustinConstant.HMAC_SHA256_ENCRYPTION_ALGO); - mac.init(new SecretKeySpec(secret.getBytes(AustinConstant.CHARSET_NAME), AustinConstant.HMAC_SHA256_ENCRYPTION_ALGO)); - byte[] signData = mac.doFinal(stringToSign.getBytes(AustinConstant.CHARSET_NAME)); - sign = URLEncoder.encode(new String(Base64.encodeBase64(signData)), AustinConstant.CHARSET_NAME); + Mac mac = Mac.getInstance(CommonConstant.HMAC_SHA256_ENCRYPTION_ALGO); + mac.init(new SecretKeySpec(secret.getBytes(CommonConstant.CHARSET_NAME), CommonConstant.HMAC_SHA256_ENCRYPTION_ALGO)); + byte[] signData = mac.doFinal(stringToSign.getBytes(CommonConstant.CHARSET_NAME)); + sign = URLEncoder.encode(new String(Base64.encodeBase64(signData)), CommonConstant.CHARSET_NAME); } catch (Exception e) { log.error("DingDingHandler#assembleSign fail!:{}", Throwables.getStackTraceAsString(e)); } diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/DingDingWorkNoticeHandler.java b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/DingDingWorkNoticeHandler.java index 7b47c2a..4f484d9 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/DingDingWorkNoticeHandler.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/DingDingWorkNoticeHandler.java @@ -65,7 +65,7 @@ public class DingDingWorkNoticeHandler extends BaseHandler implements Handler { @Override public boolean handler(TaskInfo taskInfo) { try { - DingDingWorkNoticeAccount account = accountUtils.getAccount(taskInfo.getSendAccount(), SendAccountConstant.DING_DING_WORK_NOTICE_ACCOUNT_KEY, SendAccountConstant.DING_DING_WORK_NOTICE_PREFIX, DingDingWorkNoticeAccount.class); + DingDingWorkNoticeAccount account = accountUtils.getAccountById(taskInfo.getSendAccount(), DingDingWorkNoticeAccount.class); OapiMessageCorpconversationAsyncsendV2Request request = assembleParam(account, taskInfo); String accessToken = redisTemplate.opsForValue().get(SendAccountConstant.DING_DING_ACCESS_TOKEN_PREFIX + taskInfo.getSendAccount()); OapiMessageCorpconversationAsyncsendV2Response response = new DefaultDingTalkClient(SEND_URL).execute(request, accessToken); @@ -179,7 +179,7 @@ public class DingDingWorkNoticeHandler extends BaseHandler implements Handler { public void recall(MessageTemplate messageTemplate) { SupportThreadPoolConfig.getPendingSingleThreadPool().execute(() -> { try { - DingDingWorkNoticeAccount account = accountUtils.getAccount(messageTemplate.getSendAccount(), SendAccountConstant.DING_DING_WORK_NOTICE_ACCOUNT_KEY, SendAccountConstant.DING_DING_WORK_NOTICE_PREFIX, DingDingWorkNoticeAccount.class); + DingDingWorkNoticeAccount account = accountUtils.getAccountById(messageTemplate.getSendAccount(), DingDingWorkNoticeAccount.class); String accessToken = redisTemplate.opsForValue().get(SendAccountConstant.DING_DING_ACCESS_TOKEN_PREFIX + messageTemplate.getSendAccount()); while (redisTemplate.opsForList().size(DING_DING_RECALL_KEY_PREFIX + messageTemplate.getId()) > 0) { String taskId = redisTemplate.opsForList().leftPop(DING_DING_RECALL_KEY_PREFIX + messageTemplate.getId()); diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/EmailHandler.java b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/EmailHandler.java index db2c7cf..a9512b2 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/EmailHandler.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/EmailHandler.java @@ -63,7 +63,7 @@ public class EmailHandler extends BaseHandler implements Handler { * @return */ private MailAccount getAccountConfig(Integer sendAccount) { - MailAccount account = accountUtils.getAccount(sendAccount, SendAccountConstant.EMAIL_ACCOUNT_KEY, SendAccountConstant.EMAIL_ACCOUNT_PREFIX, MailAccount.class); + MailAccount account = accountUtils.getAccountById(sendAccount, MailAccount.class); try { MailSSLSocketFactory sf = new MailSSLSocketFactory(); sf.setTrustAllHosts(true); diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/EnterpriseWeChatHandler.java b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/EnterpriseWeChatHandler.java index 15d34e6..4b6a6ae 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/EnterpriseWeChatHandler.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/EnterpriseWeChatHandler.java @@ -1,9 +1,11 @@ package com.java3y.austin.handler.handler.impl; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.google.common.base.Throwables; import com.java3y.austin.common.constant.AustinConstant; +import com.java3y.austin.common.constant.CommonConstant; import com.java3y.austin.common.constant.SendAccountConstant; import com.java3y.austin.common.domain.TaskInfo; import com.java3y.austin.common.dto.model.EnterpriseWeChatContentModel; @@ -38,11 +40,6 @@ import java.util.Map; @Slf4j public class EnterpriseWeChatHandler extends BaseHandler implements Handler { - /** - * 切割userId 的分隔符 - */ - private static final String DELIMITER = "|"; - @Autowired private AccountUtils accountUtils; @@ -53,7 +50,7 @@ public class EnterpriseWeChatHandler extends BaseHandler implements Handler { @Override public boolean handler(TaskInfo taskInfo) { try { - WxCpDefaultConfigImpl accountConfig = accountUtils.getAccount(taskInfo.getSendAccount(), SendAccountConstant.ENTERPRISE_WECHAT_ACCOUNT_KEY, SendAccountConstant.ENTERPRISE_WECHAT_PREFIX, WxCpDefaultConfigImpl.class); + WxCpDefaultConfigImpl accountConfig = accountUtils.getAccountById(taskInfo.getSendAccount(), WxCpDefaultConfigImpl.class); WxCpMessageServiceImpl messageService = new WxCpMessageServiceImpl(initService(accountConfig)); WxCpMessageSendResult result = messageService.send(buildWxCpMessage(taskInfo, accountConfig.getAgentId())); if (Integer.valueOf(WxMpErrorMsgEnum.CODE_0.getCode()).equals(result.getErrCode())) { @@ -93,7 +90,7 @@ public class EnterpriseWeChatHandler extends BaseHandler implements Handler { if (AustinConstant.SEND_ALL.equals(CollUtil.getFirst(taskInfo.getReceiver()))) { userId = CollUtil.getFirst(taskInfo.getReceiver()); } else { - userId = StringUtils.join(taskInfo.getReceiver(), DELIMITER); + userId = StringUtils.join(taskInfo.getReceiver(), CommonConstant.RADICAL); } EnterpriseWeChatContentModel contentModel = (EnterpriseWeChatContentModel) taskInfo.getContentModel(); diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/EnterpriseWeChatRobotHandler.java b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/EnterpriseWeChatRobotHandler.java index f2ddae4..e66c3eb 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/EnterpriseWeChatRobotHandler.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/EnterpriseWeChatRobotHandler.java @@ -51,7 +51,7 @@ public class EnterpriseWeChatRobotHandler extends BaseHandler implements Handler @Override public boolean handler(TaskInfo taskInfo) { try { - EnterpriseWeChatRobotAccount account = accountUtils.getAccount(taskInfo.getSendAccount(), SendAccountConstant.ENTERPRISE_WECHAT_ROBOT_ACCOUNT_KEY, SendAccountConstant.ENTERPRISE_WECHAT_ROBOT_PREFIX, EnterpriseWeChatRobotAccount.class); + EnterpriseWeChatRobotAccount account = accountUtils.getAccountById(taskInfo.getSendAccount(), EnterpriseWeChatRobotAccount.class); EnterpriseWeChatRobotParam enterpriseWeChatRobotParam = assembleParam(taskInfo); String result = HttpRequest.post(account.getWebhook()).header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue()) .body(JSON.toJSONString(enterpriseWeChatRobotParam)) diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/FeiShuRobotHandler.java b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/FeiShuRobotHandler.java index dc80ae4..a18ff07 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/FeiShuRobotHandler.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/FeiShuRobotHandler.java @@ -50,7 +50,7 @@ public class FeiShuRobotHandler extends BaseHandler implements Handler { @Override public boolean handler(TaskInfo taskInfo) { try { - FeiShuRobotAccount account = accountUtils.getAccount(taskInfo.getSendAccount(), SendAccountConstant.FEI_SHU_ROBOT_ACCOUNT_KEY, SendAccountConstant.FEI_SHU_ROBOT_PREFIX, FeiShuRobotAccount.class); + FeiShuRobotAccount account = accountUtils.getAccountById(taskInfo.getSendAccount(), FeiShuRobotAccount.class); FeiShuRobotParam feiShuRobotParam = assembleParam(taskInfo); String result = HttpRequest.post(account.getWebhook()) .header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue()) diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/PushHandler.java b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/PushHandler.java index fed2a1d..e131431 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/PushHandler.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/PushHandler.java @@ -58,7 +58,7 @@ public class PushHandler extends BaseHandler implements Handler { public boolean handler(TaskInfo taskInfo) { try { - GeTuiAccount account = accountUtils.getAccount(taskInfo.getSendAccount(), SendAccountConstant.GE_TUI_ACCOUNT_KEY, SendAccountConstant.GE_TUI_ACCOUNT_PREFIX, GeTuiAccount.class); + GeTuiAccount account = accountUtils.getAccountById(taskInfo.getSendAccount(),GeTuiAccount.class); String token = redisTemplate.opsForValue().get(SendAccountConstant.GE_TUI_ACCESS_TOKEN_PREFIX + taskInfo.getSendAccount()); PushParam pushParam = PushParam.builder().token(token).appId(account.getAppId()).taskInfo(taskInfo).build(); diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/SmsHandler.java b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/SmsHandler.java index 4db4632..8b2cb55 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/SmsHandler.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/SmsHandler.java @@ -4,10 +4,9 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; -import com.ctrip.framework.apollo.Config; -import com.ctrip.framework.apollo.spring.annotation.ApolloConfig; import com.google.common.base.Throwables; import com.java3y.austin.common.constant.AustinConstant; +import com.java3y.austin.common.constant.CommonConstant; import com.java3y.austin.common.domain.TaskInfo; import com.java3y.austin.common.dto.model.SmsContentModel; import com.java3y.austin.common.enums.ChannelType; @@ -15,7 +14,7 @@ import com.java3y.austin.handler.domain.sms.MessageTypeSmsConfig; import com.java3y.austin.handler.domain.sms.SmsParam; import com.java3y.austin.handler.handler.BaseHandler; import com.java3y.austin.handler.handler.Handler; -import com.java3y.austin.handler.script.SmsScriptHolder; +import com.java3y.austin.handler.script.SmsScript; import com.java3y.austin.support.dao.SmsRecordDao; import com.java3y.austin.support.domain.MessageTemplate; import com.java3y.austin.support.domain.SmsRecord; @@ -25,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; +import java.util.Map; import java.util.Random; /** @@ -44,11 +44,13 @@ public class SmsHandler extends BaseHandler implements Handler { private SmsRecordDao smsRecordDao; @Autowired - private SmsScriptHolder smsScriptHolder; + private ConfigService config; @Autowired - private ConfigService config; + private Map smsScripts; + private static final String FLOW_KEY = "msgTypeSmsConfig"; + private static final String FLOW_KEY_PREFIX = "message_type_"; @Override public boolean handler(TaskInfo taskInfo) { @@ -56,9 +58,7 @@ public class SmsHandler extends BaseHandler implements Handler { .phones(taskInfo.getReceiver()) .content(getSmsContent(taskInfo)) .messageTemplateId(taskInfo.getMessageTemplateId()) - .sendAccountId(taskInfo.getSendAccount()) .build(); - try { /** * 1、动态配置做流量负载 @@ -66,15 +66,15 @@ public class SmsHandler extends BaseHandler implements Handler { */ MessageTypeSmsConfig[] messageTypeSmsConfigs = loadBalance(getMessageTypeSmsConfig(taskInfo.getMsgType())); for (MessageTypeSmsConfig messageTypeSmsConfig : messageTypeSmsConfigs) { - List recordList = smsScriptHolder.route(messageTypeSmsConfig.getScriptName()).send(smsParam); + smsParam.setScriptName(messageTypeSmsConfig.getScriptName()); + List recordList = smsScripts.get(messageTypeSmsConfig.getScriptName()).send(smsParam); if (CollUtil.isNotEmpty(recordList)) { smsRecordDao.saveAll(recordList); return true; } } } catch (Exception e) { - log.error("SmsHandler#handler fail:{},params:{}", - Throwables.getStackTraceAsString(e), JSON.toJSONString(smsParam)); + log.error("SmsHandler#handler fail:{},params:{}", Throwables.getStackTraceAsString(e), JSON.toJSONString(smsParam)); } return false; } @@ -129,14 +129,10 @@ public class SmsHandler extends BaseHandler implements Handler { * @return */ private List getMessageTypeSmsConfig(Integer msgType) { - - String apolloKey = "msgTypeSmsConfig"; - String messagePrefix = "message_type_"; - - String property = config.getProperty(apolloKey, AustinConstant.APOLLO_DEFAULT_VALUE_JSON_ARRAY); + String property = config.getProperty(FLOW_KEY, CommonConstant.EMPTY_VALUE_JSON_ARRAY); JSONArray jsonArray = JSON.parseArray(property); for (int i = 0; i < jsonArray.size(); i++) { - JSONArray array = jsonArray.getJSONObject(i).getJSONArray(messagePrefix + msgType); + JSONArray array = jsonArray.getJSONObject(i).getJSONArray(FLOW_KEY_PREFIX + msgType); if (CollUtil.isNotEmpty(array)) { List result = JSON.parseArray(JSON.toJSONString(array), MessageTypeSmsConfig.class); return result; @@ -154,7 +150,7 @@ public class SmsHandler extends BaseHandler implements Handler { private String getSmsContent(TaskInfo taskInfo) { SmsContentModel smsContentModel = (SmsContentModel) taskInfo.getContentModel(); if (StrUtil.isNotBlank(smsContentModel.getUrl())) { - return smsContentModel.getContent() + " " + smsContentModel.getUrl(); + return smsContentModel.getContent() + StrUtil.SPACE + smsContentModel.getUrl(); } else { return smsContentModel.getContent(); } diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/DingDingWorkReceipt.java b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/DingDingWorkReceipt.java index 8420a07..50092f1 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/DingDingWorkReceipt.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/DingDingWorkReceipt.java @@ -32,7 +32,7 @@ public class DingDingWorkReceipt { public void pull() { // try { // for (int index = SendAccountConstant.START; true; index = index + SendAccountConstant.STEP) { -// DingDingWorkNoticeAccount account = accountUtils.getAccount(index, SendAccountConstant.DING_DING_WORK_NOTICE_ACCOUNT_KEY, SendAccountConstant.DING_DING_WORK_NOTICE_PREFIX, DingDingWorkNoticeAccount.class); +// DingDingWorkNoticeAccount account = accountUtils.getAccountById(index, SendAccountConstant.DING_DING_WORK_NOTICE_ACCOUNT_KEY, SendAccountConstant.DING_DING_WORK_NOTICE_PREFIX, DingDingWorkNoticeAccount.class); // if (account == null) { // break; // } diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/SmsPullReceipt.java b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/SmsPullReceipt.java index bb7dd2c..94ef07f 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/SmsPullReceipt.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/SmsPullReceipt.java @@ -5,9 +5,9 @@ import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import com.alibaba.fastjson.JSON; import com.google.common.base.Throwables; -import com.java3y.austin.common.constant.AustinConstant; -import com.java3y.austin.common.dto.account.TencentSmsAccount; -import com.java3y.austin.common.dto.account.YunPianSmsAccount; +import com.java3y.austin.common.constant.CommonConstant; +import com.java3y.austin.common.dto.account.sms.TencentSmsAccount; +import com.java3y.austin.common.dto.account.sms.YunPianSmsAccount; import com.java3y.austin.common.enums.ChannelType; import com.java3y.austin.common.enums.SmsStatus; import com.java3y.austin.common.enums.SmsSupplier; @@ -52,7 +52,7 @@ public class SmsPullReceipt { * eg accountList:[{"sms_10":{"url":"sms.tencentcloudapi.com","region":"ap-guangzhou","secretId":"234234","secretKey":"234324324","smsSdkAppId":"2343242","templateId":"234234","signName":"Java3y公众号","supplierId":10,"supplierName":"腾讯云"}},{"sms_20":{"url":"https://sms.yunpian.com/v2/sms/tpl_batch_send.json","apikey":"23423432","tpl_id":"23423432","supplierId":20,"supplierName":"云片"}}] */ public void pull() { - List channelAccountList = channelAccountDao.findAllByIsDeletedEqualsAndSendChannelEquals(AustinConstant.FALSE, ChannelType.SMS.getCode()); + List channelAccountList = channelAccountDao.findAllByIsDeletedEqualsAndSendChannelEquals(CommonConstant.FALSE, ChannelType.SMS.getCode()); for (ChannelAccount channelAccount : channelAccountList) { Integer supplierId = JSON.parseObject(channelAccount.getAccountConfig()).getInteger("supplierId"); if (SmsSupplier.TENCENT.getCode().equals(supplierId)) { diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/TencentSmsScript.java b/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/TencentSmsScript.java index 8df4d12..32cfec0 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/TencentSmsScript.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/TencentSmsScript.java @@ -6,13 +6,10 @@ import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.IdUtil; import com.alibaba.fastjson.JSON; import com.google.common.base.Throwables; -import com.java3y.austin.common.constant.SendAccountConstant; -import com.java3y.austin.common.dto.account.TencentSmsAccount; +import com.java3y.austin.common.dto.account.sms.TencentSmsAccount; import com.java3y.austin.common.enums.SmsStatus; import com.java3y.austin.handler.domain.sms.SmsParam; -import com.java3y.austin.handler.script.BaseSmsScript; import com.java3y.austin.handler.script.SmsScript; -import com.java3y.austin.handler.script.SmsScriptHandler; import com.java3y.austin.support.domain.SmsRecord; import com.java3y.austin.support.utils.AccountUtils; import com.tencentcloudapi.common.Credential; @@ -24,6 +21,7 @@ import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse; import com.tencentcloudapi.sms.v20210111.models.SendStatus; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Date; @@ -38,8 +36,8 @@ import java.util.List; */ @Slf4j -@SmsScriptHandler("TencentSmsScript") -public class TencentSmsScript extends BaseSmsScript implements SmsScript { +@Component("TencentSmsScript") +public class TencentSmsScript implements SmsScript { private static final Integer PHONE_NUM = 11; @@ -49,7 +47,7 @@ public class TencentSmsScript extends BaseSmsScript implements SmsScript { @Override public List send(SmsParam smsParam) { try { - TencentSmsAccount tencentSmsAccount = accountUtils.getAccount(smsParam.getSendAccountId(), SendAccountConstant.SMS_ACCOUNT_KEY, SendAccountConstant.SMS_PREFIX, TencentSmsAccount.class); + TencentSmsAccount tencentSmsAccount = accountUtils.getSmsAccountByScriptName(smsParam.getScriptName(), TencentSmsAccount.class); SmsClient client = init(tencentSmsAccount); SendSmsRequest request = assembleReq(smsParam, tencentSmsAccount); SendSmsResponse response = client.SendSms(request); diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/YunPianSmsScript.java b/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/YunPianSmsScript.java index 19f6f9e..021e789 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/YunPianSmsScript.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/YunPianSmsScript.java @@ -8,19 +8,18 @@ import cn.hutool.http.Header; import cn.hutool.http.HttpRequest; import com.alibaba.fastjson.JSON; import com.google.common.base.Throwables; -import com.java3y.austin.common.constant.SendAccountConstant; -import com.java3y.austin.common.dto.account.YunPianSmsAccount; +import com.java3y.austin.common.constant.CommonConstant; +import com.java3y.austin.common.dto.account.sms.YunPianSmsAccount; import com.java3y.austin.common.enums.SmsStatus; import com.java3y.austin.handler.domain.sms.SmsParam; import com.java3y.austin.handler.domain.sms.YunPianSendResult; -import com.java3y.austin.handler.script.BaseSmsScript; import com.java3y.austin.handler.script.SmsScript; -import com.java3y.austin.handler.script.SmsScriptHandler; import com.java3y.austin.support.domain.SmsRecord; import com.java3y.austin.support.utils.AccountUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import java.util.*; @@ -30,8 +29,8 @@ import java.util.*; * 发送短信接入文档:https://www.yunpian.com/official/document/sms/zh_CN/domestic_list */ @Slf4j -@SmsScriptHandler("YunPianSmsScript") -public class YunPianSmsScript extends BaseSmsScript implements SmsScript { +@Component("YunPianSmsScript") +public class YunPianSmsScript implements SmsScript { @Autowired private AccountUtils accountUtils; @@ -39,7 +38,7 @@ public class YunPianSmsScript extends BaseSmsScript implements SmsScript { public List send(SmsParam smsParam) { try { - YunPianSmsAccount account = accountUtils.getAccount(smsParam.getSendAccountId(), SendAccountConstant.SMS_ACCOUNT_KEY, SendAccountConstant.SMS_PREFIX, YunPianSmsAccount.class); + YunPianSmsAccount account = accountUtils.getSmsAccountByScriptName(smsParam.getScriptName(), YunPianSmsAccount.class); Map params = assembleParam(smsParam, account); String result = HttpRequest.post(account.getUrl()) @@ -91,7 +90,7 @@ public class YunPianSmsScript extends BaseSmsScript implements SmsScript { .msgContent(smsParam.getContent()) .seriesId(datum.getSid()) .chargingNum(Math.toIntExact(datum.getCount())) - .status("0".equals(datum.getCode()) ? SmsStatus.SEND_SUCCESS.getCode() : SmsStatus.SEND_FAIL.getCode()) + .status(CommonConstant.ZERO.equals(datum.getCode()) ? SmsStatus.SEND_SUCCESS.getCode() : SmsStatus.SEND_FAIL.getCode()) .reportContent(datum.getMsg()) .created(Math.toIntExact(DateUtil.currentSeconds())) .updated(Math.toIntExact(DateUtil.currentSeconds())) diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/wechat/impl/MiniProgramAccountServiceImpl.java b/austin-handler/src/main/java/com/java3y/austin/handler/wechat/impl/MiniProgramAccountServiceImpl.java index 6c400bf..a7f83d5 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/wechat/impl/MiniProgramAccountServiceImpl.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/wechat/impl/MiniProgramAccountServiceImpl.java @@ -33,9 +33,7 @@ public class MiniProgramAccountServiceImpl implements MiniProgramAccountService @Override public void send(WeChatMiniProgramParam miniProgramParam) throws Exception { - WeChatMiniProgramAccount miniProgramAccount = accountUtils.getAccount(miniProgramParam.getSendAccount(), - SendAccountConstant.WECHAT_MINI_PROGRAM_ACCOUNT_KEY, - SendAccountConstant.WECHAT_MINI_PROGRAM_PREFIX, + WeChatMiniProgramAccount miniProgramAccount = accountUtils.getAccountById(miniProgramParam.getSendAccount(), WeChatMiniProgramAccount.class); WxMaSubscribeService wxMaSubscribeService = initService(miniProgramAccount); diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/wechat/impl/OfficialAccountServiceImpl.java b/austin-handler/src/main/java/com/java3y/austin/handler/wechat/impl/OfficialAccountServiceImpl.java index 7766713..a4359d2 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/wechat/impl/OfficialAccountServiceImpl.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/wechat/impl/OfficialAccountServiceImpl.java @@ -31,7 +31,7 @@ public class OfficialAccountServiceImpl implements OfficialAccountService { @Override public List send(WeChatOfficialParam officialParam) throws Exception { - WeChatOfficialAccount officialAccount = accountUtils.getAccount(officialParam.getSendAccount(), SendAccountConstant.WECHAT_OFFICIAL_ACCOUNT_KEY, SendAccountConstant.WECHAT_OFFICIAL__PREFIX, WeChatOfficialAccount.class); + WeChatOfficialAccount officialAccount = accountUtils.getAccountById(officialParam.getSendAccount(),WeChatOfficialAccount.class); WxMpService wxMpService = initService(officialAccount); List messages = assembleReq(officialParam, officialAccount); List messageIds = new ArrayList<>(messages.size()); diff --git a/austin-service-api-impl/src/main/java/com/java3y/austin/service/api/impl/action/AssembleAction.java b/austin-service-api-impl/src/main/java/com/java3y/austin/service/api/impl/action/AssembleAction.java index 9e0692c..c70d95c 100644 --- a/austin-service-api-impl/src/main/java/com/java3y/austin/service/api/impl/action/AssembleAction.java +++ b/austin-service-api-impl/src/main/java/com/java3y/austin/service/api/impl/action/AssembleAction.java @@ -8,6 +8,7 @@ import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import com.google.common.base.Throwables; import com.java3y.austin.common.constant.AustinConstant; +import com.java3y.austin.common.constant.CommonConstant; import com.java3y.austin.common.domain.TaskInfo; import com.java3y.austin.common.dto.model.ContentModel; import com.java3y.austin.common.enums.ChannelType; @@ -38,6 +39,8 @@ import java.util.*; @Service public class AssembleAction implements BusinessProcess { + private static final String LINK_NAME = "url"; + @Autowired private MessageTemplateDao messageTemplateDao; @@ -48,7 +51,7 @@ public class AssembleAction implements BusinessProcess { try { Optional messageTemplate = messageTemplateDao.findById(messageTemplateId); - if (!messageTemplate.isPresent() || messageTemplate.get().getIsDeleted().equals(AustinConstant.TRUE)) { + if (!messageTemplate.isPresent() || messageTemplate.get().getIsDeleted().equals(CommonConstant.TRUE)) { context.setNeedBreak(true).setResponse(BasicResultVO.fail(RespStatusEnum.TEMPLATE_NOT_FOUND)); return; } @@ -126,10 +129,10 @@ public class AssembleAction implements BusinessProcess { } // 如果 url 字段存在,则在url拼接对应的埋点参数 - String url = (String) ReflectUtil.getFieldValue(contentModel, "url"); + String url = (String) ReflectUtil.getFieldValue(contentModel, LINK_NAME); if (StrUtil.isNotBlank(url)) { String resultUrl = TaskInfoUtils.generateUrl(url, messageTemplate.getId(), messageTemplate.getTemplateType()); - ReflectUtil.setFieldValue(contentModel, "url", resultUrl); + ReflectUtil.setFieldValue(contentModel, LINK_NAME, resultUrl); } return contentModel; } diff --git a/austin-service-api-impl/src/main/java/com/java3y/austin/service/api/impl/action/PreParamCheckAction.java b/austin-service-api-impl/src/main/java/com/java3y/austin/service/api/impl/action/PreParamCheckAction.java index 4e48e07..6255859 100644 --- a/austin-service-api-impl/src/main/java/com/java3y/austin/service/api/impl/action/PreParamCheckAction.java +++ b/austin-service-api-impl/src/main/java/com/java3y/austin/service/api/impl/action/PreParamCheckAction.java @@ -2,6 +2,7 @@ package com.java3y.austin.service.api.impl.action; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; +import com.java3y.austin.common.constant.AustinConstant; import com.java3y.austin.common.enums.RespStatusEnum; import com.java3y.austin.common.vo.BasicResultVO; import com.java3y.austin.service.api.domain.MessageParam; @@ -24,11 +25,6 @@ import java.util.stream.Collectors; @Service public class PreParamCheckAction implements BusinessProcess { - /** - * 最大的人数 - */ - private static final Integer BATCH_RECEIVER_SIZE = 100; - @Override public void process(ProcessContext context) { SendTaskModel sendTaskModel = context.getProcessModel(); @@ -50,13 +46,14 @@ public class PreParamCheckAction implements BusinessProcess { context.setNeedBreak(true).setResponse(BasicResultVO.fail(RespStatusEnum.CLIENT_BAD_PARAMETERS)); return; } - sendTaskModel.setMessageParamList(resultMessageParamList); // 3.过滤receiver大于100的请求 - if (messageParamList.stream().anyMatch(messageParam -> messageParam.getReceiver().split(StrUtil.COMMA).length > BATCH_RECEIVER_SIZE)) { + if (resultMessageParamList.stream().anyMatch(messageParam -> messageParam.getReceiver().split(StrUtil.COMMA).length > AustinConstant.BATCH_RECEIVER_SIZE)) { context.setNeedBreak(true).setResponse(BasicResultVO.fail(RespStatusEnum.TOO_MANY_RECEIVER)); return; } + sendTaskModel.setMessageParamList(resultMessageParamList); + } } diff --git a/austin-support/src/main/java/com/java3y/austin/support/dao/ChannelAccountDao.java b/austin-support/src/main/java/com/java3y/austin/support/dao/ChannelAccountDao.java index d149aea..42b3bb6 100644 --- a/austin-support/src/main/java/com/java3y/austin/support/dao/ChannelAccountDao.java +++ b/austin-support/src/main/java/com/java3y/austin/support/dao/ChannelAccountDao.java @@ -35,4 +35,5 @@ public interface ChannelAccountDao extends JpaRepository { * @return */ Long countByIsDeletedEquals(Integer deleted); + } diff --git a/austin-support/src/main/java/com/java3y/austin/support/dao/MessageTemplateDao.java b/austin-support/src/main/java/com/java3y/austin/support/dao/MessageTemplateDao.java index a856f68..cf1bb62 100644 --- a/austin-support/src/main/java/com/java3y/austin/support/dao/MessageTemplateDao.java +++ b/austin-support/src/main/java/com/java3y/austin/support/dao/MessageTemplateDao.java @@ -20,7 +20,7 @@ public interface MessageTemplateDao extends JpaRepository * @param pageable 分页对象 * @return */ - List findAllByIsDeletedEquals(Integer deleted, Pageable pageable); + List findAllByIsDeletedEqualsOrderByUpdatedDesc(Integer deleted, Pageable pageable); /** diff --git a/austin-support/src/main/java/com/java3y/austin/support/pending/AbstractLazyPending.java b/austin-support/src/main/java/com/java3y/austin/support/pending/AbstractLazyPending.java index 6e5f313..e3d6075 100644 --- a/austin-support/src/main/java/com/java3y/austin/support/pending/AbstractLazyPending.java +++ b/austin-support/src/main/java/com/java3y/austin/support/pending/AbstractLazyPending.java @@ -40,7 +40,7 @@ public abstract class AbstractLazyPending { /** * 是否终止线程 */ - private Boolean stop = false; + private volatile Boolean stop = false; /** * 单线程消费 阻塞队列的数据 diff --git a/austin-support/src/main/java/com/java3y/austin/support/utils/AccountUtils.java b/austin-support/src/main/java/com/java3y/austin/support/utils/AccountUtils.java index 37ce656..afd5d75 100644 --- a/austin-support/src/main/java/com/java3y/austin/support/utils/AccountUtils.java +++ b/austin-support/src/main/java/com/java3y/austin/support/utils/AccountUtils.java @@ -5,6 +5,9 @@ import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.google.common.base.Throwables; import com.java3y.austin.common.constant.AustinConstant; +import com.java3y.austin.common.constant.CommonConstant; +import com.java3y.austin.common.dto.account.sms.SmsAccount; +import com.java3y.austin.common.enums.ChannelType; import com.java3y.austin.support.dao.ChannelAccountDao; import com.java3y.austin.support.domain.ChannelAccount; import com.java3y.austin.support.service.ConfigService; @@ -12,6 +15,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.util.List; import java.util.Optional; /** @@ -31,6 +35,7 @@ public class AccountUtils { /** + * local.properties配置格式 * (key:smsAccount)短信参数示例:[{"sms_10":{"url":"sms.tencentcloudapi.com","region":"ap-guangzhou","secretId":"AKIDhDxxxxxxxx1WljQq","secretKey":"B4hwww39yxxxrrrrgxyi","smsSdkAppId":"1423123125","templateId":"1182097","signName":"Java3y公众号","supplierId":10,"supplierName":"腾讯云"}},{"sms_20":{"url":"https://sms.yunpian.com/v2/sms/tpl_batch_send.json","apikey":"caffff8234234231b5cd7","tpl_id":"523333332","supplierId":20,"supplierName":"云片"}}] * (key:emailAccount)邮件参数示例:[{"email_10":{"host":"smtp.qq.com","port":465,"user":"23423423@qq.com","pass":"23423432432423423","from":"234@qq.com","starttlsEnable":true,"auth":true,"sslEnable":true}},{"email_20":{"host":"smtp.163.com","port":465,"user":"22222@163.com","pass":"23432423","from":"234324324234@163.com","starttlsEnable":false,"auth":true,"sslEnable":true}}] * (key:enterpriseWechatAccount)企业微信参数示例:[{"enterprise_wechat_10":{"corpId":"wwf87603333e00069c","corpSecret":"-IFWxS2222QxzPIorNV11144D915DM","agentId":10044442,"token":"rXROB3333Kf6i","aesKey":"MKZtoFxHIM44444M7ieag3r9ZPUsl"}}] @@ -38,12 +43,10 @@ public class AccountUtils { * (key:dingDingWorkNoticeAccount) 钉钉工作消息参数示例:[{"ding_ding_work_notice_10":{"appKey":"dingh6yyyyyyycrlbx","appSecret":"tQpvmkR863333yyyyyHP3QHyyyymy9Ao1yoL1oQX5NsdfsWHvWKbTu","agentId":"1523123123183622"}}] * (key:officialAccount) 微信服务号模板消息参数示例:[{"official_10":{"appId":"wxecb4693d2eef1ea7","secret":"624asdfsa1640d769ba20120821","templateId":"JHUk6eE9T5Ts7asdfsadfiKNDQsk-Q","url":"http://weixin.qq.com/download","miniProgramId":"xiaochengxuappid12345","path":"index?foo=bar"}}] * (key:miniProgramAccount) 微信小程序订阅消息参数示例:[{"mini_program_10":{"appId":"wxecb4693d2eef1ea7","appSecret":"6240870f4d91701640d769ba20120821","templateId":"JHUk6eE9T5TasdfCrQsk-Q","grantType":"client_credential","miniProgramState":"trial","page":"index?foo=bar"}}] + * (key:alipayMiniProgramAccount) 支付宝小程序订阅消息参数实例:[{"alipay_mini_program_10":{"privateKey":"MIIEvQIBADANB......","alipayPublicKey":"MIIBIjANBg...","appId":"2014********7148","userTemplateId":"MDI4YzIxMDE2M2I5YTQzYjUxNWE4MjA4NmU1MTIyYmM=","page":"page/component/index"}}] */ - public T getAccount(Integer sendAccountId, String apolloKey, String prefix, Class clazz) { + public T getAccountById(Integer sendAccountId, Class clazz) { - /** - * 优先读数据库的,数据库没有才读配置 - */ try { Optional optionalChannelAccount = channelAccountDao.findById(Long.valueOf(sendAccountId)); if (optionalChannelAccount.isPresent()) { @@ -51,19 +54,39 @@ public class AccountUtils { return JSON.parseObject(channelAccount.getAccountConfig(), clazz); } } catch (Exception e) { - log.warn("AccountUtils#getAccount not found:{}", Throwables.getStackTraceAsString(e)); + log.error("AccountUtils#getAccount fail!", Throwables.getStackTraceAsString(e)); } + log.error("AccountUtils#getAccount not found!:{}", sendAccountId); + return null; + } - String accountValues = config.getProperty(apolloKey, AustinConstant.APOLLO_DEFAULT_VALUE_JSON_ARRAY); - JSONArray jsonArray = JSON.parseArray(accountValues); - for (int i = 0; i < jsonArray.size(); i++) { - JSONObject jsonObject = jsonArray.getJSONObject(i); - T object = jsonObject.getObject(prefix + sendAccountId, clazz); - if (object != null) { - return object; + /** + * 通过脚本名 匹配到对应的短信账号 + * @param scriptName 脚本名 + * @param clazz + * @param + * @return + */ + public T getSmsAccountByScriptName(String scriptName, Class clazz) { + try { + List channelAccountList = channelAccountDao.findAllByIsDeletedEqualsAndSendChannelEquals(CommonConstant.FALSE, ChannelType.SMS.getCode()); + for (ChannelAccount channelAccount : channelAccountList) { + try { + SmsAccount smsAccount = JSON.parseObject(channelAccount.getAccountConfig(), SmsAccount.class); + if (smsAccount.getScriptName().equals(scriptName)) { + return JSON.parseObject(channelAccount.getAccountConfig(), clazz); + } + } catch (Exception e) { + log.error("AccountUtils#getSmsAccount parse fail! e:{},account:{}", Throwables.getStackTraceAsString(e), JSON.toJSONString(channelAccount)); + } } + } catch (Exception e) { + log.error("AccountUtils#getSmsAccount fail! e:{}", Throwables.getStackTraceAsString(e)); } + + log.error("AccountUtils#getSmsAccount not found!:{}", scriptName); return null; } + } diff --git a/austin-support/src/main/java/com/java3y/austin/support/utils/NacosUtils.java b/austin-support/src/main/java/com/java3y/austin/support/utils/NacosUtils.java index dc6a54a..10f7707 100644 --- a/austin-support/src/main/java/com/java3y/austin/support/utils/NacosUtils.java +++ b/austin-support/src/main/java/com/java3y/austin/support/utils/NacosUtils.java @@ -24,6 +24,10 @@ import java.util.Properties; public class NacosUtils { @Value("${austin.nacos.server}") private String nacosServer; + @Value("${austin.nacos.username}") + private String nacosUsername; + @Value("${austin.nacos.password}") + private String nacosPassword; @Value("${austin.nacos.group}") private String nacosGroup; @Value("${austin.nacos.dataId}") @@ -51,6 +55,8 @@ public class NacosUtils { try { request.put(PropertyKeyConst.SERVER_ADDR, nacosServer); request.put(PropertyKeyConst.NAMESPACE, nacosNamespace); + request.put(PropertyKeyConst.USERNAME,nacosUsername); + request.put(PropertyKeyConst.PASSWORD,nacosPassword); context = NacosFactory.createConfigService(request) .getConfig(nacosDataId, nacosGroup, 5000); } catch (NacosException e) { diff --git a/austin-support/src/main/java/com/java3y/austin/support/utils/RedisUtils.java b/austin-support/src/main/java/com/java3y/austin/support/utils/RedisUtils.java index 38d23b4..f8977de 100644 --- a/austin-support/src/main/java/com/java3y/austin/support/utils/RedisUtils.java +++ b/austin-support/src/main/java/com/java3y/austin/support/utils/RedisUtils.java @@ -3,6 +3,7 @@ package com.java3y.austin.support.utils; import cn.hutool.core.collection.CollUtil; import com.google.common.base.Throwables; import com.java3y.austin.common.constant.AustinConstant; +import com.java3y.austin.common.constant.CommonConstant; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisCallback; @@ -170,7 +171,7 @@ public class RedisUtils { try { Long execute = redisTemplate.execute(redisScript, keys, args); - return AustinConstant.TRUE.equals(execute.intValue()); + return CommonConstant.TRUE.equals(execute.intValue()); } catch (Exception e) { log.error("redis execLimitLua fail! e:{}", Throwables.getStackTraceAsString(e)); diff --git a/austin-support/src/main/java/com/java3y/austin/support/utils/TaskInfoUtils.java b/austin-support/src/main/java/com/java3y/austin/support/utils/TaskInfoUtils.java index d3d9272..87936f9 100644 --- a/austin-support/src/main/java/com/java3y/austin/support/utils/TaskInfoUtils.java +++ b/austin-support/src/main/java/com/java3y/austin/support/utils/TaskInfoUtils.java @@ -2,6 +2,8 @@ package com.java3y.austin.support.utils; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.java3y.austin.common.constant.CommonConstant; import java.util.Date; @@ -13,7 +15,7 @@ import java.util.Date; public class TaskInfoUtils { private static final int TYPE_FLAG = 1000000; - private static final char PARAM = '?'; + private static final String CODE = "track_code_bid"; /** * 生成BusinessId @@ -45,10 +47,10 @@ public class TaskInfoUtils { public static String generateUrl(String url, Long templateId, Integer templateType) { url = url.trim(); Long businessId = generateBusinessId(templateId, templateType); - if (url.indexOf(PARAM) == -1) { - return url + "?track_code_bid=" + businessId; + if (url.indexOf(CommonConstant.QM) == -1) { + return url + CommonConstant.QM_STRING + CODE + CommonConstant.EQUAL_STRING + businessId; } else { - return url + "&track_code_bid=" + businessId; + return url + CommonConstant.AND_STRING + CODE + CommonConstant.EQUAL_STRING + businessId; } } diff --git a/austin-web/src/main/java/com/java3y/austin/web/config/SwaggerConfiguration.java b/austin-web/src/main/java/com/java3y/austin/web/config/SwaggerConfiguration.java index 10fa89f..fb0a879 100644 --- a/austin-web/src/main/java/com/java3y/austin/web/config/SwaggerConfiguration.java +++ b/austin-web/src/main/java/com/java3y/austin/web/config/SwaggerConfiguration.java @@ -23,6 +23,8 @@ public class SwaggerConfiguration { /** * 对C端用户的接口文档 * + * 地址:http://localhost:8080/swagger-ui/index.html + * * @return */ @Bean diff --git a/austin-web/src/main/java/com/java3y/austin/web/controller/ChannelAccountController.java b/austin-web/src/main/java/com/java3y/austin/web/controller/ChannelAccountController.java index 308beb6..f1f5e0e 100644 --- a/austin-web/src/main/java/com/java3y/austin/web/controller/ChannelAccountController.java +++ b/austin-web/src/main/java/com/java3y/austin/web/controller/ChannelAccountController.java @@ -2,6 +2,7 @@ package com.java3y.austin.web.controller; import cn.hutool.core.util.StrUtil; +import com.java3y.austin.common.constant.AustinConstant; import com.java3y.austin.common.vo.BasicResultVO; import com.java3y.austin.support.domain.ChannelAccount; import com.java3y.austin.web.service.ChannelAccountService; @@ -11,8 +12,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; -import java.util.Arrays; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; /** @@ -24,7 +24,7 @@ import java.util.stream.Collectors; @RestController @RequestMapping("/account") @Api("渠道账号管理接口") -@CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true", allowedHeaders = "*") +@CrossOrigin(origins = AustinConstant.ORIGIN_VALUE, allowCredentials = "true", allowedHeaders = "*") public class ChannelAccountController { @Autowired @@ -47,7 +47,16 @@ public class ChannelAccountController { @GetMapping("/queryByChannelType") @ApiOperation("/根据渠道标识查询相关的记录") public BasicResultVO query(Integer channelType) { - return BasicResultVO.success(channelAccountService.queryByChannelType(channelType)); + List channelAccounts = channelAccountService.queryByChannelType(channelType); + + List> result = new ArrayList<>(); + for (ChannelAccount channelAccount : channelAccounts) { + HashMap optionKV = new HashMap<>(); + optionKV.put("label", channelAccount.getName()); + optionKV.put("value", String.valueOf(channelAccount.getId())); + result.add(optionKV); + } + return BasicResultVO.success(result); } /** diff --git a/austin-web/src/main/java/com/java3y/austin/web/controller/DataController.java b/austin-web/src/main/java/com/java3y/austin/web/controller/DataController.java index 6477ceb..c886021 100644 --- a/austin-web/src/main/java/com/java3y/austin/web/controller/DataController.java +++ b/austin-web/src/main/java/com/java3y/austin/web/controller/DataController.java @@ -1,6 +1,7 @@ package com.java3y.austin.web.controller; import cn.hutool.core.util.StrUtil; +import com.java3y.austin.common.constant.AustinConstant; import com.java3y.austin.common.enums.RespStatusEnum; import com.java3y.austin.common.vo.BasicResultVO; import com.java3y.austin.web.service.DataService; @@ -23,7 +24,7 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/trace") @Api("获取数据接口(全链路追踪)") -@CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true", allowedHeaders = "*") +@CrossOrigin(origins = AustinConstant.ORIGIN_VALUE, allowCredentials = "true", allowedHeaders = "*") public class DataController { @Autowired private DataService dataService; diff --git a/austin-web/src/main/java/com/java3y/austin/web/controller/MaterialController.java b/austin-web/src/main/java/com/java3y/austin/web/controller/MaterialController.java index 5ef2cc4..c48da71 100644 --- a/austin-web/src/main/java/com/java3y/austin/web/controller/MaterialController.java +++ b/austin-web/src/main/java/com/java3y/austin/web/controller/MaterialController.java @@ -7,6 +7,7 @@ import com.dingtalk.api.DefaultDingTalkClient; import com.dingtalk.api.DingTalkClient; import com.dingtalk.api.request.OapiMediaUploadRequest; import com.dingtalk.api.response.OapiMediaUploadResponse; +import com.java3y.austin.common.constant.AustinConstant; import com.java3y.austin.common.constant.SendAccountConstant; import com.java3y.austin.common.enums.ChannelType; import com.java3y.austin.common.enums.FileType; @@ -26,15 +27,16 @@ import org.springframework.web.multipart.MultipartFile; /** * 素材管理接口 + * * @author 3y */ @Slf4j @RestController @RequestMapping("/material") @Api("素材管理接口") -@CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true", allowedHeaders = "*") -public class MaterialController { +@CrossOrigin(origins = AustinConstant.ORIGIN_VALUE, allowCredentials = "true", allowedHeaders = "*") +public class MaterialController { @Autowired private MaterialService materialService; diff --git a/austin-web/src/main/java/com/java3y/austin/web/controller/MessageTemplateController.java b/austin-web/src/main/java/com/java3y/austin/web/controller/MessageTemplateController.java index f115be1..f16b51d 100644 --- a/austin-web/src/main/java/com/java3y/austin/web/controller/MessageTemplateController.java +++ b/austin-web/src/main/java/com/java3y/austin/web/controller/MessageTemplateController.java @@ -5,6 +5,7 @@ import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.google.common.base.Throwables; +import com.java3y.austin.common.constant.AustinConstant; import com.java3y.austin.common.enums.RespStatusEnum; import com.java3y.austin.common.vo.BasicResultVO; import com.java3y.austin.service.api.domain.MessageParam; @@ -23,6 +24,7 @@ import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -42,7 +44,7 @@ import java.util.stream.Collectors; @RestController @RequestMapping("/messageTemplate") @Api("发送消息") -@CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true", allowedHeaders = "*") +@CrossOrigin(origins = AustinConstant.ORIGIN_VALUE, allowCredentials = "true", allowedHeaders = "*") public class MessageTemplateController { @Autowired @@ -73,7 +75,7 @@ public class MessageTemplateController { */ @GetMapping("/list") @ApiOperation("/列表页") - public BasicResultVO queryList(MessageTemplateParam messageTemplateParam) { + public BasicResultVO queryList(@Validated MessageTemplateParam messageTemplateParam) { List> result = ConvertMap.flatList(messageTemplateService.queryList(messageTemplateParam)); long count = messageTemplateService.count(); diff --git a/austin-web/src/main/java/com/java3y/austin/web/service/impl/ChannelAccountServiceImpl.java b/austin-web/src/main/java/com/java3y/austin/web/service/impl/ChannelAccountServiceImpl.java index 94b01e7..70c9b85 100644 --- a/austin-web/src/main/java/com/java3y/austin/web/service/impl/ChannelAccountServiceImpl.java +++ b/austin-web/src/main/java/com/java3y/austin/web/service/impl/ChannelAccountServiceImpl.java @@ -2,6 +2,7 @@ package com.java3y.austin.web.service.impl; import cn.hutool.core.date.DateUtil; import com.java3y.austin.common.constant.AustinConstant; +import com.java3y.austin.common.constant.CommonConstant; import com.java3y.austin.support.dao.ChannelAccountDao; import com.java3y.austin.support.domain.ChannelAccount; import com.java3y.austin.web.service.ChannelAccountService; @@ -22,7 +23,7 @@ public class ChannelAccountServiceImpl implements ChannelAccountService { public ChannelAccount save(ChannelAccount channelAccount) { if (channelAccount.getId() == null) { channelAccount.setCreated(Math.toIntExact(DateUtil.currentSeconds())); - channelAccount.setIsDeleted(AustinConstant.FALSE); + channelAccount.setIsDeleted(CommonConstant.FALSE); } channelAccount.setUpdated(Math.toIntExact(DateUtil.currentSeconds())); return channelAccountDao.save(channelAccount); @@ -30,7 +31,7 @@ public class ChannelAccountServiceImpl implements ChannelAccountService { @Override public List queryByChannelType(Integer channelType) { - return channelAccountDao.findAllByIsDeletedEqualsAndSendChannelEquals(AustinConstant.FALSE, channelType); + return channelAccountDao.findAllByIsDeletedEqualsAndSendChannelEquals(CommonConstant.FALSE, channelType); } @Override diff --git a/austin-web/src/main/java/com/java3y/austin/web/service/impl/MessageTemplateServiceImpl.java b/austin-web/src/main/java/com/java3y/austin/web/service/impl/MessageTemplateServiceImpl.java index 11f9ca6..d457982 100644 --- a/austin-web/src/main/java/com/java3y/austin/web/service/impl/MessageTemplateServiceImpl.java +++ b/austin-web/src/main/java/com/java3y/austin/web/service/impl/MessageTemplateServiceImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.java3y.austin.common.constant.AustinConstant; +import com.java3y.austin.common.constant.CommonConstant; import com.java3y.austin.common.enums.AuditStatus; import com.java3y.austin.common.enums.MessageStatus; import com.java3y.austin.common.enums.RespStatusEnum; @@ -44,12 +45,12 @@ public class MessageTemplateServiceImpl implements MessageTemplateService { @Override public List queryList(MessageTemplateParam param) { PageRequest pageRequest = PageRequest.of(param.getPage() - 1, param.getPerPage()); - return messageTemplateDao.findAllByIsDeletedEquals(AustinConstant.FALSE, pageRequest); + return messageTemplateDao.findAllByIsDeletedEqualsOrderByUpdatedDesc(CommonConstant.FALSE, pageRequest); } @Override public Long count() { - return messageTemplateDao.countByIsDeletedEquals(AustinConstant.FALSE); + return messageTemplateDao.countByIsDeletedEquals(CommonConstant.FALSE); } @Override @@ -68,7 +69,7 @@ public class MessageTemplateServiceImpl implements MessageTemplateService { @Override public void deleteByIds(List ids) { Iterable messageTemplates = messageTemplateDao.findAllById(ids); - messageTemplates.forEach(messageTemplate -> messageTemplate.setIsDeleted(AustinConstant.TRUE)); + messageTemplates.forEach(messageTemplate -> messageTemplate.setIsDeleted(CommonConstant.TRUE)); for (MessageTemplate messageTemplate : messageTemplates) { if (messageTemplate.getCronTaskId()!=null && messageTemplate.getCronTaskId() > 0) { cronTaskService.deleteCronTask(messageTemplate.getCronTaskId()); @@ -137,7 +138,7 @@ public class MessageTemplateServiceImpl implements MessageTemplateService { .setMsgStatus(MessageStatus.INIT.getCode()).setAuditStatus(AuditStatus.WAIT_AUDIT.getCode()) .setCreator("Java3y").setUpdator("Java3y").setTeam("公众号Java3y").setAuditor("3y") .setCreated(Math.toIntExact(DateUtil.currentSeconds())) - .setIsDeleted(AustinConstant.FALSE); + .setIsDeleted(CommonConstant.FALSE); } diff --git a/austin-web/src/main/java/com/java3y/austin/web/vo/MessageTemplateParam.java b/austin-web/src/main/java/com/java3y/austin/web/vo/MessageTemplateParam.java index 6b53215..13bba44 100644 --- a/austin-web/src/main/java/com/java3y/austin/web/vo/MessageTemplateParam.java +++ b/austin-web/src/main/java/com/java3y/austin/web/vo/MessageTemplateParam.java @@ -5,6 +5,9 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import javax.validation.constraints.NotNull; + + /** * 消息模板管理 请求参数 * @@ -20,11 +23,13 @@ public class MessageTemplateParam { /** * 当前页码 */ + @NotNull private Integer page; /** * 当前页大小 */ + @NotNull private Integer perPage; /** diff --git a/austin-web/src/main/resources/application.properties b/austin-web/src/main/resources/application.properties index 2b31cf3..81c188d 100644 --- a/austin-web/src/main/resources/application.properties +++ b/austin-web/src/main/resources/application.properties @@ -122,6 +122,8 @@ apollo.bootstrap.namespaces=boss.austin,dynamic-tp-apollo-dtp.yml ########################################## nacos start ########################################## austin.nacos.server= +austin.nacos.username= +austin.nacos.password= austin.nacos.dataId=austin austin.nacos.group=DEFAULT_GROUP austin.nacos.namespace=9537c674-f3a6-4203-b286-ef0c36bfacb2 diff --git a/austin-web/src/main/resources/local.properties b/austin-web/src/main/resources/local.properties index 807c7f2..b2bafc8 100644 --- a/austin-web/src/main/resources/local.properties +++ b/austin-web/src/main/resources/local.properties @@ -1,11 +1,4 @@ discardMsgIds = [] deduplicationRule = {"deduplication_10":{"num":1,"time":300},"deduplication_20":{"num":5}} -emailAccount = [{"email_10":{"host":"smtp.qq.com","port":465,"user":"23423432@qq.com","pass":"234324324","from":"123123@qq.com","starttlsEnable":"true","auth":true,"sslEnable":true}},{"email_20":{"host":"smtp.163.com","port":465,"user":"23423423@163.com","pass":"234234324","from":"112312312@163.com","starttlsEnable":"false","auth":true,"sslEnable":true}}] -smsAccount = [{"sms_10":{"url":"sms.tencentcloudapi.com","region":"ap-guangzhou","secretId":"234234","secretKey":"234324324","smsSdkAppId":"2343242","templateId":"234234","signName":"Java3y公众号","supplierId":10,"supplierName":"腾讯云"}},{"sms_20":{"url":"https://sms.yunpian.com/v2/sms/tpl_batch_send.json","apikey":"23423432","tpl_id":"23423432","supplierId":20,"supplierName":"云片"}}] -enterpriseWechatAccount = [{"enterprise_wechat_10":{"corpId":"23423423","corpSecret":"-234324234","agentId":1000002,"token":"234234","aesKey":"23423423"}}] -dingDingRobotAccount = [{"ding_ding_robot_10":{"secret":"234324324324","webhook":"https://oapi.dingtalk.com/robot/send?access_token=8d03b68d081f732343243242343247328b0c3003d164715d2c6c6e56"}}] -dingDingWorkNoticeAccount =[{"ding_ding_work_notice_10":{"appKey":"23423423","appSecret":"tQpvmkR863dYcuKDVfM23432432432423Nlx_fYLLLlpPJWHvWKbTu","agentId":"1523423423242"}}] -flowControlRule = {"flow_control_40":1} -geTuiAccount = [{"ge_tui_account_10":{"appId":"23423423","appKey":"234234234","masterSecret":"2342342342342"}}] -feiShuAccountRobot = [{"fei_shu_robot_10":{"webhook":"https://open.feishu.cn/open-apis/bot/v2/hook/c24b09ef-9aa3-4aad-9239-a7e05fcaf6ec"}}] msgTypeSmsConfig = [{"message_type_10":[{"weights":99,"scriptName":"TencentSmsScript"},{"weights":1,"scriptName":"YunPianSmsScript"}]},{"message_type_20":[{"weights":99,"scriptName":"TencentSmsScript"},{"weights":1,"scriptName":"YunPianSmsScript"}]},{"message_type_30":[{"weights":20,"scriptName":"TencentSmsScript"}]},{"message_type_40":[{"weights":20,"scriptName":"TencentSmsScript"}]}] +flowControl = {"flow_control_40":1} \ No newline at end of file diff --git a/docker/rabbitmq/docker-compose-rabbitmq-3.7.8-management.yml b/docker/rabbitmq/docker-compose-rabbitmq-3.7.8-management.yml new file mode 100644 index 0000000..7b2d98d --- /dev/null +++ b/docker/rabbitmq/docker-compose-rabbitmq-3.7.8-management.yml @@ -0,0 +1,18 @@ +version: '3' +services: + rabbitmq: + image: registry.cn-hangzhou.aliyuncs.com/zhengqing/rabbitmq:3.7.8-management # 原镜像`rabbitmq:3.7.8-management` 【 注:该版本包含了web控制页面 】 + container_name: rabbitmq # 容器名为'rabbitmq' + hostname: my-rabbit + restart: unless-stopped # 指定容器退出后的重启策略为始终重启,但是不考虑在Docker守护进程启动时就已经停止了的容器 + environment: # 设置环境变量,相当于docker run命令中的-e + TZ: Asia/Shanghai + LANG: en_US.UTF-8 + RABBITMQ_DEFAULT_VHOST: my_vhost # 主机名 + RABBITMQ_DEFAULT_USER: admin # 登录账号 + RABBITMQ_DEFAULT_PASS: admin # 登录密码 + volumes: # 数据卷挂载路径设置,将本机目录映射到容器目录 + - "./rabbitmq/data:/var/lib/rabbitmq" + ports: # 映射端口 + - "5672:5672" + - "15672:15672" diff --git a/docker/rabbitmq/docker-compose-rabbitmq-cluster.yml b/docker/rabbitmq/docker-compose-rabbitmq-cluster.yml new file mode 100644 index 0000000..165e5f5 --- /dev/null +++ b/docker/rabbitmq/docker-compose-rabbitmq-cluster.yml @@ -0,0 +1,47 @@ +# 环境变量可参考: https://www.rabbitmq.com/configure.html +# https://github.com/rabbitmq/rabbitmq-server/blob/master/deps/rabbit/docs/rabbitmq.conf.example +# https://www.rabbitmq.com/clustering.html#erlang-cookie +version: '3' + +# 网桥 -> 方便相互通讯 +networks: + rabbitmq: + driver: bridge + +services: + rabbitmq-1: + image: registry.cn-hangzhou.aliyuncs.com/zhengqing/rabbitmq:3.9.1-management # 镜像`rabbitmq:3.9.1-management` 【 注:该版本包含了web控制页面 】 + container_name: rabbitmq-1 # 容器名为'rabbitmq-1' + hostname: my-rabbit-1 + restart: unless-stopped # 指定容器退出后的重启策略为始终重启,但是不考虑在Docker守护进程启动时就已经停止了的容器 + environment: # 设置环境变量,相当于docker run命令中的-e + TZ: Asia/Shanghai + LANG: en_US.UTF-8 + volumes: # 数据卷挂载路径设置,将本机目录映射到容器目录 + - "./rabbitmq-cluster/rabbitmq-1/config/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf" + - "./rabbitmq-cluster/rabbitmq-1/data:/var/lib/rabbitmq" + - "./rabbitmq-cluster/plugins/rabbitmq_delayed_message_exchange-3.9.0.ez:/opt/rabbitmq/plugins/rabbitmq_delayed_message_exchange-3.9.0.ez" + - "./rabbitmq-cluster/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie" + ports: # 映射端口 + - "5672:5672" + - "15672:15672" + networks: + - rabbitmq + + rabbitmq-2: + image: registry.cn-hangzhou.aliyuncs.com/zhengqing/rabbitmq:3.9.1-management # 镜像`rabbitmq:3.9.1-management` 【 注:该版本包含了web控制页面 】 + container_name: rabbitmq-2 # 容器名为'rabbitmq-2' + hostname: my-rabbit-2 + restart: unless-stopped # 指定容器退出后的重启策略为始终重启,但是不考虑在Docker守护进程启动时就已经停止了的容器 + environment: # 设置环境变量,相当于docker run命令中的-e + TZ: Asia/Shanghai + LANG: en_US.UTF-8 + volumes: # 数据卷挂载路径设置,将本机目录映射到容器目录 + - "./rabbitmq-cluster/rabbitmq-2/config/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf" + - "./rabbitmq-cluster/rabbitmq-2/data:/var/lib/rabbitmq" + - "./rabbitmq-cluster/plugins/rabbitmq_delayed_message_exchange-3.9.0.ez:/opt/rabbitmq/plugins/rabbitmq_delayed_message_exchange-3.9.0.ez" + - "./rabbitmq-cluster/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie" + ports: # 映射端口 + - "5673:5672" + networks: + - rabbitmq diff --git a/docker/rabbitmq/docker-compose-rabbitmq.yml b/docker/rabbitmq/docker-compose-rabbitmq.yml new file mode 100644 index 0000000..8f8639a --- /dev/null +++ b/docker/rabbitmq/docker-compose-rabbitmq.yml @@ -0,0 +1,21 @@ +# 环境变量可参考: https://www.rabbitmq.com/configure.html +# https://github.com/rabbitmq/rabbitmq-server/blob/master/deps/rabbit/docs/rabbitmq.conf.example +version: '3' +services: + rabbitmq: + image: registry.cn-hangzhou.aliyuncs.com/zhengqing/rabbitmq:3.9.15-management # 镜像`rabbitmq:3.9.1-management` 【 注:该版本包含了web控制页面 】 + container_name: rabbitmq # 容器名为'rabbitmq' + hostname: my-rabbit + restart: unless-stopped # 指定容器退出后的重启策略为始终重启,但是不考虑在Docker守护进程启动时就已经停止了的容器 + environment: # 设置环境变量,相当于docker run命令中的-e + TZ: Asia/Shanghai + LANG: en_US.UTF-8 + volumes: # 数据卷挂载路径设置,将本机目录映射到容器目录 + - "./rabbitmq/config/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf" + - "./rabbitmq/config/10-default-guest-user.conf:/etc/rabbitmq/conf.d/10-default-guest-user.conf" + - "./rabbitmq/data:/var/lib/rabbitmq" + - "./rabbitmq/plugins/rabbitmq_delayed_message_exchange-3.9.0.ez:/opt/rabbitmq/plugins/rabbitmq_delayed_message_exchange-3.9.0.ez" +# - "./rabbitmq/log:/var/log/rabbitmq" + ports: # 映射端口 + - "5672:5672" + - "15672:15672" diff --git a/docker/rabbitmq/rabbitmq-cluster/.erlang.cookie b/docker/rabbitmq/rabbitmq-cluster/.erlang.cookie new file mode 100644 index 0000000..35c330d --- /dev/null +++ b/docker/rabbitmq/rabbitmq-cluster/.erlang.cookie @@ -0,0 +1 @@ +rabbit-cookie diff --git a/docker/rabbitmq/rabbitmq-cluster/init-rabbitmq.sh b/docker/rabbitmq/rabbitmq-cluster/init-rabbitmq.sh new file mode 100644 index 0000000..fa9dcd2 --- /dev/null +++ b/docker/rabbitmq/rabbitmq-cluster/init-rabbitmq.sh @@ -0,0 +1,44 @@ +#!/bin/bash + + +#################################### +# @description 添加RabbitMQ节点到集群 +# 可参考 https://www.rabbitmq.com/clustering.html#creating-ram +# @params $? => 代表上一个命令执行后的退出状态: 0->成功,1->失败 +# @example => sh init-rabbitmq.sh +# @author topsuder +# @date 2022/11/16 14:24 +#################################### + + +# reset first node +echo "Reset first rabbitmq node." +docker exec rabbitmq-1 /bin/bash -c 'rabbitmqctl stop_app' +docker exec rabbitmq-1 /bin/bash -c 'rabbitmqctl reset' +docker exec rabbitmq-1 /bin/bash -c 'rabbitmqctl start_app' + + +# build cluster +echo "Starting to build rabbitmq cluster with two ram nodes." +docker exec rabbitmq-2 /bin/bash -c 'rabbitmqctl stop_app' +docker exec rabbitmq-2 /bin/bash -c 'rabbitmqctl reset' +# 参数“--ram”表示设置为内存节点,忽略此参数默认为磁盘节点 +docker exec rabbitmq-2 /bin/bash -c 'rabbitmqctl join_cluster rabbit@my-rabbit-1' +# docker exec rabbitmq-2 /bin/bash -c 'rabbitmqctl join_cluster --ram rabbit@my-rabbit-1' +docker exec rabbitmq-2 /bin/bash -c 'rabbitmqctl start_app' + + +# check cluster status +#echo "Check cluster status:" +#docker exec rabbitmq-1 /bin/bash -c 'rabbitmqctl cluster_status' +#docker exec rabbitmq-2 /bin/bash -c 'rabbitmqctl cluster_status' + + +#echo "Starting to create user." +#docker exec rabbitmq-1 /bin/bash -c 'rabbitmqctl add_user admin admin@123' + +#echo "Set tags for new user." +#docker exec rabbitmq-1 /bin/bash -c 'rabbitmqctl set_user_tags admin administrator' + +#echo "Grant permissions to new user." +#docker exec rabbitmq-1 /bin/bash -c "rabbitmqctl set_permissions -p '/' admin '.*' '.*' '.*'" diff --git a/docker/rabbitmq/rabbitmq-cluster/plugins/rabbitmq_delayed_message_exchange-3.9.0.ez b/docker/rabbitmq/rabbitmq-cluster/plugins/rabbitmq_delayed_message_exchange-3.9.0.ez new file mode 100644 index 0000000000000000000000000000000000000000..2301a0dec445c9539d98c4013950276ffbe30f89 GIT binary patch literal 36358 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]}, +## +# loopback_users.guest = true + +## Uncomment the following line if you want to allow access to the +## guest user from anywhere on the network. +# loopback_users.guest = false + +## TLS configuration. +## +## Related doc guide: https://rabbitmq.com/ssl.html. +## +# listeners.ssl.1 = 5671 +# +# ssl_options.verify = verify_peer +# ssl_options.fail_if_no_peer_cert = false +# ssl_options.cacertfile = /path/to/cacert.pem +# ssl_options.certfile = /path/to/cert.pem +# ssl_options.keyfile = /path/to/key.pem +# +# ssl_options.honor_cipher_order = true +# ssl_options.honor_ecc_order = true +# +## These are highly recommended for TLSv1.2 but cannot be used +## with TLSv1.3. If TLSv1.3 is enabled, these lines MUST be removed. +# ssl_options.client_renegotiation = false +# ssl_options.secure_renegotiate = true +# +## Limits what TLS versions the server enables for client TLS +## connections. See https://www.rabbitmq.com/ssl.html#tls-versions for details. +## +## Cutting edge TLS version which requires recent client runtime +## versions and has no cipher suite in common with earlier TLS versions. +# ssl_options.versions.1 = tlsv1.3 +## Enables TLSv1.2 for best compatibility +# ssl_options.versions.2 = tlsv1.2 +## Older TLS versions have known vulnerabilities and are being phased out +## from wide use. + +## Limits what cipher suites the server will use for client TLS +## connections. Narrowing this down can prevent some clients +## from connecting. +## If TLSv1.3 is enabled and cipher suites are overridden, TLSv1.3-specific +## cipher suites must also be explicitly enabled. +## See https://www.rabbitmq.com/ssl.html#cipher-suites and https://wiki.openssl.org/index.php/TLS1.3#Ciphersuites +## for details. +# +## The example below uses TLSv1.3 cipher suites only +# +# ssl_options.ciphers.1 = TLS_AES_256_GCM_SHA384 +# ssl_options.ciphers.2 = TLS_AES_128_GCM_SHA256 +# ssl_options.ciphers.3 = TLS_CHACHA20_POLY1305_SHA256 +# ssl_options.ciphers.4 = TLS_AES_128_CCM_SHA256 +# ssl_options.ciphers.5 = TLS_AES_128_CCM_8_SHA256 +# +## The example below uses TLSv1.2 cipher suites only +# +# ssl_options.ciphers.1 = ECDHE-ECDSA-AES256-GCM-SHA384 +# ssl_options.ciphers.2 = ECDHE-RSA-AES256-GCM-SHA384 +# ssl_options.ciphers.3 = ECDHE-ECDSA-AES256-SHA384 +# ssl_options.ciphers.4 = ECDHE-RSA-AES256-SHA384 +# ssl_options.ciphers.5 = ECDH-ECDSA-AES256-GCM-SHA384 +# ssl_options.ciphers.6 = ECDH-RSA-AES256-GCM-SHA384 +# ssl_options.ciphers.7 = ECDH-ECDSA-AES256-SHA384 +# ssl_options.ciphers.8 = ECDH-RSA-AES256-SHA384 +# ssl_options.ciphers.9 = DHE-RSA-AES256-GCM-SHA384 +# ssl_options.ciphers.10 = DHE-DSS-AES256-GCM-SHA384 +# ssl_options.ciphers.11 = DHE-RSA-AES256-SHA256 +# ssl_options.ciphers.12 = DHE-DSS-AES256-SHA256 +# ssl_options.ciphers.13 = ECDHE-ECDSA-AES128-GCM-SHA256 +# ssl_options.ciphers.14 = ECDHE-RSA-AES128-GCM-SHA256 +# ssl_options.ciphers.15 = ECDHE-ECDSA-AES128-SHA256 +# ssl_options.ciphers.16 = ECDHE-RSA-AES128-SHA256 +# ssl_options.ciphers.17 = ECDH-ECDSA-AES128-GCM-SHA256 +# ssl_options.ciphers.18 = ECDH-RSA-AES128-GCM-SHA256 +# ssl_options.ciphers.19 = ECDH-ECDSA-AES128-SHA256 +# ssl_options.ciphers.20 = ECDH-RSA-AES128-SHA256 +# ssl_options.ciphers.21 = DHE-RSA-AES128-GCM-SHA256 +# ssl_options.ciphers.22 = DHE-DSS-AES128-GCM-SHA256 +# ssl_options.ciphers.23 = DHE-RSA-AES128-SHA256 +# ssl_options.ciphers.24 = DHE-DSS-AES128-SHA256 +# ssl_options.ciphers.25 = ECDHE-ECDSA-AES256-SHA +# ssl_options.ciphers.26 = ECDHE-RSA-AES256-SHA +# ssl_options.ciphers.27 = DHE-RSA-AES256-SHA +# ssl_options.ciphers.28 = DHE-DSS-AES256-SHA +# ssl_options.ciphers.29 = ECDH-ECDSA-AES256-SHA +# ssl_options.ciphers.30 = ECDH-RSA-AES256-SHA +# ssl_options.ciphers.31 = ECDHE-ECDSA-AES128-SHA +# ssl_options.ciphers.32 = ECDHE-RSA-AES128-SHA +# ssl_options.ciphers.33 = DHE-RSA-AES128-SHA +# ssl_options.ciphers.34 = DHE-DSS-AES128-SHA +# ssl_options.ciphers.35 = ECDH-ECDSA-AES128-SHA +# ssl_options.ciphers.36 = ECDH-RSA-AES128-SHA + +# ssl_options.bypass_pem_cache = true + +## Select an authentication/authorisation backend to use. +## +## Alternative backends are provided by plugins, such as rabbitmq-auth-backend-ldap. +## +## NB: These settings require certain plugins to be enabled. +## +## Related doc guides: +## +## * https://rabbitmq.com/plugins.html +## * https://rabbitmq.com/access-control.html +## + +# auth_backends.1 = rabbit_auth_backend_internal + +## uses separate backends for authentication and authorisation, +## see below. +# auth_backends.1.authn = rabbit_auth_backend_ldap +# auth_backends.1.authz = rabbit_auth_backend_internal + +## The rabbitmq_auth_backend_ldap plugin allows the broker to +## perform authentication and authorisation by deferring to an +## external LDAP server. +## +## Relevant doc guides: +## +## * https://rabbitmq.com/ldap.html +## * https://rabbitmq.com/access-control.html +## +## uses LDAP for both authentication and authorisation +# auth_backends.1 = rabbit_auth_backend_ldap + +## uses HTTP service for both authentication and +## authorisation +# auth_backends.1 = rabbit_auth_backend_http + +## uses two backends in a chain: HTTP first, then internal +# auth_backends.1 = rabbit_auth_backend_http +# auth_backends.2 = rabbit_auth_backend_internal + +## Authentication +## The built-in mechanisms are 'PLAIN', +## 'AMQPLAIN', and 'EXTERNAL' Additional mechanisms can be added via +## plugins. +## +## Related doc guide: https://rabbitmq.com/authentication.html. +## +# auth_mechanisms.1 = PLAIN +# auth_mechanisms.2 = AMQPLAIN + +## The rabbitmq-auth-mechanism-ssl plugin makes it possible to +## authenticate a user based on the client's x509 (TLS) certificate. +## Related doc guide: https://rabbitmq.com/authentication.html. +## +## To use auth-mechanism-ssl, the EXTERNAL mechanism should +## be enabled: +## +# auth_mechanisms.1 = PLAIN +# auth_mechanisms.2 = AMQPLAIN +# auth_mechanisms.3 = EXTERNAL + +## To force x509 certificate-based authentication on all clients, +## exclude all other mechanisms (note: this will disable password-based +## authentication even for the management UI!): +## +# auth_mechanisms.1 = EXTERNAL + +## This pertains to both the rabbitmq-auth-mechanism-ssl plugin and +## STOMP ssl_cert_login configurations. See the RabbitMQ STOMP plugin +## configuration section later in this file and the README in +## https://github.com/rabbitmq/rabbitmq-auth-mechanism-ssl for further +## details. +## +## To use the TLS cert's CN instead of its DN as the username +## +# ssl_cert_login_from = common_name + +## TLS handshake timeout, in milliseconds. +## +# ssl_handshake_timeout = 5000 + + +## Cluster name +## +# cluster_name = dev3.eng.megacorp.local + +## Password hashing implementation. Will only affect newly +## created users. To recalculate hash for an existing user +## it's necessary to update her password. +## +## To use SHA-512, set to rabbit_password_hashing_sha512. +## +# password_hashing_module = rabbit_password_hashing_sha256 + +## When importing definitions exported from versions earlier +## than 3.6.0, it is possible to go back to MD5 (only do this +## as a temporary measure!) by setting this to rabbit_password_hashing_md5. +## +# password_hashing_module = rabbit_password_hashing_md5 + +## +## Default User / VHost +## ==================== +## + +## On first start RabbitMQ will create a vhost and a user. These +## config items control what gets created. +## Relevant doc guide: https://rabbitmq.com/access-control.html +## +# default_vhost = / +# default_user = guest +# default_pass = guest + +# default_permissions.configure = .* +# default_permissions.read = .* +# default_permissions.write = .* + +## Tags for default user +## +## For more details about tags, see the documentation for the +## Management Plugin at https://rabbitmq.com/management.html. +## +# default_user_tags.administrator = true + +## Define other tags like this: +# default_user_tags.management = true +# default_user_tags.custom_tag = true + +## +## Additional network and protocol related configuration +## ===================================================== +## + +## Set the server AMQP 0-9-1 heartbeat timeout in seconds. +## RabbitMQ nodes will send heartbeat frames at roughly +## the (timeout / 2) interval. Two missed heartbeats from +## a client will close its connection. +## +## Values lower than 6 seconds are very likely to produce +## false positives and are not recommended. +## +## Related doc guides: +## +## * https://rabbitmq.com/heartbeats.html +## * https://rabbitmq.com/networking.html +## +# heartbeat = 60 + +## Set the max permissible size of an AMQP frame (in bytes). +## +# frame_max = 131072 + +## Set the max frame size the server will accept before connection +## tuning occurs +## +# initial_frame_max = 4096 + +## Set the max permissible number of channels per connection. +## 0 means "no limit". +## +# channel_max = 128 + +## Customising TCP Listener (Socket) Configuration. +## +## Related doc guides: +## +## * https://rabbitmq.com/networking.html +## * https://www.erlang.org/doc/man/inet.html#setopts-2 +## + +# tcp_listen_options.backlog = 128 +# tcp_listen_options.nodelay = true +# tcp_listen_options.exit_on_close = false +# +# tcp_listen_options.keepalive = true +# tcp_listen_options.send_timeout = 15000 +# +# tcp_listen_options.buffer = 196608 +# tcp_listen_options.sndbuf = 196608 +# tcp_listen_options.recbuf = 196608 + +## +## Resource Limits & Flow Control +## ============================== +## +## Related doc guide: https://rabbitmq.com/memory.html. + +## Memory-based Flow Control threshold. +## +# vm_memory_high_watermark.relative = 0.4 + +## Alternatively, we can set a limit (in bytes) of RAM used by the node. +## +# vm_memory_high_watermark.absolute = 1073741824 + +## Or you can set absolute value using memory units (with RabbitMQ 3.6.0+). +## Absolute watermark will be ignored if relative is defined! +## +# vm_memory_high_watermark.absolute = 2GB +## +## Supported unit symbols: +## +## k, kiB: kibibytes (2^10 - 1,024 bytes) +## M, MiB: mebibytes (2^20 - 1,048,576 bytes) +## G, GiB: gibibytes (2^30 - 1,073,741,824 bytes) +## kB: kilobytes (10^3 - 1,000 bytes) +## MB: megabytes (10^6 - 1,000,000 bytes) +## GB: gigabytes (10^9 - 1,000,000,000 bytes) + + + +## Fraction of the high watermark limit at which queues start to +## page message out to disc in order to free up memory. +## For example, when vm_memory_high_watermark is set to 0.4 and this value is set to 0.5, +## paging can begin as early as when 20% of total available RAM is used by the node. +## +## Values greater than 1.0 can be dangerous and should be used carefully. +## +## One alternative to this is to use durable queues and publish messages +## as persistent (delivery mode = 2). With this combination queues will +## move messages to disk much more rapidly. +## +## Another alternative is to configure queues to page all messages (both +## persistent and transient) to disk as quickly +## as possible, see https://rabbitmq.com/lazy-queues.html. +## +# vm_memory_high_watermark_paging_ratio = 0.5 + +## Selects Erlang VM memory consumption calculation strategy. Can be `allocated`, `rss` or `legacy` (aliased as `erlang`), +## Introduced in 3.6.11. `rss` is the default as of 3.6.12. +## See https://github.com/rabbitmq/rabbitmq-server/issues/1223 and rabbitmq/rabbitmq-common#224 for background. +# vm_memory_calculation_strategy = rss + +## Interval (in milliseconds) at which we perform the check of the memory +## levels against the watermarks. +## +# memory_monitor_interval = 2500 + +## The total memory available can be calculated from the OS resources +## - default option - or provided as a configuration parameter. +# total_memory_available_override_value = 2GB + +## Set disk free limit (in bytes). Once free disk space reaches this +## lower bound, a disk alarm will be set - see the documentation +## listed above for more details. +## +## Absolute watermark will be ignored if relative is defined! +# disk_free_limit.absolute = 50000 + +## Or you can set it using memory units (same as in vm_memory_high_watermark) +## with RabbitMQ 3.6.0+. +# disk_free_limit.absolute = 500KB +# disk_free_limit.absolute = 50mb +# disk_free_limit.absolute = 5GB + +## Alternatively, we can set a limit relative to total available RAM. +## +## Values lower than 1.0 can be dangerous and should be used carefully. +# disk_free_limit.relative = 2.0 + +## +## Clustering +## ===================== +## +# cluster_partition_handling = ignore + +## Pauses all nodes on the minority side of a partition. The cluster +## MUST have an odd number of nodes (3, 5, etc) +# cluster_partition_handling = pause_minority + +## pause_if_all_down strategy require additional configuration +# cluster_partition_handling = pause_if_all_down + +## Recover strategy. Can be either 'autoheal' or 'ignore' +# cluster_partition_handling.pause_if_all_down.recover = ignore + +## Node names to check +# cluster_partition_handling.pause_if_all_down.nodes.1 = rabbit@localhost +# cluster_partition_handling.pause_if_all_down.nodes.2 = hare@localhost + +## Mirror sync batch size, in messages. Increasing this will speed +## up syncing but total batch size in bytes must not exceed 2 GiB. +## Available in RabbitMQ 3.6.0 or later. +## +# mirroring_sync_batch_size = 4096 + +## Make clustering happen *automatically* at startup. Only applied +## to nodes that have just been reset or started for the first time. +## +## Relevant doc guide: https://rabbitmq.com//cluster-formation.html +## + +# cluster_formation.peer_discovery_backend = rabbit_peer_discovery_classic_config +# +# cluster_formation.classic_config.nodes.1 = rabbit1@hostname +# cluster_formation.classic_config.nodes.2 = rabbit2@hostname +# cluster_formation.classic_config.nodes.3 = rabbit3@hostname +# cluster_formation.classic_config.nodes.4 = rabbit4@hostname + +## DNS-based peer discovery. This backend will list A records +## of the configured hostname and perform reverse lookups for +## the addresses returned. + +# cluster_formation.peer_discovery_backend = rabbit_peer_discovery_dns +# cluster_formation.dns.hostname = discovery.eng.example.local + +## This node's type can be configured. If you are not sure +## what node type to use, always use 'disc'. +# cluster_formation.node_type = disc + +## Interval (in milliseconds) at which we send keepalive messages +## to other cluster members. Note that this is not the same thing +## as net_ticktime; missed keepalive messages will not cause nodes +## to be considered down. +## +# cluster_keepalive_interval = 10000 + +## +## Statistics Collection +## ===================== +## + +## Statistics collection interval (in milliseconds). Increasing +## this will reduce the load on management database. +## +# collect_statistics_interval = 5000 + +## Fine vs. coarse statistics +# +# This value is no longer meant to be configured directly. +# +# See https://www.rabbitmq.com/management.html#fine-stats. + +## +## Ra Settings +## ===================== +## +# raft.segment_max_entries = 65536 +# raft.wal_max_size_bytes = 1048576 +# raft.wal_max_batch_size = 4096 +# raft.snapshot_chunk_size = 1000000 + +## +## Misc/Advanced Options +## ===================== +## +## NB: Change these only if you understand what you are doing! +## + +## Timeout used when waiting for Mnesia tables in a cluster to +## become available. +## +# mnesia_table_loading_retry_timeout = 30000 + +## Retries when waiting for Mnesia tables in the cluster startup. Note that +## this setting is not applied to Mnesia upgrades or node deletions. +## +# mnesia_table_loading_retry_limit = 10 + +## Size in bytes below which to embed messages in the queue index. +## Related doc guide: https://rabbitmq.com/persistence-conf.html +## +# queue_index_embed_msgs_below = 4096 + +## You can also set this size in memory units +## +# queue_index_embed_msgs_below = 4kb + +## Whether or not to enable background periodic forced GC runs for all +## Erlang processes on the node in "waiting" state. +## +## Disabling background GC may reduce latency for client operations, +## keeping it enabled may reduce median RAM usage by the binary heap +## (see https://www.erlang-solutions.com/blog/erlang-garbage-collector.html). +## +## Before trying this option, please take a look at the memory +## breakdown (https://www.rabbitmq.com/memory-use.html). +## +# background_gc_enabled = false + +## Target (desired) interval (in milliseconds) at which we run background GC. +## The actual interval will vary depending on how long it takes to execute +## the operation (can be higher than this interval). Values less than +## 30000 milliseconds are not recommended. +## +# background_gc_target_interval = 60000 + +## Whether or not to enable proxy protocol support. +## Once enabled, clients cannot directly connect to the broker +## anymore. They must connect through a load balancer that sends the +## proxy protocol header to the broker at connection time. +## This setting applies only to AMQP clients, other protocols +## like MQTT or STOMP have their own setting to enable proxy protocol. +## See the plugins documentation for more information. +## +# proxy_protocol = false + +## Overriden product name and version. +## They are set to "RabbitMQ" and the release version by default. +# product.name = RabbitMQ +# product.version = 1.2.3 + +## "Message of the day" file. +## Its content is used to expand the logged and printed banners. +## Default to /etc/rabbitmq/motd on Unix, %APPDATA%\RabbitMQ\motd.txt +## on Windows. +# motd_file = /etc/rabbitmq/motd + +## Consumer timeout +## If a message delivered to a consumer has not been acknowledge before this timer +## triggers the channel will be force closed by the broker. This ensure that +## faultly consumers that never ack will not hold on to messages indefinitely. +## +# consumer_timeout = 900000 + +## ---------------------------------------------------------------------------- +## Advanced Erlang Networking/Clustering Options. +## +## Related doc guide: https://rabbitmq.com/clustering.html +## ---------------------------------------------------------------------------- + +# ====================================== +# Kernel section +# ====================================== + +## Timeout used to detect peer unavailability, including CLI tools. +## Related doc guide: https://www.rabbitmq.com/nettick.html. +## +# net_ticktime = 60 + +## Inter-node communication port range. +## The parameters inet_dist_listen_min and inet_dist_listen_max +## can be configured in the classic config format only. +## Related doc guide: https://www.rabbitmq.com/networking.html#epmd-inet-dist-port-range. + + +## ---------------------------------------------------------------------------- +## RabbitMQ Management Plugin +## +## Related doc guide: https://rabbitmq.com/management.html. +## ---------------------------------------------------------------------------- + +# ======================================= +# Management section +# ======================================= + +## Preload schema definitions from the following JSON file. +## Related doc guide: https://rabbitmq.com/management.html#load-definitions. +## +# management.load_definitions = /path/to/exported/definitions.json + +## Log all requests to the management HTTP API to a file. +## +# management.http_log_dir = /path/to/access.log + +## HTTP listener and embedded Web server settings. +# ## See https://rabbitmq.com/management.html for details. +# +# management.tcp.port = 15672 +# management.tcp.ip = 0.0.0.0 +# +# management.tcp.shutdown_timeout = 7000 +# management.tcp.max_keepalive = 120 +# management.tcp.idle_timeout = 120 +# management.tcp.inactivity_timeout = 120 +# management.tcp.request_timeout = 120 +# management.tcp.compress = true + +## HTTPS listener settings. +## See https://rabbitmq.com/management.html and https://rabbitmq.com/ssl.html for details. +## +# management.ssl.port = 15671 +# management.ssl.cacertfile = /path/to/ca_certificate.pem +# management.ssl.certfile = /path/to/server_certificate.pem +# management.ssl.keyfile = /path/to/server_key.pem + +## More TLS options +# management.ssl.honor_cipher_order = true +# management.ssl.honor_ecc_order = true + +## These are highly recommended for TLSv1.2 but cannot be used +## with TLSv1.3. If TLSv1.3 is enabled, these lines MUST be removed. +# management.ssl.client_renegotiation = false +# management.ssl.secure_renegotiate = true + +## Supported TLS versions +# management.ssl.versions.1 = tlsv1.2 + +## Cipher suites the server is allowed to use +# management.ssl.ciphers.1 = ECDHE-ECDSA-AES256-GCM-SHA384 +# management.ssl.ciphers.2 = ECDHE-RSA-AES256-GCM-SHA384 +# management.ssl.ciphers.3 = ECDHE-ECDSA-AES256-SHA384 +# management.ssl.ciphers.4 = ECDHE-RSA-AES256-SHA384 +# management.ssl.ciphers.5 = ECDH-ECDSA-AES256-GCM-SHA384 +# management.ssl.ciphers.6 = ECDH-RSA-AES256-GCM-SHA384 +# management.ssl.ciphers.7 = ECDH-ECDSA-AES256-SHA384 +# management.ssl.ciphers.8 = ECDH-RSA-AES256-SHA384 +# management.ssl.ciphers.9 = DHE-RSA-AES256-GCM-SHA384 + +## URL path prefix for HTTP API and management UI +# management.path_prefix = /a-prefix + +## One of 'basic', 'detailed' or 'none'. See +## https://rabbitmq.com/management.html#fine-stats for more details. +# management.rates_mode = basic + +## Configure how long aggregated data (such as message rates and queue +## lengths) is retained. Please read the plugin's documentation in +## https://rabbitmq.com/management.html#configuration for more +## details. +## Your can use 'minute', 'hour' and 'day' keys or integer key (in seconds) +# management.sample_retention_policies.global.minute = 5 +# management.sample_retention_policies.global.hour = 60 +# management.sample_retention_policies.global.day = 1200 + +# management.sample_retention_policies.basic.minute = 5 +# management.sample_retention_policies.basic.hour = 60 + +# management.sample_retention_policies.detailed.10 = 5 + +## ---------------------------------------------------------------------------- +## RabbitMQ Shovel Plugin +## +## Related doc guide: https://rabbitmq.com/shovel.html +## ---------------------------------------------------------------------------- + +## See advanced.config.example for a Shovel plugin example + + +## ---------------------------------------------------------------------------- +## RabbitMQ STOMP Plugin +## +## Related doc guide: https://rabbitmq.com/stomp.html +## ---------------------------------------------------------------------------- + +# ======================================= +# STOMP section +# ======================================= + +## See https://rabbitmq.com/stomp.html for details. + +## TCP listeners. +## +# stomp.listeners.tcp.1 = 127.0.0.1:61613 +# stomp.listeners.tcp.2 = ::1:61613 + +## TCP listener settings +## +# stomp.tcp_listen_options.backlog = 2048 +# stomp.tcp_listen_options.recbuf = 131072 +# stomp.tcp_listen_options.sndbuf = 131072 +# +# stomp.tcp_listen_options.keepalive = true +# stomp.tcp_listen_options.nodelay = true +# +# stomp.tcp_listen_options.exit_on_close = true +# stomp.tcp_listen_options.send_timeout = 120 + +## Proxy protocol support +## +# stomp.proxy_protocol = false + +## TLS listeners +## See https://rabbitmq.com/stomp.html and https://rabbitmq.com/ssl.html for details. +# stomp.listeners.ssl.default = 61614 +# +# ssl_options.cacertfile = path/to/cacert.pem +# ssl_options.certfile = path/to/cert.pem +# ssl_options.keyfile = path/to/key.pem +# ssl_options.verify = verify_peer +# ssl_options.fail_if_no_peer_cert = true + + +## Number of Erlang processes that will accept connections for the TCP +## and TLS listeners. +## +# stomp.num_acceptors.tcp = 10 +# stomp.num_acceptors.ssl = 1 + +## Additional TLS options + +## Extract a name from the client's certificate when using TLS. +## +# stomp.ssl_cert_login = true + +## Set a default user name and password. This is used as the default login +## whenever a CONNECT frame omits the login and passcode headers. +## +## Please note that setting this will allow clients to connect without +## authenticating! +## +# stomp.default_user = guest +# stomp.default_pass = guest + +## If a default user is configured, or you have configured use TLS client +## certificate based authentication, you can choose to allow clients to +## omit the CONNECT frame entirely. If set to true, the client is +## automatically connected as the default user or user supplied in the +## TLS certificate whenever the first frame sent on a session is not a +## CONNECT frame. +## +# stomp.implicit_connect = true + +## Whether or not to enable proxy protocol support. +## Once enabled, clients cannot directly connect to the broker +## anymore. They must connect through a load balancer that sends the +## proxy protocol header to the broker at connection time. +## This setting applies only to STOMP clients, other protocols +## like MQTT or AMQP have their own setting to enable proxy protocol. +## See the plugins or broker documentation for more information. +## +# stomp.proxy_protocol = false + +## ---------------------------------------------------------------------------- +## RabbitMQ MQTT Adapter +## +## See https://github.com/rabbitmq/rabbitmq-mqtt/blob/stable/README.md +## for details +## ---------------------------------------------------------------------------- + +# ======================================= +# MQTT section +# ======================================= + +## TCP listener settings. +## +# mqtt.listeners.tcp.1 = 127.0.0.1:61613 +# mqtt.listeners.tcp.2 = ::1:61613 + +## TCP listener options (as per the broker configuration). +## +# mqtt.tcp_listen_options.backlog = 4096 +# mqtt.tcp_listen_options.recbuf = 131072 +# mqtt.tcp_listen_options.sndbuf = 131072 +# +# mqtt.tcp_listen_options.keepalive = true +# mqtt.tcp_listen_options.nodelay = true +# +# mqtt.tcp_listen_options.exit_on_close = true +# mqtt.tcp_listen_options.send_timeout = 120 + +## TLS listener settings +## ## See https://rabbitmq.com/mqtt.html and https://rabbitmq.com/ssl.html for details. +# +# mqtt.listeners.ssl.default = 8883 +# +# ssl_options.cacertfile = /path/to/tls/ca_certificate_bundle.pem +# ssl_options.certfile = /path/to/tls/server_certificate.pem +# ssl_options.keyfile = /path/to/tls/server_key.pem +# ssl_options.verify = verify_peer +# ssl_options.fail_if_no_peer_cert = true +# + + +## Number of Erlang processes that will accept connections for the TCP +## and TLS listeners. +## +# mqtt.num_acceptors.tcp = 10 +# mqtt.num_acceptors.ssl = 10 + +## Whether or not to enable proxy protocol support. +## Once enabled, clients cannot directly connect to the broker +## anymore. They must connect through a load balancer that sends the +## proxy protocol header to the broker at connection time. +## This setting applies only to STOMP clients, other protocols +## like STOMP or AMQP have their own setting to enable proxy protocol. +## See the plugins or broker documentation for more information. +## +# mqtt.proxy_protocol = false + +## Set the default user name and password used for anonymous connections (when client +## provides no credentials). Anonymous connections are highly discouraged! +## +# mqtt.default_user = guest +# mqtt.default_pass = guest + +## Enable anonymous connections. If this is set to false, clients MUST provide +## credentials in order to connect. See also the mqtt.default_user/mqtt.default_pass +## keys. Anonymous connections are highly discouraged! +## +# mqtt.allow_anonymous = true + +## If you have multiple vhosts, specify the one to which the +## adapter connects. +## +# mqtt.vhost = / + +## Specify the exchange to which messages from MQTT clients are published. +## +# mqtt.exchange = amq.topic + +## Specify TTL (time to live) to control the lifetime of non-clean sessions. +## +# mqtt.subscription_ttl = 1800000 + +## Set the prefetch count (governing the maximum number of unacknowledged +## messages that will be delivered). +## +# mqtt.prefetch = 10 + + +## ---------------------------------------------------------------------------- +## RabbitMQ AMQP 1.0 Support +## +## See https://github.com/rabbitmq/rabbitmq-amqp1.0/blob/stable/README.md. +## ---------------------------------------------------------------------------- + +# ======================================= +# AMQP 1.0 section +# ======================================= + + +## Connections that are not authenticated with SASL will connect as this +## account. See the README for more information. +## +## Please note that setting this will allow clients to connect without +## authenticating! +## +# amqp1_0.default_user = guest + +## Enable protocol strict mode. See the README for more information. +## +# amqp1_0.protocol_strict_mode = false + +## Logging settings. +## +## See https://rabbitmq.com/logging.html for details. +## + +## Log directory, taken from the RABBITMQ_LOG_BASE env variable by default. +## +# log.dir = /var/log/rabbitmq + +## Logging to file. Can be false or a filename. +## Default: +# log.file = rabbit.log + +## To disable logging to a file +# log.file = false + +## Log level for file logging +## +# log.file.level = info + +## File rotation config. No rotation by default. +## DO NOT SET rotation date to ''. Leave the value unset if "" is the desired value +# log.file.rotation.date = $D0 +# log.file.rotation.size = 0 + +## Logging to console (can be true or false) +## +# log.console = false + +## Log level for console logging +## +# log.console.level = info + +## Logging to the amq.rabbitmq.log exchange (can be true or false) +## +# log.exchange = false + +## Log level to use when logging to the amq.rabbitmq.log exchange +## +# log.exchange.level = info + + + +## ---------------------------------------------------------------------------- +## RabbitMQ LDAP Plugin +## +## Related doc guide: https://rabbitmq.com/ldap.html. +## +## ---------------------------------------------------------------------------- + +# ======================================= +# LDAP section +# ======================================= + +## +## Connecting to the LDAP server(s) +## ================================ +## + +## Specify servers to bind to. You *must* set this in order for the plugin +## to work properly. +## +# auth_ldap.servers.1 = your-server-name-goes-here + +## You can define multiple servers +# auth_ldap.servers.2 = your-other-server + +## Connect to the LDAP server using TLS +## +# auth_ldap.use_ssl = false + +## Specify the LDAP port to connect to +## +# auth_ldap.port = 389 + +## LDAP connection timeout, in milliseconds or 'infinity' +## +# auth_ldap.timeout = infinity + +## Or number +# auth_ldap.timeout = 500 + +## Enable logging of LDAP queries. +## One of +## - false (no logging is performed) +## - true (verbose logging of the logic used by the plugin) +## - network (as true, but additionally logs LDAP network traffic) +## +## Defaults to false. +## +# auth_ldap.log = false + +## Also can be true or network +# auth_ldap.log = true +# auth_ldap.log = network + +## +## Authentication +## ============== +## + +## Pattern to convert the username given through AMQP to a DN before +## binding +## +# auth_ldap.user_dn_pattern = cn=${username},ou=People,dc=example,dc=com + +## Alternatively, you can convert a username to a Distinguished +## Name via an LDAP lookup after binding. See the documentation for +## full details. + +## When converting a username to a dn via a lookup, set these to +## the name of the attribute that represents the user name, and the +## base DN for the lookup query. +## +# auth_ldap.dn_lookup_attribute = userPrincipalName +# auth_ldap.dn_lookup_base = DC=gopivotal,DC=com + +## Controls how to bind for authorisation queries and also to +## retrieve the details of users logging in without presenting a +## password (e.g., SASL EXTERNAL). +## One of +## - as_user (to bind as the authenticated user - requires a password) +## - anon (to bind anonymously) +## - {UserDN, Password} (to bind with a specified user name and password) +## +## Defaults to 'as_user'. +## +# auth_ldap.other_bind = as_user + +## Or can be more complex: +# auth_ldap.other_bind.user_dn = User +# auth_ldap.other_bind.password = Password + +## If user_dn and password defined - other options is ignored. + +# ----------------------------- +# Too complex section of LDAP +# ----------------------------- + +## +## Authorisation +## ============= +## + +## The LDAP plugin can perform a variety of queries against your +## LDAP server to determine questions of authorisation. +## +## Related doc guide: https://rabbitmq.com/ldap.html#authorisation. + +## Following configuration should be defined in advanced.config file +## DO NOT UNCOMMENT THESE LINES! + +## Set the query to use when determining vhost access +## +## {vhost_access_query, {in_group, +## "ou=${vhost}-users,ou=vhosts,dc=example,dc=com"}}, + +## Set the query to use when determining resource (e.g., queue) access +## +## {resource_access_query, {constant, true}}, + +## Set queries to determine which tags a user has +## +## {tag_queries, []} +# ]}, +# ----------------------------- diff --git a/docker/rabbitmq/rabbitmq/config/rabbitmq_bak.config b/docker/rabbitmq/rabbitmq/config/rabbitmq_bak.config new file mode 100644 index 0000000..0169efa --- /dev/null +++ b/docker/rabbitmq/rabbitmq/config/rabbitmq_bak.config @@ -0,0 +1,925 @@ +%% -*- mode: erlang -*- +%% ---------------------------------------------------------------------------- +%% Classic RabbitMQ configuration format example. +%% This format should be considered DEPRECATED. +%% +%% Users of RabbitMQ 3.7.x +%% or later should prefer the new style format (rabbitmq.conf) +%% in combination with an advanced.config file (as needed). +%% +%% Related doc guide: https://www.rabbitmq.com/configure.html. See +%% https://rabbitmq.com/documentation.html for documentation ToC. +%% ---------------------------------------------------------------------------- +[ + {rabbit, + [%% + %% Networking + %% ==================== + %% + %% Related doc guide: https://www.rabbitmq.com/networking.html. + + %% By default, RabbitMQ will listen on all interfaces, using + %% the standard (reserved) AMQP port. + %% + %% {tcp_listeners, [5672]}, + + %% To listen on a specific interface, provide a tuple of {IpAddress, Port}. + %% For example, to listen only on localhost for both IPv4 and IPv6: + %% + %% {tcp_listeners, [{"127.0.0.1", 5672}, + %% {"::1", 5672}]}, + + %% TLS listeners are configured in the same fashion as TCP listeners, + %% including the option to control the choice of interface. + %% + %% {ssl_listeners, [5671]}, + + %% Number of Erlang processes that will accept connections for the TCP + %% and TLS listeners. + %% + %% {num_tcp_acceptors, 10}, + %% {num_ssl_acceptors, 1}, + + %% Maximum time for AMQP 0-8/0-9/0-9-1 handshake (after socket connection + %% and TLS handshake), in milliseconds. + %% + %% {handshake_timeout, 10000}, + + %% Set to 'true' to perform reverse DNS lookups when accepting a + %% connection. Hostnames will then be shown instead of IP addresses + %% in rabbitmqctl and the management plugin. + %% + %% {reverse_dns_lookups, false}, + + %% + %% Security, Access Control + %% ======================== + %% + %% Related doc guide: https://www.rabbitmq.com/access-control.html. + + %% The default "guest" user is only permitted to access the server + %% via a loopback interface (e.g. localhost). + %% {loopback_users, [<<"guest">>]}, + %% + %% Uncomment the following line if you want to allow access to the + %% guest user from anywhere on the network. + %% {loopback_users, []}, + + + %% TLS configuration. + %% + %% Related doc guide: https://www.rabbitmq.com/ssl.html. + %% + %% {ssl_options, [{cacertfile, "/path/to/testca/cacert.pem"}, + %% {certfile, "/path/to/server/cert.pem"}, + %% {keyfile, "/path/to/server/key.pem"}, + %% {verify, verify_peer}, + %% {fail_if_no_peer_cert, false}]}, + + %% Choose the available SASL mechanism(s) to expose. + %% The two default (built in) mechanisms are 'PLAIN' and + %% 'AMQPLAIN'. Additional mechanisms can be added via + %% plugins. + %% + %% Related doc guide: https://www.rabbitmq.com/authentication.html. + %% + %% {auth_mechanisms, ['PLAIN', 'AMQPLAIN']}, + + %% Select an authentication database to use. RabbitMQ comes bundled + %% with a built-in auth-database, based on mnesia. + %% + %% {auth_backends, [rabbit_auth_backend_internal]}, + + %% Configurations supporting the rabbitmq_auth_mechanism_ssl and + %% rabbitmq_auth_backend_ldap plugins. + %% + %% NB: These options require that the relevant plugin is enabled. + %% Related doc guide: https://www.rabbitmq.com/plugins.html for further details. + + %% The RabbitMQ-auth-mechanism-ssl plugin makes it possible to + %% authenticate a user based on the client's TLS certificate. + %% + %% To use auth-mechanism-ssl, add to or replace the auth_mechanisms + %% list with the entry 'EXTERNAL'. + %% + %% {auth_mechanisms, ['EXTERNAL']}, + + %% The rabbitmq_auth_backend_ldap plugin allows the broker to + %% perform authentication and authorisation by deferring to an + %% external LDAP server. + %% + %% For more information about configuring the LDAP backend, see + %% https://www.rabbitmq.com/ldap.html. + %% + %% Enable the LDAP auth backend by adding to or replacing the + %% auth_backends entry: + %% + %% {auth_backends, [rabbit_auth_backend_ldap]}, + + %% This pertains to both the rabbitmq_auth_mechanism_ssl plugin and + %% STOMP ssl_cert_login configurations. See the rabbitmq_stomp + %% configuration section later in this file and the README in + %% https://github.com/rabbitmq/rabbitmq-auth-mechanism-ssl for further + %% details. + %% + %% To use the TLS cert's CN instead of its DN as the username + %% + %% {ssl_cert_login_from, distinguished_name}, + + %% TLS handshake timeout, in milliseconds. + %% + %% {ssl_handshake_timeout, 5000}, + + %% Makes RabbitMQ accept SSLv3 client connections by default. + %% DO NOT DO THIS IF YOU CAN HELP IT. + %% + %% {ssl_allow_poodle_attack, false}, + + %% Password hashing implementation. Will only affect newly + %% created users. To recalculate hash for an existing user + %% it's necessary to update her password. + %% + %% When importing definitions exported from versions earlier + %% than 3.6.0, it is possible to go back to MD5 (only do this + %% as a temporary measure!) by setting this to rabbit_password_hashing_md5. + %% + %% To use SHA-512, set to rabbit_password_hashing_sha512. + %% + %% {password_hashing_module, rabbit_password_hashing_sha256}, + + %% Configuration entry encryption. + %% Related doc guide: https://www.rabbitmq.com/configure.html#configuration-encryption + %% + %% To specify the passphrase in the configuration file: + %% + %% {config_entry_decoder, [{passphrase, <<"mypassphrase">>}]} + %% + %% To specify the passphrase in an external file: + %% + %% {config_entry_decoder, [{passphrase, {file, "/path/to/passphrase/file"}}]} + %% + %% To make the broker request the passphrase when it starts: + %% + %% {config_entry_decoder, [{passphrase, prompt}]} + %% + %% To change encryption settings: + %% + %% {config_entry_decoder, [{cipher, aes_cbc256}, + %% {hash, sha512}, + %% {iterations, 1000}]} + + %% + %% Default User / VHost + %% ==================== + %% + + %% On first start RabbitMQ will create a vhost and a user. These + %% config items control what gets created. See + %% https://www.rabbitmq.com/access-control.html for further + %% information about vhosts and access control. + %% + %% {default_vhost, <<"/">>}, + %% {default_user, <<"guest">>}, + %% {default_pass, <<"guest">>}, + %% {default_permissions, [<<".*">>, <<".*">>, <<".*">>]}, + + %% Tags for default user + %% + %% Related doc guide: https://www.rabbitmq.com/management.html. + %% + %% {default_user_tags, [administrator]}, + + %% + %% Additional network and protocol related configuration + %% ===================================================== + %% + + %% Sets the default AMQP 0-9-1 heartbeat timeout in seconds. + %% Values lower than 6 can produce false positives and are not + %% recommended. + %% + %% Related doc guides: + %% + %% * https://www.rabbitmq.com/heartbeats.html + %% * https://www.rabbitmq.com/networking.html + %% + %% {heartbeat, 60}, + + %% Set the max permissible size of an AMQP frame (in bytes). + %% + %% {frame_max, 131072}, + + %% Set the max frame size the server will accept before connection + %% tuning occurs + %% + %% {initial_frame_max, 4096}, + + %% Set the max permissible number of channels per connection. + %% 0 means "no limit". + %% + %% {channel_max, 0}, + + %% Set the max permissible number of client connections to the node. + %% `infinity` means "no limit". + %% + %% This limit applies to client connections to all listeners (regardless of + %% the protocol, whether TLS is used and so on). CLI tools and inter-node + %% connections are exempt. + %% + %% When client connections are rapidly opened in succession, it is possible + %% for the total connection count to go slightly higher than the configured limit. + %% The limit works well as a general safety measure. + %% + %% Clients that are hitting the limit will see their TCP connections fail or time out. + %% + %% Introduced in 3.6.13. + %% + %% Related doc guide: https://www.rabbitmq.com/networking.html. + %% + %% {connection_max, infinity}, + + %% TCP socket options. + %% + %% Related doc guide: https://www.rabbitmq.com/networking.html. + %% + %% {tcp_listen_options, [{backlog, 128}, + %% {nodelay, true}, + %% {exit_on_close, false}]}, + + %% + %% Resource Limits & Flow Control + %% ============================== + %% + %% Related doc guide: https://www.rabbitmq.com/memory.html, https://www.rabbitmq.com/memory-use.html. + + %% Memory-based Flow Control threshold. + %% + %% {vm_memory_high_watermark, 0.4}, + + %% Alternatively, we can set a limit (in bytes) of RAM used by the node. + %% + %% {vm_memory_high_watermark, {absolute, 1073741824}}, + %% + %% Or you can set absolute value using memory units (with RabbitMQ 3.6.0+). + %% + %% {vm_memory_high_watermark, {absolute, "1024M"}}, + %% + %% Supported unit symbols: + %% + %% k, kiB: kibibytes (2^10 - 1,024 bytes) + %% M, MiB: mebibytes (2^20 - 1,048,576 bytes) + %% G, GiB: gibibytes (2^30 - 1,073,741,824 bytes) + %% kB: kilobytes (10^3 - 1,000 bytes) + %% MB: megabytes (10^6 - 1,000,000 bytes) + %% GB: gigabytes (10^9 - 1,000,000,000 bytes) + + %% Fraction of the high watermark limit at which queues start to + %% page message out to disc in order to free up memory. + %% For example, when vm_memory_high_watermark is set to 0.4 and this value is set to 0.5, + %% paging can begin as early as when 20% of total available RAM is used by the node. + %% + %% Values greater than 1.0 can be dangerous and should be used carefully. + %% + %% One alternative to this is to use durable queues and publish messages + %% as persistent (delivery mode = 2). With this combination queues will + %% move messages to disk much more rapidly. + %% + %% Another alternative is to configure queues to page all messages (both + %% persistent and transient) to disk as quickly + %% as possible, see https://www.rabbitmq.com/lazy-queues.html. + %% + %% {vm_memory_high_watermark_paging_ratio, 0.5}, + + %% Selects Erlang VM memory consumption calculation strategy. Can be `allocated`, `rss` or `legacy` (aliased as `erlang`), + %% Introduced in 3.6.11. `rss` is the default as of 3.6.12. + %% See https://github.com/rabbitmq/rabbitmq-server/issues/1223 and rabbitmq/rabbitmq-common#224 for background. + %% {vm_memory_calculation_strategy, rss}, + + %% Interval (in milliseconds) at which we perform the check of the memory + %% levels against the watermarks. + %% + %% {memory_monitor_interval, 2500}, + + %% The total memory available can be calculated from the OS resources + %% - default option - or provided as a configuration parameter: + %% {total_memory_available_override_value, "5000MB"}, + + %% Set disk free limit (in bytes). Once free disk space reaches this + %% lower bound, a disk alarm will be set - see the documentation + %% listed above for more details. + %% + %% {disk_free_limit, 50000000}, + %% + %% Or you can set it using memory units (same as in vm_memory_high_watermark) + %% with RabbitMQ 3.6.0+. + %% {disk_free_limit, "50MB"}, + %% {disk_free_limit, "50000kB"}, + %% {disk_free_limit, "2GB"}, + + %% Alternatively, we can set a limit relative to total available RAM. + %% + %% Values lower than 1.0 can be dangerous and should be used carefully. + %% {disk_free_limit, {mem_relative, 2.0}}, + + %% + %% Clustering + %% ===================== + %% + + %% Queue master location strategy: + %% * <<"min-masters">> + %% * <<"client-local">> + %% * <<"random">> + %% + %% Related doc guide: https://www.rabbitmq.com/ha.html#queue-master-location + %% + %% {queue_master_locator, <<"client-local">>}, + + %% Batch size (number of messages) used during eager queue mirror synchronisation. + %% Related doc guide: https://www.rabbitmq.com/ha.html#batch-sync. When average message size is relatively large + %% (say, 10s of kilobytes or greater), reducing this value will decrease peak amount + %% of RAM used by newly joining nodes that need eager synchronisation. + %% + %% {mirroring_sync_batch_size, 4096}, + + %% Enables flow control between queue mirrors. + %% Disabling this can be dangerous and is not recommended. + %% When flow control is disabled, queue masters can outpace mirrors and not allow mirrors to catch up. + %% Mirrors will end up using increasingly more RAM, eventually triggering a memory alarm. + %% + %% {mirroring_flow_control, true}, + + %% Additional server properties to announce to connecting clients. + %% + %% {server_properties, []}, + + %% How to respond to cluster partitions. + %% Related doc guide: https://www.rabbitmq.com/partitions.html + %% + %% {cluster_partition_handling, ignore}, + + %% Mirror sync batch size, in messages. Increasing this will speed + %% up syncing but total batch size in bytes must not exceed 2 GiB. + %% Available in RabbitMQ 3.6.0 or later. + %% + %% {mirroring_sync_batch_size, 4096}, + + %% Make clustering happen *automatically* at startup - only applied + %% to nodes that have just been reset or started for the first time. + %% Related doc guide: https://www.rabbitmq.com/clustering.html#auto-config + %% + %% {cluster_nodes, {['rabbit@my.host.com'], disc}}, + + %% Interval (in milliseconds) at which we send keepalive messages + %% to other cluster members. Note that this is not the same thing + %% as net_ticktime; missed keepalive messages will not cause nodes + %% to be considered down. + %% + %% {cluster_keepalive_interval, 10000}, + + %% + %% Statistics Collection + %% ===================== + %% + + %% Set (internal) statistics collection granularity. + %% + %% {collect_statistics, none}, + + %% Statistics collection interval (in milliseconds). Increasing + %% this will reduce the load on management database. + %% + %% {collect_statistics_interval, 5000}, + + %% Enables vhosts tracing. + %% + %% {trace_vhosts, []}, + + %% Explicitly enable/disable HiPE compilation. + %% + %% {hipe_compile, false}, + + %% Number of delegate processes to use for intra-cluster communication. + %% On a node which is part of cluster, has more than 16 cores and plenty of network bandwidth, + %% it may make sense to increase this value. + %% + %% {delegate_count, 16}, + + %% Number of times to retry while waiting for internal database tables (Mnesia tables) to sync + %% from a peer. In deployments where nodes can take a long time to boot, this value + %% may need increasing. + %% + %% {mnesia_table_loading_retry_limit, 10}, + + %% Amount of time in milliseconds which this node will wait for internal database tables (Mnesia tables) to sync + %% from a peer. In deployments where nodes can take a long time to boot, this value + %% may need increasing. + %% + %% {mnesia_table_loading_retry_timeout, 30000}, + + %% Size in bytes below which to embed messages in the queue index. + %% Related doc guide: https://www.rabbitmq.com/persistence-conf.html + %% + %% {queue_index_embed_msgs_below, 4096}, + + %% Maximum number of queue index entries to keep in journal + %% Related doc guide: https://www.rabbitmq.com/persistence-conf.html. + %% + %% {queue_index_max_journal_entries, 32768}, + + %% Number of credits that a queue process is given by the message store + %% By default, a queue process is given 4000 message store credits, + %% and then 800 for every 800 messages that it processes. + %% + %% {msg_store_credit_disc_bound, {4000, 800}}, + + %% Minimum number of messages with their queue position held in RAM required + %% to trigger writing their queue position to disk. + %% + %% This value MUST be higher than the initial msg_store_credit_disc_bound value, + %% otherwise paging performance may worsen. + %% + %% {msg_store_io_batch_size, 4096}, + + %% Number of credits that a connection, channel or queue are given. + %% + %% By default, every connection, channel or queue is given 400 credits, + %% and then 200 for every 200 messages that it sends to a peer process. + %% Increasing these values may help with throughput but also can be dangerous: + %% high credit flow values are no different from not having flow control at all. + %% + %% Related doc guide: https://www.rabbitmq.com/blog/2015/10/06/new-credit-flow-settings-on-rabbitmq-3-5-5/ + %% and http://alvaro-videla.com/2013/09/rabbitmq-internals-credit-flow-for-erlang-processes.html. + %% + %% {credit_flow_default_credit, {400, 200}}, + + %% Number of milliseconds before a channel operation times out. + %% + %% {channel_operation_timeout, 15000}, + + %% Number of queue operations required to trigger an explicit garbage collection. + %% Increasing this value may reduce CPU load and increase peak RAM consumption of queues. + %% + %% {queue_explicit_gc_run_operation_threshold, 1000}, + + %% Number of lazy queue operations required to trigger an explicit garbage collection. + %% Increasing this value may reduce CPU load and increase peak RAM consumption of lazy queues. + %% + %% {lazy_queue_explicit_gc_run_operation_threshold, 1000}, + + %% Number of times disk monitor will retry free disk space queries before + %% giving up. + %% + %% {disk_monitor_failure_retries, 10}, + + %% Milliseconds to wait between disk monitor retries on failures. + %% + %% {disk_monitor_failure_retry_interval, 120000}, + + %% Whether or not to enable background periodic forced GC runs for all + %% Erlang processes on the node in "waiting" state. + %% + %% Disabling background GC may reduce latency for client operations, + %% keeping it enabled may reduce median RAM usage by the binary heap + %% (see https://www.erlang-solutions.com/blog/erlang-garbage-collector.html). + %% + %% Before enabling this option, please take a look at the memory + %% breakdown (https://www.rabbitmq.com/memory-use.html). + %% + %% {background_gc_enabled, false}, + + %% Interval (in milliseconds) at which we run background GC. + %% + %% {background_gc_target_interval, 60000}, + + %% Message store operations are stored in a sequence of files called segments. + %% This controls max size of a segment file. + %% Increasing this value may speed up (sequential) disk writes but will slow down segment GC process. + %% DO NOT CHANGE THIS for existing installations. + %% + %% {msg_store_file_size_limit, 16777216}, + + %% Whether or not to enable file write buffering. + %% + %% {fhc_write_buffering, true}, + + %% Whether or not to enable file read buffering. Enabling + %% this may slightly speed up reads but will also increase + %% node's memory consumption, in particular on boot. + %% + %% {fhc_read_buffering, false} + + ]}, + + %% ---------------------------------------------------------------------------- + %% Advanced Erlang Networking/Clustering Options. + %% + %% Related doc guide: https://www.rabbitmq.com/clustering.html + %% ---------------------------------------------------------------------------- + {kernel, + [%% Sets the net_kernel tick time. + %% Please see http://erlang.org/doc/man/kernel_app.html and + %% https://www.rabbitmq.com/nettick.html for further details. + %% + %% {net_ticktime, 60} + ]}, + + %% ---------------------------------------------------------------------------- + %% RabbitMQ Management Plugin + %% + %% Related doc guide: https://www.rabbitmq.com/management.html + %% ---------------------------------------------------------------------------- + + {rabbitmq_management, + [%% Preload schema definitions from a previously exported definitions file. See + %% https://www.rabbitmq.com/management.html#load-definitions + %% + %% {load_definitions, "/path/to/exported/definitions.json"}, + + %% Log all requests to the management HTTP API to a directory. + %% + %% {http_log_dir, "/path/to/rabbitmq/logs/http"}, + + %% Change the port on which the HTTP listener listens, + %% specifying an interface for the web server to bind to. + %% Also set the listener to use TLS and provide TLS options. + %% + %% {listener, [{port, 12345}, + %% {ip, "127.0.0.1"}, + %% {ssl, true}, + %% {ssl_opts, [{cacertfile, "/path/to/cacert.pem"}, + %% {certfile, "/path/to/cert.pem"}, + %% {keyfile, "/path/to/key.pem"}]}]}, + + %% One of 'basic', 'detailed' or 'none'. See + %% https://www.rabbitmq.com/management.html#fine-stats for more details. + %% {rates_mode, basic}, + + %% Configure how long aggregated data (such as message rates and queue + %% lengths) is retained. Please read the plugin's documentation in + %% https://www.rabbitmq.com/management.html#configuration for more + %% details. + %% + %% {sample_retention_policies, + %% [{global, [{60, 5}, {3600, 60}, {86400, 1200}]}, + %% {basic, [{60, 5}, {3600, 60}]}, + %% {detailed, [{10, 5}]}]} + ]}, + + %% ---------------------------------------------------------------------------- + %% RabbitMQ Shovel Plugin + %% + %% Related doc guide: https://www.rabbitmq.com/shovel.html + %% ---------------------------------------------------------------------------- + + {rabbitmq_shovel, + [{shovels, + [%% A named shovel worker. + %% {my_first_shovel, + %% [ + + %% List the source broker(s) from which to consume. + %% + %% {sources, + %% [%% URI(s) and pre-declarations for all source broker(s). + %% {brokers, ["amqp://user:password@host.domain/my_vhost"]}, + %% {declarations, []} + %% ]}, + + %% List the destination broker(s) to publish to. + %% {destinations, + %% [%% A singular version of the 'brokers' element. + %% {broker, "amqp://"}, + %% {declarations, []} + %% ]}, + + %% Name of the queue to shovel messages from. + %% + %% {queue, <<"your-queue-name-goes-here">>}, + + %% Optional prefetch count. + %% + %% {prefetch_count, 10}, + + %% when to acknowledge messages: + %% - no_ack: never (auto) + %% - on_publish: after each message is republished + %% - on_confirm: when the destination broker confirms receipt + %% + %% {ack_mode, on_confirm}, + + %% Overwrite fields of the outbound basic.publish. + %% + %% {publish_fields, [{exchange, <<"my_exchange">>}, + %% {routing_key, <<"from_shovel">>}]}, + + %% Static list of basic.properties to set on re-publication. + %% + %% {publish_properties, [{delivery_mode, 2}]}, + + %% The number of seconds to wait before attempting to + %% reconnect in the event of a connection failure. + %% + %% {reconnect_delay, 2.5} + + %% ]} %% End of my_first_shovel + ]} + %% Rather than specifying some values per-shovel, you can specify + %% them for all shovels here. + %% + %% {defaults, [{prefetch_count, 0}, + %% {ack_mode, on_confirm}, + %% {publish_fields, []}, + %% {publish_properties, [{delivery_mode, 2}]}, + %% {reconnect_delay, 2.5}]} + ]}, + + %% ---------------------------------------------------------------------------- + %% RabbitMQ STOMP Plugin + %% + %% Related doc guide: https://www.rabbitmq.com/stomp.html + %% ---------------------------------------------------------------------------- + + {rabbitmq_stomp, + [%% Network Configuration - the format is generally the same as for the broker + + %% Listen only on localhost (ipv4 & ipv6) on a specific port. + %% {tcp_listeners, [{"127.0.0.1", 61613}, + %% {"::1", 61613}]}, + + %% Listen for TLS connections on a specific port. + %% {ssl_listeners, [61614]}, + + %% Number of Erlang processes that will accept connections for the TCP + %% and TLS listeners. + %% + %% {num_tcp_acceptors, 10}, + %% {num_ssl_acceptors, 1}, + + %% Additional TLS options + + %% Extract a name from the client's certificate when using TLS. + %% + %% {ssl_cert_login, true}, + + %% Set a default user name and password. This is used as the default login + %% whenever a CONNECT frame omits the login and passcode headers. + %% + %% Please note that setting this will allow clients to connect without + %% authenticating! + %% + %% {default_user, [{login, "guest"}, + %% {passcode, "guest"}]}, + + %% If a default user is configured, or you have configured use TLS client + %% certificate based authentication, you can choose to allow clients to + %% omit the CONNECT frame entirely. If set to true, the client is + %% automatically connected as the default user or user supplied in the + %% TLS certificate whenever the first frame sent on a session is not a + %% CONNECT frame. + %% + %% {implicit_connect, true}, + + %% Whether or not to enable proxy protocol support. + %% Once enabled, clients cannot directly connect to the broker + %% anymore. They must connect through a load balancer that sends the + %% proxy protocol header to the broker at connection time. + %% This setting applies only to STOMP clients, other protocols + %% like MQTT or AMQP have their own setting to enable proxy protocol. + %% See the plugins or broker documentation for more information. + %% + %% {proxy_protocol, false} + ]}, + + %% ---------------------------------------------------------------------------- + %% RabbitMQ MQTT Plugin + %% + %% Related doc guide: https://github.com/rabbitmq/rabbitmq-mqtt/blob/stable/README.md + %% + %% ---------------------------------------------------------------------------- + + {rabbitmq_mqtt, + [%% Set the default user name and password. Will be used as the default login + %% if a connecting client provides no other login details. + %% + %% Please note that setting this will allow clients to connect without + %% authenticating! + %% + %% {default_user, <<"guest">>}, + %% {default_pass, <<"guest">>}, + + %% Enable anonymous access. If this is set to false, clients MUST provide + %% login information in order to connect. See the default_user/default_pass + %% configuration elements for managing logins without authentication. + %% + %% {allow_anonymous, true}, + + %% If you have multiple chosts, specify the one to which the + %% adapter connects. + %% + %% {vhost, <<"/">>}, + + %% Specify the exchange to which messages from MQTT clients are published. + %% + %% {exchange, <<"amq.topic">>}, + + %% Specify TTL (time to live) to control the lifetime of non-clean sessions. + %% + %% {subscription_ttl, 1800000}, + + %% Set the prefetch count (governing the maximum number of unacknowledged + %% messages that will be delivered). + %% + %% {prefetch, 10}, + + %% TLS listeners. + %% See https://www.rabbitmq.com/networking.html + %% + %% {tcp_listeners, [1883]}, + %% {ssl_listeners, []}, + + %% Number of Erlang processes that will accept connections for the TCP + %% and TLS listeners. + %% See https://www.rabbitmq.com/networking.html + %% + %% {num_tcp_acceptors, 10}, + %% {num_ssl_acceptors, 1}, + + %% TCP socket options. + %% See https://www.rabbitmq.com/networking.html + %% + %% {tcp_listen_options, [ + %% {backlog, 128}, + %% {linger, {true, 0}}, + %% {exit_on_close, false} + %% ]}, + + %% Whether or not to enable proxy protocol support. + %% Once enabled, clients cannot directly connect to the broker + %% anymore. They must connect through a load balancer that sends the + %% proxy protocol header to the broker at connection time. + %% This setting applies only to MQTT clients, other protocols + %% like STOMP or AMQP have their own setting to enable proxy protocol. + %% See the plugins or broker documentation for more information. + %% + %% {proxy_protocol, false} + ]}, + + %% ---------------------------------------------------------------------------- + %% RabbitMQ AMQP 1.0 Support + %% + %% Related doc guide: https://github.com/rabbitmq/rabbitmq-amqp1.0/blob/stable/README.md + %% + %% ---------------------------------------------------------------------------- + + {rabbitmq_amqp1_0, + [%% Connections that are not authenticated with SASL will connect as this + %% account. See the README for more information. + %% + %% Please note that setting this will allow clients to connect without + %% authenticating! + %% + %% {default_user, "guest"}, + + %% Enable protocol strict mode. See the README for more information. + %% + %% {protocol_strict_mode, false} + ]}, + + %% ---------------------------------------------------------------------------- + %% RabbitMQ LDAP Plugin + %% + %% Related doc guide: https://www.rabbitmq.com/ldap.html. + %% + %% ---------------------------------------------------------------------------- + + {rabbitmq_auth_backend_ldap, + [%% + %% Connecting to the LDAP server(s) + %% ================================ + %% + + %% Specify servers to bind to. You *must* set this in order for the plugin + %% to work properly. + %% + %% {servers, ["your-server-name-goes-here"]}, + + %% Connect to the LDAP server using TLS + %% + %% {use_ssl, false}, + + %% Specify the LDAP port to connect to + %% + %% {port, 389}, + + %% LDAP connection timeout, in milliseconds or 'infinity' + %% + %% {timeout, infinity}, + + %% Enable logging of LDAP queries. + %% One of + %% - false (no logging is performed) + %% - true (verbose logging of the logic used by the plugin) + %% - network (as true, but additionally logs LDAP network traffic) + %% + %% Defaults to false. + %% + %% {log, false}, + + %% + %% Authentication + %% ============== + %% + + %% Pattern to convert the username given through AMQP to a DN before + %% binding + %% + %% {user_dn_pattern, "cn=${username},ou=People,dc=example,dc=com"}, + + %% Alternatively, you can convert a username to a Distinguished + %% Name via an LDAP lookup after binding. See the documentation for + %% full details. + + %% When converting a username to a dn via a lookup, set these to + %% the name of the attribute that represents the user name, and the + %% base DN for the lookup query. + %% + %% {dn_lookup_attribute, "userPrincipalName"}, + %% {dn_lookup_base, "DC=gopivotal,DC=com"}, + + %% Controls how to bind for authorisation queries and also to + %% retrieve the details of users logging in without presenting a + %% password (e.g., SASL EXTERNAL). + %% One of + %% - as_user (to bind as the authenticated user - requires a password) + %% - anon (to bind anonymously) + %% - {UserDN, Password} (to bind with a specified user name and password) + %% + %% Defaults to 'as_user'. + %% + %% {other_bind, as_user}, + + %% + %% Authorisation + %% ============= + %% + + %% The LDAP plugin can perform a variety of queries against your + %% LDAP server to determine questions of authorisation. See + %% https://www.rabbitmq.com/ldap.html#authorisation for more + %% information. + + %% Set the query to use when determining vhost access + %% + %% {vhost_access_query, {in_group, + %% "ou=${vhost}-users,ou=vhosts,dc=example,dc=com"}}, + + %% Set the query to use when determining resource (e.g., queue) access + %% + %% {resource_access_query, {constant, true}}, + + %% Set queries to determine which tags a user has + %% + %% {tag_queries, []} + ]}, + + %% Lager controls logging. + %% See https://github.com/basho/lager for more documentation + {lager, [ + %% + %% Log directory, taken from the RABBITMQ_LOG_BASE env variable by default. + %% {log_root, "/var/log/rabbitmq"}, + %% + %% All log messages go to the default "sink" configured with + %% the `handlers` parameter. By default, it has a single + %% lager_file_backend handler writing messages to "$nodename.log" + %% (ie. the value of $RABBIT_LOGS). + %% {handlers, [ + %% {lager_file_backend, [{file, "rabbit.log"}, + %% {level, info}, + %% {date, ""}, + %% {size, 0}]} + %% ]}, + %% + %% Extra sinks are used in RabbitMQ to categorize messages. By + %% default, those extra sinks are configured to forward messages + %% to the default sink (see above). "rabbit_log_lager_event" + %% is the default category where all RabbitMQ messages without + %% a category go. Messages in the "channel" category go to the + %% "rabbit_channel_lager_event" Lager extra sink, and so on. + %% {extra_sinks, [ + %% {rabbit_log_lager_event, [{handlers, [ + %% {lager_forwarder_backend, + %% [lager_event, info]}]}]}, + %% {rabbit_channel_lager_event, [{handlers, [ + %% {lager_forwarder_backend, + %% [lager_event, info]}]}]}, + %% {rabbit_connection_lager_event, [{handlers, [ + %% {lager_forwarder_backend, + %% [lager_event, info]}]}]}, + %% {rabbit_mirroring_lager_event, [{handlers, [ + %% {lager_forwarder_backend, + %% [lager_event, info]}]}]} + %% ]} + ]} +]. diff --git a/docker/rabbitmq/rabbitmq/plugins/rabbitmq_delayed_message_exchange-3.9.0.ez b/docker/rabbitmq/rabbitmq/plugins/rabbitmq_delayed_message_exchange-3.9.0.ez new file mode 100644 index 0000000000000000000000000000000000000000..2301a0dec445c9539d98c4013950276ffbe30f89 GIT binary patch literal 36358 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 0 表示 Slave +brokerId=0 + +# nameServer地址,分号分割 +# namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876 + +# 启动IP,如果 docker 报 com.alibaba.rocketmq.remoting.exception.RemotingConnectException: connect to <192.168.0.88:10909> failed +# 解决方式1 加上一句 producer.setVipChannelEnabled(false);,解决方式2 brokerIP1 设置宿主机IP,不要使用docker 内部IP +# brokerIP1=192.168.0.88 + +# 在发送消息时,自动创建服务器不存在的topic,默认创建的队列数 +defaultTopicQueueNums=4 + +# 是否允许 Broker 自动创建 Topic,建议线下开启,线上关闭 !!!这里仔细看是 false,false,false +autoCreateTopicEnable=true + +# 是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭 +autoCreateSubscriptionGroup=true + +# Broker 对外服务的监听端口 +listenPort=10911 + +# 删除文件时间点,默认凌晨4点 +deleteWhen=04 + +# 文件保留时间,默认48小时 +fileReservedTime=120 + +# commitLog 每个文件的大小默认1G +mapedFileSizeCommitLog=1073741824 + +# ConsumeQueue 每个文件默认存 30W 条,根据业务情况调整 +mapedFileSizeConsumeQueue=300000 + +# destroyMapedFileIntervalForcibly=120000 +# redeleteHangedFileInterval=120000 +# 检测物理文件磁盘空间 +diskMaxUsedSpaceRatio=88 +# 存储路径 +# storePathRootDir=/home/ztztdata/rocketmq-all-4.1.0-incubating/store +# commitLog 存储路径 +# storePathCommitLog=/home/ztztdata/rocketmq-all-4.1.0-incubating/store/commitlog +# 消费队列存储 +# storePathConsumeQueue=/home/ztztdata/rocketmq-all-4.1.0-incubating/store/consumequeue +# 消息索引存储路径 +# storePathIndex=/home/ztztdata/rocketmq-all-4.1.0-incubating/store/index +# checkpoint 文件存储路径 +# storeCheckpoint=/home/ztztdata/rocketmq-all-4.1.0-incubating/store/checkpoint +# abort 文件存储路径 +# abortFile=/home/ztztdata/rocketmq-all-4.1.0-incubating/store/abort +# 限制的消息大小 +maxMessageSize=65536 + +# flushCommitLogLeastPages=4 +# flushConsumeQueueLeastPages=2 +# flushCommitLogThoroughInterval=10000 +# flushConsumeQueueThoroughInterval=60000 + +# Broker 的角色 +# - ASYNC_MASTER 异步复制Master +# - SYNC_MASTER 同步双写Master +# - SLAVE +brokerRole=ASYNC_MASTER + +# 刷盘方式 +# - ASYNC_FLUSH 异步刷盘 +# - SYNC_FLUSH 同步刷盘 +flushDiskType=ASYNC_FLUSH + +# 发消息线程池数量 +# sendMessageThreadPoolNums=128 +# 拉消息线程池数量 +# pullMessageThreadPoolNums=128 diff --git a/pom.xml b/pom.xml index 4714d02..d6100f8 100644 --- a/pom.xml +++ b/pom.xml @@ -196,6 +196,13 @@ ${weixin-java} + + + com.alipay.sdk + alipay-sdk-java + 4.33.39.ALL + + com.aliyun From 246e325e6531eb45ba30f66e7e773c72586d975b Mon Sep 17 00:00:00 2001 From: 3y Date: Sat, 26 Nov 2022 16:49:40 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E4=BC=98=E5=8C=96=20=E6=8B=89=E5=8F=96?= =?UTF-8?q?=E5=9B=9E=E6=89=A7=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/dto/account/TencentSmsAccount.java | 47 ------- .../common/dto/account/sms/SmsAccount.java | 8 +- .../dto/account/sms/TencentSmsAccount.java | 5 +- .../dto/account/sms/YunPianSmsAccount.java | 3 +- .../austin/handler/domain/sms/SmsParam.java | 2 +- .../austin/handler/handler/Handler.java | 1 - .../impl/DingDingWorkNoticeHandler.java | 19 +++ .../handler/handler/impl/PushHandler.java | 7 +- .../handler/handler/impl/SmsHandler.java | 1 - .../handler/receipt/DingDingWorkReceipt.java | 51 ------- .../handler/receipt/MessageReceipt.java | 19 ++- .../handler/receipt/SmsPullReceipt.java | 126 ------------------ .../receipt/stater/ReceiptMessageStater.java | 14 ++ .../impl/DingDingWorkReceiptStaterImpl.java | 33 +++++ .../impl/SmsPullReceiptStarterImpl.java | 60 +++++++++ .../austin/handler/script/BaseSmsScript.java | 42 ------ .../austin/handler/script/SmsScript.java | 13 +- .../handler/script/SmsScriptHandler.java | 27 ---- .../handler/script/SmsScriptHolder.java | 25 ---- .../handler/script/impl/TencentSmsScript.java | 78 ++++++++++- .../handler/script/impl/YunPianSmsScript.java | 7 + .../austin/support/utils/AccountUtils.java | 4 - 22 files changed, 240 insertions(+), 352 deletions(-) delete mode 100644 austin-common/src/main/java/com/java3y/austin/common/dto/account/TencentSmsAccount.java delete mode 100644 austin-handler/src/main/java/com/java3y/austin/handler/receipt/DingDingWorkReceipt.java delete mode 100644 austin-handler/src/main/java/com/java3y/austin/handler/receipt/SmsPullReceipt.java create mode 100644 austin-handler/src/main/java/com/java3y/austin/handler/receipt/stater/ReceiptMessageStater.java create mode 100644 austin-handler/src/main/java/com/java3y/austin/handler/receipt/stater/impl/DingDingWorkReceiptStaterImpl.java create mode 100644 austin-handler/src/main/java/com/java3y/austin/handler/receipt/stater/impl/SmsPullReceiptStarterImpl.java delete mode 100644 austin-handler/src/main/java/com/java3y/austin/handler/script/BaseSmsScript.java delete mode 100644 austin-handler/src/main/java/com/java3y/austin/handler/script/SmsScriptHandler.java delete mode 100644 austin-handler/src/main/java/com/java3y/austin/handler/script/SmsScriptHolder.java diff --git a/austin-common/src/main/java/com/java3y/austin/common/dto/account/TencentSmsAccount.java b/austin-common/src/main/java/com/java3y/austin/common/dto/account/TencentSmsAccount.java deleted file mode 100644 index 81a1353..0000000 --- a/austin-common/src/main/java/com/java3y/austin/common/dto/account/TencentSmsAccount.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.java3y.austin.common.dto.account; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * 腾讯短信参数 - * - * 参数示例: - * [{"sms_10":{"url":"sms.tencentcloudapi.com","region":"ap-guangzhou","secretId":"AKIDhDUUDfffffMEqBF1WljQq","secretKey":"B4h39yWnfffff7D2btue7JErDJ8gxyi","smsSdkAppId":"140025","templateId":"11897","signName":"Java3y公众号","supplierId":10,"supplierName":"腾讯云"}}] - * - * @author 3y - */ -@Data -@Builder -@AllArgsConstructor -@NoArgsConstructor -public class TencentSmsAccount { - - /** - * api相关 - */ - private String url; - private String region ; - - /** - * 账号相关 - */ - private String secretId; - private String secretKey; - private String smsSdkAppId; - private String templateId; - private String signName; - - /** - * 标识渠道商Id - */ - private Integer supplierId; - - /** - * 标识渠道商名字 - */ - private String supplierName; - -} diff --git a/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/SmsAccount.java b/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/SmsAccount.java index 31a5215..9d1c99f 100644 --- a/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/SmsAccount.java +++ b/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/SmsAccount.java @@ -1,7 +1,6 @@ package com.java3y.austin.common.dto.account.sms; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @@ -20,9 +19,10 @@ public class SmsAccount { protected String supplierName; /** - * 【重要】类名,定位到具体的处理下发和回执逻辑 - * - * 依据scriptName对应具体的某一个短信账号 + * 【重要】类名,定位到具体的处理"下发"/"回执"逻辑 + * 依据ScriptName对应具体的某一个短信账号 */ protected String scriptName; + + } diff --git a/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/TencentSmsAccount.java b/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/TencentSmsAccount.java index 1f28379..73fe7cf 100644 --- a/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/TencentSmsAccount.java +++ b/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/TencentSmsAccount.java @@ -1,13 +1,12 @@ package com.java3y.austin.common.dto.account.sms; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * 腾讯短信参数 - * + *

* 账号参数示例: * {"url":"sms.tencentcloudapi.com","region":"ap-guangzhou","secretId":"AKIDhDxxxxxxxx1WljQq","secretKey":"B4hwww39yxxxrrrrgxyi","smsSdkAppId":"1423123125","templateId":"1182097","signName":"Java3y公众号","supplierId":10,"supplierName":"腾讯云","scriptName":"TencentSmsScript"} * @@ -22,7 +21,7 @@ public class TencentSmsAccount extends SmsAccount { * api相关 */ private String url; - private String region ; + private String region; /** * 账号相关 diff --git a/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/YunPianSmsAccount.java b/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/YunPianSmsAccount.java index 1421569..f33e369 100644 --- a/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/YunPianSmsAccount.java +++ b/austin-common/src/main/java/com/java3y/austin/common/dto/account/sms/YunPianSmsAccount.java @@ -1,13 +1,12 @@ package com.java3y.austin.common.dto.account.sms; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * 云片账号信息 - * + *

* 账号参数示例: * {"url":"https://sms.yunpian.com/v2/sms/tpl_batch_send.json","apikey":"caffff8234234231b5cd7","tpl_id":"523333332","supplierId":20,"supplierName":"云片","scriptName":"YunPianSmsScript"} * diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/domain/sms/SmsParam.java b/austin-handler/src/main/java/com/java3y/austin/handler/domain/sms/SmsParam.java index f9cf01a..827b26d 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/domain/sms/SmsParam.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/domain/sms/SmsParam.java @@ -25,7 +25,7 @@ public class SmsParam { private Set phones; /** - * 发送渠道账号的脚本名标识 + * 渠道账号的脚本名标识 */ private String scriptName; diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/handler/Handler.java b/austin-handler/src/main/java/com/java3y/austin/handler/handler/Handler.java index d9c3165..88291b5 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/handler/Handler.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/handler/Handler.java @@ -24,5 +24,4 @@ public interface Handler { */ void recall(MessageTemplate messageTemplate); - } diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/DingDingWorkNoticeHandler.java b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/DingDingWorkNoticeHandler.java index 4f484d9..19e5954 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/DingDingWorkNoticeHandler.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/DingDingWorkNoticeHandler.java @@ -59,6 +59,7 @@ public class DingDingWorkNoticeHandler extends BaseHandler implements Handler { private static final String SEND_URL = "https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2"; private static final String RECALL_URL = "https://oapi.dingtalk.com/topapi/message/corpconversation/recall"; + private static final String PULL_URL = "https://oapi.dingtalk.com/topapi/message/corpconversation/getsendresult"; private static final String DING_DING_RECALL_KEY_PREFIX = "RECALL_"; private static final String RECALL_BIZ_TYPE = "DingDingWorkNoticeHandler#recall"; @@ -195,5 +196,23 @@ public class DingDingWorkNoticeHandler extends BaseHandler implements Handler { } }); } + + /** + * 拉取回执 + */ + public void pull(Long accountId) { + try { +// DingDingWorkNoticeAccount account = accountUtils.getAccountById(accountId, DingDingWorkNoticeAccount.class); +// String accessToken = redisTemplate.opsForValue().get(SendAccountConstant.DING_DING_ACCESS_TOKEN_PREFIX + accountId); +// DingTalkClient client = new DefaultDingTalkClient(PULL_URL); +// OapiMessageCorpconversationGetsendresultRequest req = new OapiMessageCorpconversationGetsendresultRequest(); +// req.setAgentId(Long.valueOf(account.getAgentId())); +// req.setTaskId(456L); +// OapiMessageCorpconversationGetsendresultResponse rsp = client.execute(req, accessToken); +// System.out.println(rsp.getBody()); + } catch (Exception e) { + log.error("DingDingWorkNoticeHandler#pull fail:{}", Throwables.getStackTraceAsString(e)); + } + } } diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/PushHandler.java b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/PushHandler.java index e131431..8856452 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/PushHandler.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/PushHandler.java @@ -58,7 +58,7 @@ public class PushHandler extends BaseHandler implements Handler { public boolean handler(TaskInfo taskInfo) { try { - GeTuiAccount account = accountUtils.getAccountById(taskInfo.getSendAccount(),GeTuiAccount.class); + GeTuiAccount account = accountUtils.getAccountById(taskInfo.getSendAccount(), GeTuiAccount.class); String token = redisTemplate.opsForValue().get(SendAccountConstant.GE_TUI_ACCESS_TOKEN_PREFIX + taskInfo.getSendAccount()); PushParam pushParam = PushParam.builder().token(token).appId(account.getAppId()).taskInfo(taskInfo).build(); @@ -83,6 +83,7 @@ public class PushHandler extends BaseHandler implements Handler { /** * 单推 + * * @param pushParam * @return http result */ @@ -101,7 +102,7 @@ public class PushHandler extends BaseHandler implements Handler { /** * 批量推送 * - * @param taskId 个推 返回的任务Id + * @param taskId 个推 返回的任务Id * @param pushParam * @return */ @@ -122,6 +123,7 @@ public class PushHandler extends BaseHandler implements Handler { /** * 群推前需要构建taskId + * * @param pushParam * @return http result */ @@ -161,6 +163,7 @@ public class PushHandler extends BaseHandler implements Handler { } return param; } + @Override public void recall(MessageTemplate messageTemplate) { diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/SmsHandler.java b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/SmsHandler.java index 8b2cb55..c4e1e7f 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/SmsHandler.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/handler/impl/SmsHandler.java @@ -5,7 +5,6 @@ import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.google.common.base.Throwables; -import com.java3y.austin.common.constant.AustinConstant; import com.java3y.austin.common.constant.CommonConstant; import com.java3y.austin.common.domain.TaskInfo; import com.java3y.austin.common.dto.model.SmsContentModel; diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/DingDingWorkReceipt.java b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/DingDingWorkReceipt.java deleted file mode 100644 index 50092f1..0000000 --- a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/DingDingWorkReceipt.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.java3y.austin.handler.receipt; - - -import cn.hutool.core.util.StrUtil; -import com.dingtalk.api.DefaultDingTalkClient; -import com.dingtalk.api.DingTalkClient; -import com.dingtalk.api.request.OapiMessageCorpconversationGetsendresultRequest; -import com.dingtalk.api.response.OapiMessageCorpconversationGetsendresultResponse; -import com.java3y.austin.common.constant.SendAccountConstant; -import com.java3y.austin.common.dto.account.DingDingWorkNoticeAccount; -import com.java3y.austin.support.utils.AccountUtils; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.stereotype.Component; - -/** - * 拉取钉钉工作消息回执信息 - * - * @author 3y - */ -@Component -@Slf4j -public class DingDingWorkReceipt { - - private static final String URL = "https://oapi.dingtalk.com/topapi/message/corpconversation/getsendresult"; - @Autowired - private StringRedisTemplate redisTemplate; - - @Autowired - private AccountUtils accountUtils; - public void pull() { -// try { -// for (int index = SendAccountConstant.START; true; index = index + SendAccountConstant.STEP) { -// DingDingWorkNoticeAccount account = accountUtils.getAccountById(index, SendAccountConstant.DING_DING_WORK_NOTICE_ACCOUNT_KEY, SendAccountConstant.DING_DING_WORK_NOTICE_PREFIX, DingDingWorkNoticeAccount.class); -// if (account == null) { -// break; -// } -// String accessToken = redisTemplate.opsForValue().get(SendAccountConstant.DING_DING_ACCESS_TOKEN_PREFIX + index); -// DingTalkClient client = new DefaultDingTalkClient(URL); -// OapiMessageCorpconversationGetsendresultRequest req = new OapiMessageCorpconversationGetsendresultRequest(); -// req.setAgentId(Long.valueOf(account.getAgentId())); -// req.setTaskId(456L); -// OapiMessageCorpconversationGetsendresultResponse rsp = client.execute(req, accessToken); -// System.out.println(rsp.getBody()); -// } -// } catch (Exception e) { -// log.error("DingDingWorkReceipt#pull"); -// } - } -} diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/MessageReceipt.java b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/MessageReceipt.java index 1ad7ce3..a5cd6c9 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/MessageReceipt.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/MessageReceipt.java @@ -1,15 +1,18 @@ package com.java3y.austin.handler.receipt; +import com.google.common.base.Throwables; +import com.java3y.austin.handler.receipt.stater.ReceiptMessageStater; import com.java3y.austin.support.config.SupportThreadPoolConfig; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; +import java.util.List; /** - * 拉取回执信息 + * 拉取回执信息 入口 * * @author 3y */ @@ -18,18 +21,20 @@ import javax.annotation.PostConstruct; public class MessageReceipt { @Autowired - private SmsPullReceipt smsPullReceipt; + private List receiptMessageStaterList; @PostConstruct private void init() { SupportThreadPoolConfig.getPendingSingleThreadPool().execute(() -> { while (true) { - // TODO 回执这里自行打开(免得报错) - // smsPullReceipt.pull(); try { - Thread.sleep(200); - } catch (InterruptedException e) { - e.printStackTrace(); + for (ReceiptMessageStater receiptMessageStater : receiptMessageStaterList) { + // 拉取回执需要打开下面一行注释 + //receiptMessageStater.start(); + } + Thread.sleep(2000); + } catch (Exception e) { + log.error("MessageReceiptApplication#fail:{}", Throwables.getStackTraceAsString(e)); } } }); diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/SmsPullReceipt.java b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/SmsPullReceipt.java deleted file mode 100644 index 94ef07f..0000000 --- a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/SmsPullReceipt.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.java3y.austin.handler.receipt; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DatePattern; -import cn.hutool.core.date.DateUtil; -import com.alibaba.fastjson.JSON; -import com.google.common.base.Throwables; -import com.java3y.austin.common.constant.CommonConstant; -import com.java3y.austin.common.dto.account.sms.TencentSmsAccount; -import com.java3y.austin.common.dto.account.sms.YunPianSmsAccount; -import com.java3y.austin.common.enums.ChannelType; -import com.java3y.austin.common.enums.SmsStatus; -import com.java3y.austin.common.enums.SmsSupplier; -import com.java3y.austin.support.dao.ChannelAccountDao; -import com.java3y.austin.support.dao.SmsRecordDao; -import com.java3y.austin.support.domain.ChannelAccount; -import com.java3y.austin.support.domain.SmsRecord; -import com.tencentcloudapi.common.Credential; -import com.tencentcloudapi.common.profile.ClientProfile; -import com.tencentcloudapi.common.profile.HttpProfile; -import com.tencentcloudapi.sms.v20210111.SmsClient; -import com.tencentcloudapi.sms.v20210111.models.PullSmsSendStatus; -import com.tencentcloudapi.sms.v20210111.models.PullSmsSendStatusRequest; -import com.tencentcloudapi.sms.v20210111.models.PullSmsSendStatusResponse; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - - -/** - * 拉取短信回执信息 - * - * @author 3y - */ -@Component -@Slf4j -public class SmsPullReceipt { - - @Autowired - private ChannelAccountDao channelAccountDao; - - @Autowired - private SmsRecordDao smsRecordDao; - - /** - * 拉取消息并入库 - *

- * eg accountList:[{"sms_10":{"url":"sms.tencentcloudapi.com","region":"ap-guangzhou","secretId":"234234","secretKey":"234324324","smsSdkAppId":"2343242","templateId":"234234","signName":"Java3y公众号","supplierId":10,"supplierName":"腾讯云"}},{"sms_20":{"url":"https://sms.yunpian.com/v2/sms/tpl_batch_send.json","apikey":"23423432","tpl_id":"23423432","supplierId":20,"supplierName":"云片"}}] - */ - public void pull() { - List channelAccountList = channelAccountDao.findAllByIsDeletedEqualsAndSendChannelEquals(CommonConstant.FALSE, ChannelType.SMS.getCode()); - for (ChannelAccount channelAccount : channelAccountList) { - Integer supplierId = JSON.parseObject(channelAccount.getAccountConfig()).getInteger("supplierId"); - if (SmsSupplier.TENCENT.getCode().equals(supplierId)) { - TencentSmsAccount tencentSmsAccount = JSON.parseObject(channelAccount.getAccountConfig(), TencentSmsAccount.class); - pullTencent(tencentSmsAccount); - } else if (SmsSupplier.YUN_PAIN.getCode().equals(supplierId)) { - YunPianSmsAccount yunPianSmsAccount = JSON.parseObject(channelAccount.getAccountConfig(), YunPianSmsAccount.class); - pullYunPain(yunPianSmsAccount); - } - } - } - - /** - * 拉取腾讯的回执 - * - * @param account - */ - private void pullTencent(TencentSmsAccount account) { - try { - /** - * 初始化客户端 - */ - HttpProfile httpProfile = new HttpProfile(); - httpProfile.setEndpoint(account.getUrl()); - ClientProfile clientProfile = new ClientProfile(); - clientProfile.setHttpProfile(httpProfile); - SmsClient client = new SmsClient(new Credential(account.getSecretId(), account.getSecretKey()), account.getRegion(), clientProfile); - - /** - * 每次拉取10条 - */ - PullSmsSendStatusRequest req = new PullSmsSendStatusRequest(); - req.setLimit(10L); - req.setSmsSdkAppId(account.getSmsSdkAppId()); - - /** - * 拉取回执后入库 - */ - PullSmsSendStatusResponse resp = client.PullSmsSendStatus(req); - List smsRecordList = new ArrayList<>(); - if (resp != null && resp.getPullSmsSendStatusSet() != null && resp.getPullSmsSendStatusSet().length > 0) { - for (PullSmsSendStatus pullSmsSendStatus : resp.getPullSmsSendStatusSet()) { - SmsRecord smsRecord = SmsRecord.builder() - .sendDate(Integer.valueOf(DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN))) - .messageTemplateId(0L) - .phone(Long.valueOf(pullSmsSendStatus.getSubscriberNumber())) - .supplierId(account.getSupplierId()) - .supplierName(account.getSupplierName()) - .msgContent("") - .seriesId(pullSmsSendStatus.getSerialNo()) - .chargingNum(0) - .status("SUCCESS".equals(pullSmsSendStatus.getReportStatus()) ? SmsStatus.RECEIVE_SUCCESS.getCode() : SmsStatus.RECEIVE_FAIL.getCode()) - .reportContent(pullSmsSendStatus.getDescription()) - .updated(Math.toIntExact(pullSmsSendStatus.getUserReceiveTime())) - .created(Math.toIntExact(DateUtil.currentSeconds())) - .build(); - smsRecordList.add(smsRecord); - } - } - if (!CollUtil.isEmpty(smsRecordList)) { - smsRecordDao.saveAll(smsRecordList); - } - } catch (Exception e) { - log.error("TencentSmsReceipt#init fail!{}", Throwables.getStackTraceAsString(e)); - } - } - - private void pullYunPain(YunPianSmsAccount yunPianSmsAccount) { - - } -} diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/stater/ReceiptMessageStater.java b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/stater/ReceiptMessageStater.java new file mode 100644 index 0000000..5895227 --- /dev/null +++ b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/stater/ReceiptMessageStater.java @@ -0,0 +1,14 @@ +package com.java3y.austin.handler.receipt.stater; + +/** + * 拉取回执信息 启动器 + * + * @author 3y + */ +public interface ReceiptMessageStater { + + /** + * 启动拉取回执 + */ + void start(); +} diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/stater/impl/DingDingWorkReceiptStaterImpl.java b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/stater/impl/DingDingWorkReceiptStaterImpl.java new file mode 100644 index 0000000..aca3495 --- /dev/null +++ b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/stater/impl/DingDingWorkReceiptStaterImpl.java @@ -0,0 +1,33 @@ +package com.java3y.austin.handler.receipt.stater.impl; + +import com.java3y.austin.common.constant.CommonConstant; +import com.java3y.austin.common.enums.ChannelType; +import com.java3y.austin.handler.handler.impl.DingDingWorkNoticeHandler; +import com.java3y.austin.handler.receipt.stater.ReceiptMessageStater; +import com.java3y.austin.support.dao.ChannelAccountDao; +import com.java3y.austin.support.domain.ChannelAccount; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +/** + * 拉取 钉钉工作消息的回执 内容 【未完成】 + * + * @author 3y + */ +public class DingDingWorkReceiptStaterImpl implements ReceiptMessageStater { + + @Autowired + private DingDingWorkNoticeHandler workNoticeHandler; + + @Autowired + private ChannelAccountDao channelAccountDao; + + @Override + public void start() { + List accountList = channelAccountDao.findAllByIsDeletedEqualsAndSendChannelEquals(CommonConstant.FALSE, ChannelType.DING_DING_WORK_NOTICE.getCode()); + for (ChannelAccount channelAccount : accountList) { + workNoticeHandler.pull(channelAccount.getId()); + } + } +} diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/receipt/stater/impl/SmsPullReceiptStarterImpl.java b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/stater/impl/SmsPullReceiptStarterImpl.java new file mode 100644 index 0000000..39e8728 --- /dev/null +++ b/austin-handler/src/main/java/com/java3y/austin/handler/receipt/stater/impl/SmsPullReceiptStarterImpl.java @@ -0,0 +1,60 @@ +package com.java3y.austin.handler.receipt.stater.impl; + +import cn.hutool.core.collection.CollUtil; +import com.alibaba.fastjson.JSON; +import com.google.common.base.Throwables; +import com.java3y.austin.common.constant.CommonConstant; +import com.java3y.austin.common.dto.account.sms.SmsAccount; +import com.java3y.austin.common.enums.ChannelType; +import com.java3y.austin.handler.receipt.stater.ReceiptMessageStater; +import com.java3y.austin.handler.script.SmsScript; +import com.java3y.austin.support.dao.ChannelAccountDao; +import com.java3y.austin.support.dao.SmsRecordDao; +import com.java3y.austin.support.domain.ChannelAccount; +import com.java3y.austin.support.domain.SmsRecord; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + + +/** + * 拉取短信回执信息 + * + * @author 3y + */ +@Component +@Slf4j +public class SmsPullReceiptStarterImpl implements ReceiptMessageStater { + + @Autowired + private ChannelAccountDao channelAccountDao; + + @Autowired + private Map scriptMap; + + @Autowired + private SmsRecordDao smsRecordDao; + + /** + * 拉取消息并入库 + */ + public void start() { + try { + List channelAccountList = channelAccountDao.findAllByIsDeletedEqualsAndSendChannelEquals(CommonConstant.FALSE, ChannelType.SMS.getCode()); + for (ChannelAccount channelAccount : channelAccountList) { + SmsAccount smsAccount = JSON.parseObject(channelAccount.getAccountConfig(), SmsAccount.class); + List smsRecordList = scriptMap.get(smsAccount.getScriptName()).pull(smsAccount.getScriptName()); + if (CollUtil.isNotEmpty(smsRecordList)) { + smsRecordDao.saveAll(smsRecordList); + } + } + } catch (Exception e) { + log.error("SmsPullReceiptStarter#start fail:{}", Throwables.getStackTraceAsString(e)); + + } + + } +} diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/script/BaseSmsScript.java b/austin-handler/src/main/java/com/java3y/austin/handler/script/BaseSmsScript.java deleted file mode 100644 index 931cb5c..0000000 --- a/austin-handler/src/main/java/com/java3y/austin/handler/script/BaseSmsScript.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.java3y.austin.handler.script; - - -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.ArrayUtils; -import org.springframework.beans.factory.annotation.Autowired; - -import javax.annotation.PostConstruct; -import java.lang.annotation.Annotation; - -/** - * sms发送脚本的抽象类 - * - * @author 3y - */ -@Slf4j -public abstract class BaseSmsScript implements SmsScript { - - @Autowired - private SmsScriptHolder smsScriptHolder; - - @PostConstruct - public void registerProcessScript() { - if (ArrayUtils.isEmpty(this.getClass().getAnnotations())) { - log.error("BaseSmsScript can not find annotation!"); - return; - } - Annotation handlerAnnotations = null; - for (Annotation annotation : this.getClass().getAnnotations()) { - if (annotation instanceof SmsScriptHandler) { - handlerAnnotations = annotation; - break; - } - } - if (handlerAnnotations == null) { - log.error("handler annotations not declared"); - return; - } - //注册handler - smsScriptHolder.putHandler(((SmsScriptHandler) handlerAnnotations).value(), this); - } -} diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/script/SmsScript.java b/austin-handler/src/main/java/com/java3y/austin/handler/script/SmsScript.java index 35d6e98..4facaba 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/script/SmsScript.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/script/SmsScript.java @@ -9,16 +9,25 @@ import java.util.List; /** * 短信脚本 接口 + * * @author 3y */ public interface SmsScript { /** * 发送短信 + * * @param smsParam - * @return 渠道商接口返回值 - + * @return 渠道商发送接口返回值 */ List send(SmsParam smsParam); + + /** + * 拉取回执 + * @param scriptName 标识账号的脚本名 + * @return 渠道商回执接口返回值 + */ + List pull(String scriptName); + } diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/script/SmsScriptHandler.java b/austin-handler/src/main/java/com/java3y/austin/handler/script/SmsScriptHandler.java deleted file mode 100644 index 7b285e8..0000000 --- a/austin-handler/src/main/java/com/java3y/austin/handler/script/SmsScriptHandler.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.java3y.austin.handler.script; - -import org.springframework.stereotype.Component; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - - -/** - * 标识 短信渠道 - * - * @author 3y - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Component -public @interface SmsScriptHandler { - - /** - * 这里输入脚本名 - * - * @return - */ - String value(); -} diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/script/SmsScriptHolder.java b/austin-handler/src/main/java/com/java3y/austin/handler/script/SmsScriptHolder.java deleted file mode 100644 index b02244e..0000000 --- a/austin-handler/src/main/java/com/java3y/austin/handler/script/SmsScriptHolder.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.java3y.austin.handler.script; - - -import org.springframework.stereotype.Component; - -import java.util.HashMap; -import java.util.Map; - -/** - * sendAccount->SmsScript的映射关系 - * - * @author 3y - */ -@Component -public class SmsScriptHolder { - - private Map handlers = new HashMap<>(8); - - public void putHandler(String scriptName, SmsScript handler) { - handlers.put(scriptName, handler); - } - public SmsScript route(String scriptName) { - return handlers.get(scriptName); - } -} diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/TencentSmsScript.java b/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/TencentSmsScript.java index 32cfec0..4681020 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/TencentSmsScript.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/TencentSmsScript.java @@ -16,9 +16,7 @@ import com.tencentcloudapi.common.Credential; import com.tencentcloudapi.common.profile.ClientProfile; import com.tencentcloudapi.common.profile.HttpProfile; import com.tencentcloudapi.sms.v20210111.SmsClient; -import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest; -import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse; -import com.tencentcloudapi.sms.v20210111.models.SendStatus; +import com.tencentcloudapi.sms.v20210111.models.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -49,17 +47,38 @@ public class TencentSmsScript implements SmsScript { try { TencentSmsAccount tencentSmsAccount = accountUtils.getSmsAccountByScriptName(smsParam.getScriptName(), TencentSmsAccount.class); SmsClient client = init(tencentSmsAccount); - SendSmsRequest request = assembleReq(smsParam, tencentSmsAccount); + SendSmsRequest request = assembleSendReq(smsParam, tencentSmsAccount); SendSmsResponse response = client.SendSms(request); - return assembleSmsRecord(smsParam, response, tencentSmsAccount); + return assembleSendSmsRecord(smsParam, response, tencentSmsAccount); } catch (Exception e) { log.error("TencentSmsScript#send fail:{},params:{}", Throwables.getStackTraceAsString(e), JSON.toJSONString(smsParam)); return null; } } + @Override + public List pull(String scriptName) { + try { + TencentSmsAccount account = accountUtils.getSmsAccountByScriptName(scriptName, TencentSmsAccount.class); + SmsClient client = init(account); + PullSmsSendStatusRequest req = assemblePullReq(account); + PullSmsSendStatusResponse resp = client.PullSmsSendStatus(req); + return assemblePullSmsRecord(account, resp); + } catch (Exception e) { + log.error("TencentSmsReceipt#pull fail!{}", Throwables.getStackTraceAsString(e)); + return null; + } + } - private List assembleSmsRecord(SmsParam smsParam, SendSmsResponse response, TencentSmsAccount tencentSmsAccount) { + /** + * 组装 发送消息的 返回值 + * + * @param smsParam + * @param response + * @param tencentSmsAccount + * @return + */ + private List assembleSendSmsRecord(SmsParam smsParam, SendSmsResponse response, TencentSmsAccount tencentSmsAccount) { if (response == null || ArrayUtil.isEmpty(response.getSendStatusSet())) { return null; } @@ -94,7 +113,7 @@ public class TencentSmsScript implements SmsScript { /** * 组装发送短信参数 */ - private SendSmsRequest assembleReq(SmsParam smsParam, TencentSmsAccount account) { + private SendSmsRequest assembleSendReq(SmsParam smsParam, TencentSmsAccount account) { SendSmsRequest req = new SendSmsRequest(); String[] phoneNumberSet1 = smsParam.getPhones().toArray(new String[smsParam.getPhones().size() - 1]); req.setPhoneNumberSet(phoneNumberSet1); @@ -122,5 +141,50 @@ public class TencentSmsScript implements SmsScript { return client; } + /** + * 组装 拉取回执信息 + * + * @param account + * @param resp + * @return + */ + private List assemblePullSmsRecord(TencentSmsAccount account, PullSmsSendStatusResponse resp) { + List smsRecordList = new ArrayList<>(); + if (resp != null && resp.getPullSmsSendStatusSet() != null && resp.getPullSmsSendStatusSet().length > 0) { + for (PullSmsSendStatus pullSmsSendStatus : resp.getPullSmsSendStatusSet()) { + SmsRecord smsRecord = SmsRecord.builder() + .sendDate(Integer.valueOf(DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN))) + .messageTemplateId(0L) + .phone(Long.valueOf(pullSmsSendStatus.getSubscriberNumber())) + .supplierId(account.getSupplierId()) + .supplierName(account.getSupplierName()) + .msgContent("") + .seriesId(pullSmsSendStatus.getSerialNo()) + .chargingNum(0) + .status("SUCCESS".equals(pullSmsSendStatus.getReportStatus()) ? SmsStatus.RECEIVE_SUCCESS.getCode() : SmsStatus.RECEIVE_FAIL.getCode()) + .reportContent(pullSmsSendStatus.getDescription()) + .updated(Math.toIntExact(pullSmsSendStatus.getUserReceiveTime())) + .created(Math.toIntExact(DateUtil.currentSeconds())) + .build(); + smsRecordList.add(smsRecord); + } + } + return smsRecordList; + } + + /** + * 组装 拉取回执 入参 + * + * @param account + * @return + */ + private PullSmsSendStatusRequest assemblePullReq(TencentSmsAccount account) { + PullSmsSendStatusRequest req = new PullSmsSendStatusRequest(); + req.setLimit(10L); + req.setSmsSdkAppId(account.getSmsSdkAppId()); + return req; + } + + } diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/YunPianSmsScript.java b/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/YunPianSmsScript.java index 021e789..991096f 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/YunPianSmsScript.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/script/impl/YunPianSmsScript.java @@ -56,6 +56,13 @@ public class YunPianSmsScript implements SmsScript { } + @Override + public List pull(String scriptName) { + YunPianSmsAccount account = accountUtils.getSmsAccountByScriptName(scriptName, YunPianSmsAccount.class); + // ..... + return null; + } + /** * 组装参数 * diff --git a/austin-support/src/main/java/com/java3y/austin/support/utils/AccountUtils.java b/austin-support/src/main/java/com/java3y/austin/support/utils/AccountUtils.java index afd5d75..813641b 100644 --- a/austin-support/src/main/java/com/java3y/austin/support/utils/AccountUtils.java +++ b/austin-support/src/main/java/com/java3y/austin/support/utils/AccountUtils.java @@ -1,10 +1,7 @@ package com.java3y.austin.support.utils; import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; import com.google.common.base.Throwables; -import com.java3y.austin.common.constant.AustinConstant; import com.java3y.austin.common.constant.CommonConstant; import com.java3y.austin.common.dto.account.sms.SmsAccount; import com.java3y.austin.common.enums.ChannelType; @@ -84,7 +81,6 @@ public class AccountUtils { } catch (Exception e) { log.error("AccountUtils#getSmsAccount fail! e:{}", Throwables.getStackTraceAsString(e)); } - log.error("AccountUtils#getSmsAccount not found!:{}", scriptName); return null; } From 65059e8cf29e35d46ea60d18edc6f6bffbd4d1e3 Mon Sep 17 00:00:00 2001 From: 3y Date: Sun, 27 Nov 2022 10:44:37 +0800 Subject: [PATCH 4/5] update READMD.MD --- README.md | 31 +++++++++++++++++-------------- sql/austin.sql | 26 -------------------------- 2 files changed, 17 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index bf70fd4..ca4c95b 100644 --- a/README.md +++ b/README.md @@ -63,14 +63,14 @@ austin项目**核心流程**:`austin-api`接收到发送消息请求,直接 目前引用的中间件教程的安装姿势均基于`Centos 7.6`,austin项目**强依赖**`MySQL`/`Redis`/(**大概需要2G内存**),**弱依赖**`kafka`/`prometheus`/`graylog`/`flink`/`xxl-job`/`apollo`(**完全部署所有的服务,大概8G+内存**)。如果缺少相关的组件可戳:[安装相关组件教程](INSTALL.md)。 -> 实在想要`clone`项目后不用自己部署环境直接在本地启动`debug`,我这提供了[会员服务](https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247505577&idx=1&sn=5114f8f583755899c2946fbea0b22e4b&chksm=ebd497a8dca31ebe8f98344483a00c860863dfc3586e51eed95b25988151427fee8101311f4f&token=735778370&lang=zh_CN#rd),**直连**部署好的服务器 +> 实在想要`clone`项目后不用自己部署环境直接在本地启动`debug`,我这提供了[会员服务](https://mp.weixin.qq.com/s/4cOhNG5JBSvBkbSKTfw7eQ),**直连**部署好的服务器 **1**、austin使用的MySQL版本**5.7x**。如果目前使用的MySQL版本8.0,注意改变`pom.xml`所依赖的版本 **2**、填写`application.properties`中`austin-database`对应的`ip/port/username/password`信息 -**3**、执行`sql`文件夹下的`austin.sql`创建对应的表以及插入测试数据 +**3**、执行`sql`文件夹下的`austin.sql`创建对应的表 **4**、如果配置`austin.mq.pipeline=kafka`,需要填写`application.properties`中`austin.kafka`对应的`ip`/`port`信息 @@ -80,15 +80,7 @@ austin项目**核心流程**:`austin-api`接收到发送消息请求,直接 **7**、以上配置信息都在`application.properties`文件中修改。(`prometheus`/`graylog`/`flink`/`xxl-job`/`apollo`可选) -**8**、发送渠道**账号的信息**都配置在**local.properties**,配置的示例参照`com.java3y.austin.support.utils#getAccount`中的注释 - -**10**、调用http接口`com.java3y.austin.web.controller#send`给自己发一条邮件或短信感受(**邮件门槛相对较低,建议配置邮件**) - -```shell -curl -XPOST "127.0.0.1:8080/send" -H 'Content-Type: application/json' -d '{"code":"send","messageParam":{"extra":null,"receiver":"13719333899"},"messageTemplateId":1}' -``` - -**11**、austin前端管理系统部署(一分钟即能打开),戳[GitHub](https://github.com/ZhongFuCheng3y/austin-admin)或[Gitee](https://gitee.com/zhongfucheng/austin-admin)查看 +**8**、austin前端管理系统部署(一分钟即能打开),戳[GitHub](https://github.com/ZhongFuCheng3y/austin-admin)或[Gitee](https://gitee.com/zhongfucheng/austin-admin)查看 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a023d9082fa644bda9b50144e02985cb~tplv-k3u1fbpfcp-zoom-1.image) @@ -98,6 +90,14 @@ curl -XPOST "127.0.0.1:8080/send" -H 'Content-Type: application/json' -d '{"co ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/01d028359e6e4229825a7fd8cc22c6c7~tplv-k3u1fbpfcp-zoom-1.image) +**9**、发送渠道**账号的信息**都在**账号**,配置的示例可参照对应的提示案例 + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2039b96045494ded8e6f55528eb20b1d~tplv-k3u1fbpfcp-watermark.image?) + +**10**、新建一个模板消息(**邮件门槛相对较低,建议配置邮件**) + +**11**、测试发送一条消息 + **12**、正常使用**数据管理**(查看实时数据链路下发)需要将`austin-stream`的`jar`包上传至`Flink`,根据[部署文档](INSTALL.md)启动Flink。在打`jar`包前需要填写`com.java3y.austin.stream.constants.AustinFlinkConstant`中的`redis`和`kafka`的`ip/port`(注:日志的topic在`application.properties`中的`austin.business.log.topic.name`。如果没有该topic,需要提前创建,并使用Kafka作为消息队列实现) **13**、正常使用**定时任务**需要部署`xxl-job`,根据[部署文档](INSTALL.md)启动xxl的调度中心,并在`application.properteis`中填写 `austin.xxl.job.ip`和`austin.xxl.job.port` @@ -124,7 +124,7 @@ curl -XPOST "127.0.0.1:8080/send" -H 'Content-Type: application/json' -d '{"co 5、除了项目,还可以问我些学习经验、学习路线、简历编写、面试经验等等问题,技术和学习上的知识**知无不言** -详情可以看戳:[我开通了付费渠道](https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247507166&idx=1&sn=d4437089c2db18b90a6d3ec742380554&chksm=ebd49ddfdca314c94d49a02da2ecb1358ac08d86616b6f1fce34720cc96e81d3006a51e86beb&token=28465847&lang=zh_CN#rd) +详情可以看戳:[我开通了付费渠道](https://mp.weixin.qq.com/s/4cOhNG5JBSvBkbSKTfw7eQ) ## 项目交流 @@ -147,6 +147,8 @@ curl -XPOST "127.0.0.1:8080/send" -H 'Content-Type: application/json' -d '{"co ## 里程碑 +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/80fc1c550ee145eda95cfac4bd617a04~tplv-k3u1fbpfcp-watermark.image?) + - [x] Maven+SpringBoot项目搭建 - [x] logback日志记录项目运行时信息,引入common/guava/Hutool/Lombok/fastjson/OkHttp工具包 - [x] 接入腾讯云渠道发送一条短信 @@ -178,9 +180,10 @@ curl -XPOST "127.0.0.1:8080/send" -H 'Content-Type: application/json' -d '{"co - [x] 完成接入钉钉工作渠道所有类型的消息,包括对文件素材的上传功能 - [x] Kafka消息支持tag过滤 - [x] MQ层支持可插拔,默认使用eventbus单机队列,Kafka变为弱依赖 +- [x] 渠道账号改为读取数据库,优化短信回执拉取功能 - [ ] 总体架构已完成,持续做基础建设和优化代码 -**近期更新时间**:7月11号 +**近期更新时间**:11月27号 -**近期更新功能**:MQ层可插拔重构 \ No newline at end of file +**近期更新功能**:渠道账号改为读取数据库,优化短信回执拉取功能 \ No newline at end of file diff --git a/sql/austin.sql b/sql/austin.sql index 7975658..f23e2ec 100644 --- a/sql/austin.sql +++ b/sql/austin.sql @@ -77,29 +77,3 @@ CREATE TABLE `channel_account` AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT ='渠道账号信息'; - - --- 实时类型 短信(无占位符) -INSERT INTO austin.message_template (id, name, audit_status, flow_id, msg_status, cron_task_id, cron_crowd_path, - expect_push_time, id_type, send_channel, template_type, msg_type, msg_content, - send_account, creator, updator, auditor, team, proposer, is_deleted, created, - updated) -VALUES (1, '买一送十活动', 10, '', 10, null, '', '', 30, 30, 20, 20, '{"content":"6666","url":"","title":""}', 10, 'Java3y', - 'Java3y', '3y', '公众号Java3y', '三歪', 0, 1646274112, 1646275242); - --- 实时类型 邮件(无占位符) -INSERT INTO austin.message_template (id, name, audit_status, flow_id, msg_status, cron_task_id, cron_crowd_path, - expect_push_time, id_type, send_channel, template_type, msg_type, msg_content, - send_account, creator, updator, auditor, team, proposer, is_deleted, created, - updated) -VALUES (2, '校招信息', 10, '', 10, null, '', '', 50, 40, 20, 10, '{"content":"你已成功获取到offer","url":"","title":"招聘通知"}', 10, - 'Java3y', 'Java3y', '3y', '公众号Java3y', '鸡蛋', 0, 1646274195, 1646274195); - --- 实时类型 短信(有占位符)占位符key 为 content -INSERT INTO austin.message_template (id, name, audit_status, flow_id, msg_status, cron_task_id, cron_crowd_path, - expect_push_time, id_type, send_channel, template_type, msg_type, msg_content, - send_account, creator, updator, auditor, team, proposer, is_deleted, created, - updated) -VALUES (3, '验证码通知', 10, '', 10, null, '', '', 30, 30, 20, 30, '{"content":"{$content}","url":"","title":""}', 10, - 'Java3y', 'Java3y', '3y', '公众号Java3y', '孙悟空', 0, 1646275213, 1646275213); - From d87b501a1b6f0574cf5176d5b5f6ba044a7fd6f6 Mon Sep 17 00:00:00 2001 From: 3y Date: Mon, 28 Nov 2022 19:12:10 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=8F=B7=E6=8E=A8=E9=80=81=EF=BC=9A=E8=8E=B7=E5=8F=96=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- austin-handler/pom.xml | 11 +- .../impl/OfficialAccountServiceImpl.java | 6 +- austin-support/pom.xml | 20 +++- .../austin/support/utils/WxServiceUtils.java | 60 +++++++++++ .../controller/ChannelAccountController.java | 11 +- .../controller/OfficialAccountController.java | 100 ++++++++++++++++++ .../austin/web/vo/amis/CommonAmisVo.java | 27 +++++ 7 files changed, 211 insertions(+), 24 deletions(-) create mode 100644 austin-support/src/main/java/com/java3y/austin/support/utils/WxServiceUtils.java create mode 100644 austin-web/src/main/java/com/java3y/austin/web/controller/OfficialAccountController.java create mode 100644 austin-web/src/main/java/com/java3y/austin/web/vo/amis/CommonAmisVo.java diff --git a/austin-handler/pom.xml b/austin-handler/pom.xml index eb6ef31..94ca8bb 100644 --- a/austin-handler/pom.xml +++ b/austin-handler/pom.xml @@ -41,16 +41,7 @@ com.sun.mail javax.mail - - - com.github.binarywang - weixin-java-mp - - - - com.github.binarywang - weixin-java-miniapp - + diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/wechat/impl/OfficialAccountServiceImpl.java b/austin-handler/src/main/java/com/java3y/austin/handler/wechat/impl/OfficialAccountServiceImpl.java index a4359d2..6ce3703 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/wechat/impl/OfficialAccountServiceImpl.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/wechat/impl/OfficialAccountServiceImpl.java @@ -1,10 +1,10 @@ package com.java3y.austin.handler.wechat.impl; -import com.java3y.austin.common.constant.SendAccountConstant; import com.java3y.austin.common.dto.account.WeChatOfficialAccount; import com.java3y.austin.handler.domain.wechat.WeChatOfficialParam; import com.java3y.austin.handler.wechat.OfficialAccountService; import com.java3y.austin.support.utils.AccountUtils; +import com.java3y.austin.support.utils.WxServiceUtils; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; @@ -31,8 +31,8 @@ public class OfficialAccountServiceImpl implements OfficialAccountService { @Override public List send(WeChatOfficialParam officialParam) throws Exception { - WeChatOfficialAccount officialAccount = accountUtils.getAccountById(officialParam.getSendAccount(),WeChatOfficialAccount.class); - WxMpService wxMpService = initService(officialAccount); + WxMpService wxMpService = WxServiceUtils.wxMpServiceMap.get(officialParam.getSendAccount()); + WeChatOfficialAccount officialAccount = WxServiceUtils.accountHashMap.get(officialParam.getSendAccount()); List messages = assembleReq(officialParam, officialAccount); List messageIds = new ArrayList<>(messages.size()); for (WxMpTemplateMessage wxMpTemplateMessage : messages) { diff --git a/austin-support/pom.xml b/austin-support/pom.xml index f6dfb2b..0a20790 100644 --- a/austin-support/pom.xml +++ b/austin-support/pom.xml @@ -94,21 +94,31 @@ dynamic-tp-spring-boot-starter-apollo - - com.aliyun - alibaba-dingtalk-service-sdk - org.springframework.amqp spring-rabbit - com.alibaba.boot nacos-config-spring-boot-starter + + com.aliyun + alibaba-dingtalk-service-sdk + + + + com.github.binarywang + weixin-java-mp + + + + com.github.binarywang + weixin-java-miniapp + + \ No newline at end of file diff --git a/austin-support/src/main/java/com/java3y/austin/support/utils/WxServiceUtils.java b/austin-support/src/main/java/com/java3y/austin/support/utils/WxServiceUtils.java new file mode 100644 index 0000000..cc39aba --- /dev/null +++ b/austin-support/src/main/java/com/java3y/austin/support/utils/WxServiceUtils.java @@ -0,0 +1,60 @@ +package com.java3y.austin.support.utils; + + +import com.alibaba.fastjson.JSON; +import com.java3y.austin.common.constant.CommonConstant; +import com.java3y.austin.common.dto.account.WeChatOfficialAccount; +import com.java3y.austin.common.enums.ChannelType; +import com.java3y.austin.support.dao.ChannelAccountDao; +import com.java3y.austin.support.domain.ChannelAccount; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 微信服务号/微信小程序 工具类 + * + * @author 3y + */ +@Component +@Slf4j +public class WxServiceUtils { + + public static Map wxMpServiceMap = new HashMap<>(); + public static Map accountHashMap = new HashMap<>(); + + @Autowired + private ChannelAccountDao channelAccountDao; + + @PostConstruct + public void init() { + List officialAccountList = channelAccountDao.findAllByIsDeletedEqualsAndSendChannelEquals(CommonConstant.FALSE, ChannelType.OFFICIAL_ACCOUNT.getCode()); + for (ChannelAccount channelAccount : officialAccountList) { + WeChatOfficialAccount weChatOfficialAccount = JSON.parseObject(channelAccount.getAccountConfig(), WeChatOfficialAccount.class); + wxMpServiceMap.put(channelAccount.getId(), initService(weChatOfficialAccount)); + accountHashMap.put(channelAccount.getId(), weChatOfficialAccount); + } + } + + /** + * 初始化微信服务号 + * + * @return + */ + public WxMpService initService(WeChatOfficialAccount officialAccount) { + WxMpService wxMpService = new WxMpServiceImpl(); + WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl(); + config.setAppId(officialAccount.getAppId()); + config.setSecret(officialAccount.getSecret()); + wxMpService.setWxMpConfigStorage(config); + return wxMpService; + } +} diff --git a/austin-web/src/main/java/com/java3y/austin/web/controller/ChannelAccountController.java b/austin-web/src/main/java/com/java3y/austin/web/controller/ChannelAccountController.java index f1f5e0e..c6c393d 100644 --- a/austin-web/src/main/java/com/java3y/austin/web/controller/ChannelAccountController.java +++ b/austin-web/src/main/java/com/java3y/austin/web/controller/ChannelAccountController.java @@ -6,6 +6,7 @@ import com.java3y.austin.common.constant.AustinConstant; import com.java3y.austin.common.vo.BasicResultVO; import com.java3y.austin.support.domain.ChannelAccount; import com.java3y.austin.web.service.ChannelAccountService; +import com.java3y.austin.web.vo.amis.CommonAmisVo; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; @@ -47,14 +48,12 @@ public class ChannelAccountController { @GetMapping("/queryByChannelType") @ApiOperation("/根据渠道标识查询相关的记录") public BasicResultVO query(Integer channelType) { - List channelAccounts = channelAccountService.queryByChannelType(channelType); + List result = new ArrayList<>(); - List> result = new ArrayList<>(); + List channelAccounts = channelAccountService.queryByChannelType(channelType); for (ChannelAccount channelAccount : channelAccounts) { - HashMap optionKV = new HashMap<>(); - optionKV.put("label", channelAccount.getName()); - optionKV.put("value", String.valueOf(channelAccount.getId())); - result.add(optionKV); + CommonAmisVo commonAmisVo = CommonAmisVo.builder().label(channelAccount.getName()).value(String.valueOf(channelAccount.getId())).build(); + result.add(commonAmisVo); } return BasicResultVO.success(result); } diff --git a/austin-web/src/main/java/com/java3y/austin/web/controller/OfficialAccountController.java b/austin-web/src/main/java/com/java3y/austin/web/controller/OfficialAccountController.java new file mode 100644 index 0000000..2fbef1d --- /dev/null +++ b/austin-web/src/main/java/com/java3y/austin/web/controller/OfficialAccountController.java @@ -0,0 +1,100 @@ +package com.java3y.austin.web.controller; + + +import cn.hutool.core.util.StrUtil; +import com.google.common.base.Throwables; +import com.java3y.austin.common.constant.AustinConstant; +import com.java3y.austin.common.enums.RespStatusEnum; +import com.java3y.austin.common.vo.BasicResultVO; +import com.java3y.austin.support.utils.WxServiceUtils; +import com.java3y.austin.web.vo.amis.CommonAmisVo; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.template.WxMpTemplate; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.List; + +/** + * 微信服务号 + * + * @author 3y + */ +@Slf4j +@RestController +@RequestMapping("/officialAccount") +@Api("微信服务号") +@CrossOrigin(origins = {AustinConstant.ORIGIN_VALUE, "https://aisuda.bce.baidu.com"}, allowCredentials = "true", allowedHeaders = "*") +public class OfficialAccountController { + + + /** + * @param id 账号Id + * @return + */ + @GetMapping("/template/list") + @ApiOperation("/根据账号Id获取模板列表") + public BasicResultVO queryList(Long id) { + try { + List result = new ArrayList<>(); + + WxMpService wxMpService = WxServiceUtils.wxMpServiceMap.get(id); + List allPrivateTemplate = wxMpService.getTemplateMsgService().getAllPrivateTemplate(); + for (WxMpTemplate wxMpTemplate : allPrivateTemplate) { + CommonAmisVo commonAmisVo = CommonAmisVo.builder().label(wxMpTemplate.getTitle()).value(wxMpTemplate.getTemplateId()).build(); + result.add(commonAmisVo); + } + return BasicResultVO.success(result); + } catch (Exception e) { + log.error("OfficialAccountController#queryList fail:{}", Throwables.getStackTraceAsString(e)); + return BasicResultVO.fail(RespStatusEnum.SERVICE_ERROR); + } + } + + + /** + * 根据账号Id和模板ID获取模板列表 + * + * @return + * @ + */ + @GetMapping("/template/detailTemplate") + @ApiOperation("/根据账号Id和模板ID获取模板列表") + public BasicResultVO queryDetailList(Long id, String wxTemplateId) { + try { + List result = new ArrayList<>(); + + WxMpService wxMpService = WxServiceUtils.wxMpServiceMap.get(id); + List allPrivateTemplate = wxMpService.getTemplateMsgService().getAllPrivateTemplate(); + for (WxMpTemplate wxMpTemplate : allPrivateTemplate) { + if (wxTemplateId.equals(wxMpTemplate.getTemplateId())) { + String[] data = wxMpTemplate.getContent().split(StrUtil.LF); + for (String datum : data) { + String name = datum.substring(datum.indexOf("{{") + 2, datum.indexOf(".")); + CommonAmisVo commonAmisVo = CommonAmisVo.builder() + .name(name) + .type("text") + .required(true) + .build(); + if (datum.contains("first")) { + commonAmisVo.setLabel("名字"); + } else if (datum.contains("remark")) { + commonAmisVo.setLabel("备注"); + } else { + commonAmisVo.setLabel(datum.split(":")[0]); + + } + result.add(commonAmisVo); + } + } + } + return BasicResultVO.success(result); + } catch (Exception e) { + log.error("OfficialAccountController#queryList fail:{}", Throwables.getStackTraceAsString(e)); + return BasicResultVO.fail(RespStatusEnum.SERVICE_ERROR); + } + } +} diff --git a/austin-web/src/main/java/com/java3y/austin/web/vo/amis/CommonAmisVo.java b/austin-web/src/main/java/com/java3y/austin/web/vo/amis/CommonAmisVo.java new file mode 100644 index 0000000..fe89b17 --- /dev/null +++ b/austin-web/src/main/java/com/java3y/austin/web/vo/amis/CommonAmisVo.java @@ -0,0 +1,27 @@ +package com.java3y.austin.web.vo.amis; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * amis的通用转化类 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class CommonAmisVo { + + private String type; + + private String label; + + private String value; + + private String name; + + private boolean required; +}