该系列博客主要记录笔者的开发过程,参考B站系列视频:【SpringBoot项目实战完整版】SpringBoot+MyBatis+MySQL电脑商城项目实战_哔哩哔哩_bilibili
所用的一些版本信息:
IDEA开发、JDK1.8版本以上、maven3.61版本以上,springboot,DataGrip管理数据库
用户登录大致页面
主要功能:
查询用户的用户名和密码,存在用户密码正确则表明登录成功,之后跳转系统的index.html页面
1.登录-持久层
1.1 规划sql语句
依据用户提交用户名进行select查询
在用户登录时已经编写了findByUserName函数,所以无需重复编写。
1.2 接口设计和抽象方法
同样,这一步无需重复开发
1.3 单元测试
同样,这一步无需重复开发
2.登录-业务层
2.1 规划异常
1.密码匹配失败异常,PasswordNotMatchException异常。
package com.cy.store.service.ex;
/** 密码不匹配异常 */
public class PasswordNotMatchException extends ServiceException{
public PasswordNotMatchException() {
super();
}
public PasswordNotMatchException(String message) {
super(message);
}
public PasswordNotMatchException(String message, Throwable cause) {
super(message, cause);
}
public PasswordNotMatchException(Throwable cause) {
super(cause);
}
protected PasswordNotMatchException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
2.用户名没有被找到的异常,UsernameNotFoundException异常。
package com.cy.store.service.ex;
/** 用户名未找到异常 */
public class UsernameNotFoundException extends ServiceException{
public UsernameNotFoundException() {
}
public UsernameNotFoundException(String message) {
super(message);
}
public UsernameNotFoundException(String message, Throwable cause) {
super(message, cause);
}
public UsernameNotFoundException(Throwable cause) {
super(cause);
}
public UsernameNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
2.2 设计业务层接口和抽象方法
1.在IUserService中编写抽象方法login,并返回用户数据,将数据保存在cookie或session中,可以避免重复度很高的数据多次频繁操作数据进行获取。
在store\src\main\java\com\cy\store\service\IUserService.java中写抽象方法:
/**
* 用户登录方法
* @param username
* @param password
* @return 当前匹配的用户数据,没有则返回null
*/
User login(String username, String password);
2.3 抽象方法的实现
在store\src\main\java\com\cy\store\service\impl\UserServiceImpl.java中对抽象方法进行实现:
@Override
public User login(String username, String password) {
//根据用户名称查询用户数据是否存在,不存在则抛异常
User result = userMapper.findByUserName(username);
if(result == null){
throw new UsernameNotFoundException("用户数据不存在");
}
//检测用户密码是否匹配
String newMD5password = getMD5Password(password, result.getSalt());
if(!newMD5password.equals(result.getPassword())){
throw new PasswordNotMatchException("用户密码错误");
}
//判断是否逻辑删除(is_delete)
if(result.getIsDelete()==1){
throw new UsernameNotFoundException("用户数据不存在");
}
//返回的user是为了辅助页面数据展示使用,只需要部分字段,提升前后端数据传递的性能
User user = new User();
user.setUid(result.getUid());
user.setUsername(result.getUsername());
user.setAvatar(result.getAvatar());
return result;
}
2.4 单元测试
在 store\src\test\java\com\cy\store\service\UserServiceTests.java 中进行单元测试
@Test
public void login(){
User user = userService.login("liyujie", "123");
System.out.println(user);
}
成功!
3.登录-控制层
3.1 处理异常
业务层抛出的异常是什么,在统一异常类BaseController进行捕获处理。
在store\src\main\java\com\cy\store\controller\BaseController.java 中添加异常处理
package com.cy.store.controller;
/** 控制层类的基类 */
import com.cy.store.service.ex.*;
import com.cy.store.util.JsonResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
/** 主要是处理异常 */
public class BaseController {
/** 操作成功的状态码 */
public static final int OK = 200;
// 请求处理方法,这个方法的返回值,需要传统给前端数据
@ExceptionHandler(ServiceException.class) //该注解用于统一处理抛出的异常
public JsonResult<Void> handleException(Throwable e){
JsonResult<Void> result = new JsonResult<>(e);
if(e instanceof UsernameDuplicatedException){
result.setState(4000);
result.setMessage("用户名占用");
}else if(e instanceof UsernameNotFoundException){
result.setState(4001);
result.setMessage("用户数据不存在异常");
}else if(e instanceof PasswordNotMatchException){
result.setState(4002);
result.setMessage("用户的密码错误的异常");
}else if(e instanceof InsertException){
result.setState(5000);
result.setMessage("注册时产生未知异常");
}
return result;
}
}
3.2 设计请求
请求路径:/users/login
请求方式:POST
请求数据:String username, String password,HttpSession
响应结果:JsonResult<User>
3.3 处理请求
在store\src\main\java\com\cy\store\controller\UserController.java 编写处理请求方法
@RequestMapping("login")
public JsonResult<User> login(String username, String password){
User data = userService.login(username, password);
return new JsonResult<User>(OK,data);
}
4.登录-前端页面
在 store\src\main\resources\static\web\login.html 中添加如下代码:
<script type="text/javascript">
$("#btn-login").click(function(){
$.ajax({
url:"/users/login",
type: "POST",
data: $('#form-login').serialize(),
dataType:"JSON",
success:function (json){
console.log(json)
if(json.state == 200){
alert("登录成功");
//跳转系统主页 相对路径写法确定跳转页面
location.href = "index.html";
}else{
alert("登录失败" + json.message);
}
},
error: function(xhr){
alert("登录时产生未知异常" + xhr.message)
}
});
});
</script>
5.登录-session存储用户数据
用户会话session,主要存储在服务器中,用于保存服务器的临时数据的对象,所保存的数据可以在整个项目通过访问获取。首次登录时将获取的用户数据转移到session对象中。
在该项目里将session封装在BaseController中。
在store\src\main\java\com\cy\store\service\impl\UserServiceImpl.java 中编写获取session对象的uid或username的方法
/**
* 获取session对象中的uid
* @param session
* @return 当前登录的用户的uid值
*/
protected final Integer getuidFromSession(HttpSession session){
return Integer.valueOf(session.getAttribute("uid").toString());
}
/**
* 获取session对象的username
* @param session
* @return
*/
protected final String getusernameFromSession(HttpSession session){
return session.getAttribute("username").toString();
}
在登录方法中将数据封装在session对象中,服务器本身自动创建session对象。
springBoot直接使用session对象,直接将httpSession类型的对象作为请求处理的参数(客户端每次提交都会戴上cookie,cookie中有sessionid,而springboot让我们可以直接使用session对象)。
在store\src\main\java\com\cy\store\controller\UserController.java 中的login方法中添加代码如下:
@RequestMapping("login")
public JsonResult<User> login(String username, String password, HttpSession session){
User data = userService.login(username, password);
//向session对象中完成数据绑定
session.setAttribute("uid", data.getUid());
session.setAttribute("username",data.getUsername());
return new JsonResult<User>(OK,data);
}
6.登录-拦截器
防止用户在未登录时通过url请求访问到登录后才能访问的网页。
首先将所有的请求统一拦截到拦截器中,可以在拦截器中定义过滤的规则。不满足系统的设置的过滤规则,统一的处理时重新去打开login.html页面(重定向和转发),推荐使用重定向。
在SpringBoot项目中拦截器的定义和使用。SpringBoot是依靠springMVC来完成的。SpringMVC提供了一个HanlerInterceptor接口,用于表示定义一个拦截器。
6.1 编写拦截器
在 store\src\main\java\com\cy\store\interceptor 下编写如下代码:
package com.cy.store.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** 定义一个拦截器 */
public class LoginInterceptor implements HandlerInterceptor {
/**
* 在调用所有处理请求的方法之前自动调用执行方法
* 检测全局session对象中是否有uid数据,有则放行,没有则重定向到login页面
* @param request 请求对象
* @param response 响应对象
* @param handler 处理器(url+Controller:映射)
* @return true则放行,false表示拦截
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//HttpServletRequest对象获取session对象
Object obj = request.getSession().getAttribute("uid");
if(obj==null){
//用户没登录,重定向到login
response.sendRedirect("/web/login.html");
//结束后续调用
return false;
}
//请求放行
return true;
}
/**
* 在modelAndView对象返回之后被调用的方法
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
/**
* 在整个请求关联的所有资源被执行完毕最后所执行的方法
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
6.2 注册过滤器
添加白名单:哪些资源可以在不登录的情况下访问: 登录,注册,主页,商品页面等。
添加黑名单:用户登录状态下才可以访问的资源。
借助webMvcConfigure接口可以将用户定义的拦截器进行注册,才能保证拦截器生效和使用,配置信息简易存放在项目Config包结构下。
package com.cy.store.config;
import com.cy.store.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
import java.util.List;
/** 处理器拦截器的注册 */
@Configuration
public class LoginInterceptorConfigurer implements WebMvcConfigurer {
/**
* 将自定义的拦截器进行注册
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//创建自定义的拦截器对象
HandlerInterceptor interceptor = new LoginInterceptor();
/**
* 配置白名单:存放在List集合汇总
*/
List<String> patterns = new ArrayList<>();
//静态资源
patterns.add("/bootstrap3/**");
patterns.add("/css/**");
patterns.add("/images/**");
patterns.add("/js/**");
//web下放行页面
patterns.add("/web/register.html");
patterns.add("/web/login.html");
patterns.add("/web/index.html");
patterns.add("/web/product.html");
//url
patterns.add("/users/reg");
patterns.add("/users/login");
//添加自定义的拦截器对象,完成拦截器注册
registry.addInterceptor(interceptor)
.addPathPatterns("/**") //黑名单,表示拦截的url是什么
.excludePathPatterns(patterns);//白名单,表示除了什么url
WebMvcConfigurer.super.addInterceptors(registry);
}
}
tips:这里切勿反复进行重定向访问,可能会被浏览器认为是非法操作。
测试:
登录成功!
跳转成功!
输入url: http://localhost:8081/web/password.html 访问
重定向成功!