最近在做尚医通的医院预约挂号管理系统时,做到了使用阿里云的短信验证服务,但是现在阿里云的短信验证服务审核听说非常严格,主要是我没有专门的域名,无法通过审核,而腾讯云又又又太贵啦,50RMB起卖,因此我选择到了较为合适的榛子云平台(20RMB–>540条短信),个人测试是完全够用了,这里附带上榛子云平台的链接以及操作指南平台
登录平台
文档平台
废话说完了,抓紧进入正题,使用Springboot+redis+榛子云的登录验证服务
在编译代码前,我们需要先清楚正常的登录流程是怎么样的
- 用户在前端页面输入手机号,然后点击发送验证码,前端对用户的手机号校验后传到后台系统
- 后台系统接收到用户的手机号,然后随机生成验证码
- 后台系统将接收到的验证码发送通过云服务平台发送短信给用户,告知它的验证码是多少
- 与此同时,后台发送完验证码后将(用户手机号,用户的验证码)存入redis,并设置过期时间
- 用户在过期时间之内登录
当然啦,中间还会涉及到用户登录中的token等操作,上面仅是整理登录过程中的思路,下面进入到操作实战部分!
<dependencies>
<dependency>
<groupId>com.zhenzikjgroupId>
<artifactId>zhenzismsartifactId>
<version>2.0.2version>
dependency>
dependencies>
# 服务端口
server.port=8204
# 服务名
spring.application.name=service-msm
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
sms.apiUrl=https://sms_developer.zhenzikj.com
#下图给说明
sms.appId=个人的appId
sms.appSecret=个人的密钥
package com.hang.msm.config;
import com.zhenzi.sms.ZhenziSmsClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
@Configuration
public class SmsConfig {
@Value("${sms.apiUrl}")
private String apiUrl;
@Value("${sms.appId}")
private String appId;
@Value("${sms.appSecret}")
private String appSecret;
public String printRandom(){
//取随机产生的验证码(4位数字)
Random rnd = new Random();
int randNum = rnd.nextInt(8999) + 1000;
//将整型数字转化成字符串
String randStr = String.valueOf(randNum);
return randStr;
}
public String sendMessage(String randNum,String phoneNum) throws Exception {
ZhenziSmsClient client = new ZhenziSmsClient(apiUrl, appId, appSecret);
Map<String, Object> params = new HashMap<String, Object>();
params.put("number", phoneNum);
params.put("templateId","9915"); //短信模板ID,在个人中心的短信模板中查看
String[] templateParams = new String[2];
templateParams[0] = randNum;
templateParams[1] = "5分钟";
params.put("templateParams", templateParams);
return client.send(params);
}
}
package com.hang.msm.controller;
import com.hang.common.result.Result;
import com.hang.msm.config.SmsConfig;
import com.hang.msm.service.MsmService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
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;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/api/msm")
public class MsmApiController {
@Autowired
private MsmService msmService;
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Autowired
private SmsConfig smsConfig;
//发送手机验证码
/**业务逻辑
* 1.从redis中获取验证码,如果可以获取到,那么就返回结果
* 2.如果无法获取,那么就生成验证码,然后通过短信服务发送
* 3.生成的验证码放入的redis中,并且生成对应的过期时间
*/
@GetMapping("send/{phone}")
public Result sendCode(@PathVariable("phone") String phone){
//key:phone value-->code
String code = redisTemplate.opsForValue().get(phone);
if(!StringUtils.isEmpty(code)){
//验证码不为空,返回结果即可
return Result.ok();
}
//如果是空,那么生成验证码并发送
String random = smsConfig.printRandom();
boolean sendSuccess = msmService.send(phone,random);
if(sendSuccess){
//发送成功(需要设置有效时间:5min)
redisTemplate.opsForValue().set(phone,random,5, TimeUnit.MINUTES);
return Result.ok();
}else {
//发送失败
return Result.fail().message("发送短信失败");
}
}
}
package com.hang.msm.service.impl;
import com.hang.msm.config.SmsConfig;
import com.hang.msm.service.MsmService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@Service
public class MsmServiceImpl implements MsmService {
@Autowired
private SmsConfig smsConfig;
@Override
public boolean send(String phone, String code) {
//判断手机号是否为空
if(StringUtils.isEmpty(phone)){
return false;
}
//整合榛子云的短信服务发送
try {
smsConfig.sendMessage(code,phone);
} catch (Exception e) {
//打印相对应的异常
e.printStackTrace();
return false;
}
return true;
}
}
至此,短信发送的服务就编写完毕,然后在登录模块中对其进行调用即可
登录模块中我就不多赘述了,就是获取到前端填写的验证码和redis中存取的验证码进行校验登录,并且返回对应的token信息,需要详尽的代码的可以发留言后面有时间了发布出来
package com.hang.user.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hang.common.exception.YyghException;
import com.hang.common.helper.JwtHelper;
import com.hang.common.result.ResultCodeEnum;
import com.hang.model.user.UserInfo;
import com.hang.user.mapper.UserInfoMapper;
import com.hang.user.service.UserInfoService;
import com.hang.vo.user.LoginVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
@Autowired
private RedisTemplate<String,String> redisTemplate;
/***登录方法
*
* @param loginVo
* @return
*/
@Override
public Map<String, Object> loginUser(LoginVo loginVo) {
//获取手机号和验证码
String phone = loginVo.getPhone();
String code = loginVo.getCode();
//先从loginVo中获取手机号和验证码是否为空
if(StringUtils.isEmpty(phone)||StringUtils.isEmpty(code)){
//抛出对应的异常
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
//验证码是否正确
String redisCode = redisTemplate.opsForValue().get(phone);
if(!code.equals(redisCode)){
//验证码不正确
throw new YyghException(ResultCodeEnum.CODE_ERROR);
}
//用户是否注册
QueryWrapper<UserInfo> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("phone",phone);
UserInfo userInfo = baseMapper.selectOne(queryWrapper);
if(userInfo==null){
//用户未注册,进行用户注册(应该返回信息通知用户进入注册界面进行注册)
userInfo = new UserInfo();
//这里简单进行注册
userInfo.setName("");
userInfo.setPhone(phone);
userInfo.setStatus(1);
baseMapper.insert(userInfo);
}
//状态校验
//先判断用户是否正常
if(userInfo.getStatus()==0){
throw new YyghException(ResultCodeEnum.LOGIN_DISABLED_ERROR);
}
Map<String,Object> map =new HashMap<>();
String name = userInfo.getName();
//用户名为空先去调真实名,真实名仍为空调用手机号
if(StringUtils.isEmpty(name)){
name = userInfo.getNickName();
}
if(StringUtils.isEmpty(name)){
name = userInfo.getPhone();
}
map.put("name",name);
//登录信息返回(登录信息,登录用户名,token)
/*什么是token
* token放在请求头中,每次请求的时候可以对请求头中的token进行校验
* 如果token校验成功,那么表示已经登录并且具有权限访问,反之则拒绝访问
* 同样地,我们可以设置token的过期时间,过期后即失效
*
* token具有一定的规则,一般可以结合JWT工具进行生成
*
* JWT一般由三部分组成:
* JWT头
* 声明类型
* 使用的加密算法
* 载荷信息
* 存放有效信息
* 签名
* base64之后的头和载荷信息,以及密钥secret组成
*
* 将签名得到的编码再根据使用的加密算法进行加密,从而得到jwt
*/
//生成token信息,并且返回
String token = JwtHelper.createToken(userInfo.getId(), name);
map.put("token",token);
return map;
}
}
最后前端调用这些接口即可进行登录
可以在榛子云的控制台中查看短信服务使用情况
不拘泥于解决方案的束缚,尝试使用其它解决方案去解决问题,也是人生的一门必修课,加油~