Spring Security之基于方法配置权限

前言

Spring Security有两种配置方式,今天重点是绍基于方法配置的方式。

基于方法配置权限

这个主要是有一些注解提供给大家使用,今天来给大家一个demo(参考自官方sample)。

maven就不多累赘了。重点看看配置。

  • 基于角色配置
/*** 启用方法安全:@EnableMethodSecurity* * 启用@secured注解: **securedEnabled = true*** <p>会导入配置:SecuredMethodSecurityConfiguration<p>* * 启用@PreAuthorize@PostAuthorize@PreFilter@PostFilter:**prePostEnabled = false*** <p>会导入配置:PrePostMethodSecurityConfiguration</p>* * 启用jsr250相关的安全注解:**jsr250Enabled = true*** <p>会导入配置:Jsr250MethodSecurityConfiguration</p>* <p>jsr250包括@RolesAllowed@PermitAll@DenyAll</p>*/ 
@Configuration
@EnableMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class AspectjSecurityConfig {}/*** 这个类的所有方法都需要有ROLE_USER角色才能执行*/
@Service
@Secured("ROLE_USER")
public class SecuredService {public void secureMethod() {// nothing}}@Service
public class SecuredService {public void publicMethod() {// nothing}/*** 这个方法需要ROLE_USER才能执行* 这三种配置方式都是等价的,只是提供支持的Advice不同。*/ @Secured("ROLE_USER")// @RolesAllowed("ROLE_USER")// @PreAuthorize("hasRole('ROLE_USER')")public void secureMethod() {// nothing}@PreAuthorize("arguments[0] ne 'tony'")// @PreAuthorize("filterObject ne 'tony'")public void preAuthorize(@RequestParam("userCode") String userCode) {// @preAuthorize不会赋值ROOT的filterObject和returnObject,因此无法使用入参。只能使用MethodSecurityExpressionRoot的其他方法// 这个方法的实验现象为:不管传什么都能通过表达式logger.info("preAuthorize:{}", userCode);// nothing}/** 	* 这个入参会被过滤地只剩下与authentication.name一样的*/ @PreFilter("filterObject == authentication.name")public void preFilter(@RequestParam("userCodeList") List<String> userCodeList) {// http://localhost:8090/foo/preFilter?userCodeList=leo,tonylogger.info("preAuthorize:{}", userCode);}/*** 这个方法需要ROLE_USER才能执行*/ @PostAuthorize("returnObject ne 'tony'")public String postAuthorize(@RequestParam("userCode") String userCode) {// 传入的是不是tony,会抛出异常: 403logger.info("postAuthorize:{}", userCode);return userCode;}@GetMapping("/preAuthorizeSpel")@PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')")public void preAuthorizeHasRole() {logger.info("preAuthorizeSpel:{}", userCode);// nothing}/*** 这个方法需要ROLE_USER才能执行*/ @PostFilter("returnObject == authentication.name")public List<String> PostFilter(@RequestParam("userCodeList") List<String> userCodeList) {// 实验结果就是,传入了的参数有值,但只有跟用户名一样的才会返回logger.info("PostFilter:{}", userCodeList);return userCodeList;}}

以上就是怎么使用,接着我们看下是如何实现的。

基于方法授权方式的实现原理

前面我们说过,是基于AOP实现的。那么,现在我们从源码层面来看看。

我们可以看到上面按照@EnableMethodSecurity的配置,分别对应地导入三个配置。
但是,我们可以先从AOP的角度设想一下,我们需要的是哪种类型的通知?不妨归类一下:

注解类型通知类型描述/备注
@PreFilter@PreAuthorize@Secured以及JSR-250的相关注解前置通知这些很明显都是需要先校验/过滤参数再执行目标方法
@PostFilter@PostAuthorize后置通知先执行目标方法,再校验/过滤结果集

接着我们来看看Spring Security的设计:

AuthorizationManagerBeforeMethodInterceptor

在执行方法之前进行鉴权,这也意味着,当权限不足时,他会抛出异常。
实际上,他是一个通用的增强,全取决于你怎么使用它。
在SpringSecurity的配置里,他可以负责@PreAuthor,也可以负责@Secured,甚至还能负责jsr250的相关注解。
需要提醒一点,一个实例对象只能一种注解哈。

但是,大家有没有想过一个问题:为什么@PreAuthor@Secured以及jsr250的相关注解,都能交给他来处理?
又或者说,他的设计是如何将这三者的处理抽象统一起来的?更具体一点,此三者有何共同之处,可以进行抽象和统一的??

要回答这个问题,我们需要先从这个三者的入手:
共同点:

都需要在执行方法前进行权限校验,校验不通过则都需要抛出异常,阻断方法调用。

异同点:

注解配置描述执行
@PreAuthorize配置的是SPEL表达式通过执行表达式来得出是否满足访问权限
@Secured指定角色需要校验当前用户是否拥有指定角色
jsr250的@PermitAll-任何人都可以访问
jsr250的@DenyAll-任何人都不能访问
jsr250的@RolesAllowed指定角色需要校验当前用户是否拥有指定角色

从共同点出发,本质上无非就是鉴权嘛,这不是很符合Spring Security的AuthorizationManager的职责吗?
然后就是不同点,我们发现无非就是权限的配置来源不同需要解析不同的注解咯。
有了这个思路,接下来我们翻找源码,理解起来就容易多了。

public final class AuthorizationManagerBeforeMethodInterceptorimplements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {// 构造器,需要传入Pointcut,和AuthorizationManager<MethodInvocation>,以便校验权限。public AuthorizationManagerBeforeMethodInterceptor(Pointcut pointcut,AuthorizationManager<MethodInvocation> authorizationManager) {Assert.notNull(pointcut, "pointcut cannot be null");Assert.notNull(authorizationManager, "authorizationManager cannot be null");this.pointcut = pointcut;this.authorizationManager = authorizationManager;}/*** 创建负责处理@PreAuthorize的增强*/public static AuthorizationManagerBeforeMethodInterceptor preAuthorize(PreAuthorizeAuthorizationManager authorizationManager) {AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class), authorizationManager);interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder());return interceptor;}/*** 创建负责处理@Secured*/public static AuthorizationManagerBeforeMethodInterceptor secured(SecuredAuthorizationManager authorizationManager) {AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(AuthorizationMethodPointcuts.forAnnotations(Secured.class), authorizationManager);interceptor.setOrder(AuthorizationInterceptorsOrder.SECURED.getOrder());return interceptor;}/*** 创建负责处理jsr250相关注解: @RolesAllow@PermitAll@DenyAll*/public static AuthorizationManagerBeforeMethodInterceptor jsr250(Jsr250AuthorizationManager authorizationManager) {AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(AuthorizationMethodPointcuts.forAnnotations(RolesAllowed.class, DenyAll.class, PermitAll.class),authorizationManager);interceptor.setOrder(AuthorizationInterceptorsOrder.JSR250.getOrder());return interceptor;}@Overridepublic Object invoke(MethodInvocation mi) throws Throwable {// 校验权限attemptAuthorization(mi);return mi.proceed();}private void attemptAuthorization(MethodInvocation mi) {// 通过AuthorizationManager校验权限,这意味者AuthorizationManager必须具备解析相关注解的能力,实际上他交给了另外一个组件,后面会说到。AuthorizationDecision decision = this.authorizationManager.check(this.authentication, mi);// 发布授权事件this.eventPublisher.publishAuthorizationEvent(this.authentication, mi, decision);// 不通过就抛出访问拒绝异常if (decision != null && !decision.isGranted()) {throw new AccessDeniedException("Access Denied");}}

从源码,我们可以发现之前分析的三类注解,只需要一个MethodInterceptor。而他们的不同之处则由AuthorizationManager这个同一个的接口进行统一调度。
对应地:

注解AuthorizationManager
@PreAuthorizePreAuthorizeAuthorizationManager
@SecuredSecuredAuthorizationManager
jsr250的注解Jsr250AuthorizationManager
  • SecuredAuthorizationManager:
public final class SecuredAuthorizationManager implements AuthorizationManager<MethodInvocation> {// 这是个最为简单的AuthorizationManager实现,无非就是将当前用户的权限与所需要的权限进行比较,如果找到就认为拥有访问权限。private AuthorizationManager<Collection<String>> authoritiesAuthorizationManager = new AuthoritiesAuthorizationManager();// 缓存已经解析过的方法所对应的权限private final Map<MethodClassKey, Set<String>> cachedAuthorities = new ConcurrentHashMap<>();@Overridepublic AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) {// 获取目标方法配置的访问权限Set<String> authorities = getAuthorities(mi);// 比较权限,从authoritiesAuthorizationManager.check方法的入参也能大概猜到怎么实现return authorities.isEmpty() ? null : this.authoritiesAuthorizationManager.check(authentication, authorities);}private Set<String> getAuthorities(MethodInvocation methodInvocation) {Method method = methodInvocation.getMethod();Object target = methodInvocation.getThis();Class<?> targetClass = (target != null) ? target.getClass() : null;MethodClassKey cacheKey = new MethodClassKey(method, targetClass);// 如果尚未存在相关缓存,则进行解析。// 由此可见,只有当目标方法被第一次执行/调用的时候才会出发解析动作return this.cachedAuthorities.computeIfAbsent(cacheKey, (k) -> resolveAuthorities(method, targetClass));}private Set<String> resolveAuthorities(Method method, Class<?> targetClass) {Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);// 尝试在方法上寻找目标注解@SecuredSecured secured = findSecuredAnnotation(specificMethod);// 返回@Secured注解所配置的权限return (secured != null) ? Set.of(secured.value()) : Collections.emptySet();}
}
  • Jsr250AuthorizationManager
    相较于SecuredAuthorizationManager,他负责的则是3个注解,而不是一个。
public final class Jsr250AuthorizationManager implements AuthorizationManager<MethodInvocation> {// 这是AuthorizationManager的注册器// 因为要处理三个注解,每个注解的处理逻辑虽然简单,但是确实不一样。// 而每个方法存在的注解也不一样private final Jsr250AuthorizationManagerRegistry registry = new Jsr250AuthorizationManagerRegistry();@Overridepublic AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation methodInvocation) {// 根据方法,从注册器中获取对应的AuthorizationManagerAuthorizationManager<MethodInvocation> delegate = this.registry.getManager(methodInvocation);// 执行授权校验逻辑return delegate.check(authentication, methodInvocation);}// 这是Jsr250AuthorizationManager的内部类private final class Jsr250AuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {@NonNull@OverrideAuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass) {// 解析注解Annotation annotation = findJsr250Annotation(method, targetClass);if (annotation instanceof DenyAll) {// 返回一个lambda表达式构建的AuthorizationManager,其实现为:直接返回拒绝访问return (a, o) -> new AuthorizationDecision(false);}if (annotation instanceof PermitAll) {// 返回一个lambda表达式构建的AuthorizationManager,其实现为:直接返回允许访问return (a, o) -> new AuthorizationDecision(true);}if (annotation instanceof RolesAllowed) {RolesAllowed rolesAllowed = (RolesAllowed) annotation;// 返回与@Secured一样的AuthorityAuthorizationManagerreturn AuthorityAuthorizationManager.hasAnyRole(Jsr250AuthorizationManager.this.rolePrefix,rolesAllowed.value());}// 这里应当看到,这三个注解的判断顺序。事实上,在解析注解的时候,只会返回其中之一。// 而jsr250的注解也是不能同时使用的,只能用其中一个。return NULL_MANAGER;}}
}abstract class AbstractAuthorizationManagerRegistry {private final Map<MethodClassKey, AuthorizationManager<MethodInvocation>> cachedManagers = new ConcurrentHashMap<>();final AuthorizationManager<MethodInvocation> getManager(MethodInvocation methodInvocation) {Method method = methodInvocation.getMethod();Object target = methodInvocation.getThis();Class<?> targetClass = (target != null) ? target.getClass() : null;MethodClassKey cacheKey = new MethodClassKey(method, targetClass);// 这里就跟@Secured类似了,只不过其需要兼顾3个注解,因此value变成了AuthorizationManagerreturn this.cachedManagers.computeIfAbsent(cacheKey, (k) -> resolveManager(method, targetClass));}	abstract AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass);
}
  • PreAuthorizeAuthorizationManager
    与他的同伴不同,他可是要支持SPEL的。
public final class PreAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocation> {private PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();@Overridepublic AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) {// 通过方法,从注册器获取ExpressionAttribute。ExpressionAttribute attribute = this.registry.getAttribute(mi);if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {return null;}// 通过注册器找到ExpressionHandler,并创建EvaluationContextEvaluationContext ctx = this.registry.getExpressionHandler().createEvaluationContext(authentication, mi);// 执行SPEL表达式boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);// 返回决策:是否允许访问return new ExpressionAuthorizationDecision(granted, attribute.getExpression());}
}

这里我们看到了为了支持SPEL的第一个抽象:ExpressionAttribute。他最重要的使命就是记录表达式。
再来看看,最为重要的:PreAuthorizeExpressionAttributeRegistry

final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {private final MethodSecurityExpressionHandler expressionHandler;@OverrideExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);// 寻找@PreAuthorize注解PreAuthorize preAuthorize = findPreAuthorizeAnnotation(specificMethod);if (preAuthorize == null) {return ExpressionAttribute.NULL_ATTRIBUTE;}// 解析表达式Expression preAuthorizeExpression = this.expressionHandler.getExpressionParser().parseExpression(preAuthorize.value());// 返回解析到的表达式return new ExpressionAttribute(preAuthorizeExpression);}
}abstract class AbstractExpressionAttributeRegistry<T extends ExpressionAttribute> {// 缓存方法对应的表达式private final Map<MethodClassKey, T> cachedAttributes = new ConcurrentHashMap<>();final T getAttribute(Method method, Class<?> targetClass) {MethodClassKey cacheKey = new MethodClassKey(method, targetClass);// 解析并缓存表达式return this.cachedAttributes.computeIfAbsent(cacheKey, (k) -> resolveAttribute(method, targetClass));}
}

AuthorizationManagerAfterMethodInterceptor

他与前面介绍的MethodInterceptor相呼应,一前一后,你从名字就能发现。只不过与前者相比,目前他只有一个注解需要关注@PostAuthorize。

public final class AuthorizationManagerAfterMethodInterceptorimplements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {private final AuthorizationManager<MethodInvocationResult> authorizationManager;@Overridepublic Object invoke(MethodInvocation mi) throws Throwable {// 先执行Object result = mi.proceed();// 再校验。这里应当注意到他的入参包括方法返回值attemptAuthorization(mi, result);return result;}private void attemptAuthorization(MethodInvocation mi, Object result) {// 熟悉的配方,只不过入参变成了方法返回值罢了MethodInvocationResult object = new MethodInvocationResult(mi, result);AuthorizationDecision decision = this.authorizationManager.check(this.authentication, object);this.eventPublisher.publishAuthorizationEvent(this.authentication, object, decision);if (decision != null && !decision.isGranted()) {throw new AccessDeniedException("Access Denied");}}
}public final class PostAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();@Overridepublic AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocationResult mi) {ExpressionAttribute attribute = this.registry.getAttribute(mi.getMethodInvocation());if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {return null;}MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, mi.getMethodInvocation());expressionHandler.setReturnObject(mi.getResult(), ctx);boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);return new ExpressionAuthorizationDecision(granted, attribute.getExpression());}
}

经过了前面的分析,这里也就没啥多说的了。与@PreAuthorize很相似。

PreFilterAuthorizationMethodInterceptor

负责处理@PreFilter注解的方法调用。

public final class PreFilterAuthorizationMethodInterceptorimplements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {public PreFilterAuthorizationMethodInterceptor() {// 默认处理的是@PreFilterthis.pointcut = AuthorizationMethodPointcuts.forAnnotations(PreFilter.class);}// ...@Overridepublic Object invoke(MethodInvocation mi) throws Throwable {// 从属性注册器中获取到目标方法解析好的@PreFilter的相关属性信息。PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute attribute = this.registry.getAttribute(mi);if (attribute == PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute.NULL_ATTRIBUTE) {return mi.proceed();}// 从属性注册器中获取对应的SPEL的表达式处理器,以便创建SPEL的上下文MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();EvaluationContext ctx = expressionHandler.createEvaluationContext(this.authentication, mi);// 从表达式上下文中获取需要过滤的目标Object filterTarget = findFilterTarget(attribute.getFilterTarget(), ctx, mi);// 根据SPEL表达式执行过滤expressionHandler.filter(filterTarget, attribute.getExpression(), ctx);// 执行目标方法return mi.proceed();}// ...
}

可以看出,与@PreAuthorize类似,只是少了一层AuthorizationManager的封装。原因也很简单,AuthorizationManager是用来鉴权的,而@PreFilter不需要鉴权。
只需要过滤参数即可。因此他也不会抛出访问异常。

PostFilterAuthorizationMethodInterceptor

负责处理@PostFilter。

public final class PostFilterAuthorizationMethodInterceptorimplements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {// 这个注册器与PreAuthorizeExpressionAttributeRegistry类似,都继承同一个父类。// 只有解析的注解不一样,对应ExpressionAttribute稍稍不一样,前者记录有方法参数,而后者记录有方法返回值。private PostFilterExpressionAttributeRegistry registry = new PostFilterExpressionAttributeRegistry();public PostFilterAuthorizationMethodInterceptor() {this.pointcut = AuthorizationMethodPointcuts.forAnnotations(PostFilter.class);}@Overridepublic Object invoke(MethodInvocation mi) throws Throwable {// 先执行了方法,获得返回值Object returnedObject = mi.proceed();ExpressionAttribute attribute = this.registry.getAttribute(mi);if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {return returnedObject;}// 获取ExpressionHandler,创建EvaluationContextMethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();EvaluationContext ctx = expressionHandler.createEvaluationContext(this.authentication, mi);// 执行过滤逻辑return expressionHandler.filter(returnedObject, attribute.getExpression(), ctx);}

我们可以看到@PostFilter,只是对返回进行过滤,同样也不会抛出访问异常。

小结

分析完源码之后,我们不难发现,每一个类都很小,且每一个方法都足够简单。这点是值得大家学习的。
当然,不能盲目。因为作为一款优秀的框架,必须要具备良好的可扩展性。通过分析和抽象,能够很好起到这个作用。这是框架源码设计者所追求的。
但是其弊端也很明显,那就是一个完整的功能实现,往往需要诸多组件协作完成,初学者很难入门。因此,在实际业务开发过程中,不能盲目地追求这种扩展性。
同时,面对复杂业务时,不妨多分析,是否可以像框架源码的设计者那样思考?当然,设计完成后,需要留下相当的文档。

总结

各方法注解的应用场景

注解应用场景实现原理
@Secured配置需要满足的权限,可以是角色名或者权限基于AuthorityAuthorizationManager
JSR-250的@PermitAll任何人都可以访问基于lambda表达式实现的简单的AuthorizationManager
JSR-250的@DenyAll任何人都不能访问基于lambda表达式实现的简单的AuthorizationManager
JSR-250的@RolesAllowed配置需要满足的权限,可以是角色名或者权限。可以于@Secured相互取代。基于AuthorityAuthorizationManager,可以于@Secured相互取代。
@PreAuthorize用于在方法调用之前鉴权,支持方法入参作为表达式的一部分基于SPEL表达式
@PostAuthorize用于在方法调用之后鉴权,支持方法返回值作为表达式的一部分基于SPEL表达式
@PreFilter用于过滤方法入参基于SPEL表达式
@PostFilter用于过滤方法返回值基于SPEL表达式

在搞清楚了各注解的用处和使用后,我们在回过头来看,我们可以使用哪些授权方式:

  1. RBAC(基于角色的访问控制)
    这个除了@PreFilter@PostFilter,其他注解都能实现。SPEL可以使用hasAnyRole("ADMIN","USER")

后记

至此,我们应该算全方面聊完了基于方法注解怎么配置权限这个话题。不管各个注解的使用场景,亦或者其实现原理,还是各种设计思路。
下一节,我们将聊聊基于HttpRequest的权限配置方式。这个直接使用倒是简单,但是想要定制就必须深入理解其设计和原理。加油。

参照

Method Security

Spring Security的@PreAythorize、@PostAuthorize、@PreFilter 和@PostFilter

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

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

相关文章

【LeetCode】17.电话号码的字母组合

题目 链接&#xff1a;17. 电话号码的字母组合 - 力扣&#xff08;LeetCode&#xff09; 给定一个仅包含数字2-9的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按任意顺序返回 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何…

Vue ECharts line3D点击空白处重置图表视角- 附完整示例

ECharts&#xff1a;一个基于 JavaScript 的开源可视化图表库。 目录 效果 一、介绍 1、官方文档&#xff1a;Apache ECharts 2、官方示例 二、准备工作 1、安装依赖包 2、示例版本 三、使用步骤 1、在单页面引入 echarts 2、指定容器并设置容器宽高 3、数据处理&…

leetcode 热题 100_反转链表

题解一&#xff1a; 迭代&#xff1a;逐步修改节点指针&#xff0c;注意在修改前要保存下一个节点指针。 class Solution {public ListNode reverseList(ListNode head) {ListNode pre null;while (head! null) {ListNode temp head.next;head.next pre;pre head;head te…

使用Canvas绘制一个自适应长度的折线图

要求x轴根据数据长度自适应 y轴根据数据最大值取长度值 <template><div ref"cvsContainer" class"cvs-container"><canvas ref"cvs" class"canvas"></canvas></div> </template><script set…

【Golang星辰图】Go语言之光照耀数据科学:揭开强大库的神秘面纱

Go语言赋能数据科学&#xff1a;探索多样化工具的无限可能 前言 在数据科学和分析领域&#xff0c;使用合适的工具和库对数据进行处理、分析和建模至关重要。本文将介绍一系列功能强大的Go语言库&#xff0c;涵盖了特征值分解、矩阵运算、深度学习、机器学习以及统计分析等方…

Chapter20-Ideal gases-CIE课本要点摘录、总结

20.1 Particles of a gas Brownian motion Fast modules 速率的数值大概了解下&#xff1a; average speed of the molecules:400m/s speed of sound:approximately 330m/s at STP&#xff08;standard temperature and pressure&#xff09; Standard Temperature and Pres…

计算表达式x*(2^i)的值math.ldexp(x, i)

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 计算表达式x*(2^i)的值 math.ldexp(x, i) [太阳]选择题 关于以下代码输出的结果说法正确的是&#xff1f; import math print("【执行】math.ldexp(3,2)") print(math.ldexp(3,2)) …

SpringBoot中实现API速率限制的令牌桶算法项目

这个github项目是利用Bucket4j以及 Redis 缓存和 Spring Security 过滤器对私有 API 端点实施速率限制。 需要升级到 Spring Boot 3 和 Spring Security 6 关键组件&#xff1a; RedisConfiguration.javaRateLimitingService.javaRateLimitFilter.javaBypassRateLimit.javaP…

HTML5+CSS3+移动web——CSS基础

系列文章目录 HTML5CSS3移动web——HTML 基础-CSDN博客https://blog.csdn.net/ymxk2876721452/article/details/136070953?spm1001.2014.3001.5501HTML5CSS3移动web——列表、表格、表单-CSDN博客https://blog.csdn.net/ymxk2876721452/article/details/136221443?spm1001.2…

【Spring云原生系列】SpringBoot+Spring Cloud Stream:消息驱动架构(MDA)解析,实现异步处理与解耦合

&#x1f389;&#x1f389;欢迎光临&#xff0c;终于等到你啦&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;持续更新的专栏《Spring 狂野之旅&#xff1a;从入门到入魔》 &a…

wordpress博客趣主题个人静态网页模板

博客趣页面模板适合个人博客&#xff0c;个人模板等内容分享。喜欢的可以下载套用自己熟悉的开源程序建站。 博客趣主题具有最小和清洁的设计&#xff0c;易于使用&#xff0c;并具有有趣的功能。bokequ主题简约干净的设计、在明暗风格之间进行现场切换。 下载地址 清新个人…

【金三银四】Spring面试题

目录 1、什么是Spring2、说一下Spring的IOC3、Spring的AOP4、连接点&#xff1f;切入点&#xff1f;5、Spring AOP 是通过什么实现的6、Spring Bean的生命周期是怎么样的&#xff1f;7、Spring Bean的初始化过程是怎么样8、Spring的事务传播机制有哪些&#xff1f;9、Autowired…

割点原理及封装好的割点类

作者推荐 视频算法专题 预备知识 本分析针对&#xff1a;连通无向图G。 搜索树 节点的父子关系&#xff1a;任意 节点的邻接 节点除了已处理 节点&#xff0c;都是它的子 节点。 以任意一点为根开始DFS&#xff0c;计算所有 节点的父子关系。只保留个子 节点到父 节点形成…

电商数据分析17——电商平台评价系统的数据分析与管理

电商平台评价系统的数据分析与管理 在数字经济时代&#xff0c;电商平台已成为消费者购物的首选。随之而来&#xff0c;评价系统作为连接消费者与产品的重要桥梁&#xff0c;对于购买决策和产品改进起着至关重要的作用。通过数据分析来优化评价管理&#xff0c;不仅可以提升产…

Visual Studio单步调试中监视窗口变灰的问题

在vs调试中&#xff0c;写了这样一条语句 while((nfread(buf, sizeof(float), N, pf))>0) 然而&#xff0c;在调试中&#xff0c;只要一执行while这条语句&#xff0c;监视窗口中的变量全部变为灰色&#xff0c;不能查看&#xff0c;是程序本身并没有报错&#xff0c;能够继…

Python编程与人工智能应用 MOOC题目

第二次作业 1. 2. . 3.考察“字符串的这些api函数均是提供一个拷贝本”的知识点。 4.这边的2别忘&#xff0c;前闭后开区间&#xff0c;否则对于121这样会认为是质数&#xff08;11*11&#xff09;

VMware 集群-虚拟机配置反亲和性(互斥)

简介 博客&#xff1a;https://songxwn.com/ 为实现应用系统的冗余&#xff0c;经常会双机或者多机部署&#xff08;如数据库集群等&#xff09;。在VMware 集群里面&#xff0c;要保证不同应用集群的节点虚拟机在不同的物理宿主机上&#xff0c;防止单个宿主机故障&#xff…

开发指南004-@Query参数写法

JPA的Query注解和函数参数的绑定有多种写法&#xff0c;总结如下&#xff1a; 1、使用:形参名 2、使用?数值,数值表示形参位置,1表示第一个形参,依次类推 3、使用Param("参数名"):参数名 4、获取实体类名称,使用#{#entityName}

在高并发、高性能、高可用 三高项目中如何设计适合实际业务场景的分布式id(一)

分布式ID组件&#xff1a;黄金链路上的关键基石 在现代分布式系统中&#xff0c;分布式ID组件无疑扮演着至关重要的角色。作为整个系统的黄金链路上的关键组件&#xff0c;它的稳定性和可靠性直接关乎到整个系统的正常运作。一旦分布式ID组件出现问题&#xff0c;黄金链路上的…

HTML静态网页成品作业(HTML+CSS)——阜阳剪纸介绍设计制作(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有1个页面。 二、作品演示 三、代…