安卓+SpringBoot短信验证
原理
客户端进入登陆页面,输入手机号,点击"获取验证码",这时候客户端会先做一个判断,看手机号是不是符合规范,不是的话就弹出提示框提示"手机号不规范,请重新输入",如果是规范的手机号,客户端就带上手机号作为参数向服务端发送请求,服务端接收到这个请求就调用发送短信的业务层(官方有工具类,输入参数即可),随机生成四位数字,然后可以以这个手机号为key,验证码为value存在redis,设置一个过期时间(一般都是五分钟),然后这个业务层会向这个手机号发送这个随机生成的字符串。
客户端收到短信输入验证码,点击登录,客户端又带上手机号和验证码向服务端发送请求,服务端收到请求首先看redis是不是存在这样的"以客户端提交的这个手机号为key,验证码为value"的键值对,不存在的话那就是验证码过期了,或者手机号,验证填错了,返回验证错误的信息给客户端;如果存在的话,那就验证成功了,那么接下来还要做一个判断,要看看这个手机号注册过没有,在用户注册表中查询有没有这个手机号对应的用户,有的话就是注册过的,那就返回客户端登陆成功的标识,客户端成功登录跳转首页;如果用户注册表中没有对应的用户,可以返回客户端没有注册的信息,也可以顺便用这个手机号注册一个信息,现在一般都是这种流程,注册之后,同样返回客户端登录成功的信息,客户端登陆成功,跳转首页。
流程图:
步骤:
客户端
这是安卓端登录页的代码:
package com.yunyou.fragment.Login;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.yunyou.R;
import com.yunyou.entity.Scenics.JsonRootBean;
import com.yunyou.fragment.PersonalFragment;
import com.yunyou.module.MainActivity;
import com.yunyou.utils.YyHttpRequestOrGetDataFromNet;
import com.yunyou.utils.YySharedPrefUtility;
import org.json.JSONObject;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Create by xie
* Date: 2021/10/11
* Time: 0:03
**/
public class LoginByIdentityCodeFragment extends Fragment implements View.OnClickListener{
private String phone;
private String vertify;
private String jsonLoginningInfo;
private JsonRootBean jsonRootBean;//自己写的实体类,格式化数据
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.activity_login_by_identitycode, container, false);
Button vertify = view.findViewById(R.id.vertify);
Button loginvertify = view.findViewById(R.id.loginvertify);
vertify.setOnClickListener(this);
loginvertify.setOnClickListener(this);
return view;
}
//这是碎片的实例化
public static LoginByIdentityCodeFragment newInstance() {
Bundle bundle = new Bundle();
LoginByIdentityCodeFragment fragment = new LoginByIdentityCodeFragment();
fragment.setArguments(bundle);
return fragment;
}
//这是登录的方法
public void login(){
EditText phoneEditView = getView().findViewById(R.id.phone_account);
phone = phoneEditView.getText().toString();
EditText vertifyEditView = getView().findViewById(R.id.edit_passwordvertify);
vertify = vertifyEditView.getText().toString();
//正确的手机号
if(isTruePhone(phone)){
// Toast.makeText(getActivity(),"验证码已发送至"+phone+"五分钟内有效,注意查收哦!",Toast.LENGTH_LONG).show();
try{
//后端验证登录的接口
String loginurl = getString(R.string.url2) + "vertify/"+phone+"/"+vertify;;
jsonLoginningInfo = YyHttpRequestOrGetDataFromNet.doGetJsonStringFromThread(loginurl, "");
Gson gson = new Gson();
jsonRootBean = gson.fromJson(jsonLoginningInfo,new TypeToken<JsonRootBean>(){
}.getType());
//token 存储
YySharedPrefUtility.setParam(getActivity(),YySharedPrefUtility.Token,
jsonRootBean.getContent().getToken());
System.out.println("token:"+jsonRootBean.getContent().getToken());
System.out.println("token:"+jsonRootBean.getMsg());
}
catch (Exception e){
e.printStackTrace();}
//密码正确:服务端返回1
if (jsonRootBean.getMsg().equals("registerSuccess")) {
Toast.makeText(getActivity(), "登陆成功", Toast.LENGTH_SHORT).show();
YySharedPrefUtility.setParam(getActivity(),
YySharedPrefUtility.ACCOUNTID, phone);//用户名存起来
startActivity(new Intent(getActivity(), MainActivity.class));//登录成功跳转首页
}
//用户名不存在或密码错误:服务端返回-1
else {
Toast.makeText(getActivity(), "手机号不存在或验证码不正确", Toast.LENGTH_SHORT).show();
}
}//验证码为空
else if(vertify.equals("")){
Toast.makeText(getActivity(),"验证码不能为空",Toast.LENGTH_LONG).show();
}//不是合格的手机号
else{
Toast.makeText(getActivity(),"不是合格的手机号",Toast.LENGTH_LONG).show();
}
}
//发送验证码
public void sendSms(){
EditText phoneEditView = getView().findViewById(R.id.phone_account);
phone = phoneEditView.getText().toString();
if(!isTruePhone(phone)){
Toast.makeText(getActivity(),"不是合格的手机号"+phone,Toast.LENGTH_LONG).show();
}
else{
//这里请求url
try{
String loginByVertifyurl = getString(R.string.url2) + "send/"+phone;
String result = YyHttpRequestOrGetDataFromNet.doGetJsonStringFromThread(loginByVertifyurl,"cdvdv");
Toast.makeText(getActivity(),"验证码已发送至"+phone+"五分钟内有效,注意查收哦!",Toast.LENGTH_LONG).show();
}
catch (Exception e){
e.printStackTrace();}
}
}
//验证是否为合格的手机号
public Boolean isTruePhone(String phone){
// ^((13[0-9])|(15[^4])|(18[0,2,3,5-9])|(17[0-8])|(147))\d{8}$
// String PHONE_PATTERN="^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(17([0,1,6,7,]))|(18[0-2,5-9]))\\d{8}$";
String PHONE_PATTERN = "^((13[0-9])|(15[^4])|(18[0,2,3,5-9])|(17[0-8])|(147)|(19[0-9]))\\d{8}$";
boolean isPhone = Pattern.compile(PHONE_PATTERN).matcher(phone).matches();
if(isPhone)return true;
else return false;
}
/**
* 8-16个字符,不包含空格,必须包含数字,字母或字符至少两种
* @param password
* @return
*/
public boolean isPassword(String password){
String pattern = "(?!.*\\s)(?!^[\\u4e00-\\u9fa5]+$)(?!^[0-9]+$)(?!^[A-z]+$)(?!^[^A-z0-9]+$)^.{8,16}$";
Pattern r = Pattern.compile(pattern);
Matcher m = r.matcher(password);
return m.matches();
}
/**
* 验证是否为邮箱
* @param email
* @return
*/
public boolean isEmail(String email){
String pattern = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$";
Pattern r = Pattern.compile(pattern);
Matcher m = r.matcher(email);
return m.matches();
}
//点击事件
@Override
public void onClick(View v) {
switch (v.getId()){
//发送验证码
case R.id.vertify:
sendSms();
break;
//登录
case R.id.loginvertify:
login();
break;
}
}
}
服务端
配置redis
服务端为了实现验证码过期的逻辑,要用到redis,那首先就要配置redis,我之前用ssm项目配置redis一直有问题,于是就叫别人帮忙把项目转成了SpringBoot开发
1.下载redis
最好在github,在官网现在只能下载压缩包,压缩文件用也是可以用,但是每次都要手动开启redis服务,重新设置redis密码买比较繁琐,最好在github下载二进制文件安装包,地址如下:
https://github.com/tporadowski/redis/releases
下载后进入redis安装目录进入redis.windows.conf
找到这一行,改成你的密码
然后可能要在redis安装目录下cmd输入redis-server.exe redis.windows.conf执行这个配置文件才会生效
反正最后在双击redis-cli 输入"auth 123456"(自己的密码)出现OK就是设置成功
2.添加依赖
<!--redis-Jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!--spring-reids-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.写redis配置文件
applecation.properties
spring.redis.host=localhost
#Redis服务器连接端口
spring.redis.port=6379
#Redis服务器连接密码(默认为空)
spring.redis.password=123456
#spring.redis.database=0
#连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
#连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
#连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
#连接超时时间(毫秒)
spring.redis.timeout=30000
RedisConfig配置类读取applecation.properties信息
package com.yunyou.yunyoutest.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
@Configuration
public class JedisConfig{
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.jedis.pool.max-active}")
private int maxActive;
@Value("${spring.redis.jedis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.jedis.pool.max-wait}")
private long maxWait;
@Value("${spring.redis.jedis.pool.max-idle}")
private int minIdle;
@Bean
public JedisPool jedisProvider() {
try {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxIdle(maxIdle);
config.setMaxWaitMillis(maxWait);
config.setMaxTotal(maxActive);
config.setMinIdle(minIdle);
JedisPool jedisPool = new JedisPool(config, host, port, timeout, password);
System.out.println("连接redis" + jedisPool.getResource().toString());
return jedisPool;
}catch (Exception e){
System.out.println(e);
System.out.println(host+port+timeout+ password);
System.out.println("redis连接失败");
return null;
}
}
}
还有一个redis的工具类
package com.yunyou.yunyoutest.util.Redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.List;
import java.util.Map;
@Component
public class JedisUtils {
@Autowired(required = false)
private JedisPool jedisPool;
/**
* 获取JedisPool连接池实例
*
* @return JedisPool连接池实例(通过Spring生成)
*/
public JedisPool getJedisPool() {
return jedisPool;
}
/**
* 获取Jedis实例
*
* @return Jedis实例
*/
public Jedis getJedis() {
return jedisPool.getResource();
}
/**
* Redis设置键值对
*
* @param key 键
* @param value 值
* @return 值
*/
public String set(String key, String value) {
return action(jedis -> jedis.set(key, value));
}
/**
* Redis获取键对应的值
*
* @param key 键
* @return 值
*/
public String get(String key) {
return action(jedis -> jedis.get(key));
}
/**
* Redis是否存在当前键
*
* @param key 查询的键
* @return 是否存在
*/
public Boolean exists(String key) {
return action(jedis -> jedis.exists(key));
}
/**
* 设置Key的过期时间,单位以秒计
*
* @param key 键
* @param seconds 秒数
* @return 1为设置成功,0为设置失败(Jedis返回的就是Long,不知道为嘛要用Long)
*/
public Long expire(String key, int seconds) {
return action(jedis -> jedis.expire(key, seconds));
}
/**
* 返回key的过期时间
*
* @param key 键
* @return 返回过期时长(以秒为单位) 若不存在该key或该key不存在过期时间则返回-1
*/
public Long ttl(String key) {
return action(jedis -> jedis.ttl(key));
}
/**
* 将key对应的值+1
* (由于在Redis中都是字符串,所以Redis会将字符串转换为最大64bit的有符号整数类型后再增加)
* (如果key不存在,则会将key对应的值设置为0后再执行增加操作)
* (如果value的类型错误,则会异常报错!)
*
* @param key 键
* @return 返回增加的结果,
*/
public Long incr(String key) {
return action(jedis -> jedis.incr(key));
}
/**
* Redis设置Hash
*
* @param key 键
* @param value 值(一个Map)
* @return 存在则更新并返回0,不存在则新建并返回1
*/
public Long hSet(String key, Map<String, String> value) {
return action(jedis -> jedis.hset(key,value));
}
/**
* Redis获取Hash
*
* @param key 键
* @return hashMap
*/
public Map<String, String> hGet(String key) {
return action(jedis -> jedis.hgetAll(key));
}
/**
* Redis设置hashMap
*
* @param key map对应的键
* @param field map中键
* @param value map中键对应的值
* @return 如果存在此map且存在此field,则更新数据并返回0,否则创建数据并返回1
*/
public Long hSet(String key, String field, String value) {
return action(jedis -> jedis.hset(key, field, value));
}
/**
* Redis获取Hash
*
* @param key 键
* @param field map中的键
* @return map中键对应的值
*/
public String hGet(String key, String field) {
return action(jedis -> jedis.hget(key, field));
}
/**
* Redis删除Hash
*
* @param key 键
* @param field map中可变数量的键
* @return 如果field在map中存在则删除并返回1,否则不做任何操作返回0
*/
public Long hDel(String key, String... field) {
return action(jedis -> jedis.hdel(key, field));
}
/**
* Redis判断是否存在
*
* @param key 键
* @param field map中的键
* @return 判断key对应的map中是否存在field的键
*/
public Boolean hExists(String key, String field) {
return action(jedis -> jedis.hexists(key, field));
}
/**
* Redis获取hash对应的val
*
* @param key 键
* @return val的列表
*/
public List<String> hVals(String key) {
return action(jedis -> jedis.hvals(key));
}
/**
* Redis删除key对应的数据
*
* @param key 键
* @return 存在就删除且返回1,不存在不做任何操作返回0
*/
public Long del(String... key) {
return action(jedis -> jedis.del(key));
}
/**
* 封装一部分重复操作,使jedis操作更简便
*/
public <T> T action(RedisAction<T> action) {
Jedis jedis = jedisPool.getResource();
T v = action.action(jedis);
jedis.close();
return v;
}
public interface RedisAction<T> {
T action(Jedis jedis);
}
}
到这里redis就配置好了
这是服务端发送短信的代码:
业务层
package com.yunyou.yunyoutest.service.impl;
import java.util.Map;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.yunyou.yunyoutest.service.SendSms;
//这里最好申请一个阿里云账号,会有对应的信息都填到这里面
@Service
public class SendSmsImpl implements SendSms{
@Override
public Boolean sendMessage(String phoneNum,Map<String, Object> map) {
System.out.println(JSONObject.toJSONString(map));
//连接阿里云
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou",
"***********************", "**************************");
IAcsClient client = new DefaultAcsClient(profile);
//构建请求
CommonRequest request = new CommonRequest();
request.setSysMethod(MethodType.POST);
request.setSysDomain("dysmsapi.aliyuncs.com");//不要动
request.setSysVersion("2017-05-25");//不要动
request.setSysAction("SendSms");//不要动
// 自定义参数(手机号,验证码。签名。模板!)
request.putQueryParameter("RegionId", "cn-hangzhou");
request.putQueryParameter("PhoneNumbers", phoneNum);
request.putQueryParameter("SignName", "*********");
request.putQueryParameter("TemplateCode", "***********");
request.putQueryParameter("TemplateParam", JSONObject.toJSONString(map));
try {
CommonResponse response = client.getCommonResponse(request);
System.out.println(response.getData());
return response.getHttpResponse().isSuccess();
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
}
return false;
}
}
控制层
package com.yunyou.yunyoutest.controller;
import com.yunyou.yunyoutest.service.SendSms;
import com.yunyou.yunyoutest.util.Redis.JedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.data.redis.core.RedisTemplate;
//import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@Controller
public class SendSmsController {
@Autowired
private SendSms sendMessage;//业务调用
@Autowired
private JedisUtils jedisUtils;
String code;
@RequestMapping(value="send/{phone}",method=RequestMethod.GET)
@ResponseBody
public String send(@PathVariable("phone") String phone){
//调用发送方法(模拟真是业务redis)
//如果同样的手机号在规定时间内还申请了
if (jedisUtils.exists("code"+phone)){
System.out.println("这个手机号码一分钟之内申请了"+jedisUtils.get("code"+phone));
return "This phone'code in useful time!";
}
//生成验证码并存储到redis中
// code = UUID.randomUUID().toString().substring(0, 5);
int code1 = (int)(Math.random()*9999)+100;
code = Integer.toString(code1);
HashMap<String,Object> map = new HashMap<>();
map.put("code",code);
//flag是为了判断业务层是否发送成功
Boolean flag = sendMessage.sendMessage(phone,map);
if (flag){
//设置新的验证码存入Redis
System.out.println("设置新的验证码存入Redis");
jedisUtils.set("code"+phone,code);
jedisUtils.expire("code"+phone,300);
return "success to send "+phone+"!";
}
return "发送失败!";
}
}
业务端处理登录的接口
控制层 接收手机号和验证码,看redis存不存在这样的键值对,存在就看数据库里这个手机号有没有注册,没有注册就注册一下,最后都给客户端返回登录成功的标识,redis不存在就返回验证码错误的消息给客户端
package com.yunyou.yunyoutest.controller;
import com.yunyou.yunyoutest.entity.JsonRootBean;
import com.yunyou.yunyoutest.entity.LoginToken;
import com.yunyou.yunyoutest.entity.userLoginningInfo.JsonUserBaseInfoPage;
import com.yunyou.yunyoutest.entity.userLoginningInfo.JsonUserLoginningPage;
import com.yunyou.yunyoutest.entity.v_U_userLoginning_info;
import com.yunyou.yunyoutest.service.UserLoginningInfoService;
import com.yunyou.yunyoutest.util.Redis.JedisUtils;
import com.yunyou.yunyoutest.util.jwt.JWTUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
@Controller
public class UserLoginningInfoController {
@Autowired
private UserLoginningInfoService uService;
@Autowired
private JedisUtils jedisUtils;
@RequestMapping(value="vertify/{phone}/{vertifycode}",method=RequestMethod.GET)
@ResponseBody
public JsonRootBean send(@PathVariable String phone,@PathVariable String vertifycode) {
JsonRootBean uJsonBaseObject;
//手机号,验证码正确,看在不在缓存里
if (jedisUtils.exists("code" + phone) && vertifycode.equals(jedisUtils.get("code" + phone))) {
//判断一下这是新用户还是注册了的用户
HashMap<String, Object> paramsMap = new HashMap<String, Object>();
paramsMap.put("accountid", phone);
uJsonBaseObject = new JsonRootBean();
JsonUserBaseInfoPage jsonUserBaseInfoPage = uService.getUserBaseInfo(paramsMap);
System.out.println(jsonUserBaseInfoPage.getUserBaseInfo() + "穷叉叉");
// return uJsonBaseObject;
// 没有注册过的,这里进行注册
if(jsonUserBaseInfoPage.getUserBaseInfo().isEmpty()) {
System.out.println("没有注册过的");
//明天的任务,怎么注册呢,调用注册的业务层,sql语句写好,多表增添数据
//像一般的,userbaseinfo,userlogininginfo,registerinfo
HashMap<String, Object> registerMap = new HashMap<String, Object>();
registerMap.put("accountid", phone);
registerMap.put("credential", "123456");
registerMap.put("typename", "手机号");
registerMap.put("rolename", "学员");
uService.registerByPhone(registerMap);
uJsonBaseObject.setMsg("registerSuccess");
}//注册过的,直接登录
else {
System.out.println("已将注册的");
uJsonBaseObject.setMsg("loginSuccess");
}
HashMap<String, String> payload = new HashMap<String, String>();
payload.put("accountid", phone);
String token = JWTUtils.getToken(payload);
LoginToken loginToken = new LoginToken();
loginToken.setToken(token);
uJsonBaseObject.setContent(loginToken);
return uJsonBaseObject;
} else {
uJsonBaseObject = new JsonRootBean();
uJsonBaseObject.setMsg("loginError");
uJsonBaseObject.setStatus(-1);
return uJsonBaseObject;
}
}
}
业务层
package com.yunyou.yunyoutest.service.impl;
import com.yunyou.yunyoutest.dao.v_U_userLoginning_infoMapper;
import com.yunyou.yunyoutest.entity.userLoginningInfo.JsonUserBaseInfoPage;
import com.yunyou.yunyoutest.entity.userLoginningInfo.JsonUserLoginningPage;
import com.yunyou.yunyoutest.entity.v_U_userLoginning_info;
import com.yunyou.yunyoutest.service.UserLoginningInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class UserLoginningInfoServiceImpl implements UserLoginningInfoService{
@Autowired
private v_U_userLoginning_infoMapper uMapper;
@Override
public Boolean registerByPhone(HashMap<String, Object> paramsMap) {
int isinsertsuccess = uMapper.registerByPhone(paramsMap);
System.out.println(isinsertsuccess);
return true;
}
}
mapper层,这里是用了存储过程,因为设计多张用户表的操作,就用存储过程
<select id="registerByPhone" parameterType="java.util.Map" resultType="int">
declare @accountid varchar(20) = #{accountid}
declare @credential varchar(20) = #{credential}
declare @typename varchar(10) = #{typename}
declare @rolename varchar(30) = #{rolename}
exec dbo.RegisterByPhone @accountid,@credential,@typename,@rolename
</select>
最后不满足的就是页面太不好看了,我对页面设计真是不行,有问题欢迎在评论区!