您的位置:首页 > 娱乐 > 明星 > 深圳设计公司集中在哪_互联网推广怎么找渠道_百度官方免费下载_关键词挖掘查询工具爱站网

深圳设计公司集中在哪_互联网推广怎么找渠道_百度官方免费下载_关键词挖掘查询工具爱站网

2025/4/17 21:22:28 来源:https://blog.csdn.net/2301_80854132/article/details/146768436  浏览:    关键词:深圳设计公司集中在哪_互联网推广怎么找渠道_百度官方免费下载_关键词挖掘查询工具爱站网
深圳设计公司集中在哪_互联网推广怎么找渠道_百度官方免费下载_关键词挖掘查询工具爱站网

找往期文章包括但不限于本期文章中不懂的知识点:

个人主页:我要学编程程(ಥ_ಥ)-CSDN博客

所属专栏:JavaEE

目录

前言

拦截器

基本使用

拦截器的路径配置

统一数据返回格式

统一异常处理


前言

在实际开发中,某些功能需要强制用户登录才能使用。例如,csdn与博客园的点赞、收藏、评论,甚至于经常使用的抖音,每次打开界面如果不登录的话,就是提示你要登录(登录之后,就可以记录用户的使用啥操作....让一切操作有迹可循)。面对这种强制登录的情况,该如何实现呢?

对应到代码的话,就是下面:

@RestController
@RequestMapping("/user")
@Slf4j
public class LoginController {/*** 登录接口* @return true-登录成功,false-登录失败*/@RequestMapping("/login")public boolean login(String username, String password) {// 检验参数是否正常if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {return false;}// 检验账号密码是否正确if ("admin".equals(username) && "123456".equals(password)) {return true;}return false;}/*** 测试不登录是否可以访问该接口* @return*/@RequestMapping("/testLogin")public boolean testLogin() {log.info("成功访问到testLogin接口");return true;}}

我们前面学习的是通过Cookie-Session机制来判断用户是否登录。先来回顾一下流程:客户端首次登录时,会将用户名和密码通过HTTP请求发送到服务器,服务器验证正确之后,就会将该用户信息存储到session中,服务器就会将该session存储到本地,同时将session-id存储到HTTP响应头的set-Cookie中,当客户端接收到该请求之后,就会将set-Cookie中的session-id存储到本地,后续再次请求时,就会将session-id存储到HTTP请求头的Cookie中,这样服务器拿到Cookie中session-id之后,就会去本地查找对应的session信息,如果找到并且session并未过期,就会认为用户已经登录;反之,则会让用户重新登录。

@RestController
@RequestMapping("/user")
@Slf4j
public class LoginController {/*** 登录接口* @return true-登录成功,false-登录失败*/@RequestMapping("/login")public boolean login(String username, String password, HttpSession session) {// 检验参数是否正常if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {return false;}// 检验账号密码是否正确if ("admin".equals(username) && "123456".equals(password)) {// 设置session信息,检验后续访问其他接口时,判断用户是否登录session.setAttribute("user", username);return true;}return false;}/*** 测试不登录是否可以访问该接口* @return*/@RequestMapping("/testLogin")public boolean testLogin(HttpServletRequest request) {// 检验用户是否登录HttpSession session = request.getSession(false);if (session != null && session.getAttribute("username") != null) {// 这里处理具体的逻辑log.info("成功访问到testLogin接口");return true;}return false;}}

 通过上述代码中的判断session信息是否正确的方式,可以来拦截一下并未登录的用户。

上述方式虽能正确处理强制用户登录的情况,但需要在每个接口的逻辑中都添加判断session信息是否存在,非常的麻烦。而今天,我们就来学习SpringBoot的统一功能处理。

拦截器

要实现上述统一拦截请求,并进行Session校验的流程就需要用到拦截器。那什么是拦截器呢?拦截器是Spring框架提供的核心功能之一,主要用来拦截用户的请求,在指定方法前后,根据业务需要执行预先设定的代码。也就是说,允许开发人员提前预定义一些逻辑,在用户的请求响应前后执行,也可以在用户请求前阻止其执行。在拦截器当中,开发人员可以在应用程序中做一些通用性的操作,比如通过拦截器来拦截前端发来的请求,判断Session中是否有登录用户的信息,如果有就可以放行,如果没有就进行拦截。这样就避免了在每个方法内部重复编码的操作。

基本使用

拦截器的使用分为两个步骤:

1、定义拦截器;

2、注册拦截器;

自定义拦截器:实现HandlerInterceptor接口,并重写所有方法。

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return HandlerInterceptor.super.preHandle(request, response, handler);}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}
}

注意:实现该接口之后,可以选择啥也不重写或者选择性重写部分方法。 

默认重写完成之后,就是上述代码。

preHandle方法的含义是目标方法执行前执行,如果该方法的返回值为true的话,就代表当前方法放行;反之,则代表当前方法被拦截,就不能到达Controller层的逻辑了。

postHandle方法的含义是目标方法执行后执行(也就是HTTP请求响应返回前执行)。

afterCompletion()方法:视图渲染完毕后执行,这是最后执行的,但由于现在都是前后端分离开发,因此这里无需了解。

注意:这里的目标方法就是HTTP请求的资源路径对应的方法(并不是完整的URL),也就是Controller层@RequestMapping所对应的value值。

接下来,就是注册配置拦截器:

@Configuration // 加上,是为了spring能够在初始化容器时,去执行下面的方法
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 注册拦截器并配置拦截路径(不拦截路径)LoginInterceptor loginInterceptor = new LoginInterceptor();// 方法需要的参数是拦截器对象// "/**" 表示拦截所有的请求registry.addInterceptor(loginInterceptor).addPathPatterns("/**");}
}

上述就是实现拦截器的基本框架,如果想要实现拦截未登录的用户的话,需要将 preHandle 方法内部的逻辑具体修改:

// 拦截器
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {// 父类默认返回的是true,即对拦截的请求都放行@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("目标方法执行前...");HttpSession session = request.getSession(false);// 检验用户是否登录,如果登录放行该请求;反之,则拦截改请求if (session != null && session.getAttribute("username") != null) {return true;}return false;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {log.info("目标方法执行后...");HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}
}

接下来,测试未登录的用户是否可以访问该方法:

由此看来,当我们注册并配置了拦截器之后,同时在拦截器中实现了具体要拦截的请求是啥样时,Spring会准确无误的帮我们拦截该请求。 

拦截器的路径配置

拦截器路径是指我们定义的拦截器,需要对哪些路径进行拦截。前面在注册拦截器时,我们是通过 addPathPatterns() 方法指定要拦截哪些路径下的请求,也可以通过 excludePathPatterns() 方法指定哪些请求不拦截。

拦截路径含义说明
/*一级路径只能拦截 /login,而不能拦截 /user/login
/**任意级路径能拦截 /login,  /user/login
/user/*/user 下的一级路径只能拦截 /user/login,而不能拦截 /user/login/123 
/user/**/user 下的任意级路径能拦截 /user/login,/user/login/123 

当拦截的是一级路径,我们来观察一级路径以及二级路径的访问情况:

@Slf4j
@RestController
@RequestMapping("/user")
public class LoginController {@RequestMapping("/login")public Boolean login(String username, String password, HttpSession session) {// 检验参数是否正常if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {return false;}// 判断参数是否正确if ("admin".equals(username) && "123456".equals(password)) {// 存储用户信息到sessionlog.info("用户登录成功...");session.setAttribute("username", username);return true;}return false;}@RequestMapping("/testLogin")public String testLogin(HttpServletRequest request) {log.info("用户已经访问该接口");// 检验用户是否登录HttpSession session = request.getSession(false);if (session != null && session.getAttribute("username") != null) {return "用户已经登录: "+session.getAttribute("username");}return "用户并未登录";}
}@RestController
@Slf4j
public class LoginController2 {@RequestMapping("/testLogin2")public String testLogin2(HttpServletRequest request) {log.info("用户已经成功访问该接口");// 检验用户是否登录HttpSession session = request.getSession(false);if (session != null && session.getAttribute("username") != null) {return "用户已经登录: "+session.getAttribute("username");}return "用户并未登录";}
}

上面是访问二级路径的情况,我们再来看访问一级路径:

我们会发现,如果只要是经过了 拦截器的方法 都会是打印出 目标方法执行前 这个日志的,这也从侧面体现出 打印日志 的重要性。 

至于其余的情况这里就不再演示了。

注意:如果我们在配置拦截器时,将任意级路径都拦截的话,不仅是后端接口会被拦截掉,连前端等静态资源也会被拦截。如下所示:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>测试静态资源是否会被拦截</title>
</head>
<body><div style="color: orangered;">测试静态资源是否会被拦截</div>
</body>
</html>

启动项目之后,通过浏览器访问到对应的地址,然而我们会发现页面信息的展示是乱码,如果我们再通过 vscode 去打开对应的页面信息,发现可以正常显示:

这是由于配置的拦截器信息对 静态资源 也进行了拦截(/*),在 Spring Boot 中处理静态资源(比如.html,·js,·css,·png 等)时,默认是由ResourceHttpRequestHandler 处理的。这个处理器会自动根据文件后缀设置对应的 Content-Type。虽然设置了 Content-Type: text/html,但并没有去设置编码信息,因此浏览器会根据系统语言来自动解析编码,我们的Windows系统语言默认为中文,因此解码使用的是 GBK,即出现了乱码的情况。

如果我们把编码信息手动设置为 UTF-8 或者不拦截所有的一级路径的话,最终页面就可以正常显示。

 接下来,继续访问:

因此得排除这些路径:

由于我们这里的静态资源只有 html 文件,因此只排出 html 文件,实际开发中,可能会出现多种静态资源文件。例如,html、css、js、jpg等。如果想要正常显示的话,都得进行排除。

统一数据返回格式

在前后端分离开发中,接口返回的数据格式设计至关重要。例如,对于登录接口,如果后端返回的数据格式不统一,前端在处理时会面临诸多不便。假设后端返回的数据格式为对象或JSON数据,前端需要通过对象的属性来判断登录是否成功。但如果后端直接返回布尔类型(Boolean),前端只需判断值是否为true即可。这仅仅是处理成功情况的对比。对于HTTP请求失败的情况,前端还需要根据后端的返回来处理错误,这也增加了前端的开发复杂性。在这种情况下,前端每次编写代码时都需要参考后端的具体实现,这无疑增加了开发成本和沟通成本。那么,前后端分离开发的意义何在呢?

因此,后端接口在返回数据时,必须设置一个统一的数据返回格式。这样,前端只需熟悉一次返回格式,即可高效地进行开发,大大减少重复劳动和沟通成本,真正发挥前后端分离开发的优势。

现在我们就可以对前面登录接口和测试登录接口来进行一个封装:

@Data
public class Result {ResultCode code; // 业务响应码String message; // 业务信息Object data; // 响应的具体数据public static Result success() {Result result = new Result();result.setCode(ResultCode.success);result.setMessage("success");return result;}public static Result success(Object data) {Result result = new Result();result.setCode(ResultCode.success);result.setMessage("success");result.setData(data);return result;}public static Result error(String message) {Result result = new Result();result.setCode(ResultCode.client_error);result.setMessage(message);return result;}public static Result error() {Result result = new Result();result.setCode(ResultCode.server_error);result.setMessage("服务开小差了,请稍后再试...");return result;}
}public enum ResultCode {success(200),client_error(400),server_error(500);private int code;private ResultCode(int code) {this.code = code;}
}

封装成上述对象之后,接口在返回数据时,就需要严格按照该对象来,这里就省略了。

接下来,测试上述封装是否成功(先把配置拦截器的代码注释掉):

如果要想把这里的code改成具体的数值的话,可以在 ResultCode 类中,加上下面的代码:

    @JsonValuepublic int getCode() {return code;}

这表明在序列化时,将枚举对象序列化为对应的数字。 这个注解的作用:把枚举对象序列化时,使用 getCode() 返回的值。

上面的方式就是对返回结果进行了一个封装,但还是不够完美。如果每个接口返回的结果都需要进行封装的话,那也是比较麻烦的,因此Spring也提供了类似拦截器这样的方式,来让我们可以对返回结果进行统一处理。

1、自定类实现 ResponseBodyAdvice,并重写父类的 supports 和 beforeBodyWrite 方法;

2、将 supports 方法的内容,改为返回 true,接着在 beforeBodyWrite 方法内,使用统一数据返回对象来封装 body对象,再返回即可。

@ControllerAdvice // 交由Spring管理
public class ResponseAdvice implements ResponseBodyAdvice {/**** @param returnType the return type* @param converterType the selected converter type* @return false 表示不处理,true 表示处理*/@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {// 这里同样也是和拦截器的处理方式一样,都是可以使用 if 语句来进行判断是否需要进行处理的// 为了先观察结果,我们先统一设置返回true,即表示都进行处理。// 这里的处理是指是否去执行下面的方法return true;}/**** @param body 表示返回的正文信息* @param returnType the return type of the controller method* @param selectedContentType the content type selected through content negotiation* @param selectedConverterType the converter type selected to write to the response* @param request the current request* @param response the current response* @return*/@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {// 这里的Body表示HTTP请求需要返回的正文信息// 如果需要对响应数据进行统一处理的话,也是在这里进行响应数据的封装return Result.success(body); // 对 body 进行封装}
}

接着再次启动项目,使用 Postman 去访问测试登录接口:

之所以会出现封装了两层的现象,是因为我们前面已经将登录接口的返回对象进行了封装,而这里再次对 body 进行了一层封装。因此,我们可以先将前面封装的结果修改为最原始的状态,再次启动项目,访问接口观察:

发现服务器发生错误,我们再去看另外一个接口:

具体封装失败的原因,需要我们去看错误日志:

那为什么会出现需要将 Result 类型转换为 String 类型的情况呢?这是因为Postman(前端)请求的方法的返回值为 String 类型,但经过统一数据格式的处理之后,实际的返回类型变为 Result 类型,Spring 看到方法的返回类型是String,就默认用 StringHttpMessageConverter 去处理,但却遇到了一个非字符串对象,于是报错。而我们要做的就是将 Result对象 转换为 String对象,怎么做呢?很简单,通过 Spring 内置的 Jackson 将 Result对象 序列化为 JSON字符串,这下就可以兼容了。

// 将 Result 对象序列化为 JSON数据
ObjectMapper objectMapper = new ObjectMapper();
try {return objectMapper.writeValueAsString(Result.success(body));
} catch (JsonProcessingException e) {throw new RuntimeException(e);
}

而当我们加上上述转换为 JSON 的处理之后,会导致所有的数据都会转换为 JSON,但实际上只需要 String 类型,因此我们可以先判断 是否为 String 类型的数据。 

 // 将 Result 对象序列化为 JSON数据if (body instanceof String) {ObjectMapper objectMapper = new ObjectMapper();try {return objectMapper.writeValueAsString(Result.success(body));} catch (JsonProcessingException e) {throw new RuntimeException(e);}}return Result.success(body); // 对 body 进行封装

注意:可能只有当方法的返回值为 String 时,才会出现上述情况,而当方法的返回值为 int 或者 Boolean等类型时,却不会出现上述情况呢?这是因为Spring在传输数据时,使用的JSON数据格式,其本质是字符串,因此当方法返回值为非 String 类型时,会自动转换为 JSON数据,Boolean类型或 int类型转换字符串,Result对象 转换为 JSON数据,在传输时,JSON数据和String类型的数据并无啥区别,但是对于方法的返回值为String类型的话,Spring就不会去转换为 JSON数据了,但是在准备传输时却发现不是String类型,于是强行转换为 String 就报错了。

统一异常处理

我们在编码时,对于可能抛出异常的代码需要手动进行 try-catch,但即使我们再细致,也可能会忽略某些异常的处理,倘若恰好这个异常信息直接导致了整个程序崩溃了,如果是在测试环境倒还好,就怕是发生在实际的生产环境中。因此,我们接下来要学习如何对异常进行统一处理。

自定类异常处理类,加上 @ControllerAdvice、@ResponseBody,定义异常处理方法,加上@ExceptionHandler,方法内部定义异常处理逻辑。

@ControllerAdvice // 交由Spring管理
@ResponseBody // 返回的具体数据,而不是页面
public class ExceptionAdvice {@ExceptionHandler // 处理异常public Result handleException(Exception e) {// 对所有的异常进行返回return Result.error(e.getMessage());}
}@RestController
@RequestMapping("/test")
public class TestController {/*** 下面是常见的异常,看是否可以捕获到*/// 除0异常@RequestMapping("/t1")public Integer test1() {int a = 10 / 0;return a;}// 数组越界异常@RequestMapping("/t2")public Boolean test2() {int[] array = new int[10];int a = array[11];return a > 0;}// 空指针异常@RequestMapping("/t3")public String test3() {int[] array = null;int len = array.length;return array.toString();}
}

接下来就需要对上述代码进行测试,观察是否捕获到了异常信息:

都是能够正常捕获的,这里就只给出 test1 方法的测试结果。

但在实际开发中,可以异常的处理也分的非常细致,需要我们对不同的异常信息,对前端或者直接打印在控制台的信息中,发送不同的错误信息。

@Slf4j
@ControllerAdvice // 交由Spring管理
@ResponseBody // 返回的具体数据,而不是页面
// 也可以使用 @RestControllerAdvice 代替上面两个注解
public class ExceptionAdvice {@ExceptionHandler // 处理异常public Result handleException(Exception e) {log.error(e.getMessage());return Result.error("服务器内部错误,请联系管理员...");}@ExceptionHandler // 处理异常public Result handleException(NullPointerException e) {log.error(e.getMessage());return Result.error("空指针异常,请联系管理员...");}@ExceptionHandler // 处理异常public Result handleException(ArrayIndexOutOfBoundsException e) {log.error(e.getMessage());return Result.error("数组越界异常,请联系管理员...");}
}

在捕获异常时,和我们前面学习的 try-catch 并不是一样的,try-catch的方式是从上往下依次去捕获异常信息,如果遇到了 父类的话,就直接走父类的处理流程了,用一句话概括的话,就是谁先catch到了,就走谁的流程,但是这里在捕获异常时,是优先去匹配与当前异常信息一致的异常,其次再是匹配度稍低的异常。如果实在是匹配不到的话,就会先把异常抛出去。因此一般我们都会定义一个顶级Exception来处理预料之外的情况。

在处理详细的异常时,有两种方式:
1、在参数列表中指定异常信息的种类;
2、为@ExceptionHandler注解的value赋值为具体的异常类

上述代码使用的是第一种方式,接下来,我们使用第二种方式:

@Slf4j
@ControllerAdvice // 交由Spring管理
@ResponseBody // 返回的具体数据,而不是页面
public class ExceptionAdvice {@ExceptionHandler(Exception.class) // 处理异常public Result handleException(Exception e) {log.error(e.getMessage());return Result.error("服务器内部错误,请联系管理员...");}@ExceptionHandler(NullPointerException.class) // 处理异常public Result handleException2(Exception e) {log.error(e.getMessage());return Result.error("空指针异常,请联系管理员...");}@ExceptionHandler(ArrayIndexOutOfBoundsException.class) // 处理异常public Result handleException(Exception e) {log.error(e.getMessage());return Result.error("数组越界异常,请联系管理员...");}}

注意:当通过注解的方式来表示是那个异常信息之后,参数列表内部的异常信息就会失效,也就是说Spring会优先根据注解去捕获异常信息,而不是参数列表。

好啦!本期 初始JavaEE篇 —— SpringBoot 统一功能处理 的学习之旅 就到此结束啦!我们下一期再一起学习吧!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com