feat(新增): 短信验证码发送

Signed-off-by: bunny <1319900154@qq.com>
This commit is contained in:
bunny 2024-03-27 21:27:05 +08:00
parent ad0bcfda9b
commit b41fbf27e1
20 changed files with 474 additions and 30 deletions

View File

@ -39,5 +39,25 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.2.1</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>9.3.7.v20160115</version>
</dependency>
</dependencies>
</project>

View File

@ -9,12 +9,6 @@ import org.springframework.context.annotation.Configuration;
@Configuration
public class Knife4jConfig {
@Bean
public GroupedOpenApi adminApi() {
// 分组名称
return GroupedOpenApi.builder().group("admin请求接口").pathsToMatch("/admin/**").build();// 接口请求路径规则
}
@Bean
public OpenAPI customOpenAPI() {
// 设定作者
@ -23,9 +17,17 @@ public class Knife4jConfig {
}
@Bean
public GroupedOpenApi webApi() { // 创建了一个api接口的分组
return GroupedOpenApi.builder()
.group("web-api") // 分组名称
.pathsToMatch("/api/**").build(); // 接口请求路径规则
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder().group("admin请求接口").pathsToMatch("/admin/**").build();// 接口请求路径规则
}
@Bean
public GroupedOpenApi productApi() {
return GroupedOpenApi.builder().group("商品管理接口").pathsToMatch("/api/product/**").build(); // 接口请求路径规则
}
@Bean
public GroupedOpenApi userApi() {
return GroupedOpenApi.builder().group("用户接口").pathsToMatch("/api/user/**").build();
}
}

View File

@ -0,0 +1,238 @@
package com.atguigu.utils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HttpUtils {
public static HttpResponse doGet(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpGet request = new HttpGet(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
return httpClient.execute(request);
}
public static HttpResponse doPost(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
Map<String, String> bodys)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (bodys != null) {
List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();
for (String key : bodys.keySet()) {
nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));
}
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
request.setEntity(formEntity);
}
return httpClient.execute(request);
}
public static HttpResponse doPost(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
String body)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (StringUtils.isNotBlank(body)) {
request.setEntity(new StringEntity(body, "utf-8"));
}
return httpClient.execute(request);
}
public static HttpResponse doPost(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
byte[] body)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (body != null) {
request.setEntity(new ByteArrayEntity(body));
}
return httpClient.execute(request);
}
public static HttpResponse doPut(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
String body)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPut request = new HttpPut(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (StringUtils.isNotBlank(body)) {
request.setEntity(new StringEntity(body, "utf-8"));
}
return httpClient.execute(request);
}
public static HttpResponse doPut(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
byte[] body)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPut request = new HttpPut(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (body != null) {
request.setEntity(new ByteArrayEntity(body));
}
return httpClient.execute(request);
}
public static HttpResponse doDelete(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpDelete request = new HttpDelete(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
return httpClient.execute(request);
}
private static String buildUrl(String host, String path, Map<String, String> querys) throws UnsupportedEncodingException {
StringBuilder sbUrl = new StringBuilder();
sbUrl.append(host);
if (!StringUtils.isBlank(path)) {
sbUrl.append(path);
}
if (null != querys) {
StringBuilder sbQuery = new StringBuilder();
for (Map.Entry<String, String> query : querys.entrySet()) {
if (0 < sbQuery.length()) {
sbQuery.append("&");
}
if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
sbQuery.append(query.getValue());
}
if (!StringUtils.isBlank(query.getKey())) {
sbQuery.append(query.getKey());
if (!StringUtils.isBlank(query.getValue())) {
sbQuery.append("=");
sbQuery.append(URLEncoder.encode(query.getValue(), StandardCharsets.UTF_8));
}
}
}
if (!sbQuery.isEmpty()) {
sbUrl.append("?").append(sbQuery);
}
}
return sbUrl.toString();
}
private static HttpClient wrapClient(String host) {
HttpClient httpClient = new DefaultHttpClient();
if (host.startsWith("https://")) {
sslClient(httpClient);
}
return httpClient;
}
private static void sslClient(HttpClient httpClient) {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] xcs, String str) {
}
public void checkServerTrusted(X509Certificate[] xcs, String str) {
}
};
ctx.init(null, new TrustManager[]{tm}, null);
SSLSocketFactory ssf = new SSLSocketFactory(ctx);
ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
ClientConnectionManager ccm = httpClient.getConnectionManager();
SchemeRegistry registry = ccm.getSchemeRegistry();
registry.register(new Scheme("https", 443, ssf));
} catch (KeyManagementException ex) {
throw new RuntimeException(ex);
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException(ex);
}
}
}

View File

@ -4,7 +4,7 @@ spring:
profiles:
active: dev
application:
name: server-gateway
name: service-gateway
cloud:
nacos:
@ -36,6 +36,14 @@ spring:
uri: lb://service-product
predicates:
- Path=/*/product/**
- id: service-user
uri: lb://service-user
predicates:
- Path=/*/user/**
- id: service-order
uri: lb://service-order
predicates:
- Path=/*/order/**
logging:
level:

View File

@ -4,7 +4,7 @@
<contextName>logback</contextName>
<!-- 日志的输出目录 -->
<property name="log.path" value="logs//spzx-manager//logs"/>
<property name="log.path" value="logs//service-gateway//logs"/>
<!--控制台日志格式:彩色日志-->
<!-- magenta:洋红 -->

View File

@ -27,6 +27,11 @@
<artifactId>common-service</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- redis的起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- mybatis的起步依赖 -->
<dependency>

View File

@ -1,10 +1,10 @@
server:
port: 8511
port: 8510
spring:
profiles:
active: dev
application:
name: service-product
name: service-order
cloud:
nacos:
@ -17,6 +17,20 @@ spring:
username: ${bunny.datasource.username}
password: "${bunny.datasource.password}"
logging:
level:
com.atguigu.spzx.order.mapper: debug
com.atguigu.spzx.order.controller: info
com.atguigu.spzx.order.service: info
pattern:
dateformat: HH:mm:ss:SSS
file:
path: "logs/${spring.application.name}"
mybatis:
config-location: classpath:mybatis-config.xml
mapper-locations: classpath:/mapper/*/*.xml
type-aliases-package: com.atguigu.spzx.model
mapper-locations: classpath:/mapper/*/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
auto-mapping-behavior: full

View File

@ -4,7 +4,7 @@
<contextName>logback</contextName>
<!-- 日志的输出目录 -->
<property name="log.path" value="logs//spzx-manager//logs"/>
<property name="log.path" value="logs//service-order//logs"/>
<!--控制台日志格式:彩色日志-->
<!-- magenta:洋红 -->

View File

@ -18,10 +18,6 @@
</properties>
<dependencies>
<!-- redis的起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -4,7 +4,7 @@
<contextName>logback</contextName>
<!-- 日志的输出目录 -->
<property name="log.path" value="logs//spzx-manager//logs"/>
<property name="log.path" value="logs//service-product//logs"/>
<!--控制台日志格式:彩色日志-->
<!-- magenta:洋红 -->

View File

@ -3,8 +3,10 @@ package com.atguigu.user;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan("com.atguigu")
@Slf4j
public class UserApplication {
public static void main(String[] args) {

View File

@ -0,0 +1,26 @@
package com.atguigu.user.controller;
import com.atguigu.spzx.model.vo.result.Result;
import com.atguigu.user.service.SmsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "用户相关接口")
@RestController
@RequestMapping("/api/user/sms")
public class SmsController {
@Autowired
private SmsService smsService;
@Operation(summary = "发送短信", description = "发送短信")
@GetMapping(value = "/sendCode/{phone}")
public Result<String> sendValidateCode(@PathVariable String phone) throws Exception {
smsService.sendValidateCode(phone);
return Result.success();
}
}

View File

@ -0,0 +1,10 @@
package com.atguigu.user.service;
public interface SmsService {
/**
* 发送短信
*
* @param phone 手机号
*/
void sendValidateCode(String phone) throws Exception;
}

View File

@ -0,0 +1,34 @@
package com.atguigu.user.service.impl;
import com.atguigu.user.service.SmsService;
import com.atguigu.user.service.module.SmsServiceModule;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class SmsServiceImpl implements SmsService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 发送短信
*
* @param phone 手机号
*/
@Override
public void sendValidateCode(String phone) throws Exception {
Object isExist = redisTemplate.opsForValue().get(phone);
// 1. 生成验证码
String code = RandomStringUtils.randomNumeric(4);
// 2. 生成的验证码放到Redis中设置过期时间
redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES);
// 3. 向手机发送短信
if (isExist == null) {
SmsServiceModule.sendMessage(phone, code);
}
}
}

View File

@ -0,0 +1,41 @@
package com.atguigu.user.service.module;
import com.atguigu.exception.BunnyException;
import com.atguigu.utils.HttpUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class SmsServiceModule {
/**
* 发送短信
*
* @param phone 手机号
* @param code 验证码
*/
public static void sendMessage(String phone, String code) throws Exception {
String host = "https://gyytz.market.alicloudapi.com";
String path = "/sms/smsSend";
String method = "POST";
String appcode = "5a4cb12374904a7fa2975ec5a1a9d50f";
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "APPCODE " + appcode);
Map<String, String> querys = new HashMap<>();
querys.put("mobile", phone);
querys.put("param", "**code**:" + code + ",**minute**:5");
querys.put("smsSignId", "2e65b1bb3d054466b82f0c9d125465e2");
querys.put("templateId", "908e94ccf08b4476ba6c876d13f084ad");
Map<String, String> bodys = new HashMap<>();
HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
log.info("请求完成响应:{}", response);
if (response.getStatusLine().getStatusCode() > 300) {
throw new BunnyException("短信接码未授权");
}
}
}

View File

@ -4,4 +4,14 @@ bunny:
port: 3305
sqlData: db_spzx
username: root
password: "02120212"
password: "02120212"
redis:
host: 106.15.251.123
port: 6379
database: 2
nacos:
server-addr: z-bunny.cn:8848
discovery:
namespace: spzx

View File

@ -1,14 +1,16 @@
server:
port: 8511
port: 8512
spring:
profiles:
active: dev
application:
name: service-product
name: service-user
cloud:
nacos:
server-addr: 192.168.1.5:8848
discovery:
namespace: ${bunny.nacos.discovery.namespace}
server-addr: ${bunny.nacos.server-addr}
datasource:
type: com.zaxxer.hikari.HikariDataSource
@ -17,6 +19,26 @@ spring:
username: ${bunny.datasource.username}
password: "${bunny.datasource.password}"
data:
redis:
host: ${bunny.redis.host}
port: ${bunny.redis.port}
database: ${bunny.redis.database}
logging:
level:
com.atguigu.spzx.user.mapper: debug
com.atguigu.spzx.user.controller: info
com.atguigu.spzx.user.service: info
pattern:
dateformat: HH:mm:ss:SSS
file:
path: "logs/${spring.application.name}"
mybatis:
config-location: classpath:mybatis-config.xml
mapper-locations: classpath:/mapper/*/*.xml
type-aliases-package: com.atguigu.spzx.model
mapper-locations: classpath:/mapper/*/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
auto-mapping-behavior: full

View File

@ -0,0 +1,16 @@
-----------------▄██-█▄---------
-----------------███▄██▄--------
-----------------███████--------
-----------------▀███████-------
-------------------██████▄▄-----
-------------------█████████▄---
-------------------██████▄████--
-------▄███████████████████████-
-----▄███████████████████████▀--
---▄██████████████████████------
---███████████████████████------
---███████████████████████------
-▄▄██████████████████████▀------
-█████████████████▀█████--------
-▀██████████████▀▀-▀█████▄------
-------▀▀▀▀▀▀▀▀▀------▀▀▀▀------

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -4,7 +4,7 @@
<contextName>logback</contextName>
<!-- 日志的输出目录 -->
<property name="log.path" value="logs//spzx-manager//logs"/>
<property name="log.path" value="logs//service-user//logs"/>
<!--控制台日志格式:彩色日志-->
<!-- magenta:洋红 -->