Shiro + JWT + Spring Boot Restful 简易教程

Shiro + JWT + Spring Boot Restful 简易教程

GitHub 项目地址:github.com/Smith-Cruis… 。

原文地址:www.inlighting.org/archives/sp…。

序言

我也是半路出家的人,如果大家有什么好的意见或批评,请务必 issue 下。

如果想要直接体验,直接 clone 项目,运行 mvn spring-boot:run 命令即可进行访问。网址规则自行看教程后面。

如果想了解 Spring Security 可以看

Spring Boot 2.0+Srping Security+Thymeleaf的简易教程

Spring Boot 2 + Spring Security 5 + JWT 的单页应用Restful解决方案 (推荐)

特性

  • 完全使用了 Shiro 的注解配置,保持高度的灵活性。
  • 放弃 Cookie ,Session ,使用JWT进行鉴权,完全实现无状态鉴权。
  • JWT 密钥支持过期时间。
  • 对跨域提供支持。

准备工作

在开始本教程之前,请保证已经熟悉以下几点。

  • Spring Boot 基本语法,至少要懂得 ControllerRestControllerAutowired 等这些基本注释。其实看看官方的 Getting-Start 教程就差不多了。
  • JWT (Json Web Token)的基本概念,并且会简单操作JWT的 JAVA SDK。
  • Shiro 的基本操作,看下官方的 10 Minute Tutorial 即可。
  • 模拟 HTTP 请求工具,我使用的是 PostMan。

简要的说明下我们为什么要用 JWT ,因为我们要实现完全的前后端分离,所以不可能使用 sessioncookie 的方式进行鉴权,所以 JWT 就被派上了用场,你可以通过一个加密密钥来进行前后端的鉴权。

程序逻辑

  1. 我们 POST 用户名与密码到 /login 进行登入,如果成功返回一个加密 token,失败的话直接返回 401 错误。
  2. 之后用户访问每一个需要权限的网址请求必须在 header 中添加 Authorization 字段,例如 Authorization: tokentoken 为密钥。
  3. 后台会进行 token 的校验,如果有误会直接返回 401。

Token加密说明

  • 携带了 username 信息在 token 中。
  • 设定了过期时间。
  • 使用用户登入密码对 token 进行加密。

Token校验流程

  1. 获得 token 中携带的 username 信息。
  2. 进入数据库搜索这个用户,得到他的密码。
  3. 使用用户的密码来检验 token 是否正确。

准备Maven文件

新建一个 Maven 工程,添加相关的 dependencies。

复制代码<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.inlighting</groupId><artifactId>shiro-study</artifactId><version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.3.2</version></dependency><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.2.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>1.5.8.RELEASE</version></dependency></dependencies><build><plugins><!-- Srping Boot 打包工具 --><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>1.5.7.RELEASE</version><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin><!-- 指定JDK编译版本 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin></plugins></build>
</project>

注意指定JDK版本和编码。

构建简易的数据源

为了缩减教程的代码,我使用 HashMap 本地模拟了一个数据库,结构如下:

usernamepasswordrolepermission
smithsmith123userview
dannydanny123adminview,edit

这是一个最简单的用户权限表,如果想更加进一步了解,自行百度 RBAC。

之后再构建一个 UserService 来模拟数据库查询,并且把结果放到 UserBean 之中。

UserService.java

复制代码@Component
public class UserService {public UserBean getUser(String username) {// 没有此用户直接返回nullif (! DataSource.getData().containsKey(username))return null;UserBean user = new UserBean();Map<String, String> detail = DataSource.getData().get(username);user.setUsername(username);user.setPassword(detail.get("password"));user.setRole(detail.get("role"));user.setPermission(detail.get("permission"));return user;}
}

UserBean.java

复制代码public class UserBean {private String username;private String password;private String role;private String permission;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getRole() {return role;}public void setRole(String role) {this.role = role;}public String getPermission() {return permission;}public void setPermission(String permission) {this.permission = permission;}
}

配置 JWT

我们写一个简单的 JWT 加密,校验工具,并且使用用户自己的密码充当加密密钥,这样保证了 token 即使被他人截获也无法破解。并且我们在 token 中附带了 username 信息,并且设置密钥5分钟就会过期。

复制代码public class JWTUtil {// 过期时间5分钟private static final long EXPIRE_TIME = 5*60*1000;/*** 校验token是否正确* @param token 密钥* @param secret 用户的密码* @return 是否正确*/public static boolean verify(String token, String username, String secret) {try {Algorithm algorithm = Algorithm.HMAC256(secret);JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();DecodedJWT jwt = verifier.verify(token);return true;} catch (Exception exception) {return false;}}/*** 获得token中的信息无需secret解密也能获得* @return token中包含的用户名*/public static String getUsername(String token) {try {DecodedJWT jwt = JWT.decode(token);return jwt.getClaim("username").asString();} catch (JWTDecodeException e) {return null;}}/*** 生成签名,5min后过期* @param username 用户名* @param secret 用户的密码* @return 加密的token*/public static String sign(String username, String secret) {try {Date date = new Date(System.currentTimeMillis()+EXPIRE_TIME);Algorithm algorithm = Algorithm.HMAC256(secret);// 附带username信息return JWT.create().withClaim("username", username).withExpiresAt(date).sign(algorithm);} catch (UnsupportedEncodingException e) {return null;}}
}

构建URL

ResponseBean.java

既然想要实现 restful,那我们要保证每次返回的格式都是相同的,因此我建立了一个 ResponseBean 来统一返回的格式。

复制代码public class ResponseBean {// http 状态码private int code;// 返回信息private String msg;// 返回的数据private Object data;public ResponseBean(int code, String msg, Object data) {this.code = code;this.msg = msg;this.data = data;}public int getCode() {return code;}public void setCode(int code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public Object getData() {return data;}public void setData(Object data) {this.data = data;}
}

自定义异常

为了实现我自己能够手动抛出异常,我自己写了一个 UnauthorizedException.java

复制代码public class UnauthorizedException extends RuntimeException {public UnauthorizedException(String msg) {super(msg);}public UnauthorizedException() {super();}
}

URL结构

URL作用
/login登入
/article所有人都可以访问,但是用户与游客看到的内容不同
/require_auth登入的用户才可以进行访问
/require_roleadmin的角色用户才可以登入
/require_permission拥有view和edit权限的用户才可以访问

Controller

复制代码@RestController
public class WebController {private static final Logger LOGGER = LogManager.getLogger(WebController.class);private UserService userService;@Autowiredpublic void setService(UserService userService) {this.userService = userService;}@PostMapping("/login")public ResponseBean login(@RequestParam("username") String username,@RequestParam("password") String password) {UserBean userBean = userService.getUser(username);if (userBean.getPassword().equals(password)) {return new ResponseBean(200, "Login success", JWTUtil.sign(username, password));} else {throw new UnauthorizedException();}}@GetMapping("/article")public ResponseBean article() {Subject subject = SecurityUtils.getSubject();if (subject.isAuthenticated()) {return new ResponseBean(200, "You are already logged in", null);} else {return new ResponseBean(200, "You are guest", null);}}@GetMapping("/require_auth")@RequiresAuthenticationpublic ResponseBean requireAuth() {return new ResponseBean(200, "You are authenticated", null);}@GetMapping("/require_role")@RequiresRoles("admin")public ResponseBean requireRole() {return new ResponseBean(200, "You are visiting require_role", null);}@GetMapping("/require_permission")@RequiresPermissions(logical = Logical.AND, value = {"view", "edit"})public ResponseBean requirePermission() {return new ResponseBean(200, "You are visiting permission require edit,view", null);}@RequestMapping(path = "/401")@ResponseStatus(HttpStatus.UNAUTHORIZED)public ResponseBean unauthorized() {return new ResponseBean(401, "Unauthorized", null);}
}

处理框架异常

之前说过 restful 要统一返回的格式,所以我们也要全局处理 Spring Boot 的抛出异常。利用 @RestControllerAdvice 能很好的实现。

复制代码@RestControllerAdvice
public class ExceptionController {// 捕捉shiro的异常@ResponseStatus(HttpStatus.UNAUTHORIZED)@ExceptionHandler(ShiroException.class)public ResponseBean handle401(ShiroException e) {return new ResponseBean(401, e.getMessage(), null);}// 捕捉UnauthorizedException@ResponseStatus(HttpStatus.UNAUTHORIZED)@ExceptionHandler(UnauthorizedException.class)public ResponseBean handle401() {return new ResponseBean(401, "Unauthorized", null);}// 捕捉其他所有异常@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public ResponseBean globalException(HttpServletRequest request, Throwable ex) {return new ResponseBean(getStatus(request).value(), ex.getMessage(), null);}private HttpStatus getStatus(HttpServletRequest request) {Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");if (statusCode == null) {return HttpStatus.INTERNAL_SERVER_ERROR;}return HttpStatus.valueOf(statusCode);}
}

配置 Shiro

大家可以先看下官方的 Spring-Shiro 整合教程,有个初步的了解。不过既然我们用了 Spring-Boot,那我们肯定要争取零配置文件。

实现JWTToken

JWTToken 差不多就是 Shiro 用户名密码的载体。因为我们是前后端分离,服务器无需保存用户状态,所以不需要 RememberMe 这类功能,我们简单的实现下 AuthenticationToken 接口即可。因为 token 自己已经包含了用户名等信息,所以这里我就弄了一个字段。如果你喜欢钻研,可以看看官方的 UsernamePasswordToken 是如何实现的。

复制代码public class JWTToken implements AuthenticationToken {// 密钥private String token;public JWTToken(String token) {this.token = token;}@Overridepublic Object getPrincipal() {return token;}@Overridepublic Object getCredentials() {return token;}
}

实现Realm

realm 的用于处理用户是否合法的这一块,需要我们自己实现。

复制代码@Service
public class MyRealm extends AuthorizingRealm {private static final Logger LOGGER = LogManager.getLogger(MyRealm.class);private UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}/*** 大坑!,必须重写此方法,不然Shiro会报错*/@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof JWTToken;}/*** 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {String username = JWTUtil.getUsername(principals.toString());UserBean user = userService.getUser(username);SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();simpleAuthorizationInfo.addRole(user.getRole());Set<String> permission = new HashSet<>(Arrays.asList(user.getPermission().split(",")));simpleAuthorizationInfo.addStringPermissions(permission);return simpleAuthorizationInfo;}/*** 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {String token = (String) auth.getCredentials();// 解密获得username,用于和数据库进行对比String username = JWTUtil.getUsername(token);if (username == null) {throw new AuthenticationException("token invalid");}UserBean userBean = userService.getUser(username);if (userBean == null) {throw new AuthenticationException("User didn't existed!");}if (! JWTUtil.verify(token, username, userBean.getPassword())) {throw new AuthenticationException("Username or password error");}return new SimpleAuthenticationInfo(token, token, "my_realm");}
}

doGetAuthenticationInfo() 中用户可以自定义抛出很多异常,详情见文档。

重写 Filter

所有的请求都会先经过 Filter,所以我们继承官方的 BasicHttpAuthenticationFilter ,并且重写鉴权的方法。

代码的执行流程 preHandle -> isAccessAllowed -> isLoginAttempt -> executeLogin

复制代码public class JWTFilter extends BasicHttpAuthenticationFilter {private Logger LOGGER = LoggerFactory.getLogger(this.getClass());/*** 判断用户是否想要登入。* 检测header里面是否包含Authorization字段即可*/@Overrideprotected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {HttpServletRequest req = (HttpServletRequest) request;String authorization = req.getHeader("Authorization");return authorization != null;}/****/@Overrideprotected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpServletRequest = (HttpServletRequest) request;String authorization = httpServletRequest.getHeader("Authorization");JWTToken token = new JWTToken(authorization);// 提交给realm进行登入,如果错误他会抛出异常并被捕获getSubject(request, response).login(token);// 如果没有抛出异常则代表登入成功,返回truereturn true;}/*** 这里我们详细说明下为什么最终返回的都是true,即允许访问* 例如我们提供一个地址 GET /article* 登入用户和游客看到的内容是不同的* 如果在这里返回了false,请求会被直接拦截,用户看不到任何东西* 所以我们在这里返回true,Controller中可以通过 subject.isAuthenticated() 来判断用户是否登入* 如果有些资源只有登入用户才能访问,我们只需要在方法上面加上 @RequiresAuthentication 注解即可* 但是这样做有一个缺点,就是不能够对GET,POST等请求进行分别过滤鉴权(因为我们重写了官方的方法),但实际上对应用影响不大*/@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {if (isLoginAttempt(request, response)) {try {executeLogin(request, response);} catch (Exception e) {response401(request, response);}}return true;}/*** 对跨域提供支持*/@Overrideprotected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpServletRequest = (HttpServletRequest) request;HttpServletResponse httpServletResponse = (HttpServletResponse) response;httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {httpServletResponse.setStatus(HttpStatus.OK.value());return false;}return super.preHandle(request, response);}/*** 将非法请求跳转到 /401*/private void response401(ServletRequest req, ServletResponse resp) {try {HttpServletResponse httpServletResponse = (HttpServletResponse) resp;httpServletResponse.sendRedirect("/401");} catch (IOException e) {LOGGER.error(e.getMessage());}}
}

getSubject(request, response).login(token); 这一步就是提交给了 realm 进行处理。

配置Shiro

复制代码@Configuration
public class ShiroConfig {@Bean("securityManager")public DefaultWebSecurityManager getManager(MyRealm realm) {DefaultWebSecurityManager manager = new DefaultWebSecurityManager();// 使用自己的realmmanager.setRealm(realm);/** 关闭shiro自带的session,详情见文档* http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29*/DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();defaultSessionStorageEvaluator.setSessionStorageEnabled(false);subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);manager.setSubjectDAO(subjectDAO);return manager;}@Bean("shiroFilter")public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager) {ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();// 添加自己的过滤器并且取名为jwtMap<String, Filter> filterMap = new HashMap<>();filterMap.put("jwt", new JWTFilter());factoryBean.setFilters(filterMap);factoryBean.setSecurityManager(securityManager);factoryBean.setUnauthorizedUrl("/401");/** 自定义url规则* http://shiro.apache.org/web.html#urls-*/Map<String, String> filterRuleMap = new HashMap<>();// 所有请求通过我们自己的JWT FilterfilterRuleMap.put("/**", "jwt");// 访问401和404页面不通过我们的FilterfilterRuleMap.put("/401", "anon");factoryBean.setFilterChainDefinitionMap(filterRuleMap);return factoryBean;}/*** 下面的代码是添加注解支持*/@Bean@DependsOn("lifecycleBeanPostProcessor")public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();// 强制使用cglib,防止重复代理和可能引起代理出错的问题// https://zhuanlan.zhihu.com/p/29161098defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);return defaultAdvisorAutoProxyCreator;}@Beanpublic LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();advisor.setSecurityManager(securityManager);return advisor;}
}

里面 URL 规则自己参考文档即可 shiro.apache.org/web.html 。

总结

我就说下代码还有哪些可以进步的地方吧

  • 没有实现 Shiro 的 Cache 功能。
  • Shiro 中鉴权失败时不能够直接返回 401 信息,而是通过跳转到 /401 地址实现。

作者:Smith
链接:https://juejin.cn/post/6844903506535071758
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/629537.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【EI会议征稿通知】2024年第三届能源互联网及能源交互技术国际会议(EIEIT 2024)

2024年第三届能源互联网及能源交互技术国际会议(EIEIT 2024) 2024 3rd International Conference on the Energy Internet and Energy Interactive Technology 随着EIEIT前2届的成功举办&#xff0c;我们很荣幸地宣布&#xff0c;2024年第三届能源互联网及能源交互技术国际学术…

通过Vscode 简单创建一个vue3+element的项目

首先确保安装的nodejs是18版本以上 确保你安装了最新版本的 Node.js&#xff0c;并且你的当前工作目录正是打算创建项目的目录。在命令行中运行以下命令 VSCode打开终端 输入构建项目命令&#xff0c;个人推荐如果有cnpm使用cnpm npm create vuelatest cnpm create vuelate…

YOLOv5改进 | 检测头篇 | ASFFHead自适应空间特征融合检测头(全网首发)

一、本文介绍 本文给大家带来的改进机制是利用ASFF改进YOLOv5的检测头形成新的检测头Detect_ASFF,其主要创新是引入了一种自适应的空间特征融合方式,有效地过滤掉冲突信息,从而增强了尺度不变性。经过我的实验验证,修改后的检测头在所有的检测目标上均有大幅度的涨点效果,…

AI与区块链的完美交融创新时代的双重引擎

每个投资者都梦想早日进入“下一个亚马逊、苹果或比特币”&#xff0c;以追求代际财富。 然而&#xff0c;这些机会很少而且相距甚远&#xff0c;而且正如每一个虔诚的加密货币本地人都知道的那样&#xff0c;这条道路上常常布满了失败的项目、失信的承诺和波动。 但在 2023 …

uniapp 移动端app判断用户app版本是否是最新版(Android)

1.在uniapp项目中的App.vue文件下 <script>import { ref } from vue;const token ref();export default {onLaunch: function() {// #ifdef APP//获取打包时设置的版本号&#xff0c;然后存到storage里plus.runtime.getProperty(plus.runtime.appid, (wgtinfo) > {u…

用Python做数据分析之生成数据表

第一部分是生成数据表&#xff0c;常见的生成方法有两种&#xff0c;第一种是导入外部数据&#xff0c;第二种是直接写入数据。 Excel 中的文件菜单中提供了**外部数据的功能&#xff0c;支持数据库和文本文件和页面的多种数据源导入。 获取外部数据 python支持从多种类型的数…

mybtis动态SQL注解 脚本动态SQL\方法中构建SQL\SQL语句构造器

mybtis动态SQL注解 动态SQL注解脚本动态SQL方法中构建SQLSQL语句构造器 动态SQL注解 分类&#xff1a; 脚本动态SQL&#xff1a;XML配置方式的动态SQL&#xff0c;是用<script>的方式把它照搬过来&#xff0c;用注解来实现。适用于xml配置转换到注解配置方法中构建SQL&…

Hive数据导出的四种方法

hive数据仓库有多种数据导出方法&#xff0c;我在本篇文章中介绍下面的四种方法供大家参考&#xff1a;Insert语句导出、Hadoop命令导出、Hive shell命令导出、Export语句导出。 一、Insert语句导出 语法格式 Hive支持将select查询的结果导出成文件存放在文件系统中。语法格…

flink1.15 维表join guava cache和mysql方面优化

优化前 mysql响应慢,导致算子中数据输出追不上输入,导致显示cpu busy:100% 优化后效果两个图对应两个时刻: - - -- 优化前 select l.id,JSON_EXTRACT(r.msg,$$.key1) as msgv (select id,uid from tb1 l where id?) join (select uid,msg from tb2) r on l.uidr.uid;-- 优化…

STM32+HAL库驱动ADXL345传感器(SPI协议)

STM32HAL库驱动ADXL345传感器&#xff08;SPI协议&#xff09; ADXL345传感器简介实物STM32CubeMX配置SPI配置片选引脚配置串口配置 特别注意&#xff08;重点部分&#xff09;核心代码效果展示 ADXL345传感器简介 ADXL345 是 ADI 公司推出的基于 iMEMS 技术的 3 轴、数字输出加…

Flink(十三)【Flink SQL(上)】

前言 最近在假期实训&#xff0c;但是实在水的不行&#xff0c;三天要学完SSM&#xff0c;实在一言难尽&#xff0c;浪费那时间干什么呢。SSM 之前学了一半&#xff0c;等后面忙完了&#xff0c;再去好好重学一遍&#xff0c;毕竟这玩意真是面试必会的东西。 今天开始学习 Flin…

前端常见面试题之ajax、http

文章目录 一、手写ajax请求1. get2. post3. xhr.readyState4. xhr.status5. xhr.open 二、跨域三、cookie、localStorage和sessionStorage四、http1. http常见的状态码有哪些2. http常见的header有哪些3. 什么是RestfulAPI4. 描述一下http的缓存机制5. https 一、手写ajax请求 …

专业140+总410+哈尔滨工业大学803信号与系统和数字逻辑电路考研经验哈工大电子信息(信息与通信工程-信通)

一年的努力付出终于有了收获&#xff0c;今年专业课140&#xff0c;总分410顺利上岸哈工大803电子信息&#xff08;信息与通信-信通&#xff09;&#xff0c;回顾总结了自己这一年的复习&#xff0c;有得有失&#xff0c;希望对大家复习有所帮助。 数学 时间安排&#xff1a;…

Spring Boot中实现订单30分钟自动取消的策略

Spring Boot中实现订单30分钟自动取消的策略 简介 在电商和其他涉及到在线支付的应用中&#xff0c;通常需要实现一个功能&#xff1a;如果用户在生成订单后的一定时间内未完成支付&#xff0c;系统将自动取消该订单。本文将详细介绍基于Spring Boot框架实现订单30分钟内未支…

P9840 [ICPC2021 Nanjing R] Oops, It‘s Yesterday Twice More题解

[ICPC2021 Nanjing R] Oops, It’s Yesterday Twice More 传送门 题面翻译 有一张 n n n\times n nn 的网格图&#xff0c;每个格子上都有一只袋鼠。对于一只在 ( i , j ) (i,j) (i,j) 的袋鼠&#xff0c;有下面四个按钮&#xff1a; 按钮 U&#xff1a;如果 i > 1 …

4、python列表Lists

列表和你可以用它们做的事情。包括索引,切片和变异! 文章目录 1.列表1.1索引1.2切片1.3列表修改1.4列表函数1.5插曲:对象1.6列表方法1.6.1列表搜索1.7Tuples元组1.列表 Python 中的 List 表示有序的值序列: In [1]: primes = [2, 3, 5, 7]我们可以把其他类型的事情列入清…

“五星卡”上新!合合信息“外国人永久居留身份证”识别产品助力金融机构提升服务效率

外国人永久居留身份证&#xff08;简称“永居证”&#xff09;&#xff0c;是国家移民管理局对符合条件的外国人批准其在境内永久居留后&#xff0c;为其签发的法定身份证件。2023年12月&#xff0c;国家移民管理局正式启用签发更趋近于居民身份证技术体系的新一版永居证&#…

【Flutter 开发实战】Dart 基础篇:List 详解

嗨&#xff0c;各位朋友们&#xff0c;欢迎来到这篇博客&#xff01;今天我们将一起踏入 Dart 语言的神奇世界&#xff0c;深入了解 Dart 中的 List 类型。不用担心&#xff0c;我会尽可能用最通俗易懂的语言&#xff0c;让你对 List 有一个更深刻的理解。 Dart 中的 List Li…

SegVol: Universal and Interactive Volumetric Medical Image Segmentation

Abstract 精确的图像分割为临床研究提供了有意义且结构良好的信息。尽管在医学图像分割方面取得了显著的进展&#xff0c;但仍然缺乏一种能够分割广泛解剖类别且易于用户交互的基础分割模型。 本文提出了一种通用的交互式体医学图像分割模型——SegVol。通过对90k个未标记的C…

kibana查看和展示es数据

本文来说下使用kibana查看和展示es数据 文章目录 数据准备查询所有文档示例kibana查看和展示es数据 数据准备 可以使用es的命令或者java程序来往&#xff0c;es进行新增数据 查询所有文档示例 在 apifox 中&#xff0c;向 ES 服务器发 GET请求 &#xff1a;http://localhost:92…