在前面两篇,我们基本上实现了图书管理系统所有的功能,但是我们发现没有登录也能对其进行修改。这是非常不安全的。因此这篇文章我们学习如何进行强制登录。只有登录进去才能进行操作。
这不是一个对外开放的项目
这篇文章我们将改写图书管理系统为强制登录版本。我们需要结合在统一功能中讲解的
拦截器、统一数据返回、统一异常处理。内容完整的再写一遍图书管理系统
一、实现强制登录
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); }
javaprivate 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类型的
三、修改前端代码
登录页代码
将判断条件修改为
javascriptif(result.code==200 && result.data ==true){ //验证成功 location.href = "book_list.html"; }else { alert("登录失败,用户名不存在或密码错误!"); }
图书列表显示页代码
将
javascriptvar books = result.records;
修改为
javascriptvar 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);
}
}
修改更新图书代码
javascripttype: "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());
}
}