Spring Security 如何进行权限验证
阅读本文之前,请投票支持这款 全新设计的脚手架 ,让 Java 再次伟大!
FilterSecurityInterceptor
FilterSecurityInterceptor 是负责权限验证的过滤器。一般来说,权限验证是一系列业务逻辑处理完成以后,最后需要解决的问题。所以默认情况下 security 会把和权限有关的过滤器,放在 VirtualFilter chains 的最后一位。
FilterSecurityInterceptor.doFilter 方法定义了两个权限验证逻辑。分别是 super.beforeInvocation 方法代表的权限前置验证与 super.afterInvocation 代表的后置权限验证。
public void invoke(FilterInvocation fi) throws IOException, ServletException {if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null) && observeOncePerRequest) {// filter already applied to this request and user wants us to observe// once-per-request handling, so don't re-do security checkingfi.getChain().doFilter(fi.getRequest(), fi.getResponse());} else {// first time this request being called, so perform security checkingif (fi.getRequest() != null && observeOncePerRequest) {fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);}InterceptorStatusToken token = super.beforeInvocation(fi);try {fi.getChain().doFilter(fi.getRequest(), fi.getResponse());}finally {super.finallyInvocation(token);}super.afterInvocation(token, null);}
}
和身份验证的设计类似,前置权限验证的业务处理也是参考模板方法设计模式的理念,将核心处理流程定义在父类(super) AbstractSecurityInterceptor 中的。
InterceptorStatusToken token = super.beforeInvocation(fi);
再通过关联 AccessDecisionManager 的方式,把身份验证通过的认证对象委托给 AccessDecisionManager 执行。
Authentication authenticated = authenticateIfRequired();
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);// Attempt authorization
try {this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,accessDeniedException));throw accessDeniedException;
}// Attempt to run as a different user
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,attributes);if (runAs == null) {if (debug) {logger.debug("RunAsManager did not change Authentication object");}// no further work post-invocationreturn new InterceptorStatusToken(SecurityContextHolder.getContext(), false,attributes, object);
}
else {if (debug) {logger.debug("Switching to RunAs Authentication: " + runAs);}SecurityContext origCtx = SecurityContextHolder.getContext();SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());SecurityContextHolder.getContext().setAuthentication(runAs);// need to revert to token.Authenticated post-invocationreturn new InterceptorStatusToken(origCtx, true, attributes, object);
}
在前置权限校验执行完毕后,会使用 this.runAsManager.buildRunAs 与 SecurityContextHolder.getContext().setAuthentication(runAs) 方法将当前上下文中的身份认证成功的对象备份,然后重新拷贝一个新的鉴权成功的对象到上下文的中。
// Attempt to run as a different user
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,attributes);SecurityContext origCtx = SecurityContextHolder.getContext();SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());SecurityContextHolder.getContext().setAuthentication(runAs);
这个处理初看会有点难以理解,但是仔细思考一下,这其实是一个友好且细腻的操作。
原因是 FilterSecurityInterceptor 在大部分情况下是最后一个默认过滤器,如果说还有后续过滤操作的话,那这些肯定是你的自定义的过滤器对象行为产生的。安全起见,security 将权限认证过后的 authenticated 对象拷贝了一份副本,方便用户在后续的自定义过滤器中使用。
即使自定义过滤器中不小心修改了全局上下文中的 authenticate 对象也无妨,因为 super.finallyInvocation 方法
finally {super.finallyInvocation(token);
}
会保证自定义过滤器全部执行完毕以后,再把 copy source 「归还」到上下文中。这样,在后续的业务处理中就不用担心获取到一个不安全地 Authentication 对象了。
PrePostAnnotationSecurityMetadataSource.getAttributes
在 security 中使用的几种鉴权注解一共有以下四种:
- PreFilter
- PreAuthorize
- PostFilter
- PostAuthorize
其中最常用的是 PreAuthorize,它的含义是在执行目标方法前执行鉴权逻辑。前置过滤器执行的第一个逻辑,就是使用 getAttributes 方法获取目标方法上的注解。
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {if (method.getDeclaringClass() == Object.class) {return Collections.emptyList();}logger.trace("Looking for Pre/Post annotations for method '" + method.getName()+ "' on target class '" + targetClass + "'");PreFilter preFilter = findAnnotation(method, targetClass, PreFilter.class);PreAuthorize preAuthorize = findAnnotation(method, targetClass,PreAuthorize.class);PostFilter postFilter = findAnnotation(method, targetClass, PostFilter.class);// TODO: Can we check for void methods and throw an exception here?PostAuthorize postAuthorize = findAnnotation(method, targetClass,PostAuthorize.class);if (preFilter == null && preAuthorize == null && postFilter == null&& postAuthorize == null) {// There is no meta-data so returnlogger.trace("No expression annotations found");return Collections.emptyList();}String preFilterAttribute = preFilter == null ? null : preFilter.value();String filterObject = preFilter == null ? null : preFilter.filterTarget();String preAuthorizeAttribute = preAuthorize == null ? null : preAuthorize.value();String postFilterAttribute = postFilter == null ? null : postFilter.value();String postAuthorizeAttribute = postAuthorize == null ? null : postAuthorize.value();ArrayList<ConfigAttribute> attrs = new ArrayList<>(2);PreInvocationAttribute pre = attributeFactory.createPreInvocationAttribute(preFilterAttribute, filterObject, preAuthorizeAttribute);if (pre != null) {attrs.add(pre);}PostInvocationAttribute post = attributeFactory.createPostInvocationAttribute(postFilterAttribute, postAuthorizeAttribute);if (post != null) {attrs.add(post);}attrs.trimToSize();return attrs;}
当通过 getAttributes 获取到对应的鉴权注解后,通过 createPreInvocationAttribute 将注解的 value——一般是 el 表达式,解析成 Express 对象后封装起来等待后续取用。
String preAuthorizeAttribute = preAuthorize == null ? null : preAuthorize.value();
PreInvocationAttribute pre = attributeFactory.createPreInvocationAttribute(preFilterAttribute, filterObject, preAuthorizeAttribute);
public PreInvocationAttribute createPreInvocationAttribute(String preFilterAttribute,String filterObject, String preAuthorizeAttribute) {try {// TODO: Optimization of permitAllExpressionParser parser = getParser();Expression preAuthorizeExpression = preAuthorizeAttribute == null ? parser.parseExpression("permitAll") : parser.parseExpression(preAuthorizeAttribute);Expression preFilterExpression = preFilterAttribute == null ? null : parser.parseExpression(preFilterAttribute);return new PreInvocationExpressionAttribute(preFilterExpression,filterObject, preAuthorizeExpression);}catch (ParseException e) {throw new IllegalArgumentException("Failed to parse expression '"+ e.getExpressionString() + "'", e);}}
AccessDecisionManager.decide -> AffirmativeBased.decide
说完了 getAttribute,让我们回到 beforeInvocation 方法的后续处理。你可能已经猜到了,和身份认证一样 filter 是不会实现实际的权限验证逻辑的。权限验证被交给了过滤器关联的 AccessDecisionManage.decide 方法来决定。
AccessDecisionManager 是一个接口,它的实现一共有 3 个类。AbstractAccessDecisionManager 是实现接口的抽象类,提供模板方法的定义。AffirmativeBased、ConsensusBased、UnanimousBased 是三个继承 AbstractAccessDecisionManager 的子类实现,分别代表了三种不同的记分策略。
以 AffirmativeBased 作为代表来说明的话,主要内容就是把 authentication 与封装的鉴权注解 el 表达式传递给关联的成员 decisionVoters 的 vote 方法进行处理。
public void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {int deny = 0;for (AccessDecisionVoter voter : getDecisionVoters()) {int result = voter.vote(authentication, object, configAttributes);if (logger.isDebugEnabled()) {logger.debug("Voter: " + voter + ", returned: " + result);}switch (result) {case AccessDecisionVoter.ACCESS_GRANTED:return;case AccessDecisionVoter.ACCESS_DENIED:deny++;break;default:break;}}if (deny > 0) {throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));}// To get this far, every AccessDecisionVoter abstainedcheckAllowIfAllAbstainDecisions();}
PreInvocationAuthorizationAdviceVoter.vote
和上面的架构设计一样, AccessDecisionVoter 的实现 PreInvocationAuthorizationAdviceVoter.vote 看起来像是要开始处理真正意义上的权限问题了,但是其实依然没到最核心的部分。
vote 方法会返回一个 preAdvice.before 的结果,这个结果会被转换成 ACCESS_GRANTED : ACCESS_DENIED 这两个数字。为什么 preAdvice.before 的调用不直接返回 boolean 类型的鉴权结果?想想之前的 AffirmativeBased.decide 方法的设计思路:根据多个不同的 voter 返回的结果,我们可以使用不同的投票策略来对分数进行判定。返回一个数字,而不是 boolean 更便于计算最终得分。
public int vote(Authentication authentication, MethodInvocation method,Collection<ConfigAttribute> attributes) {// Find prefilter and preauth (or combined) attributes// if both null, abstain// else call advice with themPreInvocationAttribute preAttr = findPreInvocationAttribute(attributes);if (preAttr == null) {// No expression based metadata, so abstainreturn ACCESS_ABSTAIN;}boolean allowed = preAdvice.before(authentication, method, preAttr);return allowed ? ACCESS_GRANTED : ACCESS_DENIED;}
ExpressionBasedPreInvocationAdvice.before
这次我向你保证,ExpressionBasedPreInvocationAdvice.before 真的是专门负责判定权限校验是否通过的方法了。由于之前已经封装好了 el 表达式的 Express 对象,所以调用 ExpressionUtils.evaluateAsBoolean 就可以直接根据表达式与执行上下文 ctx -> WebSecurityExpressionRoot extends SecurityExpressionRoot 使用 getValue() 方法获取执行结果——即权限校验的结果。
public boolean before(Authentication authentication, MethodInvocation mi,PreInvocationAttribute attr) {PreInvocationExpressionAttribute preAttr = (PreInvocationExpressionAttribute) attr;EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,mi);Expression preFilter = preAttr.getFilterExpression();Expression preAuthorize = preAttr.getAuthorizeExpression();if (preFilter != null) {Object filterTarget = findFilterTarget(preAttr.getFilterTarget(), ctx, mi);expressionHandler.filter(filterTarget, preFilter, ctx);}if (preAuthorize == null) {return true;}return ExpressionUtils.evaluateAsBoolean(preAuthorize, ctx);}
举一反三一下,既然鉴权逻辑就是一个运行时的 el 表达式解析后的结果,那利用 el 表达式可以调用类方法的特性,是不是自定义一个专门的表达式处理程序就可以随心所欲地返回鉴权结果了呢?
自定义 EL 表达式
/*** sysdomain + edit permission*/public static final String SYSDOMAIN_AND_EDITPERMISSION = SYSDOMAIN + " && " + EDIT_PERMISSION;public static final String EDIT_PERMISSION_MENU = "@authorizeAppService.hasPermission('EDIT_PERMISSION_MENU')";
自定义 EL 表达式处理方法
public class AuthorizeAppService implements AuthorizeAppInterface {@Overridepublic boolean hasDomain(String domain) throws IllegalArgumentException {if (StringUtils.isEmpty(domain)) {throw new IllegalArgumentException("hasDomain accepted a invalid express parameter");}AuthUser user = (AuthUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();return StringUtils.equals(domain, user.getDomain().getCode());}@Overridepublic boolean hasRole(String... role) throws IllegalArgumentException {if (ArrayUtils.isEmpty(role)) {throw new IllegalArgumentException("hasRole accepted a invalid express parameter\"");}AuthUser user = (AuthUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();return user.getAuthRoles().stream().map(AuthRole::getCode).collect(Collectors.toList()).containsAll(CollectionUtils.arrayToList(role));}@Overridepublic boolean hasPermission(String... permission) throws IllegalArgumentException {if (ArrayUtils.isEmpty(permission)) {throw new IllegalArgumentException("hasPermission accepted a invalid express parameter\"");}AuthUser user = (AuthUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();Set<String> permissions = user.getAllpermissions().stream().map(AuthPermission::getCode).collect(Collectors.toSet());return permissions.containsAll(CollectionUtils.arrayToList(permission));}
}
最后,我们来探讨一下 security 为什么要费尽周折,通过一长串的聚合和依赖把权限校验这个处理流程传递这么长的路径来实现?为什么不在一开始就是 make el Express and return el.value() 搞定所有的事情?
我想这是因为 security 是一个 architect first 的设计产物。架构优先的设计,高扩展性都特别的重要。这一系列的聚合和依赖,都是「用组合解决问题」思想的体现。尽量用组合来解决问题会带来了很多好处,比如通过 GlobalMethodSecurityConfiguration 类进行的高度可配置化。
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class GlobalMethodSecurityConfiguration
implements ImportAware, SmartInitializingSingleton, BeanFactoryAware{@Beanpublic MethodSecurityMetadataSource methodSecurityMetadataSource() {List<MethodSecurityMetadataSource> sources = new ArrayList<>();ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(getExpressionHandler());MethodSecurityMetadataSource customMethodSecurityMetadataSource = customMethodSecurityMetadataSource();if (customMethodSecurityMetadataSource != null) {sources.add(customMethodSecurityMetadataSource);}boolean hasCustom = customMethodSecurityMetadataSource != null;boolean isPrePostEnabled = prePostEnabled();boolean isSecuredEnabled = securedEnabled();boolean isJsr250Enabled = jsr250Enabled();if (!isPrePostEnabled && !isSecuredEnabled && !isJsr250Enabled && !hasCustom) {throw new IllegalStateException("In the composition of all global method configuration, " +"no annotation support was actually activated");}if (isPrePostEnabled) {sources.add(new PrePostAnnotationSecurityMetadataSource(attributeFactory));}if (isSecuredEnabled) {sources.add(new SecuredAnnotationSecurityMetadataSource());}if (isJsr250Enabled) {GrantedAuthorityDefaults grantedAuthorityDefaults =getSingleBeanOrNull(GrantedAuthorityDefaults.class);Jsr250MethodSecurityMetadataSource jsr250MethodSecurityMetadataSource = this.context.getBean(Jsr250MethodSecurityMetadataSource.class);if (grantedAuthorityDefaults != null) {jsr250MethodSecurityMetadataSource.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());}sources.add(jsr250MethodSecurityMetadataSource);}return new DelegatingMethodSecurityMetadataSource(sources);}}
异常处理
voter 与 advicer 只做他们的份内事——对鉴权结果投票并返回。不同的得分策略,决定了同样的分数在不同的策略下,可能会有不同的最终结果。
所以 AffirmativeBased.decide 如果得出了鉴权失败的结论的话就会抛出一个 AccessDeniedException 异常。这个异常会一直沿着调用链往上抛,直到抛到 FilterSecurityIntercptor 的调用者,ExceptionTranslationFilter 中。
if (deny > 0) {throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));}
ExceptionTranslationFilter 过滤器专门处理和异常相关的事情。它的 doFilter 中定义的 AccessDeniedException 捕获处理会将异常处理交由 accessDeniedHandler.handle 处理。同时得益于组合的设计思想,accessDeniedHandler.handle 也是可配置化的。
回忆之前的自定义身份验证异常处理类 GlobalExceptionHandler。现在只要增加一个新的接口实现并重写 handle 方法,就可以在一个类里面处理身份验证和鉴权异常了。
private void handleSpringSecurityException(HttpServletRequest request,HttpServletResponse response, FilterChain chain, RuntimeException exception)
throws IOException, ServletException {if (exception instanceof AuthenticationException) {logger.debug("Authentication exception occurred; redirecting to authentication entry point",exception);sendStartAuthentication(request, response, chain,(AuthenticationException) exception);}else if (exception instanceof AccessDeniedException) {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {logger.debug("Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",exception);sendStartAuthentication(request,response,chain,new InsufficientAuthenticationException(messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication","Full authentication is required to access this resource")));}else {logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler",exception);accessDeniedHandler.handle(request, response,(AccessDeniedException) exception);}}
}
@RestControllerAdvice
public class GlobalExceptionHandler implements AuthenticationEntryPoint, AccessDeniedHandler {@Overridepublic void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {log.error("spring security 认证发生异常。", e);HttpResponseWriter.sendError(httpServletResponse, HttpServletResponse.SC_UNAUTHORIZED, "身份认证失败");}@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException {log.error("spring security 鉴权发生异常。", e);HttpResponseWriter.sendError(httpServletResponse, HttpServletResponse.SC_FORBIDDEN, "鉴权失败");}
}
相关文章:
Spring Security 如何进行权限验证
阅读本文之前,请投票支持这款 全新设计的脚手架 ,让 Java 再次伟大! FilterSecurityInterceptor FilterSecurityInterceptor 是负责权限验证的过滤器。一般来说,权限验证是一系列业务逻辑处理完成以后,最后需要解决的…...
计算机砖头书的学习建议
纸上得来终觉浅,绝知此事要躬行,技术来源于实践,光看不练意义不大,过阵子全忘记,并且没有实践来深化理论认知。 “砖头书”通常指的是那些厚重、内容详实且权威的书籍,对于计算机科学领域而言,…...
我与C语言二周目邂逅vlog——7.预处理
C语言预处理详解 C语言预处理是编译过程中的重要组成部分,用于对源代码进行文本替换和修改。预处理发生在编译的前期,通过特定的指令来控制代码的编译行为,最终生成可以交给编译器进行进一步处理的代码。预处理的目的是简化代码编写…...
Python无监督学习中的聚类:K均值与层次聚类实现详解
📘 Python无监督学习中的聚类:K均值与层次聚类实现详解 无监督学习是一类强大的算法,能够在没有标签的数据集中发现结构与模式。聚类作为无监督学习的重要组成部分,在各类数据分析任务中广泛应用。本文将深入讲解聚类算法中的两种…...
C++ 中 new 和 delete 详解,以及与 C 中 malloc 和 free 的区别
1. C 中 new 和 delete 的基本用法 在 C 中,new 和 delete 是用来动态分配和释放内存的关键字,它们是面向对象的替代方式,提供了比 C 语言更优雅的内存管理工具。 1.1 new 的使用 new 用于从堆中分配内存,并且自动调用对象的构造…...
YOLOv11来了 | 自定义目标检测
概述 YOLO11 在 2024 年 9 月 27 日的 YOLO Vision 2024 活动中宣布:https://www.youtube.com/watch?vrfI5vOo3-_A。 YOLO11 是 Ultralytics YOLO 系列的最新版本,结合了尖端的准确性、速度和效率,用于目标检测、分割、分类、定向边界框和…...
Vue3 集成Monaco Editor编辑器
Vue3 集成Monaco Editor编辑器 1. 安装依赖2. 使用3. 效果 Monaco Editor (官方链接 https://microsoft.github.io/monaco-editor/)是一个由微软开发的功能强大的在线代码编辑器,被广泛应用于各种 Web 开发场景中。以下是对 Monaco Editor 的…...
一文详解Mysql索引
背景 索引是存储引擎用于快速找到一条记录的数据结构。索引对良好的性能非常关键。尤其是当表中的数据量越来越大时,索引对性能的影响愈发重要。接下来,就来详细探索一下索引。 索引是什么 索引(Index)是帮助数据库高效获取数据的…...
基于JAVA+SpringBoot+Vue的旅游管理系统
基于JAVASpringBootVue的旅游管理系统 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末附源码下载链接🍅 哈喽兄…...
STM32_实验3_控制RGB灯
HAL_Delay 是 STM32 HAL 库中的一个函数,用于在程序中产生一个指定时间的延迟。这个函数是基于系统滴答定时器(SysTick)来实现的,因此可以实现毫秒级的延迟。 void HAL_Delay(uint32_t Delay); 配置引脚: 点击 1 到 IO…...
RISC-V笔记——Pipeline依赖
1. 前言 RISC-V的RVWMO模型主要包含了preserved program order、load value axiom、atomicity axiom、progress axiom和I/O Ordering。今天主要记录下preserved program order(保留程序顺序)中的Pipeline Dependencies(Pipeline依赖)。 2. Pipeline依赖 Pipeline依赖指的是&a…...
构建后端为etcd的CoreDNS的容器集群(六)、编写自动维护域名记录的代码脚本
本文为系列测试文章,拟基于自签名证书认证的etcd容器来构建coredns域名解析系统。 一、前置文章 构建后端为etcd的CoreDNS的容器集群(一)、生成自签名证书 构建后端为etcd的CoreDNS的容器集群(二)、下载最新的etcd容…...
Leetcode 剑指 Offer II 098.不同路径
题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer(专项突击版)系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 一个机器人位于一个 m x n 网格的左上角 (起始点在下…...
LabVIEW智能螺杆空压机测试系统
基于LabVIEW软件开发的螺杆空压机测试系统利用虚拟仪器技术进行空压机的性能测试和监控。系统能够实现对螺杆空压机关键性能参数如压力、温度、流量、转速及功率的实时采集与分析,有效提高测试效率与准确性,同时减少人工操作,提升安全性。 项…...
在 Ubuntu 22.04 上安装 PHP 8.2
在 Ubuntu 22.04 上安装 PHP 8.2,可以按照以下步骤进行: 更新系统软件包: 首先,确保你的系统软件包是最新的。 sudo apt update sudo apt upgrade 安装 PHP PPA(Personal Package Archive): U…...
Java生死簿管理小系统(简单实现)
学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……) 2、学会Oracle数据库入门到入土用法(创作中……) 3、手把手教你开发炫酷的vbs脚本制作(完善中……) 4、牛逼哄哄的 IDEA编程利器技巧(编写中……) 5、面经吐血整理的 面试技…...
【VoceChat】一个即时聊天(IM)软件,又是一个可以嵌入任何网页聊天系统
为什么要搭建私人聊天软件 在当今数字化时代,聊天软件已经成为人们日常沟通和协作的重要工具。市面上的公共聊天平台虽然方便,但也伴随着诸多隐私、安全、广告和功能限制的问题。对于那些注重数据安全、追求高效沟通的个人或团队来说,搭建一…...
【LeetCode】动态规划—96. 不同的二叉搜索树(附完整Python/C++代码)
动态规划—96. 不同的二叉搜索树 题目描述前言基本思路1. 问题定义2. 理解问题和递推关系二叉搜索树的性质:核心思路:状态定义:状态转移方程:边界条件: 3. 解决方法动态规划方法:伪代码: 4. 进一…...
Nginx UI 一个可以管理Nginx的图形化界面工具
Nginx UI 是一个基于 Web 的图形界面管理工具,支持对 Nginx 的各项配置和状态进行直观的操作和监控。 Nginx UI 的功能非常丰富: 在线查看服务器 CPU、内存、系统负载、磁盘使用率等指标 在线 ChatGPT 助理 一键申请和自动续签 Let’s encrypt 证书 在…...
Vue向上滚动加载数据时防止内容闪动
目前的需求:当前组件向上滚动加载数据,dom加载完后,页面的元素位置不能发生变化 遇到的问题:加载完数据后,又把滚轮滚到之前记录的位置时,内容发生闪动 现在的方案: 加载数据之前记录整体滚动条…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
基于SpringBoot在线拍卖系统的设计和实现
摘 要 随着社会的发展,社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统,主要的模块包括管理员;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...
