23.<Spring图书管理系统(强制登录版本)>

在前面两篇,我们基本上实现了图书管理系统所有的功能,但是我们发现没有登录也能对其进行修改。这是非常不安全的。因此这篇文章我们学习如何进行强制登录。只有登录进去才能进行操作。

这不是一个对外开放的项目

这篇文章我们将改写图书管理系统为强制登录版本。我们需要结合在统一功能中讲解的

拦截器、统一数据返回、统一异常处理。内容完整的再写一遍图书管理系统

一、实现强制登录

1.1自定义拦截器

1.新建interceptor目录。创建LoginInterceptor类去实现HandlerInterceptor接口

注意要加上@Component注解,代表已经交给Spring去管理。这个类才会被Spring检测到

2.重写preHandle方法。返回 true-放行。false拦截

3.验证用户是否登录。

javascript 复制代码
//验证用户是否登录
        HttpSession session = request.getSession();
        UserInfo userInfo = (UserInfo) session.getAttribute(Constants.USER_SESSION_KEY);

        if(userInfo == null || userInfo.getId()<1){
            response.sendRedirect("/login.html");
            return false;
        }
        return true;
  • 如果没有登录则跳转到/login.html登录页面进行登录。return false
  • 如果已经登录那么直接return true。放行

1.2注册配置拦截器

1.新建Config目录,创建WebConfig类 实现 WebMvcConfigurer 接口

注意要加上@Configuration 才能生效。

2.新建我们刚刚自定义的拦截器成员变量

java 复制代码
    @Autowired
    private LoginInterceptor loginInterceptor;

3.重写addInterceptors方法。

addInterceptor:添加拦截器

addPathPatterns:添加拦截路径

excludePathPatterns:除了这些路径不用拦截

java 复制代码
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // /**表示对所有路径生效
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns(excludePath);
    }
java 复制代码
    private List<String> excludePath = Arrays.asList(
            "/user/login",
            "/**/login.html",
            "/css/**",
            "/js/**",
            "/pic/**"
    );

自此。我们就实现了强制登录。

完整LoginInterceptor类代码

javascript 复制代码
/**
 * 拦截器
 * 实现强制登录
 */
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 处理请求前执行。
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("目标方法执行前执行:LoginInterceptor.preHandle...");
        //true-放行。false拦截

        //验证用户是否登录
        HttpSession session = request.getSession();
        UserInfo userInfo = (UserInfo) session.getAttribute(Constants.USER_SESSION_KEY);

        if(userInfo == null || userInfo.getId()<1){
            response.sendRedirect("/login.html");
            return false;
        }
        return true;
    }

    /**
     * 接口处理完成之后执行的方法
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
}

完整WebConfig类代码

javascript 复制代码
package com.qiyangyang.springbook.demos.Config;

import com.qiyangyang.springbook.demos.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Arrays;
import java.util.List;

//配合拦截器进行拦截处理
//注意一定要加上@Configuration这个注解 才会生效
@Configuration
public class WebConfig implements WebMvcConfigurer {
    private List<String> excludePath = Arrays.asList(
            "/user/login",
            "/**/login.html",
            "/css/**",
            "/js/**",
            "/pic/**"
    );

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // /**表示对所有路径生效
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns(excludePath);
    }

    //    @Override
//    public void addInterceptors(InterceptorRegistry registry) {
//        // /** 表示对所有的路径生效
//        registry.addInterceptor(loginInterceptor).addPathPatterns("/**")
//                //排除一些路径
//                .excludePathPatterns("/user/login")
//                .excludePathPatterns("/**/**.html")
//                .excludePathPatterns("/css/**")
//                .excludePathPatterns("/js/**")
//                .excludePathPatterns("/pic/**");
//    }
}

二、实现统一返回数据

2.1创建Result类

统一返回数据

1.首先创建Result类,这个类帮我们更加细致化的对后端返回结果进行描述

java 复制代码
package com.qiyangyang.springbook.demos.model;

import lombok.Data;

/**
 * 定义统一返回数据
 * 对返回结果进行细致化描述
 * @param <T>
 */

@Data
public class Result <T>{
    private Integer code;//后端响应状态码,业务状态码 200成功,-1失败,-2表示未登录
    private String Errmsg; //后端发生错误的原因
    private T data; //返回的数据

    public static <T>Result<T> success(T data){
        Result<T> result = new Result<T>();
        result.setCode(200);
        result.setData(data);
        return  result;
    }

    public static <T>Result<T> fail(T data,String errmg){
        Result<T> result = new Result<T>();
        result.setCode(-1);
        result.setErrmsg(errmg);
        result.setData(data);
        return  result;
    }
    public static <T> Result<T> fail(String errMsg){
        Result<T> result = new Result<T>();
        result.setCode(-1);
        result.setErrmsg(errMsg);
        return result;
    }

    /**
     * 未登录时
     */
    public static <T> Result<T> unlogin(){
        Result<T> result = new Result<T>();
        result.setCode(-2);
        result.setErrmsg("用户未登录");
        return result;
    }

}

2.2创建ResponseAdvice类

创建ResponseAdvice并实现ResponseBodyAdvice接口

* 统一数据返回格式 * 不需要改变程序任何接口,只需要写一个类。实现ResponseBodyAdvice。加上@ControllerAdvice注解

java 复制代码
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {

    @Autowired
    private ObjectMapper objectMapper;
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        //重写supports方法。看对哪些请求进行处理。返回true--进行处理  返回false。不进行处理
        return true;
    }
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        //重写beforeBodyWrite方法。如果body是Result类型。不再进行包装。如果不是。那么就进行包装
        //接口执行完成之后,返回结果之前会执行这个方法。
        //注意需要处理String类型的
        if (body instanceof Result){
            return body;
        }
        if (body instanceof String){
            return objectMapper.writeValueAsString(Result.success(body));
        }
        return Result.success(body);
    }
}

//重写beforeBodyWrite方法。如果body是Result类型。不再进行包装。如果不是。那么就进行包装

//接口执行完成之后,返回结果之前会执行这个方法。(beforeBodyWrite)

//注意需要处理String类型的
此时我们会发现。我们之前定义的后端接口的返回结果已经不再适用。

例如:

我们进行用户登录:

发现返回的数据是这种json类型的

三、修改前端代码

登录页代码

将判断条件修改为

javascript 复制代码
if(result.code==200 && result.data ==true){
                        //验证成功
                        location.href = "book_list.html";
                    }else {
                        alert("登录失败,用户名不存在或密码错误!");
                    }

图书列表显示页代码

javascript 复制代码
var books = result.records;

修改为

javascript 复制代码
var books = result.data.records;
javascript 复制代码
 function getBookList() {
          //ajax页面一加载就会被执行的程序。 我们还可以进行方法封装
          $.ajax({
            type: "get",
            url: "/book/getListByPage" + location.search,
            success: function (result) {
              var books = result.data.records;
              console.log(books); //如果前端没有报错,那么我们打印日志。观察后端返回结果对不对
              var findHtml = "";  //用这个变量来拼接HTML
              for (var book of books) {
                //拼接html。假如后端返回10个tr那么直接for循环拼接在这里面。findHtml
                //我们用单引号拼接,因为里面有双引号

图书分页代码

javascript 复制代码
//翻页信息
              $("#pageContainer").jqPaginator({
                totalCounts: result.count, //总记录数
                pageSize: 10, //每页的个数
                visiblePages: 5, //可视页数
                currentPage: result.pageRequest.currentPage, //当前页码

修改为

javascript 复制代码
//翻页信息
              $("#pageContainer").jqPaginator({
                totalCounts: result.data.count, //总记录数
                pageSize: 10, //每页的个数
                visiblePages: 5, //可视页数
                currentPage: result.data.pageRequest.currentPage, //当前页码

前端添加图书代码

注意这里有一个坑

我们照上面修改

javascript 复制代码
          success: function (result) {
            console.log(result.data);
            if (result == "成功添加图书") {
              alert("添加成功");
              location.href = "book_list.html";
            } else {
              alert(result.data);
            }

修改为

javascript 复制代码
          success: function (result) {
            console.log(result.data);
            if (result.code ==200 && result.data == "成功添加图书") {
              alert("添加成功");
              location.href = "book_list.html";
            } else {
              alert(result.data);
            }

会发现添加图书功能依然完成不了。且会执行else块的代码

并且报错为undefind。

这是由于返回的数据是String类型。

而上面我们进行处理的后端返回数据都是json类型。如果是json类型。那么前端会自动转换为对象进行处理。

而Sring就不会进行处理。因此代码没有按我们的预期执行。

我们通过Postman和Fiddler抓包也能看到返回的是text类型文本。而不是json。
而我们登录接口返回的是json

两种解决方式

1.前端处理,把字符串转为对象

2.后端处理,设置 content-type 返回结果 (更加合理)

这种问题也称作边界问题。

后端处理我们可以进行统一返回Json。这样更加合理。

为什么String类型会出现这种情况。

这个就是在我们统一返回结果类中进行处理的。

java 复制代码
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        //重写beforeBodyWrite方法。如果body是Result类型。不再进行包装。如果不是。那么就进行包装
        //接口执行完成之后,返回结果之前会执行这个方法。
        //注意需要处理String类型的
        if (body instanceof Result){
            return body;
        }
        if (body instanceof String){
            return objectMapper.writeValueAsString(Result.success(body));
        }
        return Result.success(body);
    }

发现body如果是String类型。我们将这个body进行了writeValueAsString转成了String

因此我们将

java 复制代码
    @RequestMapping("/addBook")
    public String addBook(BookInfo bookInfo){
        log.info("添加图书,bookInfo:{}",bookInfo);
        /**

修改为

java 复制代码
    @RequestMapping(value = "/addBook",produces = "application/json")
    public String addBook(BookInfo bookInfo){
        log.info("添加图书,bookInfo:{}",bookInfo);
        /**

这样我们返回的数据就是Json格式的了。再试试添加图书,就会发现,我们添加成功
因此我们不用返回String类型的就可以避免这个问题了
正确的返回我们本就应该返回为Result类型。试着修改代码如下。

java 复制代码
    @RequestMapping( "/addBook")
    public Result addBook(BookInfo bookInfo){
        log.info("添加图书,bookInfo:{}",bookInfo);
        /**
         * 参数校验
         */
        if(!StringUtils.hasLength(bookInfo.getBookName())
                || !StringUtils.hasLength(bookInfo.getAuthor())
                || !StringUtils.hasLength(bookInfo.getPublish())
                || bookInfo.getCount() <=0
                || bookInfo.getPrice()==null){
            return Result.fail("参数错误!!!");
        }
        /**
         * 添加图书
         */
        /**
         * 出现错误比如参数错误。添加过程出现异常,因为MySQL会出现一些异常
         * 我们使用try/catch来捕获。
         */
        try {
            bookService.insertBook(bookInfo);
        }catch (Exception e){
            log.error("添加图书失败,e:{}",e);
            return Result.fail("内部发生错误,请联系管理员!");
        }
        /**
         * 返回成功添加吐过表示图书插入成功。
         */
        return Result.success("成功添加图书");
    }

修改图书代码

查询当前 Id 图书代码修改

javascript 复制代码
        //查询当前ID图书
        $.ajax({
            type: "get",
            url: "book/queryBookById"+location.search,
            success:function(book){
                if(book!=null){
                    $("#bookId").val(book.id);
                    $("#bookName").val(book.bookName);
                    $("#bookAuthor").val(book.author);
                    $("#bookStock").val(book.count);
                    $("#bookPrice").val(book.price);
                    $("#bookPublisher").val(book.publish);
                    $("#bookStatus").val(book.status);
                }
            }

修改为

javascript 复制代码
            type: "get",
            url: "book/queryBookById"+location.search,
            success:function(result){
                //前端根据后端返回结果,针对不同的情况进行处理
                if(result.code == 200 && result.data != null){
                    var book = result.data;
                    if(book!=null){
                        $("#bookId").val(book.id);
                        $("#bookName").val(book.bookName);
                        $("#bookAuthor").val(book.author);
                        $("#bookStock").val(book.count);
                        $("#bookPrice").val(book.price);
                        $("#bookPublisher").val(book.publish);
                        $("#bookStatus").val(book.status);
                    }
                }

修改更新图书代码

javascript 复制代码
                type: "get",
                url: "/book/updateBook",
                data:$("#updateBook").serialize(),//提交整个表单
                success:function(result){
                    if(result == true){
                        alert("更新成功");
                        location.href = "book_list.html"
                    }else{
                        alert("更新失败");
                    }

修改为

javascript 复制代码
                type: "get",
                url: "/book/updateBook",
                data:$("#updateBook").serialize(),//提交整个表单
                success:function(result){
                    if(result.code==200 && result.data == true){
                        alert("更新成功");
                        location.href = "book_list.html"
                    }else{
                        alert("更新失败");
                    }

删除图书代码

javascript 复制代码
                if(result == true){
                  alert("删除成功");
                  location.href = "book_list.html";
                }

修改为

javascript 复制代码
                if(result.code ==200 && result.data == true){
                  alert("删除成功");
                  location.href = "book_list.html";
                }

批量删除图书代码

javascript 复制代码
                if(result == true){
                  alert("批量删除成功");
                  location.href = "book_list.html";
                }else{
                  alert("删除失败,请联系管理员!");
                }

修改为

javascript 复制代码
                if(result.code ==200 && result.data == true){
                  alert("批量删除成功");
                  location.href = "book_list.html";
                }else{
                  alert("删除失败,请联系管理员!");
                }

四、统一异常处理

java 复制代码
@ControllerAdvice
public class ErrorHandler {
    /**
     * 捕获异常,返回统一结果
     */
    //通过@ExceptionHandler这个注解来捕获异常
    @ExceptionHandler
    public Result handler(Exception e){
        return Result.fail(e.getMessage());
    }
}
相关推荐
编程修仙10 分钟前
Vector和ArrayList的比较
java·开发语言
fat house cat_16 分钟前
MySQL是怎么解决幻读和不可重复读的?
java·数据库·mysql
程序猿麦小七1 小时前
springboot006基于SpringBoot的网上订餐系统(源码+包运行+LW+技术指导)
java·spring boot·后端·网上订餐
Daniel521-Spark1 小时前
三、谷粒商城- Spring Cloud Alibaba(3)
java·分布式·后端·spring cloud
《源码好优多》1 小时前
基于Java Springboot图书馆管理系统
java·开发语言·spring boot
查理不安生1 小时前
【Mac】未能完成该操作 Unable to locate a Java Runtime
java·开发语言·macos
蓝天星空1 小时前
基于springboot的用户管理系统
java·spring
乐鑫科技 Espressif1 小时前
“乐鑫组件注册表”简介
java·前端·microsoft·iot·乐鑫科技
小灰灰__2 小时前
Java通过calcite实时读取kafka中的数据
java·kafka·linq
java小吕布2 小时前
Java Servlet详解:Servlet的生命周期、请求处理与响应发送
java·开发语言·servlet