spring boot 集成阿里云短信服务实现登录
引入依赖
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>2.0.23</version>
</dependency>
配置文件
aliyun:
sms:
access-key-id: access-key
access-key-secret: access-key-secret
endpoint: dysmsapi.aliyuncs.com
获取配置文件的内容 properties
package com.orchids.sms.web.custom.sms;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @ Author qwh
* @ Date 2024/6/3 16:31
*/
@Data
@ConfigurationProperties(prefix = "aliyun.sms")
public class AliyunSMSProperties {
private String accessKeyId;
private String accessKeySecret;
private String endpoint;
}
package com.orchids.sms.web.custom.sms;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.teaopenapi.models.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(AliyunSMSProperties.class)
//当配置文件中没有aliyun.sms.endpoint 也不会报错 有就读取
@ConditionalOnProperty(name = "aliyun.sms.endpoint")
public class AliyunSMSConfiguration {
@Autowired
private AliyunSMSProperties properties;
@Bean //交给spring 管理有需要就自动专配
public Client smsClient() {
Config config = new Config();
config.setAccessKeyId(properties.getAccessKeyId());
config.setAccessKeySecret(properties.getAccessKeySecret());
config.setEndpoint(properties.getEndpoint());
try {
return new Client(config);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
编写生成验证码工具类
package com.orchids.sms.web.custom.utils;
import java.util.Random;
public class CodeUtil {
public static String getCode(int length) {
StringBuilder builder = new StringBuilder();
Random random = new Random();
for (int i = 0; i < length; i++) {
builder.append(random.nextInt(10));
}
return builder.toString();
}
}
发送短信
- sevice
package com.orchids.sms.web.service;
public interface SmsService {
void sendCode(String phone, String Code);
}
- serviceImpl 测试阿里云短信服务格式必须是这个
package com.orchids.sms.web.service.impl;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.orchids.sms.web.service.SmsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class SmsServiceImpl implements SmsService {
@Autowired
private Client client;
@Override
public void sendCode(String phone, String code) {
SendSmsRequest smsRequest = new SendSmsRequest();
smsRequest.setPhoneNumbers(phone);
smsRequest.setSignName("阿里云短信测试");
smsRequest.setTemplateCode("SMS_154950909");
smsRequest.setTemplateParam("{\"code\":\"" + code + "\"}");
System.out.println("---------------"+code);
try {
client.sendSms(smsRequest);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
测试发送短信
package com.orchids.sms.web.service;
import com.orchids.sms.web.custom.utils.CodeUtil;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
/**
* @ Author qwh
* @ Date 2024/6/4 8:48
*/
@SpringBootTest
class SmsServiceTest {
@Autowired
private SmsService smsService;
@Test
void sendCode() {
smsService.sendCode("phone", CodeUtil.getCode(4));
}
}
获取登录接口
阿里云发送验证码给用户 --》用户输入验证码
后端登录 首先生成验证码 调用阿里云验证码API发送验证码 并将验证码保存到redis设置过期时间 用户输入验证码后和redis中进行比较 成功生成登录token
引入依赖
<!--官方文档:https://github.com/jwtk/jjwt#install-jdk-maven -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
<version>0.11.2</version>
</dependency>
编写生成token的工具类
package com.orchids.sms.web.custom.utils;
import com.orchids.sms.web.custom.execupation.SmsException;
import com.orchids.sms.web.entity.result.ResultCodeEnum;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
/**
* @Author qwh
* @Date 2024/6/2 21:01
*/
public class JwtUtil {
private static long tokenExpiration = 60 * 60 * 1000L;
public static SecretKey secretKey = Keys.hmacShaKeyFor("M0PKKI6pYGVWWfDZw90a0lTpGYX1d4AQ".getBytes());
public static String createToken(Long userId,String username){
String token = Jwts.builder().
setSubject("USER_INFO").
setExpiration(new Date(System.currentTimeMillis()+tokenExpiration)).
claim("userId",userId).
claim("username",username).
signWith(secretKey, SignatureAlgorithm.HS256).
compact();
return token;
}
public static Claims parsToken(String token){
if (token==null) {
throw new SmsException(ResultCodeEnum.ADMIN_LOGIN_AUTH);
}
try {
JwtParser jwtParser = Jwts.parserBuilder().setSigningKey(secretKey).build();
Jws<Claims> claims = jwtParser.parseClaimsJws(token);
return claims.getBody();
} catch (ExpiredJwtException e) {
throw new SmsException(ResultCodeEnum.TOKEN_EXPIRED);
} catch (JwtException e){
throw new SmsException(ResultCodeEnum.TOKEN_INVALID);
}
}
public static void main(String[] args) {
System.out.println(createToken(9L,"123123"));
}
}
获取验证码
- controller
package com.orchids.sms.web.controller;
import com.orchids.sms.web.custom.login.LoginUserHolder;
import com.orchids.sms.web.custom.utils.JwtUtil;
import com.orchids.sms.web.entity.result.Result;
import com.orchids.sms.web.entity.vo.LoginVo;
import com.orchids.sms.web.entity.vo.UserInfoVo;
import com.orchids.sms.web.service.LoginService;
import com.orchids.sms.web.service.UserInfoService;
import io.jsonwebtoken.Claims;
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.*;
/**
* @ Author qwh
* @ Date 2024/6/4 1:10
*/
@Tag(name = "Sms测试")
@RestController
@RequestMapping("/Sms")
public class LoginController {
@Autowired
private LoginService loginService;
@GetMapping("getCode")
@Operation(summary = "获取短信验证码")
public Result getCode(@RequestParam String phone) {
loginService.getCode(phone);
return Result.ok();
}
}
- service
package com.orchids.sms.web.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orchids.sms.web.custom.execupation.SmsException;
import com.orchids.sms.web.custom.utils.CodeUtil;
import com.orchids.sms.web.custom.utils.JwtUtil;
import com.orchids.sms.web.entity.pojo.BaseStatus;
import com.orchids.sms.web.entity.pojo.RedisConstant;
import com.orchids.sms.web.entity.pojo.UserInfo;
import com.orchids.sms.web.entity.result.ResultCodeEnum;
import com.orchids.sms.web.entity.vo.LoginVo;
import com.orchids.sms.web.service.LoginService;
import com.orchids.sms.web.service.SmsService;
import com.orchids.sms.web.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private SmsService smsService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private UserInfoService userInfoService;
@Override
public void getCode(String phone) {
//1. 检查手机号码是否为空
if (!StringUtils.hasText(phone)) {
throw new SmsException(ResultCodeEnum.APP_LOGIN_PHONE_EMPTY);
}
String key = RedisConstant.APP_LOGIN_PREFIX+phone;
Boolean hasKey = stringRedisTemplate.hasKey(key);
if (hasKey){
Long ttl = stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
if (RedisConstant.APP_LOGIN_CODE_TTL_SEC-ttl < 30 ) {
throw new SmsException(ResultCodeEnum.APP_SEND_SMS_TOO_OFTEN);
}else {
String code = CodeUtil.getCode(6);
smsService.sendCode(phone,code);
stringRedisTemplate.opsForValue().set(key,code,RedisConstant.APP_LOGIN_CODE_TTL_SEC, TimeUnit.SECONDS);
}
}else {
String code = CodeUtil.getCode(6);
smsService.sendCode(phone,code);
stringRedisTemplate.opsForValue().set(key,code,RedisConstant.APP_LOGIN_CODE_TTL_SEC, TimeUnit.SECONDS);
}
}
}
登录
- controller
@Tag(name = "Sms测试")
@RestController
@RequestMapping("/Sms")
public class LoginController {
@Autowired
private LoginService loginService;
@Autowired
private UserInfoService userInfoService;
@GetMapping("getCode")
@Operation(summary = "获取短信验证码")
public Result getCode(@RequestParam String phone) {
loginService.getCode(phone);
return Result.ok();
}
@PostMapping("login")
@Operation(summary = "登录")
public Result<String> login(@RequestBody LoginVo loginVo) {
String token = loginService.login(loginVo);
return Result.ok(token);
}
}
- service
package com.orchids.sms.web.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orchids.sms.web.custom.execupation.SmsException;
import com.orchids.sms.web.custom.utils.CodeUtil;
import com.orchids.sms.web.custom.utils.JwtUtil;
import com.orchids.sms.web.entity.pojo.BaseStatus;
import com.orchids.sms.web.entity.pojo.RedisConstant;
import com.orchids.sms.web.entity.pojo.UserInfo;
import com.orchids.sms.web.entity.result.ResultCodeEnum;
import com.orchids.sms.web.entity.vo.LoginVo;
import com.orchids.sms.web.service.LoginService;
import com.orchids.sms.web.service.SmsService;
import com.orchids.sms.web.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private SmsService smsService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private UserInfoService userInfoService;
@Override
public String login(LoginVo loginVo) {
//判断手机号码和验证码是否为空
if (!StringUtils.hasText(loginVo.getPhone())) {
throw new SmsException(ResultCodeEnum.APP_LOGIN_PHONE_EMPTY);
}
if (!StringUtils.hasText(loginVo.getCode())){
throw new SmsException(ResultCodeEnum.APP_LOGIN_CODE_EMPTY);
}
//校验验证码
String key = RedisConstant.APP_LOGIN_PREFIX + loginVo.getPhone();
String code = stringRedisTemplate.opsForValue().get(key);
if (code==null){
throw new SmsException(ResultCodeEnum.APP_LOGIN_CODE_EXPIRED);
}
if (!code.equals(loginVo.getCode())) {
throw new SmsException(ResultCodeEnum.APP_LOGIN_CODE_ERROR);
}
//判断是否用户是否存在 不存在注册
LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(UserInfo::getPhone,loginVo.getPhone());
UserInfo userInfo = userInfoService.getOne(queryWrapper);
if (userInfo==null) {
userInfo = new UserInfo();
userInfo.setPhone(loginVo.getPhone());
userInfo.setStatus(BaseStatus.ENABLE);
userInfo.setNickname("用户-"+loginVo.getPhone().substring(6));
userInfoService.save(userInfo);
}
//判断用户是否被禁用
if (userInfo.getStatus().equals(BaseStatus.DISABLE)) {
throw new SmsException(ResultCodeEnum.APP_ACCOUNT_DISABLED_ERROR);
}
//返回token
return JwtUtil.createToken(userInfo.getId(), loginVo.getPhone());
}
}
登录后获取登录信息
- controller
@GetMapping("info")
@Operation(summary = "获取登录用户信息")
public Result<UserInfoVo> info(@RequestHeader("access-token")String token) {
Claims claims = JwtUtil.parsToken(token);
Long userId = claims.get("userId", Long.class);
System.out.println(userId);
UserInfoVo userInfoVo = userInfoService.getUserInfoById(userId);
return Result.ok(userInfoVo);
}
- service
package com.orchids.sms.web.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.orchids.sms.web.entity.pojo.UserInfo;
import com.orchids.sms.web.entity.vo.UserInfoVo;
import com.orchids.sms.web.mapper.UserInfoMapper;
import com.orchids.sms.web.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo>
implements UserInfoService {
@Autowired
private UserInfoMapper userInfoMapper;
@Override
public UserInfoVo getUserInfoById(Long userId) {
System.out.println(userId);
UserInfo userInfo = userInfoMapper.selectById(userId);
System.out.println(userInfo.toString());
return new UserInfoVo(userInfo.getNickname(), userInfo.getAvatarUrl());
}
}
测试 通过 这个案例是基于我自己的程序 但是其他程序逻辑大差不多查
其中的枚举类 不同的业务会给出的 如果没有大不了自己编写
全局错误配置 按照自己的业务进行编写 以下是这个案例的代码
package com.orchids.sms.web.custom.exception;
import com.orchids.sms.web.entity.result.ResultCodeEnum;
import lombok.Data;
/**
* @Author qwh
* @Date 2024/6/1 20:18
*/
@Data
public class SmsException extends RuntimeException {
//异常状态码
private Integer code;
/**
* 通过状态码和错误消息创建异常对象
* @param message
* @param code
*/
public SmsException(String message, Integer code) {
super(message);
this.code = code;
}
/**
* 根据响应结果枚举对象创建异常对象
* @param resultCodeEnum
*/
public SmsException(ResultCodeEnum resultCodeEnum) {
super(resultCodeEnum.getMessage());
this.code = resultCodeEnum.getCode();
}
@Override
public String toString() {
return "LovehouseException{" +
"code=" + code +
", message=" + this.getMessage() +
'}';
}
}
完整的pom.xml
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.orchids</groupId>
<artifactId>Sms</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Sms</name>
<description>Sms</description>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>3.0.2</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>2.0.23</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>3.2.4</version>
</dependency>
<!--官方文档:https://github.com/jwtk/jjwt#install-jdk-maven -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
<version>0.11.2</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.orchids.sms.SmsApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
application.yaml
# 应用服务 WEB 访问端口
server:
port: 8080
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:mysql://localhost:3306/mybatisplus?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2b8
username: 数据库用户名
password: 数据库密码
hikari:
connection-test-query: SELECT 1 # 自动检测连接
connection-timeout: 60000 #数据库连接超时时间,默认30秒
idle-timeout: 500000 #空闲连接存活最大时间,默认600000(10分钟)
max-lifetime: 540000 #此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
maximum-pool-size: 12 #连接池最大连接数,默认是10
minimum-idle: 10 #最小空闲连接数量
pool-name: SPHHikariPool # 连接池名称
data:
redis:
host: localhost
port: 6379
database: 0
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
aliyun:
sms:
access-key-id: 你的阿里云assesskey
access-key-secret: 你的阿里云accesskeysecret
endpoint: dysmsapi.aliyuncs.com