开通短信服务
进入阿里云官网,进行下述操作。
点击免费开通。
点击免签名/模板审核的API发送测试
。因为这里没有营业执照,所以就使用的是测试版的。
这里绑定测试手机号码。
添写下述信息即可。
点击调用API发送短信。
获取AccessKey
点击控制台。
点击AccessKey管理。
点击继续使用AccessKey。里边获取AccessKey ID 和 AccessKey Secret。
搭建前端环境
login.html页面。这里我只是进行功能的实现,页面没有进行美化。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<script src="http://libs.baidu.com/jquery/1.10.2/jquery.min.js"></script>
<body>
<a href="#" id="toAuthentication">测试不认证是否可以调到authentication.html页面</a><br/>
<a href="#" id="toNoAuthentication">测试不认证是否可以调到此页面index.html</a>
<form>
<div>
<label>手机号:</label><input name="mobile" id="mobile" type="text">
<button type="button" id="getCode">获取短信验证码</button>
</div>
<div>
<label>验证码:</label>
<input name="code" id="code" type="text">
</div>
<div>
<button type="button" id="submits">提交</button>
</div>
</form>
</body>
<script type="text/javascript">
$("#getCode").click(function () {
sendCode($("#getCode"));
});
$("#submits").click(function () {
submit();
});
$("#toAuthentication").click(function () {
toAuthentication(1);
});
$("#toNoAuthentication").click(function () {
toAuthentication(2);
});
// 测试是否能到认证的页面
function toAuthentication(num) {
if(num == 1){
$.ajax({
url: "http://localhost:8081/authentication",
type: "get",
xhrFields:{
withCredentials:true
},
success: function (res) {
if(res.success){
location.href="authentication.html"
}
},
error: function () {
}
})
}else if(num == 2){
$.ajax({
url: "http://localhost:8081/index",
type: "get",
xhrFields:{
withCredentials:true
},
success: function (res) {
if(res.success){
location.href="index.html"
}
},
error: function () {
}
})
}
}
//用ajax发送验证码
function sendCode(obj) {
var phone = $("#mobile").val();
var result = isPhoneNum();
if (result) {
$.ajax({
url: "http://localhost:8081/sendCode",
data: {phone: phone},
dataType: "json",
type: "post",
xhrFields:{
withCredentials:true
},
success: function (res) {
if (res) {
setTime(obj);//开始倒计时
alert("验证码发送成功");
}
},
error: function () {
alert("验证码发送失败");
}
})
}
}
// 提交表单
function submit() {
var phone = $("#mobile").val();
var code = $("#code").val();
console.log(phone + " " + code)
var result = isPhoneNum();
if (result) {
$.ajax({
url: "http://localhost:8081/login",
data: {phone: phone, code: code},
dataType: "json",
type: "post",
xhrFields:{
withCredentials:true
},
success: function (res) {
console.log(res)
if (res.success) {
location.href = "index.html";
}else {
alert(res.errorMessage)
}
getInfo();
},
error: function () {
}
})
}
}
// 获取个人信息
function getInfo() {
$.ajax({
url: "http://localhost:8081/get",
type: "get",
xhrFields:{
withCredentials:true
},
success: function (res) {
console.log(res)
},
error: function () {
}
})
}
//使用递归来实现
var countdown = 60;
function setTime(obj) {
if (countdown == 0) {
obj.prop('disabled', false);
obj.text("点击获取验证码");
countdown = 60;
return;
} else {
obj.prop('disabled', true); // 设置不可点击的属性
obj.text("(" + countdown + "s)后重新发送");
countdown--;
}
setTimeout(function () {
setTime(obj)
}, 1000) //每1000毫秒执行一次
}
//校验手机
function isPhoneNum() {
var phone = $("#mobile").val();
var reg = /^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\d{8}$/;
if (!reg.test(phone)) {
alert('请输入有效的手机号码!');
return false;
} else {
return true;
}
}
</script>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>欢迎来到首页</h1>
</body>
</html>
authentication.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>需认证的页面</title>
</head>
<body>
<h1>认证成功后到达的页面</h1>
</body>
</html>
搭建后端环境
创建一个SpringBoot工程。这里SpringBoot版本为2.7.5。
这是最终的项目结构。接下来一步一步讲解。
创建表
create table t_user(
id int primary key auto_increment,
phone char(11) not null
)
导入依赖。
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</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>
<!-- mybatis-plus依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!-- SpringBoot 的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.5</version>
</dependency>
application.properties文件的配置
server.port=8081
# 数据库的配置
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/db_csdn
# mybatis-plus的配置
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
实体类
创建实体类
package com.zhang.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")
public class User {
private Integer id;
private String phone;
}
创建LoginFormVo类。主要是来负责接收登录表单的属性。
package com.zhang.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginFormVo {
private String phone;
private String code;
}
创建UserDTO对象。是数据传输对象。
package com.zhang.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.omg.CORBA.PRIVATE_MEMBER;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
private Integer id;
}
Dao层
创建UserMapper类
package com.zhang.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zhang.pojo.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
业务层
UserService接口
package com.zhang.service;
import com.zhang.util.Result;
import com.zhang.vo.LoginFormVo;
import javax.servlet.http.HttpSession;
public interface UserService {
/**
* 发送验证码
* @param phone
* @param session
* @return
*/
Result sendCode(String phone, HttpSession session);
/**
* 登录
* @param loginFormVo
* @param session
* @return
*/
Result login(LoginFormVo loginFormVo, HttpSession session);
/**
* 获取ThreadLocal中的信息
* @return
*/
Result getInfo();
}
UserService的实现类UserServiceImpl。
package com.zhang.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zhang.config.SendInfo;
import com.zhang.dto.UserDTO;
import com.zhang.mapper.UserMapper;
import com.zhang.pojo.User;
import com.zhang.service.UserService;
import com.zhang.util.Constant;
import com.zhang.util.RegexPattern;
import com.zhang.util.Result;
import com.zhang.util.UserDTOThreadLocal;
import com.zhang.vo.LoginFormVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
@Service
public class UserServiceImpl implements UserService {
private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
@Resource
private UserMapper userMapper;
@Override
public Result sendCode(String phone, HttpSession session) {
// 1. 效验手机号格式是否正确
Boolean checkResult = RegexPattern.checkPhone(phone);
// 2. 效验失败,返回手机号格式有误
if(!checkResult){
return Result.fail("手机格式有误!");
}
// 3. 正确 ---> 则随机生成6位的验证码
String code = RandomUtil.randomNumbers(4);
// 4. 将验证码存到session中
session.setAttribute(Constant.LOGIN_CODE,code);
// 5. 如果有第三方接口,则发送验证码
SendInfo.sendCode(phone,code);
log.info("发送的验证码为:" + code);
// 6. 返回结果
return Result.ok();
}
@Override
public Result login(LoginFormVo loginFormVo, HttpSession session) {
// 1. 效验手机号格式是否正确
String phone = loginFormVo.getPhone();
Boolean checkResult = RegexPattern.checkPhone(phone);
// 2. 效验失败,返回手机号格式有误
if(!checkResult){
return Result.fail("手机格式有误!");
}
// 3. 正确 ---> 从session中取出验证码
String code = (String) session.getAttribute(Constant.LOGIN_CODE);
log.info("login_session中的验证码为:" + session.getAttribute(Constant.LOGIN_CODE));
// 4. 判断验证码是否正确
if(code == null || !code.equals(loginFormVo.getCode())){
// 5. 不正确 --> 返回
return Result.fail("验证码不正确!");
}
// 6. 正确 ---> 通过手机号查询用户是否存在
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("phone",loginFormVo.getPhone());
User user = userMapper.selectOne(wrapper);
// 7. 不存在 ---> 自动注册
if(user == null){
user = new User();
user.setPhone(phone);
userMapper.insert(user);
}
// 8. 存在 --> 注意,这里将信息存储到封装的DTO类中,DTO包含User类的部分属性
// 将user对象封装为UserFTO,这样只暴露出用户的不重要的信息,更加安全
UserDTO userDTO = new UserDTO();
BeanUtil.copyProperties(user,userDTO);
// 9. 将UserDTO对象信息存储到session中
session.setAttribute(Constant.LOGIN_USER,userDTO);
// 10. 返回结果
return Result.ok();
}
@Override
public Result getInfo() {
UserDTO user = UserDTOThreadLocal.getInfo();
log.info(user.toString());
return Result.ok(user);
}
}
控制层
UserController类
package com.zhang.controller;
import com.zhang.service.UserService;
import com.zhang.util.Result;
import com.zhang.vo.LoginFormVo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
@RestController
public class UserController {
@Resource
private UserService userService;
/**
* 发送手机验证码
* @param phone
* @param session
* @return
*/
@RequestMapping("/sendCode")
public Result sendCode(String phone, HttpSession session){
return userService.sendCode(phone,session);
}
/**
* 登录
* @param loginFormVo
* @param session
* @return
*/
@RequestMapping("/login")
public Result login(LoginFormVo loginFormVo, HttpSession session){
return userService.login(loginFormVo,session);
}
/**
* 获取信息
* @return
*/
@RequestMapping("/get")
public Result getInfo(){
return userService.getInfo();
}
/**
* 测试,不认证看是否会拦截,这个是默认跳转到authentication.html页面
* @return
*/
@RequestMapping("/authentication")
public Result authentication(){
return Result.ok();
}
/**
* 测试,不认证看是否会拦截,这个是跳转到index.html页面
* @return
*/
@RequestMapping("/index")
public Result index(){
return Result.ok();
}
}
配置类
LoginIntercept拦截器类
package com.zhang.config;
import com.zhang.dto.UserDTO;
import com.zhang.util.Constant;
import com.zhang.util.UserDTOThreadLocal;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
// 拦截器
public class LoginIntercept implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 获取session
HttpSession session = request.getSession();
// 2. 获取用户信息
UserDTO userDTO = (UserDTO) session.getAttribute(Constant.LOGIN_USER);
// 3. 判断用户是否存在
if(userDTO == null){
// 4. 不存在 --> 则拦截,返回401状态
response.setStatus(401);
return false;
}
// 5. 存在 --> 将其保存在ThreadLocal中
UserDTOThreadLocal.save(userDTO);
// 6. 放行
return true;
}
}
MvcConfig 配置类
package com.zhang.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
/**
* 解决跨域问题
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路由
registry.addMapping("/**")
// 是否发送cookie
.allowCredentials(true)
// 放行哪些资源
//.allowedOrigins("*") 出现错误,使用下面这种
.allowedOriginPatterns("*") // 设置允许跨域请求的域名
.allowedMethods(new String[]{"GET","POST","PUT","DELETE"})
.allowedHeaders("*")
.exposedHeaders("*");
}
/**
* 配置拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginIntercept()) // 设置登录拦截器
.excludePathPatterns("/login","/index","/sendCode"); // 这些路径不需要通过过滤器
}
}
工具类
常量类
package com.zhang.util;
public interface Constant {
/**
* 验证码
*/
public static final String LOGIN_CODE = "code";
/**
* Session中存储的用户对象
*/
public static final String LOGIN_USER = "login_user";
/**
* 阿里云的AccessKey ID
*/
public static final String ACCESS_KEY_ID = "你自己的AccessKey ID";
/**
* 阿里云的AccessKey SECRET
*/
public static final String ACCESS_KEY_SECRET = "你自己的AccessKey SECRET";
}
RegexPattern类。效验格式是否正确
package com.zhang.util;
public class RegexPattern {
/**
* 效验手机格式是否正确
* @param phone
* @return
*/
public static Boolean checkPhone(String phone){
String phoneRegex = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";
if(phone.matches(phoneRegex)){
return true;
}
return false;
}
}
Result 类,封装的结果集。
package com.zhang.util;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private Boolean success; // 是否成功
private String errorMessage; // 返回错误消息
private Object data; // 返回数据
public static Result ok(){
return new Result(true,null,null);
}
public static Result ok(Object data){
return new Result(true,null,data);
}
public static Result fail(String errorMessage){
return new Result(false,errorMessage,null);
}
}
UserDTOThreadLocal 类,操作ThreadLocal的。
package com.zhang.util;
import com.zhang.dto.UserDTO;
public class UserDTOThreadLocal {
private static final ThreadLocal<UserDTO> local = new ThreadLocal<>();
public static void save(UserDTO userDTO){
local.set(userDTO);
}
public static void remove(){
local.remove();
}
public static UserDTO getInfo(){
return local.get();
}
}
SendInfo类,封装发送短信验证码的工具类。
package com.zhang.util;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.tea.TeaException;
import com.aliyun.teautil.Common;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.models.RuntimeOptions;
public class SendInfo {
public static Client createClient(String accessKeyId, String accessKeySecret) throws Exception {
Config config = new com.aliyun.teaopenapi.models.Config()
// 必填,您的 AccessKey ID
.setAccessKeyId(accessKeyId)
// 必填,您的 AccessKey Secret
.setAccessKeySecret(accessKeySecret);
// 访问的域名
config.endpoint = "dysmsapi.aliyuncs.com";
return new Client(config);
}
public static void sendCode(String phone,String code) {
try {
Client client = createClient(Constant.ACCESS_KEY_ID, Constant.ACCESS_KEY_SECRET);
SendSmsRequest sendSmsRequest = new SendSmsRequest()
.setSignName("阿里云短信测试")
.setTemplateCode("SMS_154950909")
.setPhoneNumbers(phone)
.setTemplateParam("{\"code\":\""+ code +"\"}");
RuntimeOptions runtime = new RuntimeOptions();
// 复制代码运行请自行打印 API 的返回值
client.sendSmsWithOptions(sendSmsRequest, runtime);
} catch (TeaException error) {
// 如有需要,请打印 error
com.aliyun.teautil.Common.assertAsString(error.message);
} catch (Exception _error) {
TeaException error = new TeaException(_error.getMessage(), _error);
// 如有需要,请打印 error
Common.assertAsString(error.message);
}
}
}