Commit 1113ff62f1dd3eb6def89cd771d9f60136c946a1

Authored by huanggang
1 parent d1ac8610

ali sms supported

Showing 26 changed files with 575 additions and 61 deletions
assistant-boot/src/main/java/com/diligrp/assistant/boot/AssistantServiceBootstrap.java
... ... @@ -17,7 +17,7 @@ import org.springframework.context.annotation.Import;
17 17 @EnableAutoConfiguration
18 18 @Import({BootConfiguration.class, SharedConfiguration.class, DfsConfiguration.class, SmsConfiguration.class,
19 19 DataConfiguration.class, ProductConfiguration.class, LoggingConfiguration.class, UidConfiguration.class})
20   -@EnableDiscoveryClient
  20 +//@EnableDiscoveryClient
21 21 public class AssistantServiceBootstrap {
22 22 public static void main(String[] args) {
23 23 SpringApplication.run(AssistantServiceBootstrap.class, args);
... ...
assistant-boot/src/main/resources/application-dev.properties
... ... @@ -42,6 +42,9 @@ dfs.oss.access-key-secret=zP2P1i0MLiOBDKO6naQZRy4Ls70cEf
42 42 #Sms configuration
43 43 sms.private-key=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAvRtBBrQX5di1jQPbUh+Lu5pMwrg6/H9/XX7qBU7dsGA/yygQAH7AYb/fpHQ1GQDolU3LVgYt3IE43QacLo09MwIDAQABAkAJ8U5kb8e0U2J+CmIJedRZO0GtX+MeD1uX51iCNJqYvbI/tKAgqd9ulc07it7tW0vGhDDj+WaVLp1R5D7bgRcpAiEA6Vc1xjoMYmT+OL+DZfipOeMTUwEePCg0Eq8DnVtalgsCIQDPeGSQ+lVijjNTEF7swM6rH5Ofa1E+ry5VRAw1ywI2eQIgdNFuYIErNg9tnqdydxiYUBy4zfNfWaqe90ObQao8naUCIQComhNIClgXZq5pA3XQ+wM458llFaaJxX1mx40QrjDXKQIgB+x7Fz2MT/GdIUhN6s1Rpfb5IIAR51ztiVEJlJ+wpdo=
44 44 sms.public-key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAL0bQQa0F+XYtY0D21Ifi7uaTMK4Ovx/f11+6gVO3bBgP8soEAB+wGG/36R0NRkA6JVNy1YGLdyBON0GnC6NPTMCAwEAAQ==
  45 +sms.alisms.end-point=dysmsapi.aliyuncs.com
  46 +sms.alisms.access-key-id=LTAIzWMk73db38Ol
  47 +sms.alisms.access-key-secret=AnWSrjln9cPCKsAq36JeakpttjSKYf
45 48 sms.smschinese.uri=http://utf8.api.smschinese.cn
46 49 sms.smschinese.uid=zhuxuegang@diligrp.com
47 50 sms.smschinese.secret-key=c0978121c3893cf9ddbc
48 51 \ No newline at end of file
... ...
assistant-boot/src/main/resources/application-prod.properties
... ... @@ -42,6 +42,9 @@ dfs.oss.access-key-secret=zP2P1i0MLiOBDKO6naQZRy4Ls70cEf
42 42 #Sms configuration
43 43 sms.private-key=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAvRtBBrQX5di1jQPbUh+Lu5pMwrg6/H9/XX7qBU7dsGA/yygQAH7AYb/fpHQ1GQDolU3LVgYt3IE43QacLo09MwIDAQABAkAJ8U5kb8e0U2J+CmIJedRZO0GtX+MeD1uX51iCNJqYvbI/tKAgqd9ulc07it7tW0vGhDDj+WaVLp1R5D7bgRcpAiEA6Vc1xjoMYmT+OL+DZfipOeMTUwEePCg0Eq8DnVtalgsCIQDPeGSQ+lVijjNTEF7swM6rH5Ofa1E+ry5VRAw1ywI2eQIgdNFuYIErNg9tnqdydxiYUBy4zfNfWaqe90ObQao8naUCIQComhNIClgXZq5pA3XQ+wM458llFaaJxX1mx40QrjDXKQIgB+x7Fz2MT/GdIUhN6s1Rpfb5IIAR51ztiVEJlJ+wpdo=
44 44 sms.public-key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAL0bQQa0F+XYtY0D21Ifi7uaTMK4Ovx/f11+6gVO3bBgP8soEAB+wGG/36R0NRkA6JVNy1YGLdyBON0GnC6NPTMCAwEAAQ==
  45 +sms.alisms.end-point=dysmsapi.aliyuncs.com
  46 +sms.alisms.access-key-id=LTAIzWMk73db38Ol
  47 +sms.alisms.access-key-secret=AnWSrjln9cPCKsAq36JeakpttjSKYf
45 48 sms.smschinese.uri=http://utf8.api.smschinese.cn
46 49 sms.smschinese.uid=zhuxuegang@diligrp.com
47 50 sms.smschinese.secret-key=c0978121c3893cf9ddbc
48 51 \ No newline at end of file
... ...
assistant-boot/src/main/resources/application-test.properties
... ... @@ -42,6 +42,9 @@ dfs.oss.access-key-secret=zP2P1i0MLiOBDKO6naQZRy4Ls70cEf
42 42 #Sms configuration
43 43 sms.private-key=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAvRtBBrQX5di1jQPbUh+Lu5pMwrg6/H9/XX7qBU7dsGA/yygQAH7AYb/fpHQ1GQDolU3LVgYt3IE43QacLo09MwIDAQABAkAJ8U5kb8e0U2J+CmIJedRZO0GtX+MeD1uX51iCNJqYvbI/tKAgqd9ulc07it7tW0vGhDDj+WaVLp1R5D7bgRcpAiEA6Vc1xjoMYmT+OL+DZfipOeMTUwEePCg0Eq8DnVtalgsCIQDPeGSQ+lVijjNTEF7swM6rH5Ofa1E+ry5VRAw1ywI2eQIgdNFuYIErNg9tnqdydxiYUBy4zfNfWaqe90ObQao8naUCIQComhNIClgXZq5pA3XQ+wM458llFaaJxX1mx40QrjDXKQIgB+x7Fz2MT/GdIUhN6s1Rpfb5IIAR51ztiVEJlJ+wpdo=
44 44 sms.public-key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAL0bQQa0F+XYtY0D21Ifi7uaTMK4Ovx/f11+6gVO3bBgP8soEAB+wGG/36R0NRkA6JVNy1YGLdyBON0GnC6NPTMCAwEAAQ==
  45 +sms.alisms.end-point=dysmsapi.aliyuncs.com
  46 +sms.alisms.access-key-id=LTAIzWMk73db38Ol
  47 +sms.alisms.access-key-secret=AnWSrjln9cPCKsAq36JeakpttjSKYf
45 48 sms.smschinese.uri=http://utf8.api.smschinese.cn
46 49 sms.smschinese.uid=zhuxuegang@diligrp.com
47 50 sms.smschinese.secret-key=c0978121c3893cf9ddbc
48 51 \ No newline at end of file
... ...
assistant-boot/src/main/resources/bootstrap.properties
... ... @@ -3,5 +3,5 @@ server.servlet.context-path=/
3 3 server.servlet.encoding.charset=UTF-8
4 4 server.servlet.encoding.force=true
5 5  
6   -spring.profiles.active=prod
  6 +spring.profiles.active=dev
7 7 spring.application.name=assistant-service
8 8 \ No newline at end of file
... ...
assistant-boot/src/main/resources/logback-spring.xml
1 1 <?xml version="1.0" encoding="UTF-8"?>
2 2 <configuration>
3 3 <!-- 日志名称 -->
4   - <property name="LOG_NAME" value="uap-boot" />
  4 + <property name="LOG_NAME" value="assistant-boot" />
5 5 <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径 -->
6 6 <property name="LOG_HOME" value="logs" />
7 7  
... ...
assistant-sms/build.gradle
... ... @@ -5,8 +5,8 @@ dependencies {
5 5 api project(':assistant-shared')
6 6 api project(':assistant-uid')
7 7 implementation 'org.apache.commons:commons-text:1.12.0'
8   -// implementation('com.aliyun:dysmsapi20170525:2.0.24') {
9   -// // 排除指定模块, 否则将导致logback初始化时xml parse异常
10   -// exclude group: 'pull-parser', module: 'pull-parser'
11   -// }
  8 + implementation('com.aliyun:dysmsapi20170525:3.0.0') {
  9 + // 排除指定模块, 否则将导致logback初始化时xml parse异常
  10 + exclude group: 'pull-parser', module: 'pull-parser'
  11 + }
12 12 }
13 13 \ No newline at end of file
... ...
assistant-sms/src/main/java/com/diligrp/assistant/sms/Constants.java
... ... @@ -2,5 +2,14 @@ package com.diligrp.assistant.sms;
2 2  
3 3 public final class Constants {
4 4 public static final String HEADER_AUTHORIZATION = "Sms-Authorization";
  5 + // 短信服务MQ延时队列
  6 + public static final String SMS_DELAY_QUEUE = "sms.delayQueue";
  7 + // 短信服务MQ延时交换机
  8 + public static final String SMS_DELAY_EXCHANGE = "sms.delayExchange";
  9 + // 短信服务MQ路由KEY
  10 + public static final String SMS_DELAY_KEY = "sms.delayKey";
  11 +
  12 + // MQ消息类型常量
  13 + public static final int MESSAGE_TEMPLATE_STATE = 1;
5 14  
6 15 }
... ...
assistant-sms/src/main/java/com/diligrp/assistant/sms/RabbitConfiguration.java 0 → 100644
  1 +package com.diligrp.assistant.sms;
  2 +
  3 +import com.diligrp.assistant.shared.domain.AsyncMessage;
  4 +import com.diligrp.assistant.sms.service.SmsScheduleService;
  5 +import jakarta.annotation.Resource;
  6 +import org.slf4j.Logger;
  7 +import org.slf4j.LoggerFactory;
  8 +import org.springframework.amqp.core.*;
  9 +import org.springframework.amqp.rabbit.annotation.RabbitHandler;
  10 +import org.springframework.amqp.rabbit.annotation.RabbitListener;
  11 +import org.springframework.context.annotation.Bean;
  12 +import org.springframework.context.annotation.Configuration;
  13 +
  14 +import java.nio.charset.StandardCharsets;
  15 +import java.util.HashMap;
  16 +import java.util.Map;
  17 +
  18 +@Configuration
  19 +public class RabbitConfiguration {
  20 + private static final Logger LOGGER = LoggerFactory.getLogger(RabbitConfiguration.class);
  21 +
  22 + @Resource
  23 + private SmsScheduleService smsScheduleService;
  24 +
  25 + /**
  26 + * 短信服务MQ延时队列
  27 + * 队列为持久化、非独占式且不自动删除的队列, 利用RabbitMQ延时插件实现延时功能https://github.com/rabbitmq/rabbitmq-delayed-message-exchange
  28 + */
  29 + @Bean
  30 + public Queue smsDelayQueue() {
  31 + return new Queue(Constants.SMS_DELAY_QUEUE, true, false, false);
  32 + }
  33 +
  34 + /**
  35 + * 短信服务MQ延时交换机
  36 + */
  37 + @Bean
  38 + public CustomExchange smsDelayExchange() {
  39 + Map<String, Object> arguments = new HashMap<>();
  40 + arguments.put("x-delayed-type", "direct");
  41 + return new CustomExchange(Constants.SMS_DELAY_EXCHANGE, "x-delayed-message", true, false, arguments);
  42 + }
  43 +
  44 + /**
  45 + * 短信服务MQ延时队列和交换机的绑定
  46 + */
  47 + @Bean
  48 + public Binding smsDelayBinding() {
  49 + return BindingBuilder.bind(smsDelayQueue()).to(smsDelayExchange()).with(Constants.SMS_DELAY_KEY).noargs();
  50 + }
  51 +
  52 + @RabbitHandler
  53 + @RabbitListener(queues = {Constants.SMS_DELAY_QUEUE})
  54 + public void onMessage(Message message) {
  55 + byte[] packet = message.getBody();
  56 + MessageProperties properties = message.getMessageProperties();
  57 + String charSet = properties != null && properties.getContentEncoding() != null
  58 + ? properties.getContentEncoding() : StandardCharsets.UTF_8.name();
  59 +
  60 + try {
  61 + String body = new String(packet, charSet);
  62 + LOGGER.info("Handling sms service job: {}", body);
  63 + AsyncMessage job = AsyncMessage.from(body);
  64 + if (job.getType() == Constants.MESSAGE_TEMPLATE_STATE) {
  65 + smsScheduleService.executeSmsTemplateJob(job.getPayload());
  66 + } else {
  67 + LOGGER.error("Unrecognized sms service job: {}", job.getType());
  68 + }
  69 + } catch (Exception ex) {
  70 + LOGGER.error("Handle sms service job exception", ex);
  71 + }
  72 + }
  73 +}
... ...
assistant-sms/src/main/java/com/diligrp/assistant/sms/SmsConfiguration.java
1 1 package com.diligrp.assistant.sms;
2 2  
3 3 import com.diligrp.assistant.shared.mybatis.MybatisMapperSupport;
4   -import com.diligrp.assistant.sms.pipeline.DefaultSmsPipelineManager;
5   -import com.diligrp.assistant.sms.pipeline.SmsChinesePipeline;
6   -import com.diligrp.assistant.sms.pipeline.SmsPipeline;
7   -import com.diligrp.assistant.sms.pipeline.SmsPipelineManager;
  4 +import com.diligrp.assistant.sms.pipeline.*;
8 5 import org.mybatis.spring.annotation.MapperScan;
9 6 import org.springframework.boot.context.properties.ConfigurationProperties;
10 7 import org.springframework.context.annotation.Bean;
... ... @@ -24,9 +21,17 @@ public class SmsConfiguration {
24 21 @Bean
25 22 public SmsPipelineManager smsPipelineManager(SmsProperties properties) {
26 23 SmsPipelineManager pipelineManager = new DefaultSmsPipelineManager();
  24 +
  25 + // TODO: 可利用数据库进行通道配置, 前期并没有必要
  26 + SmsProperties.AliSms aliSms = properties.getAlisms();
  27 + if (aliSms != null) {
  28 + SmsPipeline aliPipeline = new AliSmsPipeline(10, "阿里云短信服务通道", aliSms.getEndPoint(),
  29 + aliSms.getAccessKeyId(), aliSms.getAccessKeySecret());
  30 + pipelineManager.registerPipeline(aliPipeline);
  31 + }
  32 +
27 33 SmsProperties.SmsChinese chinese = properties.getSmschinese();
28 34 if (chinese != null) {
29   - // TODO: 可利用数据库进行通道配置, 前期并没有必要
30 35 SmsPipeline pipeline = new SmsChinesePipeline(20, "网建短信服务通道", chinese.getUri(),
31 36 chinese.getUid(), chinese.getSecretKey());
32 37 pipelineManager.registerPipeline(pipeline);
... ...
assistant-sms/src/main/java/com/diligrp/assistant/sms/SmsProperties.java
... ... @@ -8,7 +8,9 @@ public class SmsProperties {
8 8 private PrivateKey privateKey;
9 9 // 公钥
10 10 private PublicKey publicKey;
11   - // OSS配置
  11 + // 阿里云短信服务通道
  12 + private AliSms alisms;
  13 + // 网建短信服务通道
12 14 private SmsChinese smschinese;
13 15  
14 16 public PrivateKey getPrivateKey() {
... ... @@ -27,6 +29,14 @@ public class SmsProperties {
27 29 this.publicKey = publicKey;
28 30 }
29 31  
  32 + public AliSms getAlisms() {
  33 + return alisms;
  34 + }
  35 +
  36 + public void setAlisms(AliSms alisms) {
  37 + this.alisms = alisms;
  38 + }
  39 +
30 40 public SmsChinese getSmschinese() {
31 41 return smschinese;
32 42 }
... ... @@ -35,6 +45,36 @@ public class SmsProperties {
35 45 this.smschinese = smschinese;
36 46 }
37 47  
  48 + public static class AliSms {
  49 + private String endPoint;
  50 + private String accessKeyId;
  51 + private String accessKeySecret;
  52 +
  53 + public String getEndPoint() {
  54 + return endPoint;
  55 + }
  56 +
  57 + public void setEndPoint(String endPoint) {
  58 + this.endPoint = endPoint;
  59 + }
  60 +
  61 + public String getAccessKeyId() {
  62 + return accessKeyId;
  63 + }
  64 +
  65 + public void setAccessKeyId(String accessKeyId) {
  66 + this.accessKeyId = accessKeyId;
  67 + }
  68 +
  69 + public String getAccessKeySecret() {
  70 + return accessKeySecret;
  71 + }
  72 +
  73 + public void setAccessKeySecret(String accessKeySecret) {
  74 + this.accessKeySecret = accessKeySecret;
  75 + }
  76 + }
  77 +
38 78 public static class SmsChinese {
39 79 private String uri;
40 80 private String uid;
... ...
assistant-sms/src/main/java/com/diligrp/assistant/sms/client/AliSmsHttpClient.java 0 → 100644
  1 +package com.diligrp.assistant.sms.client;
  2 +
  3 +import com.aliyun.dysmsapi20170525.Client;
  4 +import com.aliyun.dysmsapi20170525.models.*;
  5 +import com.aliyun.teaopenapi.models.Config;
  6 +import com.diligrp.assistant.shared.ErrorCode;
  7 +import com.diligrp.assistant.shared.util.ObjectUtils;
  8 +import com.diligrp.assistant.sms.domain.SmsMessage;
  9 +import com.diligrp.assistant.sms.domain.SmsTemplate;
  10 +import com.diligrp.assistant.sms.exception.SmsServiceException;
  11 +import com.diligrp.assistant.sms.type.TemplateState;
  12 +import org.slf4j.Logger;
  13 +import org.slf4j.LoggerFactory;
  14 +
  15 +import java.util.stream.Collectors;
  16 +
  17 +public class AliSmsHttpClient {
  18 +
  19 + private static final Logger LOGGER = LoggerFactory.getLogger(AliSmsHttpClient.class);
  20 +
  21 + private Client client;
  22 +
  23 + public AliSmsHttpClient(String endPoint, String accessKeyId, String accessKeySecret) {
  24 + Config config = new Config().setAccessKeyId(accessKeyId).setAccessKeySecret(accessKeySecret);
  25 + config.endpoint = endPoint;
  26 + try {
  27 + this.client = new Client(config);
  28 + } catch (Exception e) {
  29 + throw new SmsServiceException(ErrorCode.SERVICE_ACCESS_ERROR, "阿里云短信通道初始化错误");
  30 + }
  31 + }
  32 +
  33 + public String createSmsTemplate(SmsTemplate template) {
  34 + AddSmsTemplateRequest request = new AddSmsTemplateRequest();
  35 + request.setTemplateName(template.getName());
  36 + request.setTemplateContent(template.getContent());
  37 + request.setTemplateType(template.getType());
  38 + request.setRemark(template.getDescription());
  39 +
  40 + AddSmsTemplateResponse response;
  41 + try {
  42 + response = this.client.addSmsTemplate(request);
  43 + } catch (Exception ex) {
  44 + LOGGER.error("Failed to create ali sms template", ex);
  45 + throw new SmsServiceException(ErrorCode.SERVICE_ACCESS_ERROR, "创建短信模板失败: 未知错误");
  46 + }
  47 +
  48 + AddSmsTemplateResponseBody body = response.getBody();
  49 + if ("OK".equalsIgnoreCase(body.getCode())) {
  50 + return body.getTemplateCode();
  51 + } else {
  52 + LOGGER.error("Failed to create ali sms template, {}:{}", body.getCode(), body.getMessage());
  53 + throw new SmsServiceException(ErrorCode.SERVICE_ACCESS_ERROR, "创建短信模板失败: " + body.getMessage());
  54 + }
  55 + }
  56 +
  57 + public void modifySmsTemplate(SmsTemplate template) {
  58 + UpdateSmsTemplateRequest request = new UpdateSmsTemplateRequest();
  59 + request.setTemplateName(template.getName());
  60 + request.setTemplateCode(template.getId());
  61 + request.setTemplateContent(template.getContent());
  62 + request.setTemplateType(template.getType());
  63 + request.setRemark(template.getDescription());
  64 +
  65 + UpdateSmsTemplateResponse response;
  66 + try {
  67 + response = this.client.updateSmsTemplate(request);
  68 + } catch (Exception ex) {
  69 + LOGGER.error("Failed to modify ali sms template", ex);
  70 + throw new SmsServiceException(ErrorCode.SERVICE_ACCESS_ERROR, "修改短信模板失败: 未知错误");
  71 + }
  72 +
  73 + UpdateSmsTemplateResponseBody body = response.getBody();
  74 + if (!"OK".equalsIgnoreCase(body.getCode())) {
  75 + LOGGER.error("Failed to modify ali sms template, {}:{}", body.getCode(), body.getMessage());
  76 + throw new SmsServiceException(ErrorCode.SERVICE_ACCESS_ERROR, "修改短信模板失败: " + body.getMessage());
  77 + }
  78 + }
  79 +
  80 + public TemplateState querySmsTemplateState(String templateId) {
  81 + QuerySmsTemplateRequest request = new QuerySmsTemplateRequest();
  82 + request.setTemplateCode(templateId);
  83 +
  84 + QuerySmsTemplateResponse response;
  85 + try {
  86 + response = this.client.querySmsTemplate(request);
  87 + } catch (Exception ex) {
  88 + LOGGER.error("Failed to query ali sms template", ex);
  89 + throw new SmsServiceException(ErrorCode.SERVICE_ACCESS_ERROR, "查询短信模板失败: 未知错误");
  90 + }
  91 +
  92 + QuerySmsTemplateResponseBody body = response.getBody();
  93 + if ("OK".equalsIgnoreCase(body.getCode())) {
  94 + Integer state = body.getTemplateStatus();
  95 + switch (state) {
  96 + case 0 -> {
  97 + return TemplateState.PENDING;
  98 + }
  99 + case 1 -> {
  100 + return TemplateState.SUCCESS;
  101 + }
  102 + default -> {
  103 + LOGGER.info("Ali sms template audit not passed: {}", body.getReason());
  104 + return TemplateState.FAILED;
  105 + }
  106 + }
  107 + } else {
  108 + LOGGER.error("Failed to query ali sms template, {}:{}", body.getCode(), body.getMessage());
  109 + throw new SmsServiceException(ErrorCode.SERVICE_ACCESS_ERROR, "查询短信模板失败: " + body.getMessage());
  110 + }
  111 + }
  112 +
  113 + public void deleteSmsTemplate(String templateId) {
  114 + DeleteSmsTemplateRequest request = new DeleteSmsTemplateRequest();
  115 + request.setTemplateCode(templateId);
  116 +
  117 + DeleteSmsTemplateResponse response;
  118 + try {
  119 + response = this.client.deleteSmsTemplate(request);
  120 + } catch (Exception ex) {
  121 + LOGGER.error("Failed to delete ali sms template", ex);
  122 + throw new SmsServiceException(ErrorCode.SERVICE_ACCESS_ERROR, "删除短信模板失败: 未知错误");
  123 + }
  124 +
  125 + DeleteSmsTemplateResponseBody body = response.getBody();
  126 + if (!"OK".equalsIgnoreCase(body.getCode())) {
  127 + LOGGER.error("Failed to delete ali sms template, {}:{}", body.getCode(), body.getMessage());
  128 + throw new SmsServiceException(ErrorCode.SERVICE_ACCESS_ERROR, "删除短信模板失败: " + body.getMessage());
  129 + }
  130 + }
  131 +
  132 + public String sendSmsMessage(SmsMessage message) {
  133 + String telephones = message.getTelephones().stream().reduce((telephone1, telephone2) ->
  134 + "".concat(telephone1).concat(",").concat(telephone2)).get();
  135 + SendSmsRequest request = new SendSmsRequest();
  136 + request.setPhoneNumbers(telephones);
  137 + request.setSignName(message.getSignature());
  138 + request.setTemplateCode(message.getTemplateId());
  139 + if (ObjectUtils.isNotEmpty(message.getParams())) {
  140 + String json = message.getParams().stream()
  141 + .filter(p -> ObjectUtils.isNotEmpty(p.getKey()) && ObjectUtils.isNotEmpty(p.getValue()))
  142 + .map(p -> String.format("\"%s\": \"%s\"", p.getKey(), p.getValue()))
  143 + .collect(Collectors.joining(",", "{", "}"));
  144 + request.setTemplateParam(json);
  145 + }
  146 +
  147 + SendSmsResponse response;
  148 + try {
  149 + response = this.client.sendSms(request);
  150 + } catch (Exception ex) {
  151 + LOGGER.error("Failed to send ali sms message", ex);
  152 + throw new SmsServiceException(ErrorCode.SERVICE_ACCESS_ERROR, "发送短信失败: 未知错误");
  153 + }
  154 +
  155 + SendSmsResponseBody body = response.getBody();
  156 + if ("OK".equalsIgnoreCase(body.getCode())) {
  157 + return body.getBizId();
  158 + } else {
  159 + LOGGER.error("Failed to send ali sms message, {}:{}", body.getCode(), body.getMessage());
  160 + throw new SmsServiceException(ErrorCode.SERVICE_ACCESS_ERROR, "发送短信失败: " + body.getMessage());
  161 + }
  162 + }
  163 +}
... ...
assistant-sms/src/main/java/com/diligrp/assistant/sms/controller/SmsMessageController.java
... ... @@ -29,7 +29,8 @@ public class SmsMessageController {
29 29 public Message<?> send(@RequestBody SmsMessageDTO request, @RequestHeader(Constants.HEADER_AUTHORIZATION) String authorization) {
30 30 SmsAccessToken.of(authorization, smsProperties.getPublicKey());
31 31 AssertUtils.notEmpty(request.getTemplateId(), "templateId missed");
32   - AssertUtils.notNull(request.getTelephone(), "telephone missed");
  32 + AssertUtils.notEmpty(request.getTelephone(), "telephone missed");
  33 + AssertUtils.notEmpty(request.getSignature(), "signature missed");
33 34  
34 35 List<String> telephones = Collections.singletonList(request.getTelephone());
35 36 if (!request.isAsync()) {
... ... @@ -46,6 +47,7 @@ public class SmsMessageController {
46 47 AssertUtils.notEmpty(request.getTemplateId(), "templateId missed");
47 48 AssertUtils.notEmpty(request.getTelephones(), "telephones missed");
48 49 AssertUtils.isTrue(request.getTelephones().size() <= 1000, "Exceed max batch limit");
  50 + AssertUtils.notEmpty(request.getSignature(), "signature missed");
49 51  
50 52 if (!request.isAsync()) {
51 53 smsMessageService.sendSmsMessage(request.getTemplateId(), request.getTelephones(), request.getParams(), request.getSignature());
... ...
assistant-sms/src/main/java/com/diligrp/assistant/sms/dao/SmsTemplateDao.java
1 1 package com.diligrp.assistant.sms.dao;
2 2  
3 3 import com.diligrp.assistant.shared.mybatis.MybatisMapperSupport;
  4 +import com.diligrp.assistant.sms.domain.TemplateStateDTO;
4 5 import com.diligrp.assistant.sms.model.SmsTemplateDo;
5 6 import org.springframework.stereotype.Repository;
6 7  
... ... @@ -15,4 +16,6 @@ public interface SmsTemplateDao extends MybatisMapperSupport {
15 16 int updateSmsTemplate(SmsTemplateDo template);
16 17  
17 18 int deleteSmsTemplate(String templateId);
  19 +
  20 + int compareAndSetState(TemplateStateDTO stateDTO);
18 21 }
... ...
assistant-sms/src/main/java/com/diligrp/assistant/sms/domain/SmsMessageDTO.java
... ... @@ -11,7 +11,7 @@ public class SmsMessageDTO {
11 11 private List<String> telephones;
12 12 // 模版变量
13 13 private List<ParamPair> params;
14   - // 消息签名 - 网建使用
  14 + // 消息签名
15 15 private String signature;
16 16 // 是否异步发送短信
17 17 private boolean async;
... ...
assistant-sms/src/main/java/com/diligrp/assistant/sms/domain/TemplateStateDTO.java 0 → 100644
  1 +package com.diligrp.assistant.sms.domain;
  2 +
  3 +import java.time.LocalDateTime;
  4 +
  5 +public class TemplateStateDTO {
  6 + // 短信模板ID
  7 + private String templateId;
  8 + // 模板状态
  9 + private Integer state;
  10 + // 数据版本
  11 + private Integer version;
  12 + // 修改时间
  13 + private LocalDateTime modifiedTime;
  14 +
  15 + public static TemplateStateDTO of(String templateId, Integer state, Integer version, LocalDateTime modifiedTime) {
  16 + TemplateStateDTO stateDTO = new TemplateStateDTO();
  17 + stateDTO.setTemplateId(templateId);
  18 + stateDTO.setState(state);
  19 + stateDTO.setVersion(version);
  20 + stateDTO.setModifiedTime(modifiedTime);
  21 + return stateDTO;
  22 + }
  23 +
  24 + public String getTemplateId() {
  25 + return templateId;
  26 + }
  27 +
  28 + public void setTemplateId(String templateId) {
  29 + this.templateId = templateId;
  30 + }
  31 +
  32 + public Integer getState() {
  33 + return state;
  34 + }
  35 +
  36 + public void setState(Integer state) {
  37 + this.state = state;
  38 + }
  39 +
  40 + public Integer getVersion() {
  41 + return version;
  42 + }
  43 +
  44 + public void setVersion(Integer version) {
  45 + this.version = version;
  46 + }
  47 +
  48 + public LocalDateTime getModifiedTime() {
  49 + return modifiedTime;
  50 + }
  51 +
  52 + public void setModifiedTime(LocalDateTime modifiedTime) {
  53 + this.modifiedTime = modifiedTime;
  54 + }
  55 +}
... ...
assistant-sms/src/main/java/com/diligrp/assistant/sms/model/SmsTemplateDo.java
... ... @@ -131,6 +131,11 @@ public class SmsTemplateDo extends BaseDo {
131 131 return this;
132 132 }
133 133  
  134 + public Builder version(Integer version) {
  135 + SmsTemplateDo.this.version = version;
  136 + return this;
  137 + }
  138 +
134 139 public Builder createdTime(LocalDateTime createdTime) {
135 140 SmsTemplateDo.this.createdTime = createdTime;
136 141 return this;
... ...
assistant-sms/src/main/java/com/diligrp/assistant/sms/pipeline/AliSmsPipeline.java
1 1 package com.diligrp.assistant.sms.pipeline;
2 2  
  3 +import com.diligrp.assistant.sms.client.AliSmsHttpClient;
3 4 import com.diligrp.assistant.sms.domain.SmsMessage;
  5 +import com.diligrp.assistant.sms.domain.SmsTemplate;
4 6 import com.diligrp.assistant.sms.type.PipelineType;
  7 +import com.diligrp.assistant.sms.type.TemplateState;
5 8  
6   -// TODO: ali sms pipeline implement
7 9 public class AliSmsPipeline extends SmsPipeline {
8   - public AliSmsPipeline(int code, String name, PipelineType type) {
9   - super(code, name, type);
  10 + private AliSmsHttpClient client;
  11 +
  12 + public AliSmsPipeline(int code, String name, String endPoint, String accessKeyId, String accessKeySecret) {
  13 + super(code, name, PipelineType.SMS_ALI);
  14 + this.client = new AliSmsHttpClient(endPoint, accessKeyId, accessKeySecret);
10 15 }
11 16  
12 17 @Override
... ... @@ -15,7 +20,27 @@ public class AliSmsPipeline extends SmsPipeline {
15 20 }
16 21  
17 22 @Override
  23 + public String createSmsTemplate(SmsTemplate template) {
  24 + return this.client.createSmsTemplate(template);
  25 + }
  26 +
  27 + @Override
  28 + public void modifySmsTemplate(SmsTemplate template) {
  29 + this.client.modifySmsTemplate(template);
  30 + }
  31 +
  32 + @Override
  33 + public TemplateState querySmsTemplateState(String templateId) {
  34 + return this.client.querySmsTemplateState(templateId);
  35 + }
  36 +
  37 + @Override
  38 + public void deleteSmsTemplate(String templateId) {
  39 + this.client.deleteSmsTemplate(templateId);
  40 + }
  41 +
  42 + @Override
18 43 public String sendSmsMessage(SmsMessage message) {
19   - return null;
  44 + return this.client.sendSmsMessage(message);
20 45 }
21 46 }
... ...
assistant-sms/src/main/java/com/diligrp/assistant/sms/pipeline/SmsPipeline.java
... ... @@ -5,7 +5,6 @@ import com.diligrp.assistant.sms.domain.SmsMessage;
5 5 import com.diligrp.assistant.sms.domain.SmsTemplate;
6 6 import com.diligrp.assistant.sms.exception.SmsServiceException;
7 7 import com.diligrp.assistant.sms.type.PipelineType;
8   -import com.diligrp.assistant.sms.type.SmsState;
9 8 import com.diligrp.assistant.sms.type.TemplateState;
10 9  
11 10 public abstract class SmsPipeline {
... ... @@ -73,16 +72,6 @@ public abstract class SmsPipeline {
73 72 */
74 73 public abstract String sendSmsMessage(SmsMessage message);
75 74  
76   - /**
77   - * 根据短信唯一标识查询短信查询状态
78   - *
79   - * @param messageId - 短信唯一标识
80   - * @return 发送状态
81   - */
82   - public SmsState querySmsMessageState(String messageId) {
83   - throw new SmsServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "短信服务通道不支持此操作");
84   - }
85   -
86 75 public void destroy() {
87 76 }
88 77  
... ...
assistant-sms/src/main/java/com/diligrp/assistant/sms/service/SmsScheduleService.java 0 → 100644
  1 +package com.diligrp.assistant.sms.service;
  2 +
  3 +import java.util.concurrent.TimeUnit;
  4 +
  5 +public interface SmsScheduleService {
  6 + /**
  7 + * 发起检查短信模板状态的定时任务
  8 + *
  9 + * @param templateId - 模板ID
  10 + * @param time - 时间间隔
  11 + * @param unit - 时间单位
  12 + */
  13 + void scheduleSmsTemplateJob(String templateId, long time, TimeUnit unit);
  14 +
  15 + /**
  16 + * 执行短信模板状态的定时任务
  17 + * 短信通道查询短信模板状态,更新本地短信模板
  18 + *
  19 + * @param templateId - 短信模板
  20 + */
  21 + void executeSmsTemplateJob(String templateId);
  22 +}
... ...
assistant-sms/src/main/java/com/diligrp/assistant/sms/service/impl/SmsMessageServiceImpl.java
... ... @@ -10,7 +10,6 @@ import com.diligrp.assistant.sms.domain.SmsMessage;
10 10 import com.diligrp.assistant.sms.exception.SmsServiceException;
11 11 import com.diligrp.assistant.sms.model.SmsMessageDo;
12 12 import com.diligrp.assistant.sms.model.SmsTemplateDo;
13   -import com.diligrp.assistant.sms.pipeline.SmsChinesePipeline;
14 13 import com.diligrp.assistant.sms.pipeline.SmsPipeline;
15 14 import com.diligrp.assistant.sms.pipeline.SmsPipelineManager;
16 15 import com.diligrp.assistant.sms.service.SmsMessageService;
... ... @@ -50,10 +49,6 @@ public class SmsMessageServiceImpl implements SmsMessageService {
50 49  
51 50 LocalDateTime now = LocalDateTime.now();
52 51 SmsPipeline pipeline = smsPipelineManager.findPipelineByCode(smsTemplate.getPipeline());
53   - if (pipeline instanceof SmsChinesePipeline && signature == null) { // 网建必须指定消息签名
54   - throw new SmsServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "未设置短信签名,不能发送短信");
55   - }
56   -
57 52 SmsMessage smsMessage = new SmsMessage(smsTemplate.getOutTemplateId(), telephones, smsTemplate.getContent(), params, signature);
58 53 String outMessageId = pipeline.sendSmsMessage(smsMessage);
59 54 List<SmsMessageDo> smsMessages = telephones.stream().map(telephone -> SmsMessageDo.builder()
... ... @@ -77,10 +72,6 @@ public class SmsMessageServiceImpl implements SmsMessageService {
77 72  
78 73 LocalDateTime now = LocalDateTime.now();
79 74 SmsPipeline pipeline = smsPipelineManager.findPipelineByCode(smsTemplate.getPipeline());
80   - if (pipeline instanceof SmsChinesePipeline && signature == null) { // 网建必须指定消息签名
81   - throw new SmsServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "未设置短信签名,不能发送短信");
82   - }
83   -
84 75 ThreadPollService.getInstance().submit(() -> {
85 76 SmsMessage smsMessage = new SmsMessage(smsTemplate.getOutTemplateId(), telephones, smsTemplate.getContent(), params, signature);
86 77 String outMessageId = pipeline.sendSmsMessage(smsMessage);
... ...
assistant-sms/src/main/java/com/diligrp/assistant/sms/service/impl/SmsScheduleServiceImpl.java 0 → 100644
  1 +package com.diligrp.assistant.sms.service.impl;
  2 +
  3 +import com.diligrp.assistant.shared.domain.AsyncMessage;
  4 +import com.diligrp.assistant.shared.service.ThreadPollService;
  5 +import com.diligrp.assistant.sms.Constants;
  6 +import com.diligrp.assistant.sms.dao.SmsTemplateDao;
  7 +import com.diligrp.assistant.sms.domain.TemplateStateDTO;
  8 +import com.diligrp.assistant.sms.model.SmsTemplateDo;
  9 +import com.diligrp.assistant.sms.pipeline.SmsPipeline;
  10 +import com.diligrp.assistant.sms.pipeline.SmsPipelineManager;
  11 +import com.diligrp.assistant.sms.service.SmsScheduleService;
  12 +import com.diligrp.assistant.sms.type.TemplateState;
  13 +import jakarta.annotation.Resource;
  14 +import org.slf4j.Logger;
  15 +import org.slf4j.LoggerFactory;
  16 +import org.springframework.amqp.core.Message;
  17 +import org.springframework.amqp.core.MessageProperties;
  18 +import org.springframework.amqp.rabbit.core.RabbitTemplate;
  19 +import org.springframework.stereotype.Service;
  20 +import org.springframework.transaction.annotation.Transactional;
  21 +
  22 +import java.nio.charset.StandardCharsets;
  23 +import java.time.LocalDateTime;
  24 +import java.util.Optional;
  25 +import java.util.concurrent.TimeUnit;
  26 +
  27 +@Service("smsScheduleService")
  28 +public class SmsScheduleServiceImpl implements SmsScheduleService {
  29 +
  30 + private static final Logger LOGGER = LoggerFactory.getLogger(SmsScheduleServiceImpl.class);
  31 +
  32 + @Resource
  33 + private SmsTemplateDao smsTemplateDao;
  34 +
  35 + @Resource
  36 + private SmsPipelineManager smsPipelineManager;
  37 +
  38 + @Resource
  39 + private RabbitTemplate rabbitTemplate;
  40 +
  41 + @Override
  42 + public void scheduleSmsTemplateJob(String templateId, long interval, TimeUnit unit) {
  43 + ThreadPollService.getInstance().submit(() -> {
  44 + MessageProperties properties = new MessageProperties();
  45 + properties.setContentEncoding(StandardCharsets.UTF_8.name());
  46 + properties.setContentType(MessageProperties.CONTENT_TYPE_BYTES);
  47 + // properties.setExpiration(String.valueOf(expiredTime));
  48 + // RabbitMQ延时插件必须设置x-delay的header才能生效
  49 + properties.setHeader("x-delay", String.valueOf(unit.toMillis(interval)));
  50 + String payload = AsyncMessage.of(Constants.MESSAGE_TEMPLATE_STATE, templateId, null).toString();
  51 + Message message = new Message(payload.getBytes(StandardCharsets.UTF_8), properties);
  52 + LOGGER.debug("Schedule a timer job for sms template: {}", templateId);
  53 + rabbitTemplate.send(Constants.SMS_DELAY_EXCHANGE, Constants.SMS_DELAY_KEY, message);
  54 + });
  55 + }
  56 +
  57 + @Override
  58 + @Transactional(rollbackFor = Exception.class)
  59 + public void executeSmsTemplateJob(String templateId) {
  60 + LOGGER.debug("Executing the timer job for sms template: {}", templateId);
  61 + Optional<SmsTemplateDo> templateOpt = smsTemplateDao.findSmsTemplateById(templateId);
  62 + templateOpt.ifPresent(template -> {
  63 + // 由于只有审核通过的模板才允许被禁用,因此禁用状态可当做审核通过
  64 + SmsPipeline pipeline = smsPipelineManager.findPipelineByCode(template.getPipeline());
  65 + if (TemplateState.PENDING.equalsTo(template.getState()) && pipeline.templateSupported()) {
  66 + LocalDateTime now = LocalDateTime.now();
  67 + try {
  68 + TemplateState state = pipeline.querySmsTemplateState(template.getOutTemplateId());
  69 + if (state != TemplateState.PENDING) {
  70 + TemplateStateDTO stateDTO = TemplateStateDTO.of(templateId, state.getCode(), template.getVersion(), now);
  71 + if (smsTemplateDao.compareAndSetState(stateDTO) == 0) {
  72 + scheduleSmsTemplateJob(templateId, 1, TimeUnit.HOURS);
  73 + }
  74 + } else if (template.getCreatedTime().plusHours(24).isAfter(now)) {
  75 + scheduleSmsTemplateJob(templateId, 1, TimeUnit.HOURS);
  76 + } else {
  77 + LOGGER.info("Overdue to schedule the timer job for sms template: {}", templateId);
  78 + }
  79 + } catch (Exception ex) {
  80 + LOGGER.error("Failed to execute the timer job for sms template: " + templateId, ex);
  81 + scheduleSmsTemplateJob(templateId, 1, TimeUnit.HOURS);
  82 + }
  83 + } else {
  84 + LOGGER.info("No need to query the template state from sms pipeline: {}", templateId);
  85 + }
  86 + });
  87 + }
  88 +}
... ...
assistant-sms/src/main/java/com/diligrp/assistant/sms/service/impl/SmsTemplateServiceImpl.java
... ... @@ -4,10 +4,12 @@ import com.diligrp.assistant.shared.ErrorCode;
4 4 import com.diligrp.assistant.sms.dao.SmsTemplateDao;
5 5 import com.diligrp.assistant.sms.domain.SmsTemplate;
6 6 import com.diligrp.assistant.sms.domain.SmsTemplateDTO;
  7 +import com.diligrp.assistant.sms.domain.TemplateStateDTO;
7 8 import com.diligrp.assistant.sms.exception.SmsServiceException;
8 9 import com.diligrp.assistant.sms.model.SmsTemplateDo;
9 10 import com.diligrp.assistant.sms.pipeline.SmsPipeline;
10 11 import com.diligrp.assistant.sms.pipeline.SmsPipelineManager;
  12 +import com.diligrp.assistant.sms.service.SmsScheduleService;
11 13 import com.diligrp.assistant.sms.service.SmsTemplateService;
12 14 import com.diligrp.assistant.sms.type.SmsType;
13 15 import com.diligrp.assistant.sms.type.TemplateState;
... ... @@ -17,6 +19,7 @@ import org.springframework.stereotype.Service;
17 19 import org.springframework.transaction.annotation.Transactional;
18 20  
19 21 import java.time.LocalDateTime;
  22 +import java.util.concurrent.TimeUnit;
20 23  
21 24 @Service("smsTemplateService")
22 25 public class SmsTemplateServiceImpl implements SmsTemplateService {
... ... @@ -28,6 +31,9 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
28 31 private SmsPipelineManager smsPipelineManager;
29 32  
30 33 @Resource
  34 + private SmsScheduleService smsScheduleService;
  35 +
  36 + @Resource
31 37 private KeyGeneratorManager keyGeneratorManager;
32 38  
33 39 @Override
... ... @@ -49,8 +55,12 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
49 55 String templateId = keyGeneratorManager.getKeyGenerator("SMS_TEMPLATE_KEY").nextId();
50 56 template.templateId(templateId).pipeline(smsTemplate.getPipeline()).type(smsTemplate.getType())
51 57 .name(smsTemplate.getName()).content(smsTemplate.getContent()).state(TemplateState.PENDING.getCode())
52   - .description(smsTemplate.getDescription()).outTemplateId(outTemplateId).createdTime(now).modifiedTime(now);
  58 + .description(smsTemplate.getDescription()).outTemplateId(outTemplateId).version(0)
  59 + .createdTime(now).modifiedTime(now);
53 60 smsTemplateDao.insertSmsTemplate(template.build());
  61 + if (pipeline.templateSupported()) { // 预约查询短信通道的模板状态
  62 + smsScheduleService.scheduleSmsTemplateJob(templateId, 1, TimeUnit.HOURS);
  63 + }
54 64 return templateId;
55 65 }
56 66  
... ... @@ -65,17 +75,15 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
65 75 if (!TemplateState.PENDING.equalsTo(template.getState()) && !TemplateState.FAILED.equalsTo(template.getState())) {
66 76 throw new SmsServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "短信模版已审核通过,不允许修改");
67 77 }
68   -
69 78 if (smsTemplate.getType() != null) {
70   - SmsType.getType(smsTemplate.getType()).orElseThrow(() ->
71   - new SmsServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "不能识别的短信模版类型"));
  79 + SmsType.getType(smsTemplate.getType()).orElseThrow(() -> new SmsServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "不能识别的短信模版类型"));
72 80 }
73   - SmsTemplateDo smsTemplateDo = SmsTemplateDo.builder().templateId(smsTemplate.getTemplateId()).type(smsTemplate.getType())
74   - .name(smsTemplate.getName()).content(smsTemplate.getContent()).description(smsTemplate.getDescription())
75   - .modifiedTime(now).build();
76   - smsTemplateDao.updateSmsTemplate(smsTemplateDo);
77 81  
78 82 SmsPipeline pipeline = smsPipelineManager.findPipelineByCode(template.getPipeline());
  83 + SmsTemplateDo smsTemplateDo = SmsTemplateDo.builder().templateId(smsTemplate.getTemplateId())
  84 + .type(smsTemplate.getType()).name(smsTemplate.getName()).content(smsTemplate.getContent())
  85 + .description(smsTemplate.getDescription()).modifiedTime(now).build();
  86 + smsTemplateDao.updateSmsTemplate(smsTemplateDo);
79 87 if (pipeline.templateSupported()) {
80 88 SmsTemplate request = new SmsTemplate(template.getOutTemplateId(), smsTemplate.getType(),
81 89 smsTemplate.getName(), smsTemplate.getContent(), smsTemplate.getDescription());
... ... @@ -98,17 +106,24 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
98 106 }
99 107 SmsPipeline pipeline = smsPipelineManager.findPipelineByCode(smsTemplate.getPipeline());
100 108 if (pipeline.templateSupported()) {
101   - // TODO:是否发起状态查询?
102   - throw new SmsServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "短信服务通道不允许进行人工审批");
  109 + TemplateState state = pipeline.querySmsTemplateState(smsTemplate.getOutTemplateId());
  110 + if (state == TemplateState.PENDING) {
  111 + throw new SmsServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "短信服务通道正在审核中,不能进行人工审核");
  112 + } else if (state == TemplateState.FAILED) {
  113 + throw new SmsServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "短信服务通道审核未通过,请修改模板重新提交审核");
  114 + }
103 115 }
104 116  
105   - SmsTemplateDo template = SmsTemplateDo.builder().templateId(templateId).state(TemplateState.SUCCESS.getCode())
106   - .modifiedTime(now).build();
107   - smsTemplateDao.updateSmsTemplate(template);
  117 + TemplateStateDTO stateDTO = TemplateStateDTO.of(templateId, TemplateState.SUCCESS.getCode(),
  118 + smsTemplate.getVersion(), now);
  119 + if (smsTemplateDao.compareAndSetState(stateDTO) == 0) {
  120 + throw new SmsServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "系统忙,请稍后重试");
  121 + }
108 122 }
109 123  
110 124 @Override
111 125 public void disableSmsTemplate(String templateId) {
  126 + LocalDateTime now = LocalDateTime.now();
112 127 SmsTemplateDo smsTemplate = smsTemplateDao.findSmsTemplateById(templateId)
113 128 .orElseThrow(() -> new SmsServiceException(ErrorCode.OBJECT_NOT_FOUND, "短信模版不存在"));
114 129 if (!TemplateState.SUCCESS.equalsTo(smsTemplate.getState())) {
... ... @@ -117,23 +132,28 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
117 132 }
118 133  
119 134 // 仅仅允许审核通过的短信模版被禁用
120   - SmsTemplateDo template = SmsTemplateDo.builder().templateId(templateId).state(TemplateState.DISABLED.getCode())
121   - .modifiedTime(LocalDateTime.now()).build();
122   - smsTemplateDao.updateSmsTemplate(template);
  135 + TemplateStateDTO stateDTO = TemplateStateDTO.of(templateId, TemplateState.DISABLED.getCode(),
  136 + smsTemplate.getVersion(), now);
  137 + if (smsTemplateDao.compareAndSetState(stateDTO) == 0) {
  138 + throw new SmsServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "系统忙,请稍后重试");
  139 + }
123 140 }
124 141  
125 142 @Override
126 143 @Transactional(rollbackFor = Exception.class)
127 144 public void enableSmsTemplate(String templateId) {
  145 + LocalDateTime now = LocalDateTime.now();
128 146 SmsTemplateDo smsTemplate = smsTemplateDao.findSmsTemplateById(templateId)
129 147 .orElseThrow(() -> new SmsServiceException(ErrorCode.OBJECT_NOT_FOUND, "短信模版不存在"));
130 148 if (!TemplateState.DISABLED.equalsTo(smsTemplate.getState())) {
131 149 throw new SmsServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "仅仅被禁用的短信模版才能被启用");
132 150 }
133 151  
134   - SmsTemplateDo template = SmsTemplateDo.builder().templateId(templateId).state(TemplateState.SUCCESS.getCode())
135   - .modifiedTime(LocalDateTime.now()).build();
136   - smsTemplateDao.updateSmsTemplate(template);
  152 + TemplateStateDTO stateDTO = TemplateStateDTO.of(templateId, TemplateState.SUCCESS.getCode(),
  153 + smsTemplate.getVersion(), now);
  154 + if (smsTemplateDao.compareAndSetState(stateDTO) == 0) {
  155 + throw new SmsServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "系统忙,请稍后重试");
  156 + }
137 157 }
138 158  
139 159 @Override
... ... @@ -144,7 +164,7 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
144 164  
145 165 SmsPipeline pipeline = smsPipelineManager.findPipelineByCode(smsTemplate.getPipeline());
146 166 if (pipeline.templateSupported()) {
147   - pipeline.deleteSmsTemplate(templateId);
  167 + pipeline.deleteSmsTemplate(smsTemplate.getOutTemplateId());
148 168 }
149 169  
150 170 smsTemplateDao.deleteSmsTemplate(templateId);
... ...
assistant-sms/src/main/resources/com/diligrp/assistant/dao/mapper/SmsTemplateDao.xml
... ... @@ -13,15 +13,16 @@
13 13 <result column="state" property="state"/>
14 14 <result column="description" property="description"/>
15 15 <result column="out_template_id" property="outTemplateId"/>
  16 + <result column="version" property="version"/>
16 17 <result column="created_time" property="createdTime"/>
17 18 <result column="modified_time" property="modifiedTime"/>
18 19 </resultMap>
19 20  
20 21 <insert id="insertSmsTemplate" parameterType="com.diligrp.assistant.sms.model.SmsTemplateDo">
21 22 INSERT INTO sms_template
22   - (template_id, pipeline, type, name, content, state, description, out_template_id, created_time, modified_time)
  23 + (template_id, pipeline, type, name, content, state, description, out_template_id, version, created_time, modified_time)
23 24 VALUES
24   - (#{templateId}, #{pipeline}, #{type}, #{name}, #{content}, #{state}, #{description}, #{outTemplateId}, #{createdTime}, #{modifiedTime})
  25 + (#{templateId}, #{pipeline}, #{type}, #{name}, #{content}, #{state}, #{description}, #{outTemplateId}, #{version}, #{createdTime}, #{modifiedTime})
25 26 </insert>
26 27  
27 28 <select id="findSmsTemplateById" parameterType="string" resultMap="SmsTemplateMap">
... ... @@ -47,6 +48,15 @@
47 48 </if>
48 49 WHERE template_id = #{templateId}
49 50 </update>
  51 +
  52 + <update id="compareAndSetState" parameterType="com.diligrp.assistant.sms.domain.TemplateStateDTO">
  53 + UPDATE sms_template SET state = #{state}, version = version + 1
  54 + <if test="modifiedTime != null">
  55 + , modified_time = #{modifiedTime}
  56 + </if>
  57 + WHERE
  58 + template_id = #{templateId} AND version = #{version}
  59 + </update>
50 60  
51 61 <update id="deleteSmsTemplate" parameterType="string">
52 62 DELETE FROM sms_template WHERE template_id = #{templateId}
... ...
scripts/assistant-data.sql
... ... @@ -5,6 +5,10 @@ VALUES (&#39;SMS_TEMPLATE_KEY&#39;, &#39;SMS消息模版ID&#39;, 1, 1, &#39;SMS%d{yyyyMMdd}%n{4}&#39;, &#39;
5 5  
6 6 INSERT INTO `sms_template`(`template_id`, `pipeline`, `type`, `name`, `content`, `state`, `description`, `out_template_id`, `created_time`, `modified_time`)
7 7 VALUES ('SMS202408080001', 20, 0, '商户注册短信模板', '验证码:${code},电子结算商户版注册验证码10分钟内有效。如非本人操作,请忽略本短信。', 2, '电子结算商户版注册验证码短信模板', NULL, '2024-08-08 17:31:55', '2024-08-08 17:55:29');
  8 +INSERT INTO `sms_template`(`template_id`, `pipeline`, `type`, `name`, `content`, `state`, `description`, `out_template_id`, `created_time`, `modified_time`)
  9 +VALUES ('SMS202408080002', 20, 1, '开通员工账号短信模板', '${name}已为您开通${shopName}店铺的员工账号,您可通过手机号直接登录店铺进行管理。请使用原有密码登录。', 2, '员工存在时开通员工账号短信模板', NULL, '2024-08-08 17:31:55', '2024-08-08 17:55:29');
  10 +INSERT INTO `sms_template`(`template_id`, `pipeline`, `type`, `name`, `content`, `state`, `description`, `out_template_id`, `created_time`, `modified_time`)
  11 +VALUES ('SMS202408080003', 20, 1, '开通员工账号短信模板', '${name}已为您开通${shopName}店铺的员工账号,默认密码为${password}。您可通过手机号直接登录店铺进行管理。', 2, '员工不存在时开通员工账号短信模板', NULL, '2024-08-08 17:31:55', '2024-08-08 17:55:29');
8 12  
9 13 -- DFS文件服务
10 14 INSERT INTO `dfs_file_repository`(`repository_id`, `name`, `pipeline`, `description`, `created_time`) VALUES ('29ad2204ca8248daa5b09be73dc26926', '商户版电子结算存储空间', 10, '存储商户版电子结算所有使用的图片-非生产环境', '2024-08-20 10:43:05');
... ...
scripts/dili-assistant.sql
... ... @@ -106,6 +106,7 @@ CREATE TABLE `sms_template` (
106 106 `state` TINYINT UNSIGNED NOT NULL COMMENT '模版状态',
107 107 `description` VARCHAR(200) COMMENT '备注',
108 108 `out_template_id` VARCHAR(80) COMMENT '外部模版ID', -- 服务通道的模版编号
  109 + `version` INT UNSIGNED NOT NULL DEFAULT '0' COMMENT '数据版本号',
109 110 `created_time` DATETIME COMMENT '创建时间',
110 111 `modified_time` DATETIME COMMENT '修改时间',
111 112 PRIMARY KEY (`id`),
... ...