Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
O
online-edu-sms
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Redmine
Redmine
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
online-edu
online-edu-sms
Commits
93a4b7eb
Commit
93a4b7eb
authored
Apr 28, 2021
by
liuyang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
短信发送服务
parents
Show whitespace changes
Inline
Side-by-side
Showing
72 changed files
with
3665 additions
and
0 deletions
+3665
-0
.gitignore
.gitignore
+139
-0
README.md
README.md
+4
-0
pom.xml
pom.xml
+241
-0
Dockerfile
src/main/docker/Dockerfile
+21
-0
docker-entrypoint.sh
src/main/docker/docker-entrypoint.sh
+3
-0
Application.java
src/main/java/com/qkdata/sms/Application.java
+22
-0
SmsController.java
src/main/java/com/qkdata/sms/api/SmsController.java
+58
-0
SmsNotifyController.java
src/main/java/com/qkdata/sms/api/SmsNotifyController.java
+42
-0
CommonEntityEnum.java
src/main/java/com/qkdata/sms/config/CommonEntityEnum.java
+75
-0
CustomEnumTypeHandler.java
...ain/java/com/qkdata/sms/config/CustomEnumTypeHandler.java
+86
-0
RedisConfiguration.java
src/main/java/com/qkdata/sms/config/RedisConfiguration.java
+91
-0
CommonResponseEnum.java
...main/java/com/qkdata/sms/constant/CommonResponseEnum.java
+16
-0
ConstantResponseEnum.java
...in/java/com/qkdata/sms/constant/ConstantResponseEnum.java
+42
-0
ResponseData.java
src/main/java/com/qkdata/sms/constant/ResponseData.java
+77
-0
ResponseDataBuilder.java
...ain/java/com/qkdata/sms/constant/ResponseDataBuilder.java
+48
-0
RestTemplateConfiguration.java
...va/com/qkdata/sms/constant/RestTemplateConfiguration.java
+52
-0
SmsChannelEnum.java
src/main/java/com/qkdata/sms/constant/SmsChannelEnum.java
+23
-0
SmsResponseEnum.java
src/main/java/com/qkdata/sms/constant/SmsResponseEnum.java
+35
-0
SmsTemplate.java
src/main/java/com/qkdata/sms/constant/SmsTemplate.java
+95
-0
SmsTypeEnum.java
src/main/java/com/qkdata/sms/constant/SmsTypeEnum.java
+27
-0
QueueConstants.java
src/main/java/com/qkdata/sms/consumer/QueueConstants.java
+10
-0
SendSmsTask.java
src/main/java/com/qkdata/sms/consumer/SendSmsTask.java
+62
-0
SmsRetryListener.java
src/main/java/com/qkdata/sms/consumer/SmsRetryListener.java
+20
-0
TaskConsumer.java
src/main/java/com/qkdata/sms/consumer/TaskConsumer.java
+128
-0
BusinessException.java
...main/java/com/qkdata/sms/exception/BusinessException.java
+29
-0
ControllerExceptionHandleAdvice.java
...qkdata/sms/exception/ControllerExceptionHandleAdvice.java
+107
-0
SmsException.java
src/main/java/com/qkdata/sms/exception/SmsException.java
+17
-0
SmsRemoteApiException.java
.../java/com/qkdata/sms/exception/SmsRemoteApiException.java
+14
-0
IndividuationSmsRequest.java
.../java/com/qkdata/sms/lmobile/IndividuationSmsRequest.java
+59
-0
IndividuationSmsVariableRequest.java
...m/qkdata/sms/lmobile/IndividuationSmsVariableRequest.java
+19
-0
MobileUnit.java
src/main/java/com/qkdata/sms/lmobile/MobileUnit.java
+22
-0
MobileUnitValue.java
src/main/java/com/qkdata/sms/lmobile/MobileUnitValue.java
+16
-0
RegularSmsRequest.java
src/main/java/com/qkdata/sms/lmobile/RegularSmsRequest.java
+76
-0
InsertTemplateCondition.java
...in/java/com/qkdata/sms/model/InsertTemplateCondition.java
+42
-0
LmobileNotifyCondition.java
...ain/java/com/qkdata/sms/model/LmobileNotifyCondition.java
+114
-0
LmobileResponse.java
src/main/java/com/qkdata/sms/model/LmobileResponse.java
+27
-0
NotifyRequestDTO.java
src/main/java/com/qkdata/sms/model/NotifyRequestDTO.java
+31
-0
SmsAliCondition.java
src/main/java/com/qkdata/sms/model/SmsAliCondition.java
+39
-0
SmsApiV1Condition.java
src/main/java/com/qkdata/sms/model/SmsApiV1Condition.java
+26
-0
SmsApiV1Response.java
src/main/java/com/qkdata/sms/model/SmsApiV1Response.java
+33
-0
SmsCondition.java
src/main/java/com/qkdata/sms/model/SmsCondition.java
+38
-0
SmsMessageCondition.java
src/main/java/com/qkdata/sms/model/SmsMessageCondition.java
+19
-0
SmsReport.java
src/main/java/com/qkdata/sms/model/SmsReport.java
+18
-0
SmsTypeAndChannelDTO.java
src/main/java/com/qkdata/sms/model/SmsTypeAndChannelDTO.java
+23
-0
SmsV1Condition.java
src/main/java/com/qkdata/sms/model/SmsV1Condition.java
+21
-0
SmsV1Property.java
src/main/java/com/qkdata/sms/model/SmsV1Property.java
+20
-0
AbstractNotifyRequest.java
...va/com/qkdata/sms/notification/AbstractNotifyRequest.java
+25
-0
AliNotifyRequest.java
...in/java/com/qkdata/sms/notification/AliNotifyRequest.java
+44
-0
LmobileNotifyRequest.java
...ava/com/qkdata/sms/notification/LmobileNotifyRequest.java
+45
-0
NotifyRequest.java
src/main/java/com/qkdata/sms/notification/NotifyRequest.java
+18
-0
NotifyTemplate.java
...main/java/com/qkdata/sms/notification/NotifyTemplate.java
+94
-0
SmsTemplateMapper.java
...ain/java/com/qkdata/sms/repository/SmsTemplateMapper.java
+15
-0
AbstractPlaceholderResolver.java
...a/com/qkdata/sms/service/AbstractPlaceholderResolver.java
+51
-0
AliPlaceholderResolver.java
...n/java/com/qkdata/sms/service/AliPlaceholderResolver.java
+23
-0
AliSmsSender.java
src/main/java/com/qkdata/sms/service/AliSmsSender.java
+124
-0
LmobilePlaceholderResolver.java
...va/com/qkdata/sms/service/LmobilePlaceholderResolver.java
+40
-0
LmobileSmsSender.java
src/main/java/com/qkdata/sms/service/LmobileSmsSender.java
+91
-0
PlaceholderResolver.java
...main/java/com/qkdata/sms/service/PlaceholderResolver.java
+27
-0
RedisService.java
src/main/java/com/qkdata/sms/service/RedisService.java
+65
-0
SmsSender.java
src/main/java/com/qkdata/sms/service/SmsSender.java
+10
-0
SmsService.java
src/main/java/com/qkdata/sms/service/SmsService.java
+235
-0
TemplateService.java
src/main/java/com/qkdata/sms/service/TemplateService.java
+63
-0
JaxbUtils.java
src/main/java/com/qkdata/sms/util/JaxbUtils.java
+75
-0
RandomDigitGenerator.java
src/main/java/com/qkdata/sms/util/RandomDigitGenerator.java
+29
-0
application-dev.yml
src/main/resources/application-dev.yml
+14
-0
application.yml
src/main/resources/application.yml
+67
-0
V1.0.0__init.sql
src/main/resources/db/migration/V1.0.0__init.sql
+23
-0
logback-spring.xml
src/main/resources/logback-spring.xml
+105
-0
SmsTemplateMapper.xml
src/main/resources/mappers/SmsTemplateMapper.xml
+29
-0
SmsTemplateMapperTest.java
src/test/java/com/qkdata/sms/SmsTemplateMapperTest.java
+44
-0
SmsTest.java
src/test/java/com/qkdata/sms/SmsTest.java
+24
-0
SplitMobileTest.java
src/test/java/com/qkdata/sms/SplitMobileTest.java
+58
-0
No files found.
.gitignore
0 → 100644
View file @
93a4b7eb
# Created by https://www.gitignore.io/api/eclipse,intellij,maven
### Eclipse ###
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
# Eclipse Core
.project
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# JDT-specific (Eclipse Java Development Tools)
.classpath
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/**/workspace.xml
.idea/**/tasks.xml
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
### Intellij Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
.DS_Store
.idea
*.iml
# modules.xml
# .idea/misc.xml
# *.ipr
### Maven ###
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
### redis ###
*.rdb
#*.jpg
#*.png
# Exclude maven wrapper
!/.mvn/wrapper/maven-wrapper.jar
# End of https://www.gitignore.io/api/eclipse,intellij,maven
.tomcatplugin
.mvn/
work/*
logs/
src/main/webapp/WEB-INF/classes/
src/main/webapp/WEB-INF/lib/
src/main/webapp/META-INF/MANIFEST.MF
src/main/webapp/upload/
\ No newline at end of file
README.md
0 → 100644
View file @
93a4b7eb
# sms
短信发送server
\ No newline at end of file
pom.xml
0 → 100644
View file @
93a4b7eb
<project
xmlns=
"http://maven.apache.org/POM/4.0.0"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>
4.0.0
</modelVersion>
<groupId>
com.qkdata
</groupId>
<artifactId>
online-edu-sms
</artifactId>
<version>
1.0.0
</version>
<packaging>
jar
</packaging>
<name>
freego-sms
</name>
<parent>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-parent
</artifactId>
<version>
2.1.3.RELEASE
</version>
</parent>
<dependencies>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot
</artifactId>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-web
</artifactId>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-actuator
</artifactId>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-data-redis
</artifactId>
<exclusions>
<exclusion>
<groupId>
io.lettuce
</groupId>
<artifactId>
lettuce-core
</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>
redis.clients
</groupId>
<artifactId>
jedis
</artifactId>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-test
</artifactId>
<scope>
test
</scope>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-configuration-processor
</artifactId>
<optional>
true
</optional>
</dependency>
<dependency>
<groupId>
com.github.pagehelper
</groupId>
<artifactId>
pagehelper-spring-boot-starter
</artifactId>
<version>
1.2.10
</version>
</dependency>
<dependency>
<groupId>
org.mybatis.spring.boot
</groupId>
<artifactId>
mybatis-spring-boot-starter
</artifactId>
<version>
${mybatis.spring.boot.version}
</version>
</dependency>
<dependency>
<groupId>
tk.mybatis
</groupId>
<artifactId>
mapper-spring-boot-starter
</artifactId>
<version>
${mybatis.mapper.version}
</version>
</dependency>
<dependency>
<groupId>
org.mybatis
</groupId>
<artifactId>
mybatis-typehandlers-jsr310
</artifactId>
<version>
${mybatis.typehandlers.jsr.version}
</version>
</dependency>
<dependency>
<groupId>
com.alibaba
</groupId>
<artifactId>
druid-spring-boot-starter
</artifactId>
<version>
1.1.10
</version>
</dependency>
<dependency>
<groupId>
mysql
</groupId>
<artifactId>
mysql-connector-java
</artifactId>
<version>
${mysql.connector.version}
</version>
</dependency>
<dependency>
<groupId>
org.apache.httpcomponents
</groupId>
<artifactId>
httpclient
</artifactId>
<version>
4.5.3
</version>
</dependency>
<dependency>
<groupId>
org.projectlombok
</groupId>
<artifactId>
lombok
</artifactId>
<version>
1.16.18
</version>
<scope>
provided
</scope>
</dependency>
<!-- 阿里云短信服务 -->
<dependency>
<groupId>
com.aliyun
</groupId>
<artifactId>
aliyun-java-sdk-core
</artifactId>
<version>
4.1.1
</version>
<!-- 注:如提示报错,先升级基础包版,无法解决可联系技术支持 -->
</dependency>
<dependency>
<groupId>
com.aliyun
</groupId>
<artifactId>
aliyun-java-sdk-dysmsapi
</artifactId>
<version>
1.1.0
</version>
</dependency>
<dependency>
<groupId>
org.apache.commons
</groupId>
<artifactId>
commons-pool2
</artifactId>
<!--<version>2.6.0</version>-->
</dependency>
<dependency>
<groupId>
com.github.rholder
</groupId>
<artifactId>
guava-retrying
</artifactId>
<version>
2.0.0
</version>
</dependency>
<dependency>
<groupId>
org.flywaydb
</groupId>
<artifactId>
flyway-core
</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-maven-plugin
</artifactId>
<version>
2.1.3.RELEASE
</version>
<configuration>
<mainClass>
${start-class}
</mainClass>
<layout>
ZIP
</layout>
<classifier>
all
</classifier>
</configuration>
<executions>
<execution>
<goals>
<goal>
repackage
</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>
com.spotify
</groupId>
<artifactId>
docker-maven-plugin
</artifactId>
<version>
${docker.plugin.version}
</version>
<configuration>
<serverId>
harbor
</serverId>
<registryUrl>
http://${docker.registry}/v2/
</registryUrl>
<imageName>
${docker.image.prefix}/${project.artifactId}:${project.version}
</imageName>
<dockerDirectory>
${project.basedir}/src/main/docker
</dockerDirectory>
<resources>
<resource>
<targetPath>
/
</targetPath>
<directory>
${project.build.directory}
</directory>
<include>
*.jar
</include>
</resource>
</resources>
<buildArgs>
<JAR_FILE>
${project.build.finalName}-all.jar
</JAR_FILE>
</buildArgs>
</configuration>
<executions>
<execution>
<phase>
install
</phase>
<goals>
<goal>
build
</goal>
</goals>
</execution>
<execution>
<id>
tag-image
</id>
<phase>
install
</phase>
<goals>
<goal>
tag
</goal>
</goals>
<configuration>
<image>
${docker.image.prefix}/${project.artifactId}:${project.version}
</image>
<newName>
${docker.registry}/${docker.image.prefix}/${project.artifactId}:${project.version}
</newName>
</configuration>
</execution>
<execution>
<id>
push-image
</id>
<phase>
install
</phase>
<goals>
<goal>
push
</goal>
</goals>
<configuration>
<imageName>
${docker.registry}/${docker.image.prefix}/${project.artifactId}:${project.version}
</imageName>
</configuration>
</execution>
<execution>
<id>
remove-image
</id>
<phase>
install
</phase>
<goals>
<goal>
removeImage
</goal>
</goals>
<configuration>
<imageName>
${docker.image.prefix}/${project.artifactId}:${project.version}
</imageName>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<project.build.sourceEncoding>
UTF-8
</project.build.sourceEncoding>
<java.version>
1.8
</java.version>
<commons-lang3.version>
3.1
</commons-lang3.version>
<mysql.connector.version>
5.1.41
</mysql.connector.version>
<druid.springboot.starter.version>
1.1.9
</druid.springboot.starter.version>
<mybatis.spring.boot.version>
1.3.0
</mybatis.spring.boot.version>
<mybatis.pagerhelper.version>
1.2.3
</mybatis.pagerhelper.version>
<mybatis.mapper.version>
2.1.2
</mybatis.mapper.version>
<mybatis.typehandlers.jsr.version>
1.0.2
</mybatis.typehandlers.jsr.version>
<docker.plugin.version>
1.1.1
</docker.plugin.version>
<docker.registry>
fywlsoft.cn:57802
</docker.registry>
<docker.image.prefix>
argus
</docker.image.prefix>
</properties>
</project>
src/main/docker/Dockerfile
0 → 100644
View file @
93a4b7eb
FROM
fywlsoft.cn:57802/library/alpine-java:8u202b08_server-jre_unlimited
ARG
JAR_FILE
ADD
${JAR_FILE} $WORKDIR
RUN
mv
${
JAR_FILE
}
app.jar
COPY
docker-entrypoint.sh /usr/share/app/docker-entrypoint.sh
RUN
chmod
+x /usr/share/app/docker-entrypoint.sh
ENV
MYSQL_URL tcp://mysql:3306
ENV
WAIT_MYSQL_TIMEOUT 30s
ENV
JAVA_OPTIONS "-Xms256m -Xmx512m -Dfile.encoding=UTF-8"
ENV
OVERRIDE_PROP ""
ENTRYPOINT
["/usr/share/app/docker-entrypoint.sh"]
EXPOSE
80
HEALTHCHECK
--timeout=5s --start-period=60s \
CMD curl -f http://localhost/sms/actuator/health || exit 1
src/main/docker/docker-entrypoint.sh
0 → 100644
View file @
93a4b7eb
#!/bin/bash
set
-ex
java
$JAVA_OPTIONS
-jar
app.jar
$OVERRIDE_PROP
src/main/java/com/qkdata/sms/Application.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
;
import
org.springframework.boot.SpringApplication
;
import
org.springframework.boot.autoconfigure.SpringBootApplication
;
import
org.springframework.scheduling.annotation.EnableAsync
;
import
org.springframework.scheduling.annotation.EnableScheduling
;
import
tk.mybatis.spring.annotation.MapperScan
;
/**
* @author chenjiahai
*/
@SpringBootApplication
@MapperScan
(
"com.qkdata.sms.**.repository"
)
@EnableScheduling
@EnableAsync
public
class
Application
{
public
static
void
main
(
String
[]
args
)
{
SpringApplication
.
run
(
Application
.
class
,
args
);
}
}
src/main/java/com/qkdata/sms/api/SmsController.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
api
;
import
com.qkdata.sms.exception.SmsException
;
import
com.qkdata.sms.service.TemplateService
;
import
com.qkdata.sms.constant.ResponseData
;
import
com.qkdata.sms.model.InsertTemplateCondition
;
import
com.qkdata.sms.model.SmsMessageCondition
;
import
com.qkdata.sms.model.SmsV1Condition
;
import
com.qkdata.sms.service.SmsService
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.web.bind.annotation.*
;
import
javax.validation.Valid
;
import
java.io.IOException
;
/**
* @author chenjiahai
* @date 17/9/25
*/
@RestController
@RequestMapping
(
"api"
)
public
class
SmsController
{
@Autowired
private
SmsService
smsService
;
@Autowired
private
TemplateService
templateService
;
@PostMapping
(
"v3"
)
public
ResponseData
sendSmsV3
(
@RequestBody
@Valid
SmsMessageCondition
condition
)
throws
Exception
{
smsService
.
send
(
condition
);
return
ResponseData
.
build
();
}
@GetMapping
(
"v1/templates"
)
public
ResponseData
smsTemplateList
()
{
return
ResponseData
.
builder
()
.
data
(
"templates"
,
templateService
.
smsTemplateList
())
.
build
();
}
@PostMapping
(
"v1/templates"
)
public
ResponseData
insertTemplate
(
@RequestBody
@Valid
InsertTemplateCondition
insertTemplateCondition
)
throws
Exception
{
return
ResponseData
.
builder
()
.
data
(
"templateId"
,
templateService
.
insertTemplate
(
insertTemplateCondition
))
.
build
();
}
@GetMapping
(
"v1/sms"
)
public
ResponseData
sendSms
(
SmsV1Condition
smsCondition
)
throws
SmsException
,
IOException
{
smsService
.
sendSms
(
smsCondition
);
return
ResponseData
.
build
();
}
}
src/main/java/com/qkdata/sms/api/SmsNotifyController.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
api
;
import
com.qkdata.sms.model.LmobileNotifyCondition
;
import
com.qkdata.sms.model.SmsReport
;
import
com.qkdata.sms.service.SmsService
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.boot.configurationprocessor.json.JSONObject
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.http.ResponseEntity
;
import
org.springframework.web.bind.annotation.*
;
import
java.util.List
;
/**
* @author chenjiahai
* @date 17/9/25
*/
@RestController
@RequestMapping
public
class
SmsNotifyController
{
@Autowired
private
SmsService
smsService
;
@PostMapping
(
"lmobile-notify"
)
@ResponseStatus
(
HttpStatus
.
OK
)
public
void
receive
(
LmobileNotifyCondition
lmobileNotifyCondition
)
{
smsService
.
lmobileNotify
(
lmobileNotifyCondition
);
}
@PostMapping
(
"ali-notify"
)
public
ResponseEntity
receive
(
@RequestBody
List
<
SmsReport
>
smsReports
)
throws
Exception
{
smsService
.
aliNotify
(
smsReports
);
JSONObject
jsonObject
=
new
JSONObject
();
jsonObject
.
put
(
"code"
,
0
);
jsonObject
.
put
(
"msg"
,
"成功"
);
return
ResponseEntity
.
ok
(
jsonObject
.
toString
());
}
}
src/main/java/com/qkdata/sms/config/CommonEntityEnum.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
config
;
import
com.fasterxml.jackson.annotation.JsonValue
;
import
java.util.HashMap
;
import
java.util.Map
;
/**
* 所有Entity中枚举类型的基础, 主要用于数据库中存储枚举的value和SpringMVC参数直接转换成Enum
*
* @author chenjiahai
* @date 17/8/3
*/
public
interface
CommonEntityEnum
{
/**
* JsonValue用于serializer
*
* @return
*/
@JsonValue
int
value
();
/**
* 获取枚举值对应的枚举
*
* @param enumClass 枚举类
* @param enumValue 枚举值
* @return 枚举
*/
static
<
E
extends
CommonEntityEnum
>
E
getEnum
(
final
Class
<
E
>
enumClass
,
final
Integer
enumValue
)
{
if
(
enumValue
==
null
)
{
return
null
;
}
try
{
return
valueOf
(
enumClass
,
enumValue
);
}
catch
(
final
IllegalArgumentException
ex
)
{
return
null
;
}
}
/**
* 获取枚举值对应的枚举
*
* @param enumClass 枚举类
* @param enumValue 枚举值
* @return 枚举
*/
static
<
E
extends
CommonEntityEnum
>
E
valueOf
(
Class
<
E
>
enumClass
,
Integer
enumValue
)
{
if
(
enumValue
==
null
)
{
throw
new
NullPointerException
(
"EnumValue is null"
);
}
return
getEnumMap
(
enumClass
).
get
(
enumValue
);
}
/**
* 获取枚举键值对
*
* @param enumClass 枚举类型
* @return 键值对
*/
static
<
E
extends
CommonEntityEnum
>
Map
<
Integer
,
E
>
getEnumMap
(
Class
<
E
>
enumClass
)
{
E
[]
enums
=
enumClass
.
getEnumConstants
();
if
(
enums
==
null
)
{
throw
new
IllegalArgumentException
(
enumClass
.
getSimpleName
()
+
" does not represent an enum type."
);
}
Map
<
Integer
,
E
>
map
=
new
HashMap
<>();
for
(
E
t
:
enums
)
{
map
.
put
(
t
.
value
(),
t
);
}
return
map
;
}
}
src/main/java/com/qkdata/sms/config/CustomEnumTypeHandler.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
config
;
import
com.qkdata.sms.constant.SmsTypeEnum
;
import
com.qkdata.sms.constant.SmsChannelEnum
;
import
org.apache.ibatis.type.BaseTypeHandler
;
import
org.apache.ibatis.type.JdbcType
;
import
org.apache.ibatis.type.MappedTypes
;
import
java.sql.CallableStatement
;
import
java.sql.PreparedStatement
;
import
java.sql.ResultSet
;
import
java.sql.SQLException
;
/**
* @author chenjiahai
*/
// org.apache.ibatis.type.TypeHandlerRegistry#register(TypeHandler<T> typeHandler)#Line:292
@MappedTypes
({
SmsChannelEnum
.
class
,
SmsTypeEnum
.
class
})
@SuppressWarnings
(
"unused"
)
public
class
CustomEnumTypeHandler
<
E
extends
CommonEntityEnum
>
extends
BaseTypeHandler
<
E
>
{
private
Class
<
E
>
type
;
public
CustomEnumTypeHandler
(
Class
<
E
>
type
)
{
if
(
type
==
null
)
{
throw
new
IllegalArgumentException
(
"Type argument cannot be null"
);
}
this
.
type
=
type
;
E
[]
enums
=
type
.
getEnumConstants
();
if
(
enums
==
null
)
{
throw
new
IllegalArgumentException
(
type
.
getSimpleName
()
+
" does not represent an enum type."
);
}
}
@Override
public
void
setNonNullParameter
(
PreparedStatement
ps
,
int
i
,
E
parameter
,
JdbcType
jdbcType
)
throws
SQLException
{
ps
.
setInt
(
i
,
parameter
.
value
());
}
@Override
public
E
getNullableResult
(
ResultSet
rs
,
String
columnName
)
throws
SQLException
{
int
i
=
rs
.
getInt
(
columnName
);
if
(
rs
.
wasNull
())
{
return
null
;
}
else
{
try
{
return
CommonEntityEnum
.
getEnum
(
type
,
i
);
}
catch
(
Exception
ex
)
{
throw
new
IllegalArgumentException
(
"Cannot convert "
+
i
+
" to "
+
type
.
getSimpleName
()
+
" by int value."
,
ex
);
}
}
}
@Override
public
E
getNullableResult
(
ResultSet
rs
,
int
columnIndex
)
throws
SQLException
{
int
i
=
rs
.
getInt
(
columnIndex
);
if
(
rs
.
wasNull
())
{
return
null
;
}
else
{
try
{
return
CommonEntityEnum
.
getEnum
(
type
,
i
);
}
catch
(
Exception
ex
)
{
throw
new
IllegalArgumentException
(
"Cannot convert "
+
i
+
" to "
+
type
.
getSimpleName
()
+
" by int value."
,
ex
);
}
}
}
@Override
public
E
getNullableResult
(
CallableStatement
cs
,
int
columnIndex
)
throws
SQLException
{
int
i
=
cs
.
getInt
(
columnIndex
);
if
(
cs
.
wasNull
())
{
return
null
;
}
else
{
try
{
return
CommonEntityEnum
.
getEnum
(
type
,
i
);
}
catch
(
Exception
ex
)
{
throw
new
IllegalArgumentException
(
"Cannot convert "
+
i
+
" to "
+
type
.
getSimpleName
()
+
" by int value."
,
ex
);
}
}
}
}
\ No newline at end of file
src/main/java/com/qkdata/sms/config/RedisConfiguration.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
config
;
import
com.fasterxml.jackson.annotation.JsonAutoDetect
;
import
com.fasterxml.jackson.annotation.PropertyAccessor
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.boot.autoconfigure.AutoConfigureAfter
;
import
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.data.redis.connection.RedisStandaloneConfiguration
;
import
org.springframework.data.redis.connection.jedis.JedisClientConfiguration
;
import
org.springframework.data.redis.connection.jedis.JedisConnectionFactory
;
import
org.springframework.data.redis.core.RedisTemplate
;
import
org.springframework.data.redis.core.StringRedisTemplate
;
import
org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer
;
import
org.springframework.data.redis.serializer.StringRedisSerializer
;
import
java.time.Duration
;
@Configuration
@AutoConfigureAfter
(
RedisAutoConfiguration
.
class
)
public
class
RedisConfiguration
{
@Value
(
"${spring.redis.database}"
)
private
int
database
;
@Value
(
"${spring.redis.host}"
)
private
String
host
;
@Value
(
"${spring.redis.password}"
)
private
String
password
;
@Value
(
"${spring.redis.port}"
)
private
int
port
;
@Value
(
"${spring.redis.timeout}"
)
private
String
timeout
;
@Bean
public
JedisConnectionFactory
jedisConnectionFactory
()
{
RedisStandaloneConfiguration
configuration
=
new
RedisStandaloneConfiguration
(
host
,
port
);
configuration
.
setDatabase
(
database
);
configuration
.
setPassword
(
password
);
JedisClientConfiguration
clientConfig
=
JedisClientConfiguration
.
builder
()
.
connectTimeout
(
Duration
.
ofSeconds
(
3
))
.
usePooling
()
.
build
();
return
new
JedisConnectionFactory
(
configuration
,
clientConfig
);
}
// @Bean
// public GenericObjectPoolConfig genericObjectPoolConfig() {
// GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
// genericObjectPoolConfig.setMaxIdle(maxIdle);
// genericObjectPoolConfig.setMinIdle(minIdle);
// genericObjectPoolConfig.setMaxTotal(maxActive);
// genericObjectPoolConfig.setMaxWaitMillis(maxWait.toMillis());
// return genericObjectPoolConfig;
// }
@Bean
public
StringRedisTemplate
stringRedisTemplate
()
{
StringRedisTemplate
stringRedisTemplate
=
new
StringRedisTemplate
();
stringRedisTemplate
.
setConnectionFactory
(
jedisConnectionFactory
());
return
stringRedisTemplate
;
}
@Bean
public
RedisTemplate
<
Object
,
Object
>
redisTemplate
()
{
RedisTemplate
<
Object
,
Object
>
redisTemplate
=
new
RedisTemplate
<>();
redisTemplate
.
setConnectionFactory
(
jedisConnectionFactory
());
// 使用Jackson2JsonRedisSerialize 替换默认序列化
Jackson2JsonRedisSerializer
jackson2JsonRedisSerializer
=
new
Jackson2JsonRedisSerializer
(
Object
.
class
);
ObjectMapper
objectMapper
=
new
ObjectMapper
();
objectMapper
.
setVisibility
(
PropertyAccessor
.
ALL
,
JsonAutoDetect
.
Visibility
.
ANY
);
objectMapper
.
enableDefaultTyping
(
ObjectMapper
.
DefaultTyping
.
NON_FINAL
);
jackson2JsonRedisSerializer
.
setObjectMapper
(
objectMapper
);
// 设置value的序列化规则和 key的序列化规则
redisTemplate
.
setKeySerializer
(
new
StringRedisSerializer
());
redisTemplate
.
setValueSerializer
(
jackson2JsonRedisSerializer
);
redisTemplate
.
afterPropertiesSet
();
return
redisTemplate
;
}
}
src/main/java/com/qkdata/sms/constant/CommonResponseEnum.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
constant
;
import
java.io.Serializable
;
/**
* API 所有ResponseEnum基础
*
* @author chenjiahai
* @date 17/5/14
*/
public
interface
CommonResponseEnum
extends
Serializable
{
Integer
value
();
String
text
();
}
src/main/java/com/qkdata/sms/constant/ConstantResponseEnum.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
constant
;
/**
* @author : songminghui
* @date : 17/9/4 上午11:53
* @description :
* @email : songminghui@shangweiec.com
*/
public
enum
ConstantResponseEnum
implements
CommonResponseEnum
{
OK
(
0
,
"ok"
),
INTERNAL_ERROR
(-
1
,
"服务器异常"
),
JSON_PARSE_ERROR
(
45000
,
"不合法的请求JSON格式"
),
MISSING_PATH_VARIABLE
(
45001
,
"缺少请求URL路径参数"
),
MISSING_REQUEST_PARAMETER
(
45002
,
"缺少请求query参数"
),
METHOD_ARGUMENT_TYPE_MIS_MATCH
(
45003
,
"不合法的请求参数类型"
),
CONSTRAINT_VIOLATION
(
45004
,
"请求参数验证未通过"
),
MEDIA_TYPE_NOT_SUPPORTED
(
45005
,
"不合法的请求Content-Type"
),
REQUEST_METHOD_NOT_SUPPORTED
(
45006
,
"不合法的请求方法"
),
NO_HANDLER_FOUND
(
45007
,
"请求资源不存在"
),
TEMPLATE_CODE_ISEXIST
(
45008
,
"短信模板code已存在"
);
private
Integer
value
;
private
String
text
;
@Override
public
Integer
value
()
{
return
value
;
}
@Override
public
String
text
()
{
return
text
;
}
ConstantResponseEnum
(
Integer
value
,
String
text
)
{
this
.
value
=
value
;
this
.
text
=
text
;
}
}
src/main/java/com/qkdata/sms/constant/ResponseData.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
constant
;
import
java.io.Serializable
;
import
java.util.LinkedHashMap
;
import
java.util.List
;
import
java.util.Map
;
import
org.springframework.http.HttpStatus
;
import
com.fasterxml.jackson.annotation.JsonIgnore
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
/**
* RESTFUL接口统一返回
*
* @author chenjiahai
* @date 17/5/9
*/
@Data
@NoArgsConstructor
public
class
ResponseData
implements
Serializable
{
private
static
final
long
serialVersionUID
=
-
3856663982622822873L
;
/**
* 业务编码
*/
private
Integer
code
=
ConstantResponseEnum
.
OK
.
value
();
/**
* 对用户友好的提示信息, 供前端显示使用
*/
private
String
message
=
ConstantResponseEnum
.
OK
.
text
();
/**
* 业务数据
*/
private
Map
<
String
,
Object
>
data
=
new
LinkedHashMap
<>();
/**
* 不合法参数
*/
private
List
<
Object
>
errors
;
@JsonIgnore
private
CommonResponseEnum
responseEnum
;
public
static
ResponseDataBuilder
builder
()
{
return
new
ResponseDataBuilder
(
new
ResponseData
());
}
/**
* 默认ResponseData: code为0, message为OK
*
* @return
*/
public
static
ResponseData
build
()
{
return
builder
().
build
();
}
public
ResponseData
(
CommonResponseEnum
responseEnum
)
{
this
.
code
=
responseEnum
.
value
();
this
.
message
=
responseEnum
.
text
();
}
public
ResponseData
(
HttpStatus
httpStatus
,
String
message
)
{
this
.
code
=
httpStatus
.
value
();
this
.
message
=
message
;
}
public
ResponseData
(
HttpStatus
httpStatus
,
String
message
,
List
<
Object
>
errors
)
{
this
(
httpStatus
,
message
);
this
.
errors
=
errors
;
}
}
src/main/java/com/qkdata/sms/constant/ResponseDataBuilder.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
constant
;
import
java.util.List
;
/**
*
* @author chenjiahai
* @date 17/5/19
*/
public
class
ResponseDataBuilder
{
private
ResponseData
responseData
;
public
ResponseDataBuilder
(
ResponseData
responseData
)
{
this
.
responseData
=
responseData
;
}
public
ResponseDataBuilder
code
(
Integer
code
)
{
responseData
.
setCode
(
code
);
return
this
;
}
public
ResponseDataBuilder
message
(
String
userMessage
)
{
responseData
.
setMessage
(
userMessage
);
return
this
;
}
public
ResponseDataBuilder
result
(
CommonResponseEnum
responseEnum
)
{
responseData
.
setCode
(
responseEnum
.
value
());
responseData
.
setMessage
(
responseEnum
.
text
());
return
this
;
}
public
ResponseDataBuilder
data
(
String
key
,
Object
value
)
{
responseData
.
getData
().
put
(
key
,
value
);
return
this
;
}
public
ResponseDataBuilder
errors
(
List
<
Object
>
errors
)
{
responseData
.
setErrors
(
errors
);
return
this
;
}
public
ResponseData
build
()
{
return
responseData
;
}
}
src/main/java/com/qkdata/sms/constant/RestTemplateConfiguration.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
constant
;
import
org.apache.http.client.HttpClient
;
import
org.apache.http.client.config.RequestConfig
;
import
org.apache.http.impl.client.CloseableHttpClient
;
import
org.apache.http.impl.client.HttpClientBuilder
;
import
org.apache.http.impl.conn.PoolingHttpClientConnectionManager
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.http.client.HttpComponentsClientHttpRequestFactory
;
import
org.springframework.web.client.RestTemplate
;
/**
*
* @author chenjiahai
* @date 17/10/9
*/
@Configuration
public
class
RestTemplateConfiguration
{
@Bean
public
PoolingHttpClientConnectionManager
poolingHttpClientConnectionManager
()
{
PoolingHttpClientConnectionManager
manager
=
new
PoolingHttpClientConnectionManager
();
manager
.
setMaxTotal
(
20
);
return
manager
;
}
@Bean
public
RequestConfig
requestConfig
()
{
return
RequestConfig
.
custom
()
.
setConnectionRequestTimeout
(
2000
)
.
setConnectTimeout
(
2000
)
.
setSocketTimeout
(
2000
)
.
build
();
}
@Bean
public
CloseableHttpClient
httpClient
(
PoolingHttpClientConnectionManager
poolingHttpClientConnectionManager
,
RequestConfig
requestConfig
)
{
return
HttpClientBuilder
.
create
()
.
setConnectionManager
(
poolingHttpClientConnectionManager
)
.
setDefaultRequestConfig
(
requestConfig
)
.
build
();
}
@Bean
public
RestTemplate
restTemplate
(
HttpClient
httpClient
)
{
HttpComponentsClientHttpRequestFactory
requestFactory
=
new
HttpComponentsClientHttpRequestFactory
();
requestFactory
.
setHttpClient
(
httpClient
);
return
new
RestTemplate
(
requestFactory
);
}
}
src/main/java/com/qkdata/sms/constant/SmsChannelEnum.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
constant
;
import
com.qkdata.sms.config.CommonEntityEnum
;
public
enum
SmsChannelEnum
implements
CommonEntityEnum
{
ALIBABA
(
1
,
"阿里短信平台"
),
LMOBILE
(
2
,
"乐信通短信平台"
);
private
Integer
value
;
private
String
text
;
SmsChannelEnum
(
Integer
value
,
String
text
)
{
this
.
value
=
value
;
this
.
text
=
text
;
}
@Override
public
int
value
()
{
return
this
.
value
;
}
}
src/main/java/com/qkdata/sms/constant/SmsResponseEnum.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
constant
;
/**
* @author chenjiahai
* @date 17/9/27
*/
public
enum
SmsResponseEnum
implements
CommonResponseEnum
{
SEND_ERROR
(
92000
,
"发送短信失败"
),
EMPTY_CAPTCHA
(
92001
,
"验证码不能为空"
),
INVALID_CAPTCHA
(
92002
,
"不合法的验证码"
),
TEMPLATE_CODE_ISEXIST
(
92003
,
"短信模板code已存在"
),
TEMPLATE_NOT_EXIST
(
92004
,
"短信模板不存在"
),
TEMPLATE_PARAMS_MISMATCHED
(
92005
,
"模板参数与模板变量不符"
),
TEMPLATE_ERROR
(
92006
,
"短信模板错误"
);
private
Integer
value
;
private
String
text
;
SmsResponseEnum
(
Integer
value
,
String
text
)
{
this
.
value
=
value
;
this
.
text
=
text
;
}
@Override
public
Integer
value
()
{
return
value
;
}
@Override
public
String
text
()
{
return
text
;
}
}
src/main/java/com/qkdata/sms/constant/SmsTemplate.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
constant
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
import
javax.persistence.*
;
import
java.time.LocalDateTime
;
/**
* 短信模板实体
*/
@Data
@NoArgsConstructor
@Entity
@Table
(
name
=
"sms_template"
)
public
class
SmsTemplate
{
/**
* 主键
*/
@Id
@Column
(
name
=
"id"
)
@GeneratedValue
(
generator
=
"JDBC"
)
private
Integer
id
;
/**
* 模板编号,自定义
*/
@Column
(
name
=
"code"
)
private
String
code
;
/**
* 模板类型, 验证码类、通知类、交互类等
*/
@Column
(
name
=
"type"
)
private
SmsTypeEnum
type
;
/**
* 优先使用渠道平台, 阿里短信平台、乐信通短信平台等
*/
@Column
(
name
=
"channel"
)
private
SmsChannelEnum
channel
;
/**
* 模板状态
*/
@Column
(
name
=
"status"
)
private
Integer
status
;
/**
* 阿里模板编号
*/
@Column
(
name
=
"alibaba_template_code"
)
private
String
alibabaTemplateCode
;
/**
* 阿里模板内容
*/
@Column
(
name
=
"alibaba_template_content"
)
private
String
alibabaTemplateContent
;
/**
* 阿里短信签名
*/
@Column
(
name
=
"alibaba_sign_name"
)
private
String
alibabaSignName
;
/**
* 乐信通模板内容
*/
@Column
(
name
=
"lmobile_template_content"
)
private
String
lmobileTemplateContent
;
/**
* 乐信通短信签名
*/
@Column
(
name
=
"lmobile_sign_name"
)
private
String
lmobileSignName
;
/**
* 创建时间
*/
@Column
(
name
=
"create_at"
)
private
LocalDateTime
createAt
;
/**
* 更新时间
*/
@Column
(
name
=
"update_at"
)
private
LocalDateTime
updateAt
;
public
SmsTemplate
(
String
code
)
{
this
.
code
=
code
;
}
}
src/main/java/com/qkdata/sms/constant/SmsTypeEnum.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
constant
;
import
com.qkdata.sms.config.CommonEntityEnum
;
/**
* 短信类型枚举
*/
public
enum
SmsTypeEnum
implements
CommonEntityEnum
{
CAPTCHA
(
1
,
"验证码类等"
),
NOTIFICATION
(
2
,
"通知类等"
);
private
Integer
value
;
private
String
text
;
SmsTypeEnum
(
Integer
value
,
String
text
)
{
this
.
value
=
value
;
this
.
text
=
text
;
}
@Override
public
int
value
()
{
return
this
.
value
;
}
}
src/main/java/com/qkdata/sms/consumer/QueueConstants.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
consumer
;
public
class
QueueConstants
{
// 验证码短信队列
public
static
final
String
CAPTCHA_QUEUE_NAME
=
"captcha:queue"
;
// 通知短信队列
public
static
final
String
NOTIFICATION_QUEUE_NAME
=
"notification:queue"
;
}
src/main/java/com/qkdata/sms/consumer/SendSmsTask.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
consumer
;
import
com.qkdata.sms.exception.SmsException
;
import
com.qkdata.sms.exception.SmsRemoteApiException
;
import
com.qkdata.sms.model.SmsCondition
;
import
com.qkdata.sms.service.SmsSender
;
import
lombok.extern.slf4j.Slf4j
;
import
java.util.concurrent.Callable
;
@Slf4j
public
class
SendSmsTask
implements
Callable
<
Boolean
>
{
private
SmsCondition
smsCondition
;
private
SmsSender
aliSmsSender
;
private
SmsSender
lmobileSmsSender
;
private
SmsSender
current
;
public
SendSmsTask
(
SmsCondition
smsCondition
,
SmsSender
aliSmsSender
,
SmsSender
lmobileSmsSender
)
{
this
.
smsCondition
=
smsCondition
;
this
.
aliSmsSender
=
aliSmsSender
;
this
.
lmobileSmsSender
=
lmobileSmsSender
;
}
@Override
public
Boolean
call
()
throws
SmsException
{
try
{
switchSmsSender
();
log
.
info
(
"即将使用短信平台: {}, 发送短信: {}"
,
this
.
current
.
getClass
().
getSimpleName
(),
this
.
smsCondition
);
current
.
send
(
smsCondition
);
}
catch
(
SmsException
e
)
{
throw
e
;
}
catch
(
SmsRemoteApiException
e
)
{
log
.
error
(
"调用短信平台接口失败"
,
e
);
return
false
;
}
return
true
;
}
private
void
switchSmsSender
()
{
Integer
smsChannel
=
this
.
smsCondition
.
getChannel
();
if
(
this
.
current
==
null
&&
smsChannel
==
1
)
{
this
.
current
=
aliSmsSender
;
}
else
if
(
this
.
current
==
null
&&
smsChannel
==
2
)
{
this
.
current
=
lmobileSmsSender
;
}
else
if
(
this
.
current
!=
null
&&
this
.
current
==
aliSmsSender
)
{
this
.
current
=
lmobileSmsSender
;
}
else
if
(
this
.
current
!=
null
&&
this
.
current
==
lmobileSmsSender
)
{
this
.
current
=
aliSmsSender
;
}
else
{
this
.
current
=
lmobileSmsSender
;
}
}
public
SmsCondition
getSmsCondition
(){
return
this
.
smsCondition
;
}
}
src/main/java/com/qkdata/sms/consumer/SmsRetryListener.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
consumer
;
import
com.github.rholder.retry.Attempt
;
import
com.github.rholder.retry.RetryListener
;
import
lombok.extern.slf4j.Slf4j
;
@Slf4j
public
class
SmsRetryListener
implements
RetryListener
{
@Override
public
<
Boolean
>
void
onRetry
(
Attempt
<
Boolean
>
attempt
)
{
log
.
info
(
"第{}次重试, 距离第一次重试的延迟: {}"
,
attempt
.
getAttemptNumber
(),
attempt
.
getDelaySinceFirstAttempt
());
if
(
attempt
.
hasException
())
{
log
.
error
(
"重试由于异常终止"
,
attempt
.
getExceptionCause
());
}
else
{
log
.
info
(
"重试返回结果: {}"
,
attempt
.
getResult
());
}
}
}
src/main/java/com/qkdata/sms/consumer/TaskConsumer.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
consumer
;
import
com.fasterxml.jackson.core.JsonProcessingException
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.github.rholder.retry.*
;
import
com.google.common.base.Predicates
;
import
com.qkdata.sms.service.RedisService
;
import
com.qkdata.sms.exception.SmsRemoteApiException
;
import
com.qkdata.sms.model.SmsCondition
;
import
com.qkdata.sms.service.SmsSender
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Qualifier
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.context.annotation.DependsOn
;
import
org.springframework.data.redis.connection.RedisConnection
;
import
org.springframework.data.redis.connection.RedisConnectionFactory
;
import
org.springframework.stereotype.Component
;
import
javax.annotation.PostConstruct
;
import
java.io.IOException
;
import
java.nio.charset.StandardCharsets
;
import
java.util.List
;
import
java.util.concurrent.ExecutionException
;
import
java.util.concurrent.LinkedBlockingQueue
;
import
java.util.concurrent.ThreadPoolExecutor
;
import
java.util.concurrent.TimeUnit
;
@Component
@DependsOn
@Slf4j
public
class
TaskConsumer
{
@Autowired
private
RedisConnectionFactory
jedisConnectionFactory
;
@Autowired
@Qualifier
(
"aliSmsSender"
)
private
SmsSender
aliSmsSender
;
@Autowired
@Qualifier
(
"lmobileSmsSender"
)
private
SmsSender
lmobileSmsSender
;
@Value
(
"${retry.count}"
)
private
Integer
retryCount
;
@Autowired
private
RedisService
redisService
;
private
static
final
String
ERROR_LIST
=
"sms:error"
;
private
static
final
ThreadPoolExecutor
THREAD_POOL_EXECUTOR
=
new
ThreadPoolExecutor
(
2
,
Runtime
.
getRuntime
().
availableProcessors
()
*
2
,
10
,
TimeUnit
.
SECONDS
,
new
LinkedBlockingQueue
<>());
@PostConstruct
public
void
init
()
{
THREAD_POOL_EXECUTOR
.
execute
(()
->
{
RedisConnection
connection
=
jedisConnectionFactory
.
getConnection
();
ObjectMapper
objectMapper
=
new
ObjectMapper
();
Retryer
<
Boolean
>
retryer
=
buildRetryer
();
while
(
true
)
{
List
<
byte
[]>
bytes
=
connection
.
listCommands
()
.
bLPop
(
0
,
QueueConstants
.
CAPTCHA_QUEUE_NAME
.
getBytes
(
StandardCharsets
.
UTF_8
),
QueueConstants
.
NOTIFICATION_QUEUE_NAME
.
getBytes
(
StandardCharsets
.
UTF_8
));
bytes
.
forEach
(
task
->
{
String
value
=
new
String
(
task
);
// 返回结果中第一个元素为队列名称本身, 第二个元素是短信内容, 故忽略
if
(
QueueConstants
.
CAPTCHA_QUEUE_NAME
.
equals
(
value
)
||
QueueConstants
.
NOTIFICATION_QUEUE_NAME
.
equals
(
value
))
{
return
;
}
SmsCondition
smsCondition
=
deserializeSms
(
objectMapper
,
value
);
if
(
smsCondition
==
null
)
{
return
;
}
retry
(
retryer
,
new
SendSmsTask
(
smsCondition
,
aliSmsSender
,
lmobileSmsSender
));
});
}
});
}
private
SmsCondition
deserializeSms
(
ObjectMapper
objectMapper
,
String
sms
)
{
SmsCondition
smsCondition
;
try
{
smsCondition
=
objectMapper
.
readValue
(
sms
,
SmsCondition
.
class
);
}
catch
(
IOException
e
)
{
log
.
error
(
"反序列化短信参数失败"
,
e
);
return
null
;
}
return
smsCondition
;
}
private
Retryer
<
Boolean
>
buildRetryer
()
{
return
RetryerBuilder
.<
Boolean
>
newBuilder
().
retryIfExceptionOfType
(
SmsRemoteApiException
.
class
)
.
retryIfResult
(
Predicates
.
equalTo
(
false
))
// .withWaitStrategy(WaitStrategies.fixedWait(5, TimeUnit.SECONDS))
.
withWaitStrategy
(
WaitStrategies
.
exponentialWait
(
1000
,
60
,
TimeUnit
.
SECONDS
))
// .withWaitStrategy(WaitStrategies.fibonacciWait(1000, 10, TimeUnit.SECONDS))
.
withStopStrategy
(
StopStrategies
.
stopAfterAttempt
(
retryCount
)).
withRetryListener
(
new
SmsRetryListener
())
.
build
();
}
private
void
retry
(
Retryer
<
Boolean
>
retryer
,
SendSmsTask
task
)
{
try
{
// TODO 重试Listener; 重试失败后, 放入另外队列中
retryer
.
call
(
task
);
}
catch
(
ExecutionException
|
RetryException
e
)
{
log
.
error
(
"重试发送失败"
,
e
);
String
smsCondition
=
objectToJsonStirng
(
task
.
getSmsCondition
());
redisService
.
rpush
(
ERROR_LIST
,
smsCondition
);
}
}
private
String
objectToJsonStirng
(
Object
object
)
{
ObjectMapper
objectMapper
=
new
ObjectMapper
();
String
result
=
""
;
try
{
result
=
objectMapper
.
writeValueAsString
(
object
);
}
catch
(
JsonProcessingException
e
)
{
log
.
error
(
"smsCondition序列化失败"
,
e
);
}
return
result
;
}
}
src/main/java/com/qkdata/sms/exception/BusinessException.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
exception
;
import
com.qkdata.sms.constant.CommonResponseEnum
;
/**
* 业务异常基类
*
* @author chenjiahai
* @date 17/5/11
*/
public
class
BusinessException
extends
Exception
{
private
static
final
long
serialVersionUID
=
1506455046341195098L
;
protected
final
CommonResponseEnum
responseEnum
;
public
BusinessException
(
CommonResponseEnum
responseEnum
)
{
this
.
responseEnum
=
responseEnum
;
}
public
BusinessException
(
String
message
,
CommonResponseEnum
responseEnum
)
{
super
(
message
);
this
.
responseEnum
=
responseEnum
;
}
public
CommonResponseEnum
responseEnum
()
{
return
responseEnum
;
}
}
src/main/java/com/qkdata/sms/exception/ControllerExceptionHandleAdvice.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
exception
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
javax.validation.ConstraintViolation
;
import
javax.validation.ConstraintViolationException
;
import
com.qkdata.sms.constant.ConstantResponseEnum
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.http.ResponseEntity
;
import
org.springframework.http.converter.HttpMessageNotReadableException
;
import
org.springframework.validation.BindingResult
;
import
org.springframework.web.HttpMediaTypeNotSupportedException
;
import
org.springframework.web.HttpRequestMethodNotSupportedException
;
import
org.springframework.web.bind.MethodArgumentNotValidException
;
import
org.springframework.web.bind.MissingPathVariableException
;
import
org.springframework.web.bind.MissingServletRequestParameterException
;
import
org.springframework.web.bind.annotation.ExceptionHandler
;
import
org.springframework.web.bind.annotation.RestControllerAdvice
;
import
com.qkdata.sms.constant.ResponseData
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.web.method.annotation.MethodArgumentTypeMismatchException
;
import
org.springframework.web.servlet.NoHandlerFoundException
;
/**
* 业务异常处理类
* @author wangcy
*
*/
@RestControllerAdvice
@Slf4j
public
class
ControllerExceptionHandleAdvice
{
@ExceptionHandler
(
value
=
MethodArgumentNotValidException
.
class
)
public
ResponseEntity
<?>
bindExceptionHandler
(
HttpServletRequest
req
,
HttpServletResponse
res
,
MethodArgumentNotValidException
e
)
{
BindingResult
bindingResult
=
e
.
getBindingResult
();
String
message
=
bindingResult
.
getFieldError
().
getDefaultMessage
();
log
.
info
(
message
);
return
ResponseEntity
.
badRequest
().
body
(
new
ResponseData
(
HttpStatus
.
BAD_REQUEST
,
message
));
}
@ExceptionHandler
(
value
=
BusinessException
.
class
)
public
ResponseEntity
<?>
businessExceptionHandler
(
HttpServletRequest
req
,
HttpServletResponse
res
,
BusinessException
e
)
{
String
message
=
e
.
getMessage
();
log
.
info
(
message
);
return
ResponseEntity
.
badRequest
().
body
(
new
ResponseData
(
HttpStatus
.
INTERNAL_SERVER_ERROR
,
message
));
}
@ExceptionHandler
(
HttpMessageNotReadableException
.
class
)
public
ResponseEntity
<?>
httpMessageConvertExceptionHanlder
(
HttpMessageNotReadableException
e
)
{
return
responseData
(
ConstantResponseEnum
.
JSON_PARSE_ERROR
,
e
);
}
@ExceptionHandler
(
MissingPathVariableException
.
class
)
public
ResponseEntity
<?>
missingPathVariableExceptionHanlder
(
MissingPathVariableException
e
)
{
return
responseData
(
ConstantResponseEnum
.
MISSING_PATH_VARIABLE
,
e
);
}
@ExceptionHandler
(
MissingServletRequestParameterException
.
class
)
public
ResponseEntity
<?>
requestParameterExceptionHandler
(
MissingServletRequestParameterException
e
)
{
return
responseData
(
ConstantResponseEnum
.
MISSING_REQUEST_PARAMETER
,
e
);
}
@ExceptionHandler
(
MethodArgumentTypeMismatchException
.
class
)
public
ResponseEntity
<?>
methodArgumentTypeMismatchExceptionHandler
(
MethodArgumentTypeMismatchException
e
)
{
return
responseData
(
ConstantResponseEnum
.
METHOD_ARGUMENT_TYPE_MIS_MATCH
,
e
);
}
@ExceptionHandler
(
ConstraintViolationException
.
class
)
public
ResponseEntity
<?>
constraintViolationExceptionHandler
(
ConstraintViolationException
e
)
{
log
(
e
);
return
ResponseEntity
.
badRequest
().
body
(
ResponseData
.
builder
()
.
code
(
ConstantResponseEnum
.
CONSTRAINT_VIOLATION
.
value
())
.
message
(
e
.
getConstraintViolations
().
stream
().
map
(
ConstraintViolation:
:
getMessage
).
findFirst
().
get
())
.
build
());
}
@ExceptionHandler
(
HttpMediaTypeNotSupportedException
.
class
)
public
ResponseEntity
<?>
httpMediaTypeNotSupportedExceptionHandler
(
HttpMediaTypeNotSupportedException
e
)
{
return
responseData
(
ConstantResponseEnum
.
MEDIA_TYPE_NOT_SUPPORTED
,
e
);
}
@ExceptionHandler
(
HttpRequestMethodNotSupportedException
.
class
)
public
ResponseEntity
<?>
httpRequestMethodNotSupportedExceptionHandler
(
HttpRequestMethodNotSupportedException
e
)
{
return
responseData
(
ConstantResponseEnum
.
REQUEST_METHOD_NOT_SUPPORTED
,
e
);
}
@ExceptionHandler
(
NoHandlerFoundException
.
class
)
public
ResponseEntity
<?>
noHandlerFoundExceptionHandler
(
NoHandlerFoundException
e
)
{
return
responseData
(
ConstantResponseEnum
.
NO_HANDLER_FOUND
,
e
);
}
private
void
log
(
Throwable
e
)
{
log
.
error
(
e
.
getMessage
(),
e
);
}
private
ResponseEntity
<?>
responseData
(
ConstantResponseEnum
responseEnum
,
Throwable
e
)
{
log
(
e
);
return
ResponseEntity
.
badRequest
().
body
(
ResponseData
.
builder
()
.
result
(
responseEnum
)
.
build
());
}
}
src/main/java/com/qkdata/sms/exception/SmsException.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
exception
;
import
com.qkdata.sms.constant.CommonResponseEnum
;
/**
*
* @author chenjiahai
* @date 17/9/27
*/
public
class
SmsException
extends
BusinessException
{
private
static
final
long
serialVersionUID
=
-
6404413103759832403L
;
public
SmsException
(
String
message
,
CommonResponseEnum
responseEnum
)
{
super
(
message
,
responseEnum
);
}
}
src/main/java/com/qkdata/sms/exception/SmsRemoteApiException.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
exception
;
import
com.qkdata.sms.constant.CommonResponseEnum
;
public
class
SmsRemoteApiException
extends
BusinessException
{
public
SmsRemoteApiException
(
CommonResponseEnum
responseEnum
)
{
super
(
responseEnum
);
}
public
SmsRemoteApiException
(
String
message
,
CommonResponseEnum
responseEnum
)
{
super
(
message
,
responseEnum
);
}
}
src/main/java/com/qkdata/sms/lmobile/IndividuationSmsRequest.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
lmobile
;
import
com.fasterxml.jackson.annotation.JsonProperty
;
import
lombok.Data
;
import
java.io.Serializable
;
/**
* 个性化短信请求参数
*/
@Data
public
class
IndividuationSmsRequest
implements
Serializable
{
private
static
final
long
serialVersionUID
=
-
1838385964799608368L
;
/**
* 提交账户, 必传
*/
@JsonProperty
(
"User"
)
private
String
user
;
/**
* 提交账户密码, 必传
*/
@JsonProperty
(
"Password"
)
private
String
password
;
/**
* 企业代码, 非必传
*/
@JsonProperty
(
"CorpId"
)
private
String
corpId
;
/**
* 产品编号, 必传
*/
@JsonProperty
(
"ProductId"
)
private
String
productId
;
/**
* 短信变量, 必传
*/
@JsonProperty
(
"SmsVariable"
)
private
String
smsVariable
;
/**
* 短信模板, 必传
*/
@JsonProperty
(
"SmsTemplate"
)
private
String
smsTemplate
;
/**
* 用户自定义参数,长度<=32, 非必传
*/
@JsonProperty
(
"key"
)
private
String
key
;
}
src/main/java/com/qkdata/sms/lmobile/IndividuationSmsVariableRequest.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
lmobile
;
import
lombok.Data
;
import
javax.xml.bind.annotation.XmlAccessType
;
import
javax.xml.bind.annotation.XmlAccessorType
;
import
javax.xml.bind.annotation.XmlElement
;
import
javax.xml.bind.annotation.XmlRootElement
;
import
java.util.List
;
@XmlRootElement
(
name
=
"ISMV"
)
@XmlAccessorType
(
XmlAccessType
.
FIELD
)
@Data
public
class
IndividuationSmsVariableRequest
{
@XmlElement
(
name
=
"VU"
)
private
List
<
MobileUnit
>
mobileUnits
;
}
src/main/java/com/qkdata/sms/lmobile/MobileUnit.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
lmobile
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
import
javax.xml.bind.annotation.XmlAccessType
;
import
javax.xml.bind.annotation.XmlAccessorType
;
import
javax.xml.bind.annotation.XmlElement
;
import
java.util.List
;
/**
* 号码单元, 一个手机号对应一个单元, 多个则对应多个
*/
@Data
@NoArgsConstructor
@XmlAccessorType
(
XmlAccessType
.
FIELD
)
public
class
MobileUnit
{
@XmlElement
(
name
=
"VT"
)
private
List
<
MobileUnitValue
>
mobileUnitValues
;
}
\ No newline at end of file
src/main/java/com/qkdata/sms/lmobile/MobileUnitValue.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
lmobile
;
import
lombok.Data
;
import
javax.xml.bind.annotation.XmlAccessType
;
import
javax.xml.bind.annotation.XmlAccessorType
;
import
javax.xml.bind.annotation.XmlElement
;
@Data
@XmlAccessorType
(
XmlAccessType
.
FIELD
)
public
class
MobileUnitValue
{
@XmlElement
(
name
=
"V"
)
private
String
value
;
}
\ No newline at end of file
src/main/java/com/qkdata/sms/lmobile/RegularSmsRequest.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
lmobile
;
import
com.fasterxml.jackson.annotation.JsonProperty
;
import
lombok.Data
;
import
java.io.Serializable
;
import
java.time.LocalDateTime
;
import
java.time.format.DateTimeFormatter
;
/**
* 用于提交发送短信的常规方法
*/
@Data
public
class
RegularSmsRequest
implements
Serializable
{
private
static
final
long
serialVersionUID
=
-
5139319999792779765L
;
private
static
final
DateTimeFormatter
FORMAT
=
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd HH:mm:ss"
);
/**
* 提交账户,必传
*/
@JsonProperty
(
"sname"
)
private
String
user
;
/**
* 提交账户的密码,必传
*/
@JsonProperty
(
"spwd"
)
private
String
password
;
/**
* 企业代码,非必传
*/
@JsonProperty
(
"scorpid"
)
private
String
corpId
;
/**
* 产品编号,必传
*/
@JsonProperty
(
"sprdid"
)
private
String
productId
;
/**
* 接收号码间用英文半角逗号","隔开,触发产品一次只能提交一个
* 其他产品一次不能超过10万个号码
*/
@JsonProperty
(
"sdst"
)
private
String
mobiles
;
/**
* 短信内容
*/
@JsonProperty
(
"smsg"
)
private
String
content
;
/**
* 短信定时时间,格式:yyyy-MM-dd HH:mm:ss,要求大于当前时间并且小于当前时间+31天
*/
private
LocalDateTime
beginDate
;
/**
* 用户自定义参数,长度<=32
*/
@JsonProperty
(
"key"
)
private
String
key
;
@JsonProperty
(
"sbegindate"
)
public
String
getBeginDate
()
{
if
(
beginDate
==
null
)
{
return
null
;
}
return
beginDate
.
format
(
FORMAT
);
}
}
src/main/java/com/qkdata/sms/model/InsertTemplateCondition.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
model
;
import
com.qkdata.sms.constant.SmsChannelEnum
;
import
com.qkdata.sms.constant.SmsTypeEnum
;
import
lombok.Data
;
import
javax.validation.constraints.NotBlank
;
import
javax.validation.constraints.NotNull
;
import
java.io.Serializable
;
/**
* 短信发送统一请求参数
*/
@Data
public
class
InsertTemplateCondition
implements
Serializable
{
private
static
final
long
serialVersionUID
=
462051340401877051L
;
@NotBlank
(
message
=
"模板code不能为空"
)
private
String
code
;
@NotNull
(
message
=
"模板类型不能为空"
)
private
SmsTypeEnum
type
;
@NotNull
(
message
=
"优先使用平台不能为空"
)
private
SmsChannelEnum
channel
;
@NotBlank
(
message
=
"阿里短信code不能为空"
)
private
String
alibabaTemplateCode
;
@NotBlank
(
message
=
"阿里短信模板不能为空"
)
private
String
alibabaTemplateContent
;
@NotBlank
(
message
=
"阿里短信签名不能为空"
)
private
String
alibabaSignName
;
@NotBlank
(
message
=
"乐信通短信模板不能为空"
)
private
String
lmobileTemplateContent
;
@NotBlank
(
message
=
"乐信通短信签名不能为空"
)
private
String
lmobileSignName
;
}
src/main/java/com/qkdata/sms/model/LmobileNotifyCondition.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
model
;
import
com.fasterxml.jackson.annotation.JsonProperty
;
import
lombok.Data
;
import
java.io.Serializable
;
/**
* 个性化短信请求参数
*/
@Data
public
class
LmobileNotifyCondition
implements
Serializable
{
private
static
final
long
serialVersionUID
=
-
1038085064793608368L
;
/**
* 客户账号
* 默认传递:是
*/
@JsonProperty
(
"AccountID"
)
private
String
accountID
;
/**
* 信息编号(同短信提交时产生的MsgID)
* 默认传递:是
*/
@JsonProperty
(
"MsgID"
)
private
String
msgID
;
/**
* 客户提交手机号码
* 默认传递:是
*/
@JsonProperty
(
"MobilePhone"
)
private
String
mobilePhone
;
/**
* 状态报告说明(运营商送达手机终端的回执),针对ReportState
* 默认传递:是
*/
@JsonProperty
(
"ReportResultInfo"
)
private
String
reportResultInfo
;
/**
* 状态报告结果,True是成功,False是失败
* 默认传递:是
*/
@JsonProperty
(
"ReportState"
)
private
Boolean
reportState
;
/**
* 状态报告时间
* 默认传递:是
*/
@JsonProperty
(
"ReportTime"
)
private
String
reportTime
;
/**
* 发送结果,针对SendState
* 默认传递:是
*/
@JsonProperty
(
"SendResultInfo"
)
private
String
sendResultInfo
;
/**
* 发送状态,供应商送达运营商网关的状态,True是成功,False是失败
* 默认传递:是
*/
@JsonProperty
(
"SendState"
)
private
Boolean
sendState
;
/**
* 短信发送时间
* 默认传递:是
*/
@JsonProperty
(
"SendedTime"
)
private
String
sendedTime
;
/**
* 长号码(下发端口号)
* 默认传递:是
*/
@JsonProperty
(
"SPNumber"
)
private
String
spNumber
;
/**
* 自定义参数(可选),与短信提交接口g_SubmitWithKey中的Key一致
* 默认传递:否
*/
@JsonProperty
(
"ClientMsgId"
)
private
String
clientMsgId
;
/**
* 扩展码
* 默认传递:否
*/
@JsonProperty
(
"ExtendNum"
)
private
String
extendNum
;
/**
* 长短信序号
* 默认传递:否
*/
@JsonProperty
(
"LongMsgNum"
)
private
String
longMsgNum
;
/**
* 长短信总条数
* 默认传递:否
*/
@JsonProperty
(
"LongMsgTotal"
)
private
String
longMsgTotal
;
}
src/main/java/com/qkdata/sms/model/LmobileResponse.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
model
;
import
com.fasterxml.jackson.annotation.JsonProperty
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
import
java.io.Serializable
;
@Data
@NoArgsConstructor
public
class
LmobileResponse
implements
Serializable
{
private
static
final
long
serialVersionUID
=
1776398138887445771L
;
@JsonProperty
(
"MsgState"
)
private
String
msgState
;
@JsonProperty
(
"State"
)
private
Integer
state
;
@JsonProperty
(
"MsgID"
)
private
String
msgId
;
@JsonProperty
(
"Reserve"
)
private
Integer
reserve
;
}
src/main/java/com/qkdata/sms/model/NotifyRequestDTO.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
model
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
import
java.io.Serializable
;
import
java.time.format.DateTimeFormatter
;
/**
* 用于提交发送短信的常规方法
*/
@Data
@NoArgsConstructor
public
class
NotifyRequestDTO
implements
Serializable
{
private
static
final
long
serialVersionUID
=
-
5139715979792739765L
;
private
static
final
DateTimeFormatter
FORMAT
=
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd HH:mm:ss"
);
private
String
sendTime
;
private
String
reportTime
;
private
String
errCode
;
private
String
errMsg
;
private
String
outId
;
}
src/main/java/com/qkdata/sms/model/SmsAliCondition.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
model
;
import
java.io.Serializable
;
import
javax.validation.constraints.Pattern
;
import
org.hibernate.validator.constraints.NotBlank
;
import
lombok.AllArgsConstructor
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
@Data
@AllArgsConstructor
@NoArgsConstructor
public
class
SmsAliCondition
implements
Serializable
{
/**
*
*/
private
static
final
long
serialVersionUID
=
8199548590381624271L
;
@NotBlank
(
message
=
"手机号不能为空"
)
@Pattern
(
regexp
=
"^1[3,4,5,6,7,8,9]\\d{9}$"
,
message
=
"手机号格式不正确"
)
private
String
mobile
;
@NotBlank
(
message
=
"签名不能为空"
)
private
String
signName
;
@NotBlank
(
message
=
"模板编号不能为空"
)
private
String
templateCode
;
@NotBlank
(
message
=
"模板参数不能为空"
)
private
String
templateParam
;
@NotBlank
(
message
=
"outId不能为空"
)
private
String
outId
;
}
src/main/java/com/qkdata/sms/model/SmsApiV1Condition.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
model
;
import
lombok.Data
;
import
java.io.Serializable
;
@Data
public
class
SmsApiV1Condition
implements
Serializable
{
private
static
final
long
serialVersionUID
=
-
6534578898552563728L
;
private
final
String
type
=
"send"
;
private
String
username
;
private
String
password_md5
;
private
String
apikey
;
private
final
String
encode
=
"UTF-8"
;
private
String
mobile
;
private
String
content
;
}
src/main/java/com/qkdata/sms/model/SmsApiV1Response.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
model
;
import
lombok.Data
;
import
java.util.HashMap
;
import
java.util.Map
;
@Data
public
class
SmsApiV1Response
{
public
static
final
Map
<
String
,
String
>
RESPONSE_RESULT
=
new
HashMap
<>();
public
static
final
String
SUCCESS_RESPONSE_KEY
=
"success"
;
static
{
RESPONSE_RESULT
.
put
(
"Missing username"
,
"用户名为空"
);
RESPONSE_RESULT
.
put
(
"Missing password"
,
"密码为空"
);
RESPONSE_RESULT
.
put
(
"Missing apikey"
,
"APIKEY为空"
);
RESPONSE_RESULT
.
put
(
"Missing recipient"
,
"收件人手机号码为空"
);
RESPONSE_RESULT
.
put
(
"Missing message content"
,
"短信内容为空或编码不正确"
);
RESPONSE_RESULT
.
put
(
"Account is blocked"
,
"账号被禁用"
);
RESPONSE_RESULT
.
put
(
"Unrecognized encoding"
,
"编码未能识别"
);
RESPONSE_RESULT
.
put
(
"APIKEY or password error"
,
"APIKEY 或密码错误"
);
RESPONSE_RESULT
.
put
(
"Unauthorized IP address"
,
"未授权IP地址"
);
RESPONSE_RESULT
.
put
(
"Account balance is insufficient"
,
"余额不足"
);
RESPONSE_RESULT
.
put
(
"Throughput Rate Exceeded"
,
"发送频率受限"
);
RESPONSE_RESULT
.
put
(
"Invalid md5 password length"
,
"MD5 密码长度非32位"
);
}
private
String
status
;
private
String
msg
;
}
src/main/java/com/qkdata/sms/model/SmsCondition.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
model
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
import
javax.validation.constraints.NotBlank
;
import
javax.validation.constraints.NotNull
;
import
javax.validation.constraints.Pattern
;
import
java.util.Map
;
@Data
@NoArgsConstructor
public
class
SmsCondition
{
/**
* 手机号
*/
@NotBlank
(
message
=
"手机号不能为空"
)
@Pattern
(
regexp
=
"^1[3,4,5,6,7,8,9]\\d{9}$"
,
message
=
"手机号格式不正确"
)
private
String
mobile
;
/**
* 模板编号
*/
@NotBlank
(
message
=
"模板编号不能为空"
)
private
String
code
;
/**
* 模板参数, JSON格式
*/
private
Map
<
String
,
Object
>
params
;
private
String
outId
;
private
String
notifyUrl
;
private
Integer
channel
;
}
src/main/java/com/qkdata/sms/model/SmsMessageCondition.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
model
;
import
lombok.Data
;
import
javax.validation.constraints.NotEmpty
;
import
java.io.Serializable
;
import
java.util.List
;
/**
* 短信发送统一请求参数
*/
@Data
public
class
SmsMessageCondition
implements
Serializable
{
private
static
final
long
serialVersionUID
=
462051340401877051L
;
@NotEmpty
(
message
=
"短信参数不能为空列表"
)
private
List
<
SmsCondition
>
conditions
;
}
src/main/java/com/qkdata/sms/model/SmsReport.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
model
;
import
lombok.Data
;
@Data
public
class
SmsReport
{
private
String
phone_number
;
private
Boolean
success
;
private
String
biz_id
;
private
String
out_id
;
private
String
send_time
;
private
String
report_time
;
private
String
err_code
;
private
String
err_msg
;
private
String
sms_size
;
}
src/main/java/com/qkdata/sms/model/SmsTypeAndChannelDTO.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
model
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
import
java.io.Serializable
;
import
java.time.format.DateTimeFormatter
;
/**
* 用于提交发送短信的常规方法
*/
@Data
@NoArgsConstructor
public
class
SmsTypeAndChannelDTO
implements
Serializable
{
private
static
final
long
serialVersionUID
=
-
1651569621968817053L
;
private
Integer
type
;
private
Integer
channel
;
}
src/main/java/com/qkdata/sms/model/SmsV1Condition.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
model
;
import
lombok.AllArgsConstructor
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
import
javax.validation.constraints.NotBlank
;
import
javax.validation.constraints.Pattern
;
@Data
@AllArgsConstructor
@NoArgsConstructor
public
class
SmsV1Condition
{
private
static
final
long
serialVersionUID
=
-
7094472942961784909L
;
@NotBlank
(
message
=
"mobile不能为空"
)
@Pattern
(
regexp
=
"^1[3,4,5,6,7,8,9]\\d{9}$"
,
message
=
"手机号格式不正确"
)
private
String
mobile
;
private
String
messageContent
;
}
src/main/java/com/qkdata/sms/model/SmsV1Property.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
model
;
import
lombok.Data
;
import
org.springframework.boot.context.properties.ConfigurationProperties
;
@ConfigurationProperties
(
prefix
=
"sms"
)
@Data
public
class
SmsV1Property
{
private
String
username
;
private
String
password
;
private
String
apikey
;
private
String
url
;
private
String
template
;
}
src/main/java/com/qkdata/sms/notification/AbstractNotifyRequest.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
notification
;
import
com.qkdata.sms.model.NotifyRequestDTO
;
public
abstract
class
AbstractNotifyRequest
implements
NotifyRequest
{
protected
NotifyRequestDTO
notifyRequestDTO
;
public
AbstractNotifyRequest
()
{
this
.
notifyRequestDTO
=
new
NotifyRequestDTO
();
// init();
}
protected
void
init
()
{
setSendTime
();
setReportTime
();
setErrCode
();
setErrMsg
();
setOutId
();
}
public
final
NotifyRequestDTO
notifyRequest
()
{
return
this
.
notifyRequestDTO
;
}
}
src/main/java/com/qkdata/sms/notification/AliNotifyRequest.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
notification
;
import
com.qkdata.sms.model.SmsReport
;
public
class
AliNotifyRequest
extends
AbstractNotifyRequest
{
private
SmsReport
smsReport
;
public
AliNotifyRequest
(
SmsReport
smsReport
)
{
super
();
this
.
smsReport
=
smsReport
;
super
.
init
();
}
@Override
public
void
setSendTime
()
{
super
.
notifyRequestDTO
.
setSendTime
(
smsReport
.
getSend_time
());
}
@Override
public
void
setReportTime
()
{
super
.
notifyRequestDTO
.
setReportTime
(
smsReport
.
getReport_time
());
}
@Override
public
void
setErrCode
()
{
if
(!
"DELIVERED"
.
equals
(
smsReport
.
getErr_code
()))
{
super
.
notifyRequestDTO
.
setErrCode
(
"UNDELIVERD"
);
}
else
{
super
.
notifyRequestDTO
.
setErrCode
(
smsReport
.
getErr_code
());
}
}
@Override
public
void
setErrMsg
()
{
super
.
notifyRequestDTO
.
setErrMsg
(
smsReport
.
getErr_msg
());
}
@Override
public
void
setOutId
()
{
super
.
notifyRequestDTO
.
setOutId
(
smsReport
.
getOut_id
());
}
}
src/main/java/com/qkdata/sms/notification/LmobileNotifyRequest.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
notification
;
import
com.qkdata.sms.model.LmobileNotifyCondition
;
public
class
LmobileNotifyRequest
extends
AbstractNotifyRequest
{
private
LmobileNotifyCondition
condition
;
public
LmobileNotifyRequest
(
LmobileNotifyCondition
condition
)
{
super
();
this
.
condition
=
condition
;
super
.
init
();
}
@Override
public
void
setSendTime
()
{
super
.
notifyRequestDTO
.
setSendTime
(
condition
.
getSendedTime
());
}
@Override
public
void
setReportTime
()
{
super
.
notifyRequestDTO
.
setReportTime
(
condition
.
getReportTime
());
}
@Override
public
void
setErrCode
()
{
if
(
condition
.
getReportState
())
{
super
.
notifyRequestDTO
.
setErrCode
(
"DELIVERED"
);
}
else
{
super
.
notifyRequestDTO
.
setErrCode
(
"UNDELIVERD"
);
}
}
@Override
public
void
setErrMsg
()
{
super
.
notifyRequestDTO
.
setErrMsg
(
condition
.
getSendResultInfo
());
}
@Override
public
void
setOutId
()
{
super
.
notifyRequestDTO
.
setOutId
(
condition
.
getClientMsgId
());
}
}
src/main/java/com/qkdata/sms/notification/NotifyRequest.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
notification
;
import
com.qkdata.sms.model.NotifyRequestDTO
;
public
interface
NotifyRequest
{
void
setSendTime
();
void
setReportTime
();
void
setErrCode
();
void
setErrMsg
();
void
setOutId
();
NotifyRequestDTO
notifyRequest
();
}
src/main/java/com/qkdata/sms/notification/NotifyTemplate.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
notification
;
import
com.aliyuncs.utils.StringUtils
;
import
com.qkdata.sms.model.NotifyRequestDTO
;
import
com.qkdata.sms.service.RedisService
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Component
;
import
org.springframework.web.client.RestTemplate
;
@Component
@Slf4j
public
class
NotifyTemplate
{
@Autowired
private
RedisService
redisService
;
@Autowired
private
RestTemplate
restTemplate
;
/**
* 将短信报告存储到Redis中
*
* @param key
* @param report
*/
private
void
pushReportToRedis
(
String
key
,
String
report
)
{
redisService
.
rpush
(
key
,
report
);
}
/**
* 实际回调
*
* @param notifyUrl
* @param notifyRequest
*/
private
void
request
(
String
notifyUrl
,
NotifyRequest
notifyRequest
)
{
if
(
notifyRequest
==
null
)
{
return
;
}
NotifyRequestDTO
notifyRequestDTO
=
notifyRequest
.
notifyRequest
();
if
(
notifyRequestDTO
==
null
)
{
return
;
}
// TODO 是否outId必须才回调
if
(
StringUtils
.
isEmpty
(
notifyRequestDTO
.
getOutId
()))
{
return
;
}
String
rep
;
try
{
rep
=
restTemplate
.
postForObject
(
notifyUrl
,
notifyRequestDTO
,
String
.
class
);
log
.
info
(
"回调:{},返回信息:{}"
,
notifyUrl
,
rep
);
}
catch
(
Exception
e
)
{
log
.
error
(
"回调:{}失败"
,
notifyUrl
,
e
);
}
// TODO 是否需要加入重试
}
/**
* 从Redis中获取回调地址
*
* @param key
* @return
*/
public
String
getNotifyUrl
(
String
key
)
{
return
(
String
)
redisService
.
get
(
key
);
}
/**
* 删除Redis中回调地址
*
* @param key
*/
private
void
deleteNotificationUrlFromRedis
(
String
key
)
{
redisService
.
delete
(
key
);
}
/**
* 回调通知流程
*
* @param pushKey 存储短信报告Key
* @param report 短信报告JSON字符串
* @param notifyUrlKey 回调地址Key
* @param notifyRequest 回调请求参数
*/
public
void
notify
(
String
pushKey
,
String
report
,
String
notifyUrl
,
String
notifyUrlKey
,
NotifyRequest
notifyRequest
)
{
request
(
notifyUrl
,
notifyRequest
);
pushReportToRedis
(
pushKey
,
report
);
deleteNotificationUrlFromRedis
(
notifyUrlKey
);
}
}
src/main/java/com/qkdata/sms/repository/SmsTemplateMapper.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
repository
;
import
com.qkdata.sms.constant.SmsTemplate
;
import
com.qkdata.sms.model.SmsTypeAndChannelDTO
;
import
org.apache.ibatis.annotations.Param
;
import
tk.mybatis.mapper.common.Mapper
;
import
java.util.List
;
public
interface
SmsTemplateMapper
extends
Mapper
<
SmsTemplate
>
{
Integer
countTemplateByCode
(
@Param
(
"codeList"
)
List
<
String
>
codeList
);
SmsTypeAndChannelDTO
getTypeAndChannelByCode
(
@Param
(
"code"
)
String
code
);
}
src/main/java/com/qkdata/sms/service/AbstractPlaceholderResolver.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
service
;
import
com.qkdata.sms.exception.SmsException
;
import
com.qkdata.sms.constant.SmsResponseEnum
;
import
org.springframework.util.StringUtils
;
import
java.util.Map
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
public
abstract
class
AbstractPlaceholderResolver
implements
PlaceholderResolver
{
protected
abstract
String
placeholderKeyword
();
protected
abstract
Pattern
pattern
();
/**
* 1. 模板中是否包含占位符, 如果不包含直接返回true
* 2. 如果包含, 判断params是否为空, 如果为空,抛出异常
* 3. 如果params不为空, 解析template,并且判断params与placeholder是否完全匹配,忽略掉params中多余的参数,如果完全匹配,返回true
* @param template
* @param params
* @return
*/
@Override
public
boolean
isMatch
(
String
template
,
Map
<
String
,
Object
>
params
)
throws
SmsException
{
if
(!
hasPlaceholder
(
template
,
placeholderKeyword
()))
{
return
true
;
}
if
(
params
==
null
||
params
.
size
()
==
0
)
{
throw
new
SmsException
(
"模板中有占位符,但是并未提供参数"
,
SmsResponseEnum
.
TEMPLATE_PARAMS_MISMATCHED
);
}
Matcher
matcher
=
pattern
().
matcher
(
template
);
while
(
matcher
.
find
())
{
String
keyword
=
matcher
.
group
(
1
);
String
value
=
(
String
)
params
.
get
(
keyword
);
if
(
StringUtils
.
isEmpty
(
value
))
{
throw
new
SmsException
(
String
.
format
(
"模板参数与模板占位符不符, 占位符: %s"
,
keyword
),
SmsResponseEnum
.
TEMPLATE_PARAMS_MISMATCHED
);
}
}
return
true
;
}
@Override
public
String
replace
(
String
template
,
Map
<
String
,
Object
>
params
)
throws
SmsException
{
return
template
;
}
}
src/main/java/com/qkdata/sms/service/AliPlaceholderResolver.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
service
;
import
lombok.extern.slf4j.Slf4j
;
import
java.util.regex.Pattern
;
@Slf4j
public
class
AliPlaceholderResolver
extends
AbstractPlaceholderResolver
{
private
static
final
Pattern
PATTERN
=
Pattern
.
compile
(
"\\$\\{(.*?)\\}"
);
private
static
final
String
PLACEHOLDER_KEYWORD
=
"$"
;
@Override
protected
String
placeholderKeyword
()
{
return
PLACEHOLDER_KEYWORD
;
}
@Override
protected
Pattern
pattern
()
{
return
PATTERN
;
}
}
src/main/java/com/qkdata/sms/service/AliSmsSender.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
service
;
import
com.aliyuncs.DefaultAcsClient
;
import
com.aliyuncs.IAcsClient
;
import
com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest
;
import
com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse
;
import
com.aliyuncs.exceptions.ClientException
;
import
com.aliyuncs.http.MethodType
;
import
com.aliyuncs.profile.DefaultProfile
;
import
com.aliyuncs.profile.IClientProfile
;
import
com.fasterxml.jackson.core.JsonProcessingException
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.qkdata.sms.constant.SmsTemplate
;
import
com.qkdata.sms.exception.SmsException
;
import
com.qkdata.sms.constant.SmsResponseEnum
;
import
com.qkdata.sms.exception.SmsRemoteApiException
;
import
com.qkdata.sms.model.SmsCondition
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.stereotype.Service
;
import
org.springframework.util.StringUtils
;
import
java.util.Map
;
@Service
@Slf4j
public
class
AliSmsSender
implements
SmsSender
{
@Autowired
private
TemplateService
templateService
;
@Autowired
private
RedisService
redisService
;
@Value
(
"${aliyun.accessKeyId}"
)
private
String
accessKeyId
;
@Value
(
"${aliyun.accessKeySecret}"
)
private
String
accessKeySecret
;
private
static
final
String
ALI_TYPE
=
":ali"
;
private
static
final
ObjectMapper
OBJECT_MAPPER
=
new
ObjectMapper
();
@Override
public
void
send
(
SmsCondition
sms
)
throws
SmsException
,
SmsRemoteApiException
{
SmsTemplate
smsTemplate
=
templateService
.
getTemplateByCode
(
sms
.
getCode
());
Map
<
String
,
Object
>
paramsMap
=
sms
.
getParams
();
String
content
=
smsTemplate
.
getAlibabaTemplateContent
();
PlaceholderResolver
placeholderResolver
=
new
AliPlaceholderResolver
();
if
(!
placeholderResolver
.
isMatch
(
content
,
paramsMap
))
{
return
;
}
try
{
SendSmsResponse
sendSmsResponse
=
submitSms
(
sms
.
getMobile
(),
smsTemplate
.
getAlibabaSignName
(),
smsTemplate
.
getAlibabaTemplateCode
(),
paramsToJson
(
paramsMap
),
sms
.
getOutId
(),
accessKeyId
,
accessKeySecret
);
if
(
sendSmsResponse
.
getCode
()
!=
null
&&
!
"OK"
.
equals
(
sendSmsResponse
.
getCode
()))
{
if
(
sendSmsResponse
.
getCode
().
equals
(
"isv.AMOUNT_NOT_ENOUGH"
))
{
log
.
error
(
"阿里短信平台余额不足:{},{}"
,
sendSmsResponse
.
getCode
(),
sendSmsResponse
.
getMessage
());
}
else
{
log
.
error
(
"阿里短信发送失败:{},{}"
,
sendSmsResponse
.
getCode
(),
sendSmsResponse
.
getMessage
());
}
throw
new
SmsRemoteApiException
(
"阿里短信发送失败"
,
SmsResponseEnum
.
SEND_ERROR
);
}
else
{
log
.
info
(
"{}---发送成功:{}"
,
sms
.
getMobile
(),
content
);
if
(!
StringUtils
.
isEmpty
(
sms
.
getOutId
())
&&
!
StringUtils
.
isEmpty
(
sms
.
getNotifyUrl
()))
{
redisService
.
set
(
sms
.
getOutId
()
+
ALI_TYPE
,
sms
.
getNotifyUrl
());
}
}
}
catch
(
Exception
e
)
{
log
.
error
(
"调用阿里云短信平台失败"
,
e
);
throw
new
SmsRemoteApiException
(
"阿里短信发送失败"
,
SmsResponseEnum
.
SEND_ERROR
);
}
}
private
String
paramsToJson
(
Map
<
String
,
Object
>
paramsMap
)
{
String
paramsJson
=
""
;
if
(
paramsMap
==
null
||
paramsMap
.
size
()
==
0
)
{
return
paramsJson
;
}
try
{
paramsJson
=
OBJECT_MAPPER
.
writeValueAsString
(
paramsMap
);
}
catch
(
JsonProcessingException
e
)
{
// do nothing
}
return
paramsJson
;
}
private
SendSmsResponse
submitSms
(
String
mobile
,
String
signName
,
String
templateCode
,
String
templateParams
,
String
outId
,
String
accessKeyId
,
String
accessKeySecret
)
throws
ClientException
{
System
.
setProperty
(
"sun.net.client.defaultConnectTimeout"
,
"10000"
);
System
.
setProperty
(
"sun.net.client.defaultReadTimeout"
,
"10000"
);
// final String product = "Dysmsapi";//短信API产品名称(短信产品名固定,无需修改)
// final String domain = "dysmsapi.aliyuncs.com";//短信API产品域名(接口地址固定,无需修改)
//初始化ascClient,暂时不支持多region(请勿修改)
IClientProfile
profile
=
DefaultProfile
.
getProfile
(
"cn-hangzhou"
,
accessKeyId
,
accessKeySecret
);
// DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient
acsClient
=
new
DefaultAcsClient
(
profile
);
SendSmsRequest
request
=
new
SendSmsRequest
();
request
.
setMethod
(
MethodType
.
POST
);
//必填:待发送手机号。支持以逗号分隔的形式进行批量调用,批量上限为1000个手机号码,批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式;发送国际/港澳台消息时,接收号码格式为00+国际区号+号码,如“0085200000000”
request
.
setPhoneNumbers
(
mobile
);
//必填:短信签名-可在短信控制台中找到
request
.
setSignName
(
signName
);
//必填:短信模板-可在短信控制台中找到,发送国际/港澳台消息时,请使用国际/港澳台短信模版
request
.
setTemplateCode
(
templateCode
);
//可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
//友情提示:如果JSON中需要带换行符,请参照标准的JSON协议对换行符的要求,比如短信内容中包含\r\n的情况在JSON中需要表示成\\r\\n,否则会导致JSON在服务端解析失败
request
.
setTemplateParam
(
templateParams
);
//可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
if
(!
StringUtils
.
isEmpty
(
outId
))
{
request
.
setOutId
(
outId
);
}
return
acsClient
.
getAcsResponse
(
request
);
}
}
src/main/java/com/qkdata/sms/service/LmobilePlaceholderResolver.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
service
;
import
com.qkdata.sms.exception.SmsException
;
import
lombok.extern.slf4j.Slf4j
;
import
java.util.Map
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
@Slf4j
public
class
LmobilePlaceholderResolver
extends
AbstractPlaceholderResolver
{
private
static
final
Pattern
PATTERN
=
Pattern
.
compile
(
"#(.*?)#"
);
private
static
final
String
PLACEHOLDER_KEYWORD
=
"#"
;
@Override
protected
String
placeholderKeyword
()
{
return
PLACEHOLDER_KEYWORD
;
}
@Override
protected
Pattern
pattern
()
{
return
PATTERN
;
}
@Override
public
String
replace
(
String
template
,
Map
<
String
,
Object
>
params
)
throws
SmsException
{
String
result
=
template
;
if
(
isMatch
(
template
,
params
))
{
Matcher
matcher
=
PATTERN
.
matcher
(
template
);
while
(
matcher
.
find
())
{
String
paramKey
=
matcher
.
group
(
1
);
String
value
=
(
String
)
params
.
get
(
paramKey
);
result
=
result
.
replace
(
matcher
.
group
(
0
),
value
);
}
}
return
result
;
}
}
src/main/java/com/qkdata/sms/service/LmobileSmsSender.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
service
;
import
com.qkdata.sms.constant.SmsTemplate
;
import
com.qkdata.sms.exception.SmsException
;
import
com.qkdata.sms.exception.SmsRemoteApiException
;
import
com.qkdata.sms.lmobile.RegularSmsRequest
;
import
com.qkdata.sms.model.LmobileResponse
;
import
com.qkdata.sms.model.SmsCondition
;
import
com.qkdata.sms.constant.SmsResponseEnum
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.stereotype.Service
;
import
org.springframework.util.StringUtils
;
import
org.springframework.web.client.RestTemplate
;
import
java.util.Map
;
@Service
@Slf4j
public
class
LmobileSmsSender
implements
SmsSender
{
@Autowired
private
TemplateService
templateService
;
@Autowired
private
RestTemplate
restTemplate
;
@Autowired
private
RedisService
redisService
;
@Value
(
"${lmobile.user}"
)
private
String
user
;
@Value
(
"${lmobile.password}"
)
private
String
password
;
@Value
(
"${lmobile.productId}"
)
private
String
productId
;
@Value
(
"${lmobile.url}"
)
private
String
url
;
private
static
final
String
LMOBILE_TYPE
=
":lmobile"
;
@Override
public
void
send
(
SmsCondition
sms
)
throws
SmsException
,
SmsRemoteApiException
{
SmsTemplate
smsTemplate
=
templateService
.
getTemplateByCode
(
sms
.
getCode
());
Map
<
String
,
Object
>
paramsMap
=
sms
.
getParams
();
String
content
=
smsTemplate
.
getLmobileTemplateContent
();
PlaceholderResolver
placeholderResolver
=
new
LmobilePlaceholderResolver
();
content
=
placeholderResolver
.
replace
(
content
,
paramsMap
);
content
=
appendSignNameToContent
(
content
,
smsTemplate
.
getLmobileSignName
());
RegularSmsRequest
regularSmsRequest
=
new
RegularSmsRequest
();
regularSmsRequest
.
setUser
(
user
);
regularSmsRequest
.
setPassword
(
password
);
regularSmsRequest
.
setProductId
(
productId
);
regularSmsRequest
.
setMobiles
(
sms
.
getMobile
());
if
(!
StringUtils
.
isEmpty
(
sms
.
getOutId
()))
{
regularSmsRequest
.
setKey
(
sms
.
getOutId
());
}
regularSmsRequest
.
setContent
(
content
);
try
{
LmobileResponse
lmobileResponse
=
restTemplate
.
postForObject
(
url
,
regularSmsRequest
,
LmobileResponse
.
class
);
if
(!
lmobileResponse
.
getState
().
equals
(
0
))
{
if
(
lmobileResponse
.
getState
().
equals
(
1025
))
{
log
.
error
(
"Lmobile短信平台余额不足:{}"
,
lmobileResponse
.
getMsgState
());
}
else
{
log
.
error
(
"Lmobile短信发送失败:{}"
,
lmobileResponse
.
getMsgState
());
}
throw
new
SmsRemoteApiException
(
"短信发送失败"
,
SmsResponseEnum
.
SEND_ERROR
);
}
else
{
log
.
info
(
"{}---发送成功:{}"
,
sms
.
getMobile
(),
content
);
if
(!
StringUtils
.
isEmpty
(
sms
.
getOutId
())
&&
!
StringUtils
.
isEmpty
(
sms
.
getNotifyUrl
()))
{
redisService
.
set
(
sms
.
getOutId
()
+
LMOBILE_TYPE
,
sms
.
getNotifyUrl
());
}
}
}
catch
(
Exception
e
)
{
log
.
error
(
"lmobie发送失败"
,
e
);
throw
new
SmsRemoteApiException
(
"短信发送失败"
,
SmsResponseEnum
.
SEND_ERROR
);
}
}
private
String
appendSignNameToContent
(
String
content
,
String
signName
)
{
return
content
+
"【"
+
signName
+
"】"
;
}
}
src/main/java/com/qkdata/sms/service/PlaceholderResolver.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
service
;
import
com.qkdata.sms.exception.SmsException
;
import
org.springframework.util.StringUtils
;
import
java.util.Map
;
public
interface
PlaceholderResolver
{
boolean
isMatch
(
String
template
,
Map
<
String
,
Object
>
params
)
throws
SmsException
;
String
replace
(
String
template
,
Map
<
String
,
Object
>
params
)
throws
SmsException
;
/**
* 模板内容是否包含占位符,简单的用$判断,所以模板正式内容不能出现此字符
* @param template 模板内容
* @param keyword 占位符关键字, ali为$, lmobile为#
* @return 是否包含
*/
default
boolean
hasPlaceholder
(
String
template
,
String
keyword
)
{
if
(!
StringUtils
.
isEmpty
(
template
)
&&
template
.
indexOf
(
keyword
)
>
0
)
{
return
true
;
}
return
false
;
}
}
src/main/java/com/qkdata/sms/service/RedisService.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
service
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.data.redis.core.RedisTemplate
;
import
org.springframework.data.redis.core.StringRedisTemplate
;
import
org.springframework.stereotype.Service
;
import
org.springframework.util.CollectionUtils
;
import
java.util.Collection
;
import
java.util.List
;
import
java.util.Objects
;
import
java.util.Set
;
import
java.util.concurrent.TimeUnit
;
@Service
@Slf4j
public
class
RedisService
{
@Autowired
private
StringRedisTemplate
stringRedisTemplate
;
@Autowired
private
RedisTemplate
redisTemplate
;
public
Long
lpush
(
String
queue
,
List
<
String
>
values
)
{
Objects
.
requireNonNull
(
queue
,
"队列名称不能为空"
);
if
(
CollectionUtils
.
isEmpty
(
values
))
{
throw
new
IllegalArgumentException
(
"队列值不能为空"
);
}
return
stringRedisTemplate
.
opsForList
().
leftPushAll
(
queue
,
values
);
}
public
void
set
(
String
key
,
Object
value
)
{
redisTemplate
.
opsForValue
().
set
(
key
,
value
);
}
public
void
set
(
String
key
,
Object
value
,
long
timeout
,
TimeUnit
unit
)
{
redisTemplate
.
opsForValue
().
set
(
key
,
value
,
timeout
,
unit
);
}
public
Object
get
(
String
key
)
{
return
redisTemplate
.
opsForValue
().
get
(
key
);
}
public
void
delete
(
String
key
)
{
redisTemplate
.
opsForValue
().
getOperations
().
delete
(
key
);
}
public
void
delete
(
Collection
keys
)
{
redisTemplate
.
opsForValue
().
getOperations
().
delete
(
keys
);
}
public
Set
keys
(
String
pattern
){
return
redisTemplate
.
keys
(
pattern
);
}
public
Long
rpush
(
String
key
,
String
value
){
return
redisTemplate
.
opsForList
().
rightPush
(
key
,
value
);
}
public
List
range
(
String
key
,
Long
start
,
Long
end
){
return
redisTemplate
.
opsForList
().
range
(
key
,
start
,
end
);
}
}
src/main/java/com/qkdata/sms/service/SmsSender.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
service
;
import
com.qkdata.sms.exception.SmsException
;
import
com.qkdata.sms.exception.SmsRemoteApiException
;
import
com.qkdata.sms.model.SmsCondition
;
public
interface
SmsSender
{
void
send
(
SmsCondition
sms
)
throws
SmsException
,
SmsRemoteApiException
;
}
src/main/java/com/qkdata/sms/service/SmsService.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
service
;
import
com.fasterxml.jackson.core.JsonProcessingException
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.qkdata.sms.consumer.QueueConstants
;
import
com.qkdata.sms.exception.SmsException
;
import
com.qkdata.sms.model.*
;
import
com.qkdata.sms.constant.SmsResponseEnum
;
import
com.qkdata.sms.model.*
;
import
com.qkdata.sms.notification.AliNotifyRequest
;
import
com.qkdata.sms.notification.LmobileNotifyRequest
;
import
com.qkdata.sms.notification.NotifyRequest
;
import
com.qkdata.sms.notification.NotifyTemplate
;
import
com.qkdata.sms.util.RandomDigitGenerator
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.boot.context.properties.EnableConfigurationProperties
;
import
org.springframework.stereotype.Service
;
import
org.springframework.util.CollectionUtils
;
import
org.springframework.util.StringUtils
;
import
org.springframework.web.client.RestTemplate
;
import
javax.validation.Valid
;
import
java.io.IOException
;
import
java.io.UnsupportedEncodingException
;
import
java.net.URLEncoder
;
import
java.security.MessageDigest
;
import
java.security.NoSuchAlgorithmException
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.LinkedList
;
import
java.util.List
;
import
static
java
.
util
.
stream
.
Collectors
.
toList
;
@Service
(
"smsService"
)
@Slf4j
@EnableConfigurationProperties
(
SmsV1Property
.
class
)
public
class
SmsService
{
@Autowired
private
RedisService
redisService
;
@Autowired
private
TemplateService
templateService
;
@Autowired
private
NotifyTemplate
notifyTemplate
;
@Autowired
private
RestTemplate
restTemplate
;
@Autowired
private
SmsV1Property
smsProperty
;
private
static
final
String
ALI_SMS_NOTIFY_KEY
=
":ali"
;
private
static
final
String
LMOBILE_SMS_NOTIFY_KEY
=
":lmobile"
;
private
static
final
String
SMS_NOTIFY_LIST_NAME
=
"sms:notify"
;
public
void
send
(
SmsMessageCondition
condition
)
throws
Exception
{
List
<
SmsCondition
>
conditions
=
condition
.
getConditions
();
if
(!
judgeTemplateCodeIsExist
(
conditions
))
{
throw
new
SmsException
(
"短信模板不存在"
,
SmsResponseEnum
.
TEMPLATE_NOT_EXIST
);
}
List
<
SmsCondition
>
captchaSms
=
new
LinkedList
<>();
List
<
SmsCondition
>
notificationSms
=
new
LinkedList
<>();
for
(
SmsCondition
smsCondition
:
conditions
)
{
SmsTypeAndChannelDTO
smsTypeAndChannelDTO
=
templateService
.
getTypeAndChannelByCode
(
smsCondition
.
getCode
());
smsCondition
.
setChannel
(
smsTypeAndChannelDTO
.
getChannel
());
if
(
smsTypeAndChannelDTO
.
getType
()
==
1
)
{
captchaSms
.
add
(
smsCondition
);
}
if
(
smsTypeAndChannelDTO
.
getType
()
==
2
)
{
notificationSms
.
add
(
smsCondition
);
}
}
if
(!
CollectionUtils
.
isEmpty
(
captchaSms
))
{
redisService
.
lpush
(
QueueConstants
.
CAPTCHA_QUEUE_NAME
,
jsonFrom
(
captchaSms
));
}
if
(!
CollectionUtils
.
isEmpty
(
notificationSms
))
{
redisService
.
lpush
(
QueueConstants
.
NOTIFICATION_QUEUE_NAME
,
jsonFrom
(
notificationSms
));
}
}
private
List
<
String
>
jsonFrom
(
List
<
SmsCondition
>
smses
)
{
if
(
CollectionUtils
.
isEmpty
(
smses
))
{
return
Collections
.
emptyList
();
}
ObjectMapper
objectMapper
=
new
ObjectMapper
();
return
smses
.
stream
().
map
(
sms
->
{
try
{
return
objectMapper
.
writeValueAsString
(
sms
);
}
catch
(
JsonProcessingException
e
)
{
// TODO
e
.
printStackTrace
();
}
return
null
;
}).
collect
(
toList
());
}
private
Boolean
judgeTemplateCodeIsExist
(
List
<
SmsCondition
>
smsConditionList
)
{
List
<
String
>
codeList
=
new
ArrayList
<>();
for
(
SmsCondition
smsCondition
:
smsConditionList
)
{
if
(!
codeList
.
contains
(
smsCondition
.
getCode
()))
{
codeList
.
add
(
smsCondition
.
getCode
());
}
}
Integer
count
=
templateService
.
getTemplateCount
(
codeList
);
if
(
count
.
equals
(
codeList
.
size
()))
{
return
true
;
}
return
false
;
}
public
void
aliNotify
(
List
<
SmsReport
>
smsReportList
)
{
for
(
SmsReport
smsReport
:
smsReportList
)
{
log
.
info
(
"阿里回调参数: {}"
,
smsReport
);
if
(
StringUtils
.
isEmpty
(
smsReport
.
getOut_id
()))
{
continue
;
}
String
notifyUrlKey
=
smsReport
.
getOut_id
()
+
ALI_SMS_NOTIFY_KEY
;
String
notifyUrl
=
notifyTemplate
.
getNotifyUrl
(
notifyUrlKey
);
if
(
StringUtils
.
isEmpty
(
notifyUrl
))
{
continue
;
}
String
notifyString
=
objectToJsonStirng
(
smsReport
);
NotifyRequest
request
=
new
AliNotifyRequest
(
smsReport
);
notifyTemplate
.
notify
(
SMS_NOTIFY_LIST_NAME
,
notifyString
,
notifyUrl
,
notifyUrlKey
,
request
);
}
}
public
void
lmobileNotify
(
LmobileNotifyCondition
lmobileNotifyCondition
)
{
log
.
info
(
"lmobile回调通知参数: {}"
,
lmobileNotifyCondition
);
if
(
StringUtils
.
isEmpty
(
lmobileNotifyCondition
.
getClientMsgId
()))
{
return
;
}
String
notifyUrlKey
=
lmobileNotifyCondition
.
getClientMsgId
()
+
LMOBILE_SMS_NOTIFY_KEY
;
String
notifyUrl
=
notifyTemplate
.
getNotifyUrl
(
notifyUrlKey
);
if
(
StringUtils
.
isEmpty
(
notifyUrl
))
{
return
;
}
String
notifyString
=
objectToJsonStirng
(
lmobileNotifyCondition
);
NotifyRequest
request
=
new
LmobileNotifyRequest
(
lmobileNotifyCondition
);
notifyTemplate
.
notify
(
SMS_NOTIFY_LIST_NAME
,
notifyString
,
notifyUrl
,
notifyUrlKey
,
request
);
}
// TODO 抽取
private
String
objectToJsonStirng
(
Object
object
)
{
ObjectMapper
objectMapper
=
new
ObjectMapper
();
String
result
=
""
;
try
{
result
=
objectMapper
.
writeValueAsString
(
object
);
}
catch
(
JsonProcessingException
e
)
{
log
.
error
(
"smsCondition序列化失败"
,
e
);
}
return
result
;
}
public
void
sendSms
(
@Valid
SmsV1Condition
smsCondition
)
throws
SmsException
,
IOException
{
log
.
info
(
"手机号:"
+
smsCondition
.
getMobile
()
+
";内容:"
+
smsCondition
.
getMessageContent
());
String
url
=
String
.
join
(
""
,
smsProperty
.
getUrl
(),
"?format=json&data={json}"
);
String
response
=
restTemplate
.
getForObject
(
url
,
String
.
class
,
requestJson
(
smsCondition
.
getMobile
(),
smsCondition
.
getMessageContent
()));
log
.
info
(
"发送短信响应结果: "
+
response
);
ObjectMapper
objectMapper
=
new
ObjectMapper
();
SmsApiV1Response
smsApiResponse
=
objectMapper
.
readValue
(
response
,
SmsApiV1Response
.
class
);
if
(
SmsApiV1Response
.
SUCCESS_RESPONSE_KEY
.
equals
(
smsApiResponse
.
getStatus
()))
{
return
;
}
else
{
throw
new
SmsException
(
String
.
format
(
"发送短信失败, 失败原因: %s"
,
SmsApiV1Response
.
RESPONSE_RESULT
.
get
(
smsApiResponse
.
getMsg
())),
SmsResponseEnum
.
SEND_ERROR
);
}
}
private
String
requestJson
(
String
mobile
,
String
messageContent
)
throws
SmsException
{
try
{
SmsApiV1Condition
condition
=
new
SmsApiV1Condition
();
condition
.
setApikey
(
smsProperty
.
getApikey
());
if
(
messageContent
==
null
||
messageContent
.
trim
().
equals
(
""
))
condition
.
setContent
(
URLEncoder
.
encode
(
content
(
mobile
),
"UTF-8"
));
else
condition
.
setContent
(
URLEncoder
.
encode
(
messageContent
,
"UTF-8"
));
condition
.
setMobile
(
mobile
);
condition
.
setUsername
(
smsProperty
.
getUsername
());
String
md5Password
=
bytesToHex
(
MessageDigest
.
getInstance
(
"MD5"
).
digest
(
smsProperty
.
getPassword
().
getBytes
(
"UTF-8"
)));
condition
.
setPassword_md5
(
md5Password
);
ObjectMapper
objectMapper
=
new
ObjectMapper
();
return
objectMapper
.
writeValueAsString
(
condition
);
}
catch
(
UnsupportedEncodingException
|
NoSuchAlgorithmException
|
JsonProcessingException
e
)
{
log
.
error
(
"发送短信失败"
,
e
);
throw
new
SmsException
(
"发送短信失败"
,
SmsResponseEnum
.
SEND_ERROR
);
}
}
private
String
content
(
String
mobile
)
{
String
captcha
=
RandomDigitGenerator
.
generate
();
return
String
.
format
(
smsProperty
.
getTemplate
(),
captcha
);
}
/**
* 二进制转十六进制
*
* @param bytes
* @return
*/
public
static
String
bytesToHex
(
byte
[]
bytes
)
{
StringBuilder
md5str
=
new
StringBuilder
();
//把数组每一字节换成16进制连成md5字符串
int
digital
;
for
(
int
i
=
0
;
i
<
bytes
.
length
;
i
++)
{
digital
=
bytes
[
i
];
if
(
digital
<
0
)
{
digital
+=
256
;
}
if
(
digital
<
16
)
{
md5str
.
append
(
"0"
);
}
md5str
.
append
(
Integer
.
toHexString
(
digital
));
}
return
md5str
.
toString
().
toLowerCase
();
}
}
src/main/java/com/qkdata/sms/service/TemplateService.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
service
;
import
com.qkdata.sms.exception.SmsException
;
import
com.qkdata.sms.constant.SmsResponseEnum
;
import
com.qkdata.sms.constant.SmsTemplate
;
import
com.qkdata.sms.model.InsertTemplateCondition
;
import
com.qkdata.sms.model.SmsTypeAndChannelDTO
;
import
com.qkdata.sms.repository.SmsTemplateMapper
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
java.util.List
;
@Service
public
class
TemplateService
{
@Autowired
private
SmsTemplateMapper
smsTemplateMapper
;
public
List
<
SmsTemplate
>
smsTemplateList
(){
return
smsTemplateMapper
.
selectAll
();
}
public
Integer
insertTemplate
(
InsertTemplateCondition
insertTemplateCondition
)
throws
Exception
{
if
(
checkTemplateExist
(
insertTemplateCondition
.
getCode
())){
throw
new
SmsException
(
"短信模板code已存在"
,
SmsResponseEnum
.
TEMPLATE_CODE_ISEXIST
);
}
SmsTemplate
smsTemplate
=
new
SmsTemplate
();
smsTemplate
.
setCode
(
insertTemplateCondition
.
getCode
());
smsTemplate
.
setType
(
insertTemplateCondition
.
getType
());
smsTemplate
.
setChannel
(
insertTemplateCondition
.
getChannel
());
smsTemplate
.
setAlibabaSignName
(
insertTemplateCondition
.
getAlibabaSignName
());
smsTemplate
.
setAlibabaTemplateCode
(
insertTemplateCondition
.
getAlibabaTemplateCode
());
smsTemplate
.
setAlibabaTemplateContent
(
insertTemplateCondition
.
getAlibabaTemplateContent
());
smsTemplate
.
setLmobileSignName
(
insertTemplateCondition
.
getLmobileSignName
());
smsTemplate
.
setLmobileTemplateContent
(
insertTemplateCondition
.
getLmobileTemplateContent
());
smsTemplateMapper
.
insertSelective
(
smsTemplate
);
return
smsTemplate
.
getId
();
}
private
Boolean
checkTemplateExist
(
String
code
){
int
count
=
smsTemplateMapper
.
selectCount
(
new
SmsTemplate
(
code
));
if
(
count
>=
1
){
return
true
;
}
return
false
;
}
public
SmsTemplate
getTemplateByCode
(
String
code
){
SmsTemplate
smsTemplate
=
smsTemplateMapper
.
selectOne
(
new
SmsTemplate
(
code
));
return
smsTemplate
;
}
public
Integer
getTemplateCount
(
List
<
String
>
codeList
){
return
smsTemplateMapper
.
countTemplateByCode
(
codeList
);
}
public
SmsTypeAndChannelDTO
getTypeAndChannelByCode
(
String
code
)
{
return
smsTemplateMapper
.
getTypeAndChannelByCode
(
code
);
}
}
src/main/java/com/qkdata/sms/util/JaxbUtils.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
util
;
import
lombok.extern.slf4j.Slf4j
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.xml.bind.JAXBContext
;
import
javax.xml.bind.JAXBException
;
import
javax.xml.bind.Marshaller
;
import
javax.xml.bind.Unmarshaller
;
import
java.io.IOException
;
import
java.io.StringReader
;
import
java.io.StringWriter
;
/**
* @author chenjiahai
*/
@Slf4j
public
class
JaxbUtils
{
private
static
JAXBContext
jaxbContext
;
//xml转java对象
@SuppressWarnings
(
"unchecked"
)
public
static
<
T
>
T
xmlToBean
(
HttpServletRequest
request
,
Class
<
T
>
c
)
{
T
t
=
null
;
try
{
jaxbContext
=
JAXBContext
.
newInstance
(
c
);
Unmarshaller
unmarshaller
=
jaxbContext
.
createUnmarshaller
();
t
=
(
T
)
unmarshaller
.
unmarshal
(
request
.
getInputStream
());
}
catch
(
JAXBException
e
)
{
log
.
error
(
"JAXB异常"
,
e
);
}
catch
(
IOException
e
)
{
log
.
error
(
"获取request中的inputStream失败"
,
e
);
}
return
t
;
}
public
static
<
T
>
T
xmlToBean
(
String
xml
,
Class
<
T
>
c
)
{
T
t
=
null
;
try
{
jaxbContext
=
JAXBContext
.
newInstance
(
c
);
Unmarshaller
unmarshaller
=
jaxbContext
.
createUnmarshaller
();
t
=
(
T
)
unmarshaller
.
unmarshal
(
new
StringReader
(
xml
));
}
catch
(
JAXBException
e
)
{
log
.
error
(
"JAXB异常"
,
e
);
}
return
t
;
}
//java对象转xml
public
static
String
beanToXml
(
Object
obj
)
{
return
beanToXml
(
obj
,
false
);
}
public
static
String
beanToXml
(
Object
obj
,
boolean
format
)
{
StringWriter
writer
=
null
;
try
{
jaxbContext
=
JAXBContext
.
newInstance
(
obj
.
getClass
());
Marshaller
marshaller
=
jaxbContext
.
createMarshaller
();
//Marshaller.JAXB_FRAGMENT:是否省略xml头信息,true省略,false不省略
marshaller
.
setProperty
(
Marshaller
.
JAXB_FRAGMENT
,
true
);
//Marshaller.JAXB_FORMATTED_OUTPUT:决定是否在转换成xml时同时进行格式化(即按标签自动换行,否则即是一行的xml)
marshaller
.
setProperty
(
Marshaller
.
JAXB_FORMATTED_OUTPUT
,
format
);
//Marshaller.JAXB_ENCODING:xml的编码方式
marshaller
.
setProperty
(
Marshaller
.
JAXB_ENCODING
,
"UTF-8"
);
writer
=
new
StringWriter
();
marshaller
.
marshal
(
obj
,
writer
);
}
catch
(
JAXBException
e
)
{
log
.
error
(
"JAXB-Java对象转XML"
,
e
);
return
""
;
}
return
writer
.
toString
();
}
}
\ No newline at end of file
src/main/java/com/qkdata/sms/util/RandomDigitGenerator.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
.
util
;
import
java.util.Random
;
public
class
RandomDigitGenerator
{
private
static
final
String
DIGITS
=
"0123456789"
;
private
static
final
int
DEFAULT_LENGTH
=
6
;
private
RandomDigitGenerator
()
{
}
public
static
String
generate
()
{
return
generate
(
DEFAULT_LENGTH
);
}
public
static
String
generate
(
int
length
)
{
Random
random
=
new
Random
();
StringBuilder
verifyCode
=
new
StringBuilder
();
for
(
int
i
=
0
;
i
<
length
;
i
++)
{
verifyCode
.
append
(
DIGITS
.
charAt
(
random
.
nextInt
(
DIGITS
.
length
()
-
1
)));
}
return
verifyCode
.
toString
();
}
}
src/main/resources/application-dev.yml
0 → 100644
View file @
93a4b7eb
server
:
port
:
9004
spring
:
datasource
:
druid
:
url
:
jdbc:mysql://localhost:3306/sms?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false
username
:
root
password
:
123456
redis
:
host
:
localhost
log
:
context
:
sms
path
:
/Users/liuyang/work/argus_work/online-edu/data/logs
src/main/resources/application.yml
0 → 100644
View file @
93a4b7eb
server
:
port
:
80
servlet
:
context-path
:
/sms
spring
:
datasource
:
druid
:
url
:
jdbc:mysql://mysql/sms?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false
username
:
root
password
:
qkdata
driver-class-name
:
com.mysql.jdbc.Driver
initialSize
:
3
minIdle
:
2
maxActive
:
10
maxWait
:
60000
timeBetweenEvictionRunsMillis
:
60000
minEvictableIdleTimeMillis
:
300000
validationQuery
:
SELECT 1 FROM DUAL
testWhileIdle
:
true
testOnBorrow
:
false
testOnReturn
:
false
poolPreparedStatements
:
false
maxPoolPreparedStatementPerConnectionSize
:
-1
filters
:
stat,slf4j,config
connectionProperties
:
druid.stat.mergeSql=true;druid.stat.slowSqlMil=3000
useGlobalDataSourceStat
:
true
filter
:
wall
:
config
:
multi-statement-allow
:
true
redis
:
host
:
redis
port
:
6379
database
:
10
timeout
:
2s
password
:
jedis
:
pool
:
min-idle
:
1
max-idle
:
8
max-active
:
8
# max-wait: -1s
flyway
:
baseline-on-migrate
:
true
placeholder-replacement
:
false
mybatis
:
mapper-locations
:
classpath*:mappers/*.xml
type-handlers-package
:
com.qkdata.sms.config
log
:
context
:
sms
path
:
/data/logs
aliyun
:
accessKeyId
:
LTAI4GCTRFRxud86a58AoV3X
accessKeySecret
:
Rpwj9DBwJpNWzwjcFeffLWqEuJ56i0
lmobile
:
user
:
todo
password
:
todo
productId
:
todo
url
:
http://api.51welink.com/json/sms/g_Submit
retry
:
count
:
5
src/main/resources/db/migration/V1.0.0__init.sql
0 → 100644
View file @
93a4b7eb
DROP
TABLE
IF
EXISTS
`sms_template`
;
CREATE
TABLE
`sms_template`
(
`id`
bigint
(
10
)
NOT
NULL
AUTO_INCREMENT
,
`code`
varchar
(
50
)
NOT
NULL
COMMENT
'模板code'
,
`type`
tinyint
(
2
)
NOT
NULL
COMMENT
'模板类型'
,
`channel`
tinyint
(
2
)
NOT
NULL
COMMENT
'优先使用平台'
,
`status`
tinyint
(
2
)
DEFAULT
NULL
COMMENT
'模板状态'
,
`alibaba_template_code`
varchar
(
50
)
NOT
NULL
COMMENT
'阿里短信code'
,
`alibaba_sign_name`
varchar
(
50
)
NOT
NULL
COMMENT
'阿里签名'
,
`lmobile_sign_name`
varchar
(
50
)
NOT
NULL
COMMENT
'乐信通签名'
,
`alibaba_template_content`
varchar
(
255
)
NOT
NULL
COMMENT
'阿里短信模板'
,
`lmobile_template_content`
varchar
(
255
)
NOT
NULL
COMMENT
'乐信通短信模板'
,
`create_at`
datetime
DEFAULT
CURRENT_TIMESTAMP
,
`update_at`
datetime
DEFAULT
CURRENT_TIMESTAMP
ON
UPDATE
CURRENT_TIMESTAMP
,
PRIMARY
KEY
(
`id`
)
USING
BTREE
)
ENGINE
=
InnoDB
AUTO_INCREMENT
=
2
DEFAULT
CHARSET
=
utf8
;
-- ----------------------------
-- Records of sms_template
-- ----------------------------
INSERT
INTO
`sms_template`
VALUES
(
1
,
'T_LG_CAPTCHA'
,
1
,
1
,
1
,
'SMS_190520446'
,
'乾坤数据'
,
''
,
'验证码${code},您正在注册成为新用户,感谢您的支持!'
,
''
,
NULL
,
NULL
);
\ No newline at end of file
src/main/resources/logback-spring.xml
0 → 100644
View file @
93a4b7eb
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 获取application配置文件中的配置 -->
<!--<springProperty scope="context" name="LOG_LEVEL" source="log.level"/>-->
<springProperty
scope=
"context"
name=
"LOG_PATH"
source=
"log.path"
/>
<springProperty
scope=
"context"
name=
"LOG_CONTEXT"
source=
"log.context"
/>
<property
name=
"PATTERN"
value=
"[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] [%X{traceId}] %-5level %logger{50} - %line %msg %n"
/>
<appender
name=
"STDOUT"
class=
"ch.qos.logback.core.ConsoleAppender"
>
<encoder>
<pattern>
${PATTERN}
</pattern>
<charset>
UTF-8
</charset>
</encoder>
</appender>
<appender
name=
"ERROR"
class=
"ch.qos.logback.core.rolling.RollingFileAppender"
>
<File>
${LOG_PATH}/${LOG_CONTEXT}-error.log
</File>
<!-- 过滤器,只记录ERROR级别的日志 -->
<filter
class=
"ch.qos.logback.classic.filter.LevelFilter"
>
<level>
ERROR
</level>
<onMatch>
ACCEPT
</onMatch>
<onMismatch>
DENY
</onMismatch>
</filter>
<rollingPolicy
class=
"ch.qos.logback.core.rolling.TimeBasedRollingPolicy"
>
<fileNamePattern>
${LOG_PATH}/${LOG_CONTEXT}-error.log.%i.%d{yyyy-MM-dd}
</fileNamePattern>
<MaxHistory>
30
</MaxHistory>
<TimeBasedFileNamingAndTriggeringPolicy
class=
"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"
>
<maxFileSize>
30MB
</maxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>
${PATTERN}
</pattern>
<charset>
UTF-8
</charset>
</encoder>
</appender>
<appender
name=
"INFO"
class=
"ch.qos.logback.core.rolling.RollingFileAppender"
>
<File>
${LOG_PATH}/${LOG_CONTEXT}.log
</File>
<filter
class=
"ch.qos.logback.classic.filter.LevelFilter"
>
<level>
ERROR
</level>
<onMatch>
DENY
</onMatch>
<onMismatch>
ACCEPT
</onMismatch>
</filter>
<rollingPolicy
class=
"ch.qos.logback.core.rolling.TimeBasedRollingPolicy"
>
<fileNamePattern>
${LOG_PATH}/${LOG_CONTEXT}.log.%i.%d{yyyy-MM-dd}
</fileNamePattern>
<maxHistory>
30
</maxHistory>
<TimeBasedFileNamingAndTriggeringPolicy
class=
"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"
>
<maxFileSize>
30MB
</maxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<Pattern>
${PATTERN}
</Pattern>
<charset>
UTF-8
</charset>
</encoder>
</appender>
<appender
name=
"SQL"
class=
"ch.qos.logback.core.rolling.RollingFileAppender"
>
<File>
${LOG_PATH}/${LOG_CONTEXT}-sql.log
</File>
<filter
class=
"ch.qos.logback.classic.filter.LevelFilter"
>
<level>
ERROR
</level>
<onMatch>
DENY
</onMatch>
<onMismatch>
ACCEPT
</onMismatch>
</filter>
<rollingPolicy
class=
"ch.qos.logback.core.rolling.TimeBasedRollingPolicy"
>
<fileNamePattern>
${LOG_PATH}/${LOG_CONTEXT}-sql.log.%i.%d{yyyy-MM-dd}
</fileNamePattern>
<maxHistory>
30
</maxHistory>
<TimeBasedFileNamingAndTriggeringPolicy
class=
"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"
>
<maxFileSize>
30MB
</maxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<Pattern>
${PATTERN}
</Pattern>
<charset>
UTF-8
</charset>
</encoder>
</appender>
<root
level=
"INFO"
>
<appender-ref
ref=
"INFO"
/>
<appender-ref
ref=
"ERROR"
/>
<appender-ref
ref=
"STDOUT"
/>
</root>
<logger
name=
"org"
level=
"INFO"
additivity=
"false"
>
<appender-ref
ref=
"INFO"
/>
<appender-ref
ref=
"ERROR"
/>
<appender-ref
ref=
"STDOUT"
/>
</logger>
<logger
name=
"com.shangwei"
level=
"INFO"
additivity=
"false"
>
<appender-ref
ref=
"INFO"
/>
<appender-ref
ref=
"ERROR"
/>
<appender-ref
ref=
"STDOUT"
/>
</logger>
<logger
name=
"druid.sql.Statement"
level=
"DEBUG"
additivity=
"false"
>
<appender-ref
ref=
"SQL"
/>
<appender-ref
ref=
"STDOUT"
/>
</logger>
</configuration>
\ No newline at end of file
src/main/resources/mappers/SmsTemplateMapper.xml
0 → 100644
View file @
93a4b7eb
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper
namespace=
"com.qkdata.sms.repository.SmsTemplateMapper"
>
<resultMap
id=
"smsTypeAndChannelDTO"
type=
"com.qkdata.sms.model.SmsTypeAndChannelDTO"
>
<result
property=
"type"
column=
"type"
/>
<result
property=
"channel"
column=
"channel"
/>
</resultMap>
<select
id=
"countTemplateByCode"
resultType=
"java.lang.Integer"
>
SELECT COUNT(*)
FROM sms_template
WHERE code IN
<foreach
item=
"code"
index=
"index"
collection=
"codeList"
open=
"("
separator=
","
close=
")"
>
#{code}
</foreach>
</select>
<select
id=
"getTypeAndChannelByCode"
resultMap=
"smsTypeAndChannelDTO"
>
SELECT type, channel
FROM sms_template
WHERE code = #{code}
</select>
</mapper>
\ No newline at end of file
src/test/java/com/qkdata/sms/SmsTemplateMapperTest.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
;
import
com.qkdata.sms.constant.SmsTemplate
;
import
com.qkdata.sms.constant.SmsChannelEnum
;
import
com.qkdata.sms.constant.SmsTypeEnum
;
import
com.qkdata.sms.repository.SmsTemplateMapper
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.boot.test.context.SpringBootTest
;
import
org.springframework.context.annotation.Profile
;
import
org.springframework.test.context.ActiveProfiles
;
import
org.springframework.test.context.junit4.SpringRunner
;
import
static
org
.
junit
.
Assert
.*;
@RunWith
(
SpringRunner
.
class
)
@SpringBootTest
(
classes
=
{
Application
.
class
})
@ActiveProfiles
(
value
=
"dev"
)
public
class
SmsTemplateMapperTest
{
@Autowired
private
SmsTemplateMapper
smsTemplateMapper
;
@Test
public
void
testAdd
()
{
SmsTemplate
smsTemplate
=
new
SmsTemplate
();
smsTemplate
.
setCode
(
"YL_CAPTCHA_1"
);
smsTemplate
.
setType
(
SmsTypeEnum
.
CAPTCHA
);
smsTemplate
.
setChannel
(
SmsChannelEnum
.
ALIBABA
);
smsTemplate
.
setStatus
(
1
);
smsTemplateMapper
.
insertSelective
(
smsTemplate
);
}
@Test
public
void
testSelectOne
()
{
String
code
=
"T_LG_CAPTCHA"
;
SmsTemplate
smsTemplate
=
smsTemplateMapper
.
selectOne
(
new
SmsTemplate
(
code
));
assertNotNull
(
smsTemplate
);
assertEquals
(
"T_LG_CAPTCHA"
,
smsTemplate
.
getCode
());
assertSame
(
SmsTypeEnum
.
CAPTCHA
,
smsTemplate
.
getType
());
assertSame
(
SmsChannelEnum
.
ALIBABA
,
smsTemplate
.
getChannel
());
}
}
src/test/java/com/qkdata/sms/SmsTest.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.qkdata.sms.service.RedisService
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.boot.test.context.SpringBootTest
;
import
org.springframework.test.context.junit4.SpringRunner
;
@RunWith
(
SpringRunner
.
class
)
@SpringBootTest
(
classes
=
{
Application
.
class
})
public
class
SmsTest
{
@Autowired
private
RedisService
redisService
;
private
ObjectMapper
objectMapper
=
new
ObjectMapper
();
@Test
public
void
testLpush
()
{
}
}
src/test/java/com/qkdata/sms/SplitMobileTest.java
0 → 100644
View file @
93a4b7eb
package
com
.
qkdata
.
sms
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.springframework.test.context.junit4.SpringRunner
;
import
org.springframework.util.StringUtils
;
import
java.util.Arrays
;
import
java.util.List
;
import
java.util.stream.LongStream
;
import
static
java
.
util
.
stream
.
Collectors
.
joining
;
import
static
java
.
util
.
stream
.
Collectors
.
toList
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertSame
;
@RunWith
(
SpringRunner
.
class
)
public
class
SplitMobileTest
{
private
String
singleMobile
;
private
String
mobiles
;
@Before
public
void
before
()
{
this
.
singleMobile
=
"15822248411"
;
this
.
mobiles
=
LongStream
.
range
(
10000000000L
,
19999999999L
)
.
limit
(
100
)
.
boxed
()
.
map
(
String:
:
valueOf
)
.
collect
(
joining
(
","
));
this
.
mobiles
=
this
.
mobiles
.
replace
(
"10000000000"
,
""
);
}
@Test
public
void
testSplitOfSingleMobile
()
{
List
<
String
>
mobiles
=
split
(
singleMobile
);
assertSame
(
1
,
mobiles
.
size
());
assertEquals
(
"15822248411"
,
mobiles
.
get
(
0
));
}
@Test
public
void
testSpliteOfMobiles
()
{
List
<
String
>
mobiles
=
split
(
this
.
mobiles
);
assertSame
(
99
,
mobiles
.
size
());
assertEquals
(
"10000000001"
,
mobiles
.
get
(
0
));
assertEquals
(
"10000000099"
,
mobiles
.
get
(
mobiles
.
size
()
-
1
));
}
private
List
<
String
>
split
(
String
mobile
)
{
return
Arrays
.
stream
(
mobile
.
split
(
","
))
.
map
(
String:
:
trim
)
.
filter
(
x
->
!
StringUtils
.
isEmpty
(
x
))
.
collect
(
toList
());
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment