《SpringBoot项目实战》第五篇—接口发生异常如何统一处理
系列文章导航
第一篇—接口参数的一些弯弯绕绕
第二篇—接口用户上下文的设计与实现
第三篇—留下用户调用接口的痕迹
第四篇—接口的权限控制
第五篇—接口发生异常如何统一处理
本文参考项目源码地址:summo-springboot-interface-demo
前言
大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。
作为一名从业已达六年的老码农,我的工作主要是开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。
BUG对于程序员来说实在是不陌生,当代码出现BUG时,异常也会随之出现,但BUG并不等于异常,BUG只是导致异常出现的一个原因。导致异常发生的原因非常多,本篇文章我也主要只讲一下接口相关的异常怎么处理。
一、接口异常的分类
在接口设计中,应该尽量避免使用异常来进行控制流程。接口应该尽可能返回明确的错误码和错误信息,而不是直接抛出异常。
1. 业务异常(Business Exception)
这是接口处理过程中可能出现的业务逻辑错误,例如参数校验失败、权限不足等。这些异常通常是预期
的,并且可以提供相应的错误码和错误信息给调用方。
2. 系统异常(System Exception)
这是接口处理过程中可能出现的非预期错误,例如数据库异常、网络异常等。这些异常通常是未知
的,并且可能导致接口无法正常响应。这种错误不仅需要记录异常信息通知系统管理员处理,还需要封装起来做好提示,不能直接把错误返回给用户。
3. 客户端异常(Client Exception)
这是调用方在使用接口时可能出现的错误,例如请求参数错误、请求超时等。这些异常通常是由于调用方的错误
导致的,接口本身没有问题。可以根据具体情况选择是否返回错误信息给调用方。
二、接口异常的常见处理办法
1. 异常捕获和处理
在接口的实现代码中,可以使用try-catch语句捕获异常,并进行相应的处理。可以选择将异常转化为合适的错误码和错误信息,然后返回给调用方。或者根据具体情况选择是否记录异常日志,并通知系统管理员进行处理。
2. 统一异常处理器
可以使用统一的异常处理器来统一处理接口异常。在Spring Boot中,可以使用@ControllerAdvice和@ExceptionHandler注解来定义一个全局的异常处理器。这样可以将所有接口抛出的异常统一处理,例如转化为特定的错误码和错误信息,并返回给调用方。
3. 抛出自定义异常
可以根据业务需求定义一些自定义的异常类,继承RuntimeException或其他合适的异常类,并在接口中抛出这些异常。这样可以在异常发生时,直接抛出异常,由上层调用方进行捕获和处理。
4. 返回错误码和错误信息
可以在接口中定义一套错误码和错误信息的规范,当发生异常时,返回对应的错误码和错误信息给调用方。这样调用方可以根据错误码进行相应的处理,例如展示错误信息给用户或者进行相应的逻辑处理。
例如这样的弹窗提示
5. 跳转到指定错误页
比如遇到401、404、500等错误时,SpringBoot框架会返回自带的错误页,在这里我们其实可以自己重写一些更美观、更友好的错误提示页,最好还能引导用户回到正确的操作上来,例如这样
而不是下面这样
三、接口异常的统一处理
通过前面两段我们可以发现,造成异常的原因很多,出现异常的地方很多,异常的处理手段也很多。基于以上三多的情况,我们需要一个地方来统一接收异常、统一处理异常,上面提到SpringBoot的@ControllerAdvice注解
作为一个全局的异常处理器来统一处理异常。但@ControllerAdvice注解
不是万能的,它有一个问题:
对于@ControllerAdvice注解来说,它主要用于处理Controller层的异常情况,即在控制器方法中发生的异常。因为它是基于Spring MVC的控制器层的异常处理机制。
而Filter层是位于控制器之前的一层过滤器,它可以用于对请求进行预处理和后处理。当请求进入Filter时,还没有进入到Controller层,所以@ControllerAdvice注解无法直接处理Filter层中的异常。
所以对于Filter中的异常,我们需要单独处理。
1. @ControllerAdvice全局异常处理器的使用
(1)自定义业务异常
由于SpringBoot框架并没有定义业务相关的错误码,所以我们需要自定义业务错误码。该错误码可以根据业务复杂程度进行分类,每个错误码对应一个具体的异常情况。这样前后端统一处理异常时可以根据错误码进行具体的处理逻辑,提高异常处理的准确性和效率。同时,定义错误码还可以方便进行异常监控和日志记录,便于排查和修复问题。
a、定义常见的异常状态码
ResponseCodeEnum.java
package com.summo.demo.model.response;public enum ResponseCodeEnum {/*** 请求成功*/SUCCESS("0000", ErrorLevels.DEFAULT, ErrorTypes.SYSTEM, "请求成功"),/*** 登录相关异常*/LOGIN_USER_INFO_CHECK("LOGIN-0001", ErrorLevels.INFO, ErrorTypes.BIZ, "用户信息错误"),/*** 权限相关异常*/NO_PERMISSIONS("PERM-0001", ErrorLevels.INFO, ErrorTypes.BIZ, "用户无权限"),/*** 业务相关异常*/BIZ_CHECK_FAIL("BIZ-0001", ErrorLevels.INFO, ErrorTypes.BIZ, "业务检查异常"),BIZ_STATUS_ILLEGAL("BIZ-0002", ErrorLevels.INFO, ErrorTypes.BIZ, "业务状态非法"),BIZ_QUERY_EMPTY("BIZ-0003", ErrorLevels.INFO, ErrorTypes.BIZ, "查询信息为空"),/*** 系统出错*/SYSTEM_EXCEPTION("SYS-0001", ErrorLevels.ERROR, ErrorTypes.SYSTEM, "系统出错啦,请稍后重试"),;/*** 枚举编码*/private final String code;/*** 错误级别*/private final String errorLevel;/*** 错误类型*/private final String errorType;/*** 描述说明*/private final String description;ResponseCodeEnum(String code, String errorLevel, String errorType, String description) {this.code = code;this.errorLevel = errorLevel;this.errorType = errorType;this.description = description;}public String getCode() {return code;}public String getErrorLevel() {return errorLevel;}public String getErrorType() {return errorType;}public String getDescription() {return description;}public static ResponseCodeEnum getByCode(Integer code) {for (ResponseCodeEnum value : values()) {if (value.getCode().equals(code)) {return value;}}return SYSTEM_EXCEPTION;}}
b、自定义业务异常类
BizException.java
package com.summo.demo.exception.biz;import com.summo.demo.model.response.ResponseCodeEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class BizException extends RuntimeException {/*** 错误码*/private ResponseCodeEnum errorCode;/*** 自定义错误信息*/private String errorMsg;}
(2) 全局异常处理器
BizGlobalExceptionHandler
package com.summo.demo.exception.handler;import javax.servlet.http.HttpServletResponse;import com.summo.demo.exception.biz.BizException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.ModelAndView;@RestControllerAdvice(basePackages = {"com.summo.demo.controller", "com.summo.demo.service"})
public class BizGlobalExceptionHandler {@ExceptionHandler(BizException.class)public ModelAndView handler(BizException ex, HttpServletResponse response) {ModelAndView modelAndView = new ModelAndView();switch (ex.getErrorCode()) {case LOGIN_USER_INFO_CHECK:// 重定向到登录页modelAndView.setViewName("redirect:/login");break;case NO_PERMISSIONS:// 设置错误信息和错误码modelAndView.addObject("errorMsg", ex.getErrorMsg());modelAndView.addObject("errorCode", ex.getErrorCode().getCode());modelAndView.setViewName("403");break;case BIZ_CHECK_FAIL:case BIZ_STATUS_ILLEGAL:case BIZ_QUERY_EMPTY:case SYSTEM_EXCEPTION:default:// 设置错误信息和错误码modelAndView.addObject("errorMsg", ex.getErrorMsg());modelAndView.addObject("errorCode", ex.getErrorCode().getCode());modelAndView.setViewName("error");}return modelAndView;}
}
(3) 测试效果
@RestControllerAdvice和@ExceptionHandler使用起来很简单,下面我们来测试一下(由于不写界面截图是在太丑,我麻烦ChatGPT帮我写了一套简单的界面
)。
a、普通业务异常捕获
第一步、打开登录页
访问链接:http://localhost:8080/login
输入账号、密码,点击登录进入首页
第二步、登录进入首页
第三步、调用一个会报错的接口
再服务启动之前我写了一个根据用户名查询用户的方法,如果查询不到用户的话我会抛出一个异常,代码如下:
public ResponseEntity<String> query(String userName) {//根据名称查询用户List<UserDO> list = userRepository.list(new QueryWrapper<UserDO>().lambda().like(UserDO::getUserName, userName));if (CollectionUtils.isEmpty(list)) {throw new BizException(ResponseCodeEnum.BIZ_QUERY_EMPTY, "根据用户名称查询用户为空!");}//返回数据return ResponseEntity.ok(JSONObject.toJSONString(list));
}
这时,我们查询一个不存在的用户
访问接口:http://localhost:8080/user/query?userName=sss
因为数据库中没有用户名为sss的这个用户,会抛出一个异常
b、403权限不足异常捕获
第一步、打开登录页
访问链接:http://localhost:8080/login
登录界面使用小B的账号登录
第二步、登录进入首页
第三步、调用删除用户的接口
调用接口:http://localhost:8080/user/delete?userId=2
由于小B的账号只有查询权限,没有删除权限,所以返回403错误页
注意👉🏻:在调试之前需要在application.yml或application.properties配置文件中增加一个配置:
server.error.whitelabel.enabled=false
这个配置的意思是是否启用默认的错误页面,这里我们自己写了一套错误页,所以不需要框架自带的配置了。
2. 自定义Filter中异常的处理
由于@ControllerAdvice注解无法捕获自定义Filter中抛出的异常,这里我们就需要使用另外一种方法进行处理:ErrorController接口。
(1) 原理解释
Spring Boot的ErrorController是一个接口,用于定义处理应用程序中发生的错误的自定义逻辑。它允许开发人员以更灵活的方式处理和响应异常,而不是依赖于默认的错误处理机制。:
- 定制错误页面:通过实现ErrorController接口,可以自定义应用程序的错误页面,以提供更好的用户体验。可以根据不同的异常类型和HTTP状态码提供不同的错误页面或错误信息。
- 记录错误日志:ErrorController可以用于捕获和记录应用程序中的异常,并将其记录到日志中。这对于问题追踪和排查非常有帮助,可以了解应用程序中发生的错误和异常的详细信息。
- 重定向或转发请求:通过ErrorController,可以根据错误的类型或其他条件,将请求重定向到不同的URL或转发到其他控制器方法。这对于根据错误情况做出不同的处理非常有用,例如重定向到自定义的错误页面或执行特定的错误处理逻辑。
(2) 使用方法
使用方法直接看看我的代码就知道了。
CustomErrorController.java
package com.summo.demo.controller;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;@Controller
public class CustomErrorController implements ErrorController {@RequestMapping("/error")public ModelAndView handleError(HttpServletRequest request, HttpServletResponse response) {//获取当前响应返回的状态码int statusCode = response.getStatus();//如果响应头中存在statusCode,则默认使用这个statusCodeif (StringUtils.isNotBlank(response.getHeader("statusCode"))) {statusCode = Integer.valueOf(response.getHeader("statusCode"));}if (statusCode == HttpServletResponse.SC_FOUND) {// 获取Location响应头的值,进行重定向String redirectLocation = response.getHeader("Location");return new ModelAndView("redirect:" + redirectLocation);} else if (statusCode == HttpServletResponse.SC_UNAUTHORIZED) {// 重定向到登录页return new ModelAndView("redirect:/login");} else if (statusCode == HttpServletResponse.SC_FORBIDDEN) {// 返回403页面return new ModelAndView("403");} else if (statusCode == HttpServletResponse.SC_NOT_FOUND) {// 返回404页面return new ModelAndView("404");} else if (statusCode == HttpServletResponse.SC_INTERNAL_SERVER_ERROR) {// 返回500页面,并传递errorMsg和errorCode到模板ModelAndView modelAndView = new ModelAndView("500");modelAndView.addObject("errorMsg", response.getHeader("errorMsg"));modelAndView.addObject("errorCode", response.getHeader("errorCode"));return modelAndView;} else {// 返回其他错误页面return new ModelAndView("error");}}}
细心的读者可能会看到,statusCode来自于两个地方,第一个是response.getStatus();第二个是response.getHeader(“statusCode”)。这两者的区别是第一个是框架自动设置的,第二个则是我根据业务逻辑设置的。
原因是在WebFilter中一旦抛出了异常,response.getStatus()一定会是500,即使这个异常是因为用户身份失效导致的。但异常又不得不抛出,所以我通过自定义response的header的方式设置了错误码,传递到/error接口。
(3) 测试效果
a、404错误页,接口找不到
第一步、打开登录页
访问链接:http://localhost:8080/login
输入账号、密码,点击登录进入首页
第二步、登录进入首页
第三步、访问一个不存在的页面
访问链接:http://localhost:8080/xxxx
由于xxxx接口没有被定义过,界面会返回404
b、401错误,用户身份标识为空或无效
这里我做的处理是,如果用户身份标识为空或无效那么我会默认跳转到登录页。
测试方法是打开一个无痕界面,随便输入一个链接:http://localhost:8080/user/query
由于Cookie中token不存在,所以我不管访问的是哪个链接,直接将状态码改为401,而CustomErrorController遇到401的错误,会默认重定向到登录页。
四、优化无痕窗口下的重新登录体验
Filter异常的全局处理除了ErrorController之外,还可以通过自定义拦截器的方式实现,这两个东西会一个就行了。这里我再说一个高级一点的东西,举个例子:
我在一个无痕窗口
调用接口:http://localhost:8080/user/query?userName=小B
因为当前窗口的Cookie中是没有token的,按照401错误的处理方式,我会重定向到登录页去。
但这个有一个问题:重新登录之后,进入的是首页,不是调用user/query接口,我还得重新去找这个接口,重新输入参数。而且这要是一个分享页那就尴尬了,登陆完不知道对方分享了啥,用户体验会很差,那么有办法优化这个问题吗?答案是有,如何做,继续看。
1. 在WebFilter中获取当前请求的全路径
所谓全路径就是“http://localhost:8080/user/query?userName=小B” ,如何获取,可以用我这个方法
/*** 获取完整的路径URL,包括参数** @param httpServletRequest* @return 路径URL
*/
private String getRequestURL(HttpServletRequest httpServletRequest) {String url = httpServletRequest.getRequestURL().toString();String query = httpServletRequest.getQueryString();if (query != null) {url += "?" + query;}return url;
}
2. 在WebFilter抛出401错误的地方设置httpServletResponse的header
如下
httpServletResponse.setHeader("redirectURL",URLEncoder.encode(getRequestURL(httpServletRequest), "utf-8"));
因为参数有可能是中文,这里需要用URLEncoder转下义。
3. 在CustomErrorController中获取到这个跳转链接
// 重定向到登录页或指定页面if (StringUtils.isNotBlank(response.getHeader("redirectURL"))) {return new ModelAndView("redirect:/login?redirectURL=" + response.getHeader("redirectURL"));}
效果如下
可以看到我们在login后面携带了一个redirectURL参数
4. 登录提交时将redirectURL参数一并提交
@PostMapping("/login")
public void userLogin(@RequestParam(required = true) String userName,@RequestParam(required = true) String password,@RequestParam(required = false) String redirectURL,HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse) {userService.login(userName, password, redirectURL, httpServletRequest, httpServletResponse);
}
5. 验证通过后重定向到redirectURL
try {//如果跳转路径不为空,则直接重定向到跳转路径if (StringUtils.isNotBlank(redirectURL)) {httpServletResponse.sendRedirect(redirectURL);return;}//跳转到登录页httpServletResponse.sendRedirect("/index");} catch (IOException e) {log.error("重定向发生异常", e);
}
以上就是这个问题的解决方案了,具体代码大家可以看我的demo:summo-springboot-interface-demo
相关文章:

《SpringBoot项目实战》第五篇—接口发生异常如何统一处理
系列文章导航 第一篇—接口参数的一些弯弯绕绕 第二篇—接口用户上下文的设计与实现 第三篇—留下用户调用接口的痕迹 第四篇—接口的权限控制 第五篇—接口发生异常如何统一处理 本文参考项目源码地址:summo-springboot-interface-demo 前言 大家好!…...

vue+golang上传微信头像
<button class"avatar" open-type"chooseAvatar" chooseavatar"onChooseAvatar"><image :src"avatarUrl" class"avatar-img"></image></button> // 微信头像修改onChooseAvatar(e) {this.uploadFil…...
JavaScript charCodeAt() 方法
charCodeAt() 方法是 JavaScript 字符串对象的一个方法,它用于返回给定位置的字符的 Unicode 编码值(整数)。Unicode 编码是一个标识字符的数字,它包含了世界上几乎所有字符的映射,包括常见字符、特殊字符和表情符号。…...

Talk | 纽约州立宾汉姆顿大学博士生丁琰:开放环境中机器人的任务与动作规划
本期为TechBeat人工智能社区第541期线上Talk。 北京时间10月26日(周四)20:00,纽约州立宾汉姆顿大学博士生—丁琰的Talk已准时在TechBeat人工智能社区开播! 他与大家分享的主题是: “开放环境中机器人的任务与动作规划”࿰…...

2023年Q3企业邮箱安全性报告:境内钓鱼邮件超过境外攻击
10月25日,Coremail邮件安全联合北京中睿天下信息技术有限公司发布《2023年第三季度企业邮箱安全性研究报告》。2023年第三季度企业邮箱安全呈现出何种态势?作为邮箱管理员,我们又该如何做好防护? 以下为精华版阅读,如需…...

WebSocket 原理揭秘:让你彻底搞懂 Websocket 原理
WebSocket 的原理 WebSocket 是什么? WebSocket 是一种新型的协议,它可以在客户端和服务器之间建立长连接,实现双向通信。在传统的 HTTP 协议中,当客户端向服务器发送请求后,服务器会返回响应,然后连接就…...
react中的函数式组件和类式组件
一、函数组件 1. 定义函数组件 在React中,函数组件(Functional Component)是一种通过纯粹的JavaScript函数定义的UI组件。函数组件采用函数的方式接收一个输入参数 props,并返回一个React元素或者一组React元素作为输出。定义函…...
Visual Studio 2022 设置 PySide6 扩展工具
前言 本人不想电脑上装一堆的IDE,所以把 Python 开发也交给了 Visual Studio,如果你不是用 Visual Studio 做 Python 开发,下文就不用看了。 PySide简介 PySide跟PyQt类似,都是支持Python的Qt包,不同的是,PyQt是第三方的,PySide是Qt官方的。 PySide的推出比PyQt晚很…...

【高效开发工具系列】Postman
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…...

汇编语言王爽第四版17.3完程可运行可调试
汇编语言王爽第四版17.3节完整程序,可调试,可运行。 最基本的字符串输入程序,具备以下功能: 1、在输入的同时需要显示这个字符串; 2、输入回车符后,一个字符串的输入结束; 3、能够删除已经输入…...

CH9329芯片应用—简介
概述 CH9329是一款串口转USB HID设备功能芯片,根据不同的工作模式,HID设备可以识别为:USB键盘设备、USB鼠标设备或者自定义HID类设备。接收串口数据,并自动根据串口工作模式进行数据解析,解析完成后按照HID类设备规范…...

mysql查看插入记录与查看mysql实时查询和插入速度
我真正关心的数据 比如一秒钟到底能插入多少行数据?慢查询有多少? 慢是一个相对概念,慢的绝对值时间是可以设置的,例如我设置long_query_time为10秒,那么但凡超过10秒的查询都可以认为是慢查询查询操作的超时时间mysql中系统变量什么意思?怎么查看系统变量? show varia…...

如何在VScode中让printf输出中文
如何在VScode中让printf输出中文? 1、在“Visual Studio Code”图标上右击,弹出对话框。见下图: 2、点击“以管理员身份运行”,得到下图: 3、点击“UTF-8”按钮,得到下图: 4、点击“通过编码重…...
qt hiRedis封装使用
qt Redis使用...

整理指定文件夹下的所有文件,以类树状图显示并生成对应超链接
最近在整理家里学习资料的时候,由于年代久远,找不到我想要找的文件,windows文件搜索速度感觉太慢。于是想要生成一份类似文件索引的东西来显示所有资料,让我可以快速的找到需要的资料路径 直接上代码 import os import datetim…...
解密代理技术:保障隐私与网络安全
在当今信息时代,网络代理技术是维护隐私和增强网络安全的关键工具。本文将深入研究Socks5代理、IP代理的应用,以及它们在网络安全、爬虫开发和HTTP协议中的关键作用。 引言 随着互联网的不断扩张,我们的在线活动变得日益复杂,也…...
k8s中,“deployment”充当什么角色?有什么功能?
在Kubernetes中,"Deployment"是一种控制器(Controller),它充当了以下主要角色和功能: 应用程序部署和管理: Deployment用于定义和管理应用程序的部署。它允许您指定应用程序的副本数(…...

深度学习:激活函数曲线总结
深度学习:激活函数曲线总结 在深度学习中有很多时候需要利用激活函数进行非线性处理,在搭建网路的时候也是非常重要的,为了更好的理解不同的激活函数的区别和差异,在这里做一个简单的总结,在pytorch中常用的激活函数的…...
Elasticsearch-06-Elasticsearch Java API Client
前言 简介 在 Elasticsearch7.15版本之后,Elasticsearch官方将它的高级客户端 RestHighLevelClient标记为弃用状态。同时推出了全新的 Java API客户端 Elasticsearch Java API Client,该客户端也将在 Elasticsearch8.0及以后版本中成为官方推荐使用的客…...

计算机网络第3章-运输层(2)
可靠数据传输原理 可靠数据传输依靠数据在一条可靠信道上进行传输。 TCP也正是依靠可靠信道进行传数据,从而数据不会被丢失。 而实现这种可靠数据传输服务是可靠数据传输协议的责任 构造可靠数据传输协议 1.经完全可靠信道的可靠数据传输:rdt1.0 在…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...

免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...