SpringBoot前后台交互 -- 登录功能实现(拦截器+异常捕获器)

1. 登录

1.1 前端

实现登录功能,需要前端向后端发送请求,前端请求如下:

javascript 复制代码
methods: {
            submitForm() {
                console.log(this.ruleForm)
                axios({
                    method:"post",
                    url:"/auth/login",
                    data:this.ruleForm
                }).then(res =>{
                    alert(res.data)
                    if (res.data.code=="200"){
                        // 登录成功提示
                        this.$message.success(res.data.msg)
                        // 存储用户名到Cookie
                        Cookies.set("empname",this.ruleForm.username)

                        // 两秒后跳转到员工页面
                        setTimeout(function (){
                            location.href = "emp.html"
                        },2000)
                    }else{
                        // 登录失败提示
                        this.$message.error(res.data.msg)
                    }
                }).catch(res =>{
                    // 网络请求异常处理
                })

            },
            resetForm(formName) {
                this.$refs[formName].resetFields();
            }
        }
  • emp.html页面
javascript 复制代码
/*在页面被加载的时候就去请求后台*/
        mounted() {
            console.log("init....")
            /*发起请求 获取数据 把数据交给渲染层展示*/
            this.init()
            /*获取用户的登录信息*/
            this.username = Cookies.get("empname")
            /*定时器获取在线人数*/
            var _this = this
            // setInterval(function (){
            //     axios.get("emp/getCount").then(resp=>{
            //         console.log("在线人数:"+resp.data)
            //         _this.personcount = resp.data
            //     })
            // },5000)
        },

前端发送请求之后需要后端接受请求,进行响应

1.2 后端

1.2.1 Emps实体类接收

java 复制代码
package com.gaohe.ssm1.pojo;

import lombok.Data;
import org.springframework.context.annotation.PropertySource;

@Data
public class Emps {
    private int id;
    private String username;
    private String password;
    private String addr;
    private int age;
    private String phone;

}

但是我们会发现前端页面的参数是这样的:

单靠Emps是无法接收number字段的,所以在这里我们可以创建一个vo类,继承Emps类,使之可以同时接收username、password和number字段

java 复制代码
package com.gaohe.ssm1.vo;

import com.gaohe.ssm1.pojo.Emps;
import lombok.Data;


public class LoginVo extends Emps {
    private boolean number;

    public boolean getNumber() {
        return number;
    }

    public void setNumber(boolean number) {
        this.number = number;
    }
}

1.2.2 Controller层

主要实现以下几个功能:

  • 根据用户名查询数据库用户信息
  • 根据查到的用户信息进行登陆判断
  • 若number为true,需要将用户名和密码存入session,方便前端保存cookies
java 复制代码
package com.gaohe.ssm1.controller;

import com.gaohe.ssm1.common.R;
import com.gaohe.ssm1.pojo.Emps;
import com.gaohe.ssm1.service.EmpsService;
import com.gaohe.ssm1.vo.LoginVo;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/auth")
public class AuthController {
    @Autowired
    private EmpsService empsService;

    @PostMapping("/login")
    public R login(@RequestBody LoginVo loginVo, HttpSession session, HttpServletResponse response){
//        接收参数
//        System.out.println(loginVo.getNumber());
        String username = loginVo.getUsername();
        String password = loginVo.getPassword();
        Emps emp1 = empsService.findByUserName(username);
        if (emp1 == null){
            return R.fail("用户名不存在");
        }
        if (!emp1.getPassword().equals(password)){
//            System.out.println(password);
//            System.out.println(emp1.getPassword());
            return R.fail("密码输入错误");
        }
        boolean number = loginVo.getNumber();
        if (number){
            // 创建两个 Cookie,用于保存用户名和密码
            Cookie username1 = new Cookie("username1", emp1.getUsername());
            Cookie pass1 = new Cookie("pass1", emp1.getPassword());
            // 设置 Cookie 的最大生存时间为 7 天(单位:秒)
            username1.setMaxAge(60 * 60 * 24 * 7);
            pass1.setMaxAge(60 * 60 * 24 * 7);
            // 设置 Cookie 的路径为根路径,确保在整个应用中可用
            username1.setPath("/");
            pass1.setPath("/");
            // 将 Cookie 添加到响应对象中
            response.addCookie(username1);
            response.addCookie(pass1);
        }
        session.setAttribute("username",emp1.getUsername());
        return R.success("登录成功");
    }
}

1.2.3 mapper层

java 复制代码
package com.gaohe.ssm1.mapper;

import com.gaohe.ssm1.pojo.Emps;
import org.apache.ibatis.annotations.*;

import java.util.List;

@Mapper
public interface EmpsMapper {
    //    查询所有
    @Select("select * from emps")
    public List<Emps> list();

    //    通过id查询
    @Select("select * from emps where id = #{id}")
    public Emps findById(int id);
    //    通过name查询
    @Select("select * from emps where username = #{username}")
    public Emps findByUserName(String username);

    //    新增
    @Insert("insert into emps(id,username,password,addr,age,phone) " +
                    "values (null ,#{username},#{password},#{addr},#{age},#{phone})")
    public int save(Emps emps);

    //    修改
    @Update("update emps set username=#{username},password = #{password}," +
            "addr=#{addr},age=#{age},phone=#{phone} where id = #{id}")
    public int update(Emps emps);

    //    删除
    @Delete("delete from emps where id = #{id}")
    public int delete(int id);
}

1.2.4 service层

java 复制代码
package com.gaohe.ssm1.service;


import com.gaohe.ssm1.pojo.Emps;

import java.util.List;

public interface EmpsService {
    //    查询所有
    public List<Emps> list();

    //    通过id查询
    public Emps findById(int id);

    public Emps findByUserName(String username);

    //    新增
    public int save(Emps emps);

    //    修改
    public int update(Emps emps);

    //    删除
    public int delete(int id);
}

1.2.5 实现类

java 复制代码
    @Override
    public Emps findByUserName(String username) {
        return empsMapper.findByUserName(username);
    }

2. SpringMvc拦截器

2.1 概念

我们登录网站时经常会看到登陆成功后可以查询到页面信息,进行一系列操作,如果我们跳过登录直接访问网站是不可以的,这可以极大的保护信息安全,实现拦截可以用到springMvc拦截

Spring MVC拦截器(Interceptor)是基于Java反射机制和动态代理实现的,用于在请求处理的不同阶段进行拦截和处理的一种机制。它类似于Servlet中的Filter,但提供了更精细的控制能力。

拦截器的主要特点:

  • 基于AOP思想实现
  • 与Spring框架深度集成
  • 可以获取Spring容器中的Bean
  • 针对Handler(Controller方法)进行拦截

作用

  • 预处理(PreHandle)
    • 在Controller方法执行前拦截
    • 常用于:权限验证、参数校验、日志记录
  • 后处理(PostHandle)
    • 在Controller方法执行后,视图渲染前拦截
    • 常用于:修改ModelAndView、记录响应数据
  • 完成后处理(AfterCompletion)
    • 在整个请求完成后拦截(视图渲染完成)
    • 常用于:资源清理、性能监控

与Filter的区别

  1. 拦截器是Spring MVC框架层面的,Filter是Servlet规范层面的

  2. 拦截器可以获取Spring容器中的Bean,Filter不能

  3. 拦截器可以针对具体Controller方法,Filter只能针对URL

  4. 拦截器有更精细的生命周期控制(pre/post/complete)

拦截器通过实现HandlerInterceptor接口或继承HandlerInterceptorAdapter类来创建,然后在Spring MVC配置中注册

2.2 实战

  • LoginInterceptor 声明拦截器,扫描加载bean
java 复制代码
package com.gaohe.ssm1.interceptor;


import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.security.auth.login.LoginException;


/**
 * 登录拦截器,用于处理用户登录状态的验证
 * 该拦截器会在请求到达Controller之前进行预处理
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {
//    前置拦截
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle login interceptor");
//        1.获取路径 2.静态资源放行
//        3.登录就放行
        Object username = request.getSession().getAttribute("username");
        if (username != null){
            return true;
        }
//        4.没有登录 拦截
        response.getWriter().write("notlogin");
        true 拦截放行 false 拦截
        return false;
     //    throw new LoginException("notlogin");
    }

//    后置拦截
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle login interceptor");
    }

//    最终拦截
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion login interceptor");
    }
}
  • 添加拦截器设定拦截访问路径
java 复制代码
package com.gaohe.ssm1.config;

import com.gaohe.ssm1.interceptor.LoginInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
@Slf4j
public class SpringMvc implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
//        拦截下路径/pages/** 放行到 /pages/
        registry.addResourceHandler("/pages/**")
                .addResourceLocations("classpath:/pages/");
//        打印日志
        log.info("静态资源已经放行");
    }

//    拦截器配置
    @Autowired
    private LoginInterceptor loginInterceptor;

//    拦截路径配置
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor).addPathPatterns("/emps");
    }
}
  • 前端请求拦截

如果响应notlogin,前端则进行页面跳转,跳转到登录页面进行登录

java 复制代码
axios.interceptors.request.use(req => {
    return req;
})
axios.interceptors.response.use(resp => {
    if(resp.data == "notlogin") {
        alert("用户未登录")
        setTimeout(function () {
            window.location.href = "/pages/login.html"
        }, 2000)
    }
    return resp;
})

3. 异常捕获器

3.1 概念

Spring MVC异常捕获器是Spring框架提供的一套统一异常处理机制,主要用于集中处理Controller层抛出的各种异常。核心注解是@ExceptionHandler,它允许开发者在Controller内部或通过@ControllerAdvice全局定义异常处理方法。

3.2 主要组件

  1. @ExceptionHandler - 标注在方法上,定义处理特定异常的逻辑

  2. @ControllerAdvice - 配合@ExceptionHandler实现全局异常处理

  3. ResponseEntityExceptionHandler - Spring提供的默认异常处理基类

3.3 实战

那我们项目中比较常见的sql异常为例

定义全局异常处理器

@RestControllerAdvice是 Spring 框架中的一个组合注解,结合了 @ControllerAdvice 和 @ResponseBody 的功能,用于全局处理控制器(Controller)中抛出的异常,并统一返回结构化的响应数据。

@ExceptionHandler 是 Spring 框架中的一个注解,用于处理控制器(Controller)中抛出的异常。它通常用在方法上,表示该方法用于捕获和处理当前 Controller 中发生的特定异常。但是只能处理当前 Controller 类中抛出的异常。

java 复制代码
package com.gaohe.ssm1.handler;

import com.gaohe.ssm1.common.R;
import com.mysql.cj.jdbc.exceptions.MysqlDataTruncation;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.sql.SQLIntegrityConstraintViolationException;

/**
 * 全局异常处理器,用于捕获和处理数据库相关异常。
 * 结合Spring的@RestControllerAdvice注解,实现全局异常统一响应格式。
 */
@RestControllerAdvice
public class SQLHandler {
//    同名异常
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R ex2(SQLIntegrityConstraintViolationException e){
        String msg = e.getMessage();
        if (msg.contains("Duplicate entry")){
            String[] split = msg.split(" ");
            msg= split[2]+"用户名已存在";
        }
        return R.fail(msg);
    }

//    字段过长 异常
    @ExceptionHandler(MysqlDataTruncation.class)
    public R ex1(MysqlDataTruncation e){
        String msg = e.getMessage();
        if (msg.contains("Data too long")) {
            String[] split = msg.split(" ");
            if (split.length > 8) { // 确保数组长度足够避免越界
                msg = split[8] + "输入过长";
            }
        }
        return R.fail(e.getMessage());
    }

}

前端接收到后端异常信息,渲染到页面

  • 执行优先级
  1. 优先匹配当前Controller中的@ExceptionHandler

  2. 其次匹配@ControllerAdvice中定义的处理器

  3. 最后是Spring默认的异常处理机制

4.优化

学习异常捕获器之后,我们可以将SpringMvc拦截器的代码进行优化,创建一个登录的异常捕获类和异常捕获器用来捕获未登录的异常,从而给前端以响应,优化代码如下:

  • LoginException登录异常类
java 复制代码
package com.gaohe.ssm1.common;

public class LoginException extends RuntimeException{
    public LoginException(String message) {
        super(message);
    }
}
  • LoginHandler登陆异常捕获器
java 复制代码
package com.gaohe.ssm1.handler;

import com.gaohe.ssm1.common.LoginException;
import com.gaohe.ssm1.common.R;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class LoginHandler {

    @ExceptionHandler(LoginException.class)
    public R ex(LoginException e){
        System.out.println(e.getMessage());
        return R.fail(e.getMessage());
    }
}
  • LoginInterceptor登录拦截器,如果验证不通过则抛出异常
java 复制代码
package com.gaohe.ssm1.interceptor;


import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.security.auth.login.LoginException;


/**
 * 登录拦截器,用于处理用户登录状态的验证
 * 该拦截器会在请求到达Controller之前进行预处理
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {
//    前置拦截
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle login interceptor");
//        1.获取路径 2.静态资源放行
//        3.登录就放行
        Object username = request.getSession().getAttribute("username");
        if (username != null){
            return true;
        }
//        4.没有登录 拦截
//        response.getWriter().write("notlogin");
        true 拦截放行 false 拦截
//        return false;
        throw new LoginException("notlogin");
    }

//    后置拦截
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle login interceptor");
    }

//    最终拦截
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion login interceptor");
    }
}
  • 前端拦截器给用户做出回应
javascript 复制代码
axios.interceptors.request.use(req => {
    return req;
})
axios.interceptors.response.use(resp => {
    if(resp.data == "notlogin") {
        alert("用户未登录")
        setTimeout(function () {
            window.location.href = "/pages/login.html"
        }, 2000)
    }
    return resp;
})

大致流程给大家画了个图,方便大家理解

相关推荐
啾啾Fun17 分钟前
精粹汇总:大厂编程规范(持续更新)
后端·规范
yt9483226 分钟前
lua读取请求体
后端·python·flask
IT_102430 分钟前
springboot从零入门之接口测试!
java·开发语言·spring boot·后端·spring·lua
湖北二师的咸鱼1 小时前
c#和c++区别
java·c++·c#
weixin_418007601 小时前
软件工程的实践
java
汪子熙1 小时前
在 Word 里编写 Visual Basic 调用 DeepSeek API
后端·算法·架构
寻月隐君2 小时前
手把手教你用 Solana Token-2022 创建支持元数据的区块链代币
后端·web3·github
陌殇殇2 小时前
Hadoop 002 — HDFS常用命令及SpringBoot整合操作
hadoop·spring boot·hdfs
lpfasd1232 小时前
备忘录模式(Memento Pattern)
java·设计模式·备忘录模式
迢迢星万里灬2 小时前
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术点解析
java·spring boot·spring·mybatis·spring mvc·面试指南