add sms code

This commit is contained in:
2025-07-30 19:04:45 +08:00
parent 7d4ba584a8
commit ed696e7c90
15 changed files with 729 additions and 1 deletions

7
DockerFile Normal file
View File

@@ -0,0 +1,7 @@
FROM eclipse-temurin:21-jdk-jammy
MAINTAINER TongTongStudio <tongtongstudios@gmail.com>
RUN mv /etc/apt/sources.list /etc/apt/sources.list.bak
VOLUME /tmp
ADD target/*.jar app.jar
EXPOSE 8088
ENTRYPOINT ["java", "-jar", "/app.jar"]

36
pom.xml
View File

@@ -1,6 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<repositories>
<repository>
<id>nexus-tencentyun</id>
<url>http://mirrors.cloud.tencent.com/nexus/repository/maven-public/</url>
</repository>
<!-- 添加其他镜像源 -->
</repositories>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
@@ -38,6 +45,15 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
@@ -54,6 +70,26 @@
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>8.0.33</version>
</dependency>
<!--异步处理-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibabacloud-dysmsapi20170525</artifactId>
<version>3.0.2</version>
</dependency>
<!--同步-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>3.1.1</version>
</dependency>
</dependencies>
<build>

View File

@@ -0,0 +1,45 @@
package com.ttstd.videotablet.controller;
import com.ttstd.videotablet.result.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
//引入 redis
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/")
public Result getMethodName() {
return Result.ok().message("Welcome to Yijiantong");
}
/**
* 存储
*
* @return
*/
@PostMapping("/set")
public Result setRedis(@RequestParam(value = "username") String username) {
//存储 key-value 键值对: "username"-"jaychou"
stringRedisTemplate.opsForValue().set("username", username);
return Result.ok().message("redis 存储成功!");
}
/**
* 读取
*
* @return
*/
@GetMapping("/get")
public Result getRedis(@RequestParam(value = "username") String username) {
//通过 key 值读取 value
String result = stringRedisTemplate.opsForValue().get(username);
return Result.ok().data("username", result);
}
}

View File

@@ -0,0 +1,105 @@
package com.ttstd.videotablet.controller;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.tea.TeaException;
import com.ttstd.videotablet.result.Result;
import com.ttstd.videotablet.sms.SendSms;
import com.ttstd.videotablet.utils.TextUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
public class SmsController {
private static final int LOGIN_CODE_TTL = 5;
//引入 redis
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/verify_code")
public Result getMethodName(@RequestParam("number") String number) {
if (TextUtils.isEmpty(number)) {
return Result.error().message("phone number is empty");
}
String oldCode = stringRedisTemplate.opsForValue().get(number);
if (TextUtils.isEmpty(oldCode)) {
String code = SendSms.generatedcode(6);
return sendCode(number, code);
} else {
return sendCode(number, oldCode);
}
}
private Result sendCode(String number, String code) {
try {
com.aliyun.dysmsapi20170525.Client client = createClient();
com.aliyun.dysmsapi20170525.models.SendSmsRequest sendSmsRequest = new com.aliyun.dysmsapi20170525.models.SendSmsRequest()
.setSignName("同同的小站")
.setTemplateCode("SMS_468370574")
.setPhoneNumbers(number)
.setTemplateParam("{\"code\":\"" + code + "\"}");
com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
// 复制代码运行请自行打印 API 的返回值
SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtime);
// System.out.println(sendSmsResponse.body.toMap());
// System.out.println(sendSmsResponse.getBody().getMessage());
// System.out.println(sendSmsResponse.getBody().getCode());
if ("OK".equals(sendSmsResponse.getBody().getCode())) {
String randomString = RandomStringUtils.randomAlphanumeric(32);
Map<String, Object> map = new HashMap<>();
map.put("verify_key", randomString);
map.put("sms", sendSmsResponse.getBody().getMessage());
//4.保存验证码到Redis,并且设置有效期5分钟
stringRedisTemplate.opsForValue().set(number, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
return Result.ok().data(map);
} else {
return Result.error().message(sendSmsResponse.getBody().getMessage());
}
} catch (TeaException error) {
// 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
// 错误 message
System.out.println(error.getMessage());
// 诊断地址
System.out.println(error.getData().get("Recommend"));
com.aliyun.teautil.Common.assertAsString(error.message);
return Result.error().data("sms", error.message);
} catch (Exception _error) {
TeaException error = new TeaException(_error.getMessage(), _error);
// 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
// 错误 message
System.out.println(error.getMessage());
// 诊断地址
System.out.println(error.getData().get("Recommend"));
com.aliyun.teautil.Common.assertAsString(error.message);
return Result.error().data("sms", error.message);
}
}
/**
* 使用AK&SK初始化账号Client
*
* @return Client
* @throws Exception
*/
public com.aliyun.dysmsapi20170525.Client createClient() throws Exception {
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。
// 建议使用更安全的 STS 方式更多鉴权访问方式请参见https://help.aliyun.com/document_detail/378657.html。
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
// 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。
.setAccessKeyId("LTAI5tEVYLeg7U3bP58n3Xkj")
// 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
.setAccessKeySecret("bbPSlxkaGgkhUPaCOP7CANaNNY1b2p");
// Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
config.endpoint = "dysmsapi.aliyuncs.com";
return new com.aliyun.dysmsapi20170525.Client(config);
}
}

View File

@@ -0,0 +1,90 @@
package com.ttstd.videotablet.handler;
import com.ttstd.videotablet.result.Result;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.resource.NoResourceFoundException;
import java.util.HashMap;
import java.util.Map;
/**
* 统一异常处理
*
* @author 爷爷的茶七里香
* @date 2022/05/30 ControllerAdvice注解的含义是当异常抛到controller层时会拦截下来
*/
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 使用ExceptionHandler注解声明处理Exception异常
*
* @param e e
* @return {@link Result}
*/
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
public Result handlerException(Exception e) {
// 控制台打印异常
e.printStackTrace();
// 返回错误格式信息
return Result.error().message(e.getMessage());
}
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MissingServletRequestParameterException.class)
public Result handlerMissingServletRequestParameterException(MissingServletRequestParameterException e) {
// 控制台打印异常
e.printStackTrace();
return Result.error().message("缺少参数");
}
@ResponseBody
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(NoResourceFoundException.class)
public Result handleNoResourceFoundException(NoResourceFoundException ex) {
ex.printStackTrace();
// 新版本 Spring推荐我没有这个方法
// return ResponseEntity.notFound().body(error);
// 旧版本 Spring兼容写法
return Result.notFound().message("检查网址是否正确");
}
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidationExceptions(MethodArgumentNotValidException ex) {
ex.printStackTrace();
Map<String, Object> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach(error -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return Result.error().message("参数不能为空").data(errors);
}
// @ResponseStatus(HttpStatus.BAD_REQUEST)
// @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
// @ResponseBody
// public Map<String, String> handleValidationException(Exception ex) {
// BindingResult result = ex instanceof MethodArgumentNotValidException
// ? ((MethodArgumentNotValidException) ex).getBindingResult()
// : ((BindException) ex).getBindingResult();
//
// Map<String, String> errors = new HashMap<>();
// result.getFieldErrors().forEach(error -> {
// errors.put(error.getField(), error.getDefaultMessage());
// });
// return errors;
// }
}

View File

@@ -0,0 +1,60 @@
package com.ttstd.videotablet.result;
/**
* 接口返回工具类
*/
public class JsonData {
private int code;
private Object data;
private String msg;
public JsonData() {
}
public JsonData(int code, Object data) {
this.code = code;
this.data = data;
}
public JsonData(int code, Object data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
}
public static JsonData buildSuccess(Object data) {
return new JsonData(0, data);
}
public static JsonData buildError(String msg) {
return new JsonData(-1, "", msg);
}
public static JsonData buildError(int code, String msg) {
return new JsonData(code, "", msg);
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}

View File

@@ -0,0 +1,129 @@
package com.ttstd.videotablet.result;
import java.util.HashMap;
import java.util.Map;
/**
* 统一返回格式类
*
* @author 爷爷的茶七里香
* @date 2022/05/30
*/
public class Result {
/**
* 是否成功
*/
private Boolean success;
/**
* 状态码
*/
private Integer code;
/**
* 返回的消息
*/
private String message;
/**
* 放置响应的数据
*/
private Map<String, Object> data = new HashMap<>();
public Result() {
}
/** 以下是定义一些常用到的格式,可以看到调用了我们创建的枚举类 */
public static Result ok() {
Result r = new Result();
r.setSuccess(ResultCodeEnum.SUCCESS.getSuccess());
r.setCode(ResultCodeEnum.SUCCESS.getCode());
r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
return r;
}
public static Result error() {
Result r = new Result();
r.setSuccess(ResultCodeEnum.UNKNOWN_REASON.getSuccess());
r.setCode(ResultCodeEnum.UNKNOWN_REASON.getCode());
r.setMessage(ResultCodeEnum.UNKNOWN_REASON.getMessage());
return r;
}
public static Result notFound() {
Result r = new Result();
r.setSuccess(ResultCodeEnum.NOT_FOUND.getSuccess());
r.setCode(ResultCodeEnum.NOT_FOUND.getCode());
r.setMessage(ResultCodeEnum.NOT_FOUND.getMessage());
return r;
}
public static Result setResult(ResultCodeEnum resultCodeEnum) {
Result r = new Result();
r.setSuccess(resultCodeEnum.getSuccess());
r.setCode(resultCodeEnum.getCode());
r.setMessage(resultCodeEnum.getMessage());
return r;
}
public Result success(Boolean success) {
this.setSuccess(success);
return this;
}
public Result message(String message) {
this.setMessage(message);
return this;
}
public Result code(Integer code) {
this.setCode(code);
return this;
}
public Result data(String key, Object value) {
this.data.put(key, value);
return this;
}
public Result data(Map<String, Object> map) {
this.setData(map);
return this;
}
/** 以下是get/set方法如果项目有集成lombok可以使用@Data注解代替 */
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Map<String, Object> getData() {
return data;
}
public void setData(Map<String, Object> data) {
this.data = data;
}
}

View File

@@ -0,0 +1,45 @@
package com.ttstd.videotablet.result;
/**
* 状态码
*
* @author 爷爷的茶七里香
* @date 2022/05/30
*/
public enum ResultCodeEnum {
SUCCESS(true, 20000, "成功"),
UNKNOWN_REASON(false, 20001, "未知错误"),
NOT_FOUND(true, 20004, "没有数据");
private final Boolean success;
private final Integer code;
private final String message;
ResultCodeEnum(Boolean success, Integer code, String message) {
this.success = success;
this.code = code;
this.message = message;
}
public Boolean getSuccess() {
return success;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
@Override
public String toString() {
return "ResultCodeEnum{" + "success=" + success + ", code=" + code + ", message='" + message + '\'' + '}';
}
}

View File

@@ -0,0 +1,95 @@
package com.ttstd.videotablet.sms;
// This file is auto-generated, don't edit it. Thanks.
import com.aliyun.auth.credentials.Credential;
import com.aliyun.auth.credentials.provider.StaticCredentialProvider;
import com.aliyun.sdk.service.dysmsapi20170525.AsyncClient;
import com.aliyun.sdk.service.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.sdk.service.dysmsapi20170525.models.SendSmsResponse;
import com.google.gson.Gson;
import darabonba.core.client.ClientOverrideConfiguration;
import java.util.concurrent.CompletableFuture;
public class SendSms {
public static void sendTest(String number) throws Exception {
// HttpClient Configuration
/*HttpClient httpClient = new ApacheAsyncHttpClientBuilder()
.connectionTimeout(Duration.ofSeconds(10)) // Set the connection timeout time, the default is 10 seconds
.responseTimeout(Duration.ofSeconds(10)) // Set the response timeout time, the default is 20 seconds
.maxConnections(128) // Set the connection pool size
.maxIdleTimeOut(Duration.ofSeconds(50)) // Set the connection pool timeout, the default is 30 seconds
// Configure the proxy
.proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress("<your-proxy-hostname>", 9001))
.setCredentials("<your-proxy-username>", "<your-proxy-password>"))
// If it is an https connection, you need to configure the certificate, or ignore the certificate(.ignoreSSL(true))
.x509TrustManagers(new X509TrustManager[]{})
.keyManagers(new KeyManager[]{})
.ignoreSSL(false)
.build();*/
// Configure Credentials authentication information, including ak, secret, token
StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder()
// Please ensure that the environment variables ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET are set.
.accessKeyId("LTAI5tEVYLeg7U3bP58n3Xkj")
.accessKeySecret("bbPSlxkaGgkhUPaCOP7CANaNNY1b2p")
//.securityToken(System.getenv("ALIBABA_CLOUD_SECURITY_TOKEN")) // use STS token
.build());
// Configure the Client
AsyncClient client = AsyncClient.builder()
.region("cn-shenzhen") // Region ID
//.httpClient(httpClient) // Use the configured HttpClient, otherwise use the default HttpClient (Apache HttpClient)
.credentialsProvider(provider)
//.serviceConfiguration(Configuration.create()) // Service-level configuration
// Client-level configuration rewrite, can set Endpoint, Http request parameters, etc.
.overrideConfiguration(
ClientOverrideConfiguration.create()
// Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
.setEndpointOverride("dysmsapi.aliyuncs.com")
//.setConnectTimeout(Duration.ofSeconds(30))
)
.build();
// Parameter settings for API request
SendSmsRequest sendSmsRequest = SendSmsRequest.builder()
.signName("同同的小站")
.templateCode("SMS_467570437")
.phoneNumbers(number)
.templateParam("{\"code\":\"" + generatedcode(4) + "\"}")
// Request-level configuration rewrite, can set Http request parameters, etc.
// .requestConfiguration(RequestConfiguration.create().setHttpHeaders(new HttpHeaders()))
.build();
// Asynchronously get the return value of the API request
CompletableFuture<SendSmsResponse> response = client.sendSms(sendSmsRequest);
// Synchronously get the return value of the API request
SendSmsResponse resp = response.get();
System.out.println(new Gson().toJson(resp));
// Asynchronous processing of return values
/*response.thenAccept(resp -> {
System.out.println(new Gson().toJson(resp));
}).exceptionally(throwable -> { // Handling exceptions
System.out.println(throwable.getMessage());
return null;
});*/
// Finally, close the client
client.close();
}
/**
* 生成?位的数字类型的短信验证码
*
* @param count
* @return
*/
public static String generatedcode(int count) {
String code = String.valueOf((int) ((Math.random() * 9 + 1) * Math.pow(10, count - 1)));
return code;
}
}

View File

@@ -0,0 +1,79 @@
package com.ttstd.videotablet.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class HashUtils {
public static String getFileMD5(File file) throws NoSuchAlgorithmException, IOException {
MessageDigest md = MessageDigest.getInstance("MD5");
FileInputStream fis = new FileInputStream(file);
byte[] dataBytes = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(dataBytes)) != -1) {
md.update(dataBytes, 0, bytesRead);
}
byte[] mdBytes = md.digest();
StringBuilder sb = new StringBuilder();
for (byte mdByte : mdBytes) {
sb.append(Integer.toString((mdByte & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
}
public static String calculateSHA1(File file) throws NoSuchAlgorithmException, IOException {
InputStream inputStream = new FileInputStream(file);
return calculateSHA1(inputStream);
}
public static String calculateSHA1(InputStream inputStream) throws NoSuchAlgorithmException, IOException {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
digest.update(buffer, 0, bytesRead);
}
byte[] hashBytes = digest.digest();
return bytesToHex(hashBytes, true);
}
public static String calculateSHA256(File file) throws NoSuchAlgorithmException, IOException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
try (InputStream inputStream = new FileInputStream(file)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
digest.update(buffer, 0, bytesRead);
}
}
byte[] hashBytes = digest.digest();
return bytesToHex(hashBytes);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
String hex = String.format("%02x", b & 0xFF);
/*大写*/
// String hex = String.format("%02X", b & 0xFF);
hexString.append(hex);
}
return hexString.toString();
}
private static String bytesToHex(byte[] bytes, boolean upcase) {
String format = upcase ? "%02X" : "%02x";
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
String hex = String.format(format, b & 0xFF);
hexString.append(hex);
}
return hexString.toString();
}
}

View File

@@ -0,0 +1,7 @@
package com.ttstd.videotablet.utils;
public class TextUtils {
public static boolean isEmpty(CharSequence str) {
return str == null || str.length() == 0;
}
}

View File

@@ -0,0 +1,24 @@
spring.application.name=VideoTablet
server.port=8088
## mysql ??????
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#spring.datasource.url=jdbc:mysql://139.199.77.221:3306/spring_boot?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.url=jdbc:mysql://127.0.0.1:3305/video_tablet_db?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=tt
spring.datasource.password=fanhuitong
# redis????
# 0????????????? Redis ???????
spring.data.redis.database=0
# 6379???????? Redis ??
spring.data.redis.port=6379
# ???????????
spring.data.redis.host=127.0.0.1
spring.data.redis.password=fanhuitong
# ???
spring.data.redis.lettuce.pool.min-idle=5
spring.data.redis.lettuce.pool.max-idle=10
spring.data.redis.lettuce.pool.max-active=8
spring.data.redis.lettuce.pool.max-wait=1ms
spring.data.redis.lettuce.shutdown-timeout=100ms

View File

@@ -0,0 +1,2 @@
spring.application.name=VideoTablet

View File

@@ -0,0 +1,2 @@
spring.application.name=VideoTablet

View File

@@ -1 +1,3 @@
spring.application.name=VideoTablet
# application.properties
# ????????
spring.profiles.active=debug