Commit 1113ff62f1dd3eb6def89cd771d9f60136c946a1
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
assistant-boot/src/main/resources/logback-spring.xml
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
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 ('SMS_TEMPLATE_KEY', 'SMS消息模版ID', 1, 1, 'SMS%d{yyyyMMdd}%n{4}', ' |
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`), | ... | ... |