摆脱困境:从计划作业中调用安全方法

假设我们已经实现了一个Spring支持的应用程序,并使用Spring Security的方法安全性表达式对其进行了保护 。

我们的下一个任务是使用安全方法实施计划作业。 更具体地说,我们必须实现一个计划的作业,该作业从我们的服务类中获取一条消息,并将接收到的消息写到日志中。

让我们开始吧。

本博客文章中描述的计划作业使用在特定于配置文件的配置文件中配置的cron表达式。 如果您不知道如何执行此操作,建议您阅读我的博客文章,其中描述了如何使用带有@Scheduled批注的特定于环境的cron表达式 。

我们的第一次尝试

让我们创建一个计划的作业,该作业调用安全方法并找出执行作业时发生的情况。 让我们先来看一下示例应用程序的服务层。

服务层

安全服务类的方法在MessageService接口中声明。 它声明了一个称为getMessage()的方法,并指定只有具有角色ROLE_USER的用户才能调用它。

MessageService接口的源代码如下所示:

import org.springframework.security.access.prepost.PreAuthorize;public interface MessageService {@PreAuthorize("hasRole('ROLE_USER')")public String getMessage();
}

我们对MessageService接口的实现非常简单。 其源代码如下:

import org.springframework.stereotype.Service;@Service
public class HelloMessageService implements MessageService {@Overridepublic String getMessage() {return "Hello World!";}
}

让我们继续并创建调用getMessage()方法的计划作业。

创建计划的作业

我们可以按照以下步骤创建计划的作业:

  1. 创建一个ScheduledJob类,并使用@Component注释对其进行注释。 这样可以确保在类路径扫描期间找到我们的计划作业(只要将其放入要扫描的程序包中)。
  2. 将私有的Logger字段添加到创建的类中,并通过调用LoggerFactory类的静态getLogger()方法来创建Logger对象。 我们将使用Logger对象将从HelloMessageService对象收到的消息写入日志。
  3. 将私有MessageService字段添加到创建的类。
  4. 将一个构造函数添加到创建的类中,并使用@Autowired注释对其进行注释。 这确保了我们可以使用构造函数注入将MessageService bean注入MessageService字段。
  5. 向创建的类添加一个公共run()方法,并使用@Scheduled批注对其进行批注。 将其cron属性的值设置为'$ {scheduling.job.cron}' 。 这意味着cron表达式是从属性文件中读取的,其值是schedule.job.cron属性的值( 有关此内容的更多信息,请参阅此博客文章 )。
  6. 通过调用MessageService接口的getMessage()方法来实现run()方法。 将收到的消息写入日志。

我们计划的作业的源代码如下所示:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;@Component
public class ScheduledJob {private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJob.class);private final MessageService messageService;@Autowiredpublic ScheduledJob(MessageService messageService) {this.messageService = messageService;}@Scheduled(cron = "${scheduling.job.cron}")public void run() {String message = messageService.getMessage();LOGGER.debug("Received message: {}", message);}
}

让我们看看调用ScheduledJob类的run()方法时会发生什么。

它不起作用

当执行我们的计划作业时,将抛出AuthenticationCredentialsNotFoundException ,并且我们看到以下堆栈跟踪:

2013-12-10 19:45:19,001 ERROR - kUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task.
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContextat org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:339)at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:198)at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:60)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)at com.sun.proxy.$Proxy31.getMessage(Unknown Source)at net.petrikainulainen.spring.trenches.scheduling.job.ScheduledJobTwo.run(ScheduledJobTwo.java:26)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:601)at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:64)at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:53)at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81)at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)at java.util.concurrent.FutureTask.run(FutureTask.java:166)at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178)at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)at java.lang.Thread.run(Thread.java:722)

该堆栈跟踪实际上非常有用。 它告诉我们安全方法无法调用,因为从SecurityContext中找不到Authentication对象。

我看到的两个最常见的解决方案是:

  • 创建一个与受保护的方法具有相同功能的单独的方法,然后修改计划的作业以使用此方法。 此方法通常具有Javadoc注释,该注释指出只有计划的作业才能调用此方法。 这种解决方案有两个问题:1)它会使代码库混乱,并且2)最终无论如何都会有人调用该方法(除非有必要,否则没人真正阅读Javadocs)。
  • 从计划作业调用的方法中删除方法安全注释。 由于明显的原因,这是一个非常糟糕的解决方案。 提示:该方法的安全是有充分理由的!

幸运的是,还有第三种方法可以解决此问题。 让我们开始查找计划作业使用的安全上下文的存储位置。

安全上下文从何而来?

我们的问题的解决方案很明确:我们必须创建一个Authentication对象,然后将其添加到SecurityContext中,然后调用安全方法。

但是,在对示例应用程序进行必要的修改之前,我们必须了解SecurityContext对象的存储位置。

如果未进行其他配置,则将安全上下文存储到ThreadLocal 。 换句话说,每个线程都有其自己的安全上下文。 这意味着在同一线程中执行的所有计划作业均共享相同的安全上下文。

假设我们有三个预定的作业。 这些作业称为ABC。 另外,我们假设这些作业是按字母顺序执行的。

如果我们使用只有一个线程的默认线程池,则所有作业共享相同的安全上下文。 如果作业B身份验证对象设置为安全上下文,则执行计划的作业时会发生以下情况:

  • 作业A无法调用安全方法,因为它在作业B之前执行。 这意味着从安全上下文中找不到身份验证对象。
  • 作业B可以调用安全方法,因为作业B在尝试调用安全方法之前将Authentication对象设置为安全上下文。
  • 作业C可以调用安全方法,因为它是在将身份验证对象设置为安全上下文的作业B之后执行的。

如果我们使用一个具有多个线程的线程池,则每个线程都有其自己的安全上下文。 如果作业AAuthentication对象设置为安全上下文,则在同一线程中执行的所有作业都将使用相同的特权执行,只要它们在作业A之后执行即可。

让我们一步一步地完成每一项工作:

  • 作业A可以调用安全方法,因为它在尝试调用安全方法之前将Authentication对象设置为安全上下文。
  • 如果作业B 与作业A在同一线程中执行,则作业B可以调用安全方法。 如果作业不在同一线程中执行,则无法调用安全方法,因为无法从安全上下文中找到Authentication对象。
  • 如果作业C 与作业A在同一线程中执行,则作业C可以调用安全方法。 如果作业不在同一线程中执行,则无法调用安全方法,因为无法从安全上下文中找到Authentication对象。

显然,解决此问题的最佳方法是确保使用所需的特权执行每个计划的作业。 该解决方案有两个好处:

  • 我们可以以任何顺序执行工作。
  • 我们不必确保作业在“正确的”线程中执行。

让我们找出当我们的应用程序使用Spring Security 3.1时如何解决这个问题。

Spring Security 3.1:需要手动工作

如果我们的应用程序使用Spring Security 3.1,则解决问题的最简单方法是

  • 在我们的工作尝试调用安全方法之前,创建一个Authentication对象并将其设置为安全上下文。
  • 在作业完成之前,从安全上下文中删除身份验证对象。

让我们从创建提供所需方法的AuthenticationUtil类开始。

创建AuthenticationUtil类

我们可以按照以下步骤创建AuthenticationUtil类:

  1. 创建AuthenticationUtil类。
  2. AuthenticationUtil类添加一个私有构造函数。 这样可以确保无法实例化该类。
  3. 将静态clearAuthentication()方法添加到该类,并按照以下步骤实现该方法:
    1. 通过调用SecurityContextHolder类的静态getContext()方法来获取SecurityContext对象。
    2. 通过调用SecurityContext接口的setContext()方法删除身份验证信息。 将null作为方法参数传递。
  4. 将静态configureAuthentication()方法添加到该类。 此方法将用户的角色作为方法参数。 通过执行以下步骤来实现此方法:
    1. 通过调用AuthorityUtils类的静态createAuthorityList()方法来创建GrantedAuthority对象的集合 。 将用户角色作为方法参数传递。
    2. 创建一个新的UsernamePasswordAuthenticationToken对象,并将以下对象作为构造函数参数传递:
      1. 第一个构造函数参数是主体。 将字符串“ user”作为第一个构造函数参数传递。
      2. 第二个构造函数参数是用户的凭据。 将作为方法参数给出的角色作为第二个构造函数参数传递。
      3. 第三个构造函数参数包含用户的权限。 将创建的Collection <GrantedAuthority>对象作为第三个构造函数参数传递。
    3. 通过调用SecurityContextHolder类的静态getContext()方法来获取SecurityContext对象。
    4. 通过调用SecurityContext接口的setAuthentication()方法将创建的Authentication对象设置为安全上下文。 将创建的UsernamePasswordAuthenticationToken作为方法参数传递。

AuthenticationUtil类的源代码如下所示:

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;import java.util.Collection;public final class AuthenticationUtil {//Ensures that this class cannot be instantiatedprivate AuthenticationUtil() {}public static void clearAuthentication() {SecurityContextHolder.getContext().setAuthentication(null);}public static void configureAuthentication(String role) {Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(role);Authentication authentication = new UsernamePasswordAuthenticationToken("user",role,authorities);SecurityContextHolder.getContext().setAuthentication(authentication);}
}

我们还没有完成。 我们仍然必须对我们的预定工作进行一些修改。 让我们找出如何进行这些修改。

修改计划的作业

我们必须对ScheduledJob类进行两次修改。 我们可以按照以下步骤进行修改:

  1. 启动作业时,调用AuthenticationUtil类的静态configureAuthentication()方法,并将字符串 'ROLE_USER'作为方法参数传递。 这样可以确保我们的计划作业可以执行与具有ROLE_USER角色的普通用户相同的方法。
  2. 在作业完成之前,调用AuthenticationUtil类的静态clearAuthentication()方法。 这从安全上下文中删除了身份验证信息。

ScheduledJob类的源代码如下所示:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;@Component
public class ScheduledJob {private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJob.class);private final MessageService messageService;@Autowiredpublic ScheduledJob(MessageService messageService) {this.messageService = messageService;}@Scheduled(cron = "${scheduling.job.cron}")public void run() {AuthenticationUtil.configureAuthentication("ROLE_USER");String message = messageService.getMessage();LOGGER.debug("Received message: {}", message);AuthenticationUtil.clearAuthentication();}
}

让我们找出运行预定作业时会发生什么。

运行计划的作业

调用作业时,以下消息将写入日志:

2013-12-17 20:41:33,019 DEBUG - ScheduledJob            - Received message: Hello World!

当我们的应用程序使用Spring Security 3.1时,一切都将正常运行。 我们的解决方案不是那么优雅,但可以。 该解决方案的明显缺点是,我们必须记住在计划的作业中调用AuthenticationUtil类的configureAuthentication()clearAuthentication()方法。

Spring Security 3.2解决了这个问题。 让我们继续前进,找出当我们的应用程序使用Spring Security 3.2时如何解决这个问题。

Spring Security 3.2:几乎就像魔术一样!

Spring Security 3.2具有全新的并发支持 ,这使我们可以将安全上下文从一个线程转移到另一个线程。 让我们找出如何配置应用程序上下文以使用Spring Security 3.2提供的功能。

配置应用程序上下文

因为我们要使用Spring Security 3.2的新并发支持,所以我们必须对应用程序上下文配置类进行以下更改( 原始配置在此博客文章中进行了描述 ):

  1. 实现SchedulingConfigurer接口。 该接口可以通过使用@EnableScheduling批注进行批注的应用程序上下文配置类来实现,并且通常用于配置使用的TaskScheduler bean或以编程方式配置执行的任务。
  2. 将私有createrSchedulerSecurityContext()方法添加到配置类。 此方法没有方法参数,它返回一个SecurityContext对象。 通过执行以下步骤来实现此方法:
    1. 通过调用SecurityContextHolder类的静态createEmptyContext()方法来创建新的SecurityContext对象。
    2. 通过调用AuthorityUtils类的静态createAuthorityList()方法来创建GrantedAuthority对象的集合 。 将字符串 “ ROLE_USER”作为方法参数传递。
    3. 创建一个新的UsernamePasswordAuthenticationToken对象,并将以下对象作为构造函数参数传递:
      1. 第一个构造函数参数是主体。 将字符串 “ user”作为第一个构造函数参数传递。
      2. 第二个构造函数参数是用户的凭据。 将字符串 “ ROLE_USER”作为第二个构造函数参数传递。
      3. 第三个构造函数参数包含用户的权限。 将创建的Collection <GrantedAuthority>对象作为第三个构造函数参数传递。
    4. 通过调用SecurityContext接口的setAuthentication()方法,将创建的UsernamePasswordAuthenticationToken对象设置为创建的安全上下文。
  3. 将公共taskExecutor()方法添加到配置类中,并使用@Bean注释对该方法进行注释。 此方法没有方法参数,并返回Executor对象。 通过执行以下步骤来实现此方法:
    1. 通过调用Executors类的静态newSingleThreadScheduledExecutor()方法来创建新的ScheduledExecutorService对象。 这将创建一个ScheduledExecutorService对象,该对象通过使用一个线程来运行所有作业。
    2. 通过调用私有的createSchedulerSecurityContext()方法来获取对SecurityContext对象的引用。
    3. 创建一个新的DelegatingSecurityContextScheduledExecutorService对象,并将以下对象作为构造函数参数传递:
      1. 第一个构造函数参数是ScheduledExecutorService对象。 该对象用于调用计划的作业。 将创建的ScheduledExecutorService对象作为第一个构造函数参数传递。
      2. 第二个构造函数参数是SecurityContext对象。 创建的DelegatingSecurityContextScheduledExecutorService对象确保每个调用的作业都使用此SecurityContext 。 将创建的SecurityContext对象作为第二个构造函数参数传递。
    4. 返回创建的DelegatingSecurityContextScheduledExecutorService对象。
  4. 实现SchedulingConfigurer接口的configureTasks()方法 。 此方法将ScheduledTaskRegistrar对象作为方法参数。 通过执行以下步骤来实现此方法:
    1. 通过调用taskExecutor()方法创建一个新的Executor对象。
    2. 通过调用ScheduledTaskRegistrar类的setScheduler()方法来设置使用的调度程序 ,并将Executor对象作为方法参数传递。

ExampleApplicationContext类的源代码如下所示(相关部分已突出显示):

import org.springframework.context.annotation.*;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.concurrent.DelegatingSecurityContextScheduledExecutorService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;@Configuration
@EnableScheduling
@ComponentScan(basePackages = {"net.petrikainulainen.spring.trenches.scheduling"
})
@Import(ExampleSecurityContext.class)
@PropertySource("classpath:application.properties")
public class ExampleApplicationContext implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {taskRegistrar.setScheduler(taskExecutor());}@Beanpublic Executor taskExecutor() {ScheduledExecutorService delegateExecutor = Executors.newSingleThreadScheduledExecutor();SecurityContext schedulerContext = createSchedulerSecurityContext();return new DelegatingSecurityContextScheduledExecutorService(delegateExecutor, schedulerContext);}private SecurityContext createSchedulerSecurityContext() {SecurityContext context = SecurityContextHolder.createEmptyContext();Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER");Authentication authentication = new UsernamePasswordAuthenticationToken("user","ROLE_USER",authorities);context.setAuthentication(authentication);return context;}@Beanpublic PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();properties.setLocation(new ClassPathResource( "application.properties" ));properties.setIgnoreResourceNotFound(false);return properties;}
}

这就对了。 此配置确保每个计划的作业都可以访问由createSchedulerSecurityContext()方法创建的SecurityContext对象。 这意味着每个计划的作业都可以调用安全的方法,这些方法可以由角色为“ ROLE_USER”的用户调用。

让我们快速看一下我们的预定工作。

那预定的工作呢?

该解决方案的最好之处在于,我们不必对ScheduledJob类进行任何更改。 其源代码如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;@Component
public class ScheduledJob {private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJob.class);private final MessageService messageService;@Autowiredpublic ScheduledJob(MessageService messageService) {this.messageService = messageService;}@Scheduled(cron = "${scheduling.job.cron}")public void run() {String message = messageService.getMessage();LOGGER.debug("Received message: {}", message);}
}

调用计划的作业时,将以下行写入日志:

2013-12-17 21:12:14,012 DEBUG - ScheduledJob            - Received message: Hello World!

很酷 对?

摘要

现在,我们已经成功创建了可以调用安全方法的计划作业。 本教程教会了我们三件事:

  • 我们了解到,通常SecurityContext对象存储在ThreadLocal中 ,这意味着在同一线程中执行的所有计划作业均共享相同的安全上下文
  • 我们了解到,如果我们的应用程序使用Spring Security 3.1,并且希望从计划的作业中调用安全方法,那么最简单的方法是在每个计划的作业中配置使用的Authentication对象。
  • 我们学习了如何使用Spring Security 3.2的并发支持,以及如何将SecurityContext对象从一个线程转移到另一个线程。

您可以从Github( Spring Security 3.1和Spring Security 3.2 )获得此博客文章的示例应用程序。

注意: Spring Security 3.2示例的XML配置目前无法正常工作。 如果有时间,我会修复它。

参考: 从工作槽中跳出来:从Petri Kainulainen博客上的JCG合作伙伴 Petri Kainulainen 调用预定作业中的安全方法 。

翻译自: https://www.javacodegeeks.com/2014/01/spring-from-the-trenches-invoking-a-secured-method-from-a-scheduled-job.html

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

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

相关文章

前端共享桌面_2020 前端学习路线总结,哎呦,不错哦!

2020 前端学习路线总结在 GitHub 看到一个很不错的前端学习路线图&#xff08;roadmap&#xff09;&#xff0c;从前端基础到前端工程化&#xff0c;再到跨端&#xff0c;都有知识点的覆盖&#xff0c;非常推荐阅读。图下面是我翻译的一个文字版&#xff0c;可以先看图再看文字…

每日一题题目16:简单的python练习题(1-10)

#1.python程序中__name__的作用是什么?__name__这个系统变量用来表示程序的运行方式. 如果程序在当前膜快运行,__name__的名称就是__main__, 如果不在(被调用),则显示为导入模块的名称.扩展:常常这样写if __name__ "__main__":来表名这是整个工程开始运行的入口.效…

Confluence 6 高级性能诊断

请在你的系统服务请求中包括下面所有的信息&#xff0c;如果可能的话&#xff0c;你也可以在请求中包括你认为最有可能出现的问题。这样的话&#xff0c;可以避免我们进一步对你系统的问题进行询问。 系统信息 Confluence 服务器 你系统信息的屏幕截图 Confluences Administrat…

RequireJS使用注意地方

使用RequireJS做异步模块加载&#xff0c;有几点值得注意的地方&#xff1a; 1.模块定义两种写法 1. 存在依赖的函数式定义 如果模块存在依赖&#xff1a;则第一个参数是依赖的名称数组&#xff1b;第二个参数是函数&#xff0c;在模块的所有依赖加载完毕后&#xff0c;该函…

WildFly上具有AngularJS的Java EE 7和Java WebSocket API(JSR 356)

这篇博客文章描述了用于WebSocket协议的Java API&#xff08;JSR 356&#xff09; &#xff08;这是Java EE 7平台的四个最新JSR之一&#xff09;&#xff0c;并提供了部署在WildFly 8上并可以在OpenShift上在线获得的具体应用程序。 [FR]版本的法语&#xff08; HTML或PDF &a…

用python做自我介绍_python入门教程NO.2 用python做个自我介绍

本文涉及的python基础语法为&#xff1a;数据类型等数据类型1. 字符串的拼接我们在上一章中已经简单介绍了一下字符串的创建方式&#xff0c;这里我们简单学习一下字符串的运算和拼接。字符串的运算字符串的加法#把字符串&#xff1a;hello赋值给变量aa hello#把字符串&#x…

想清楚映射规则,栈、队列、双端队列的实现都差不多

今天开始&#xff0c;啃读算法导论第10章。既然是啃就要有啃的样子&#xff0c;我决定将例题和习题全部用C实现一遍&#xff0c;总结同一类问题的共性。 10.1节介绍了栈&#xff0c;队列&#xff0c;双端队列及一些组合形式&#xff0c;为了突出体现思路&#xff0c;让代码更加…

日期加减加1天_2小时整理了13个时间日期函数,动图演示简单易学,收藏备用吧...

Hello&#xff0c;大家好&#xff0c;今天跟大家整理汇总了13个工作中经常用到的日期与时间函数的使用方法&#xff0c;学会它们几乎可以解决所有工作中遇到的&#xff0c;关于日期与时间提取与转换的问题。话不多说&#xff0c;让我们直接开始吧一、了解时间与日期的本质工作中…

T-1-java语言基础

一、Linux的由来和发展Linux是开源的操作系统Linux是服务器端的操作系统java主要用于服务器端二、Linux目录结构&#xff08;与Windows不同&#xff09;文件系统不同&#xff1a;Windows是盘符&#xff0c;Linux是目录。外部设备映射不同&#xff1a;Windows是盘符&#xff0c;…

点云数据显示_vispy 显示 kitti 点云数据

国内博客找了一圈&#xff0c;居然没有发现有用 vispy 做可视化的代码&#xff0c;这里做一个简单的示例&#xff0c;代码大部分来自官方。import numpy as np import vispy.scene from vispy.scene import visuals import sys# Make a canvas and add simple view canvas vis…

python函数定义关键字_Python(2)深入Python函数定义

在Python中&#xff0c;可以定义包含若干参数的函数&#xff0c;这里有几种可用的形式&#xff0c;也可以混合使用&#xff1a;1. 默认参数最常用的一种形式是为一个或多个参数指定默认值。>>> def ask_ok(prompt,retries4,complaintYes or no Please!):while True:ok…

稳定婚姻(tarjan)

传送门 这道题一开始可能以为是二分图匹配……&#xff1f;不过后来发现和二分图没啥大关系。 简单分析之后发现&#xff0c;把夫妻之间连边&#xff08;男性向女性连边&#xff09;&#xff0c;之后再将每对曾经是情侣的人连边&#xff08;女性向男性连边&#xff09;&#xf…

Webpack 常用命令总结以及常用打包压缩方法

前言&#xff1a;Webpack是一款基于node的前端打包工具&#xff0c;它可以将很多静态文件打包起来&#xff0c;自动处理依赖关系后&#xff0c;生成一个.js文件&#xff0c;然后让html来引用&#xff0c;不仅可以做到按需加载&#xff0c;而且可以减少HTTP请求&#xff0c;节约…

在Spring MVC Web应用程序中添加社交登录:单元测试

Spring Social 1.0具有spring-social-test模块&#xff0c;该模块为测试Connect实现和API绑定提供支持。 该模块已从Spring Social 1.1.0中删除&#xff0c;并由 Spring MVC Test框架替换。 问题在于&#xff0c;实际上没有有关为使用Spring Social 1.1.0的应用程序编写单元测…

CSS3 Filter详解(改变模糊度 亮度 透明度等方法)

文章目录 1.模糊2.灰度3.亮度4.对比度5.饱和度6.色相旋转7.反色8.阴影9.透明度10.褐色CSS3 Filter&#xff08;滤镜&#xff09;属性提供了提供模糊和改变元素颜色的功能。CSS3 Fitler 常用于调整图像的渲染、背景或边框显示效果。 -webkit-filter是css3的一个属性&#xff0c;…

laravel中使用offsetSet

首先不用offsetSet方法&#xff0c;使用laravel的硬添加属性如下&#xff1a; 下边使用offsetSet 转载于:https://www.cnblogs.com/qaing123/p/9672241.html

python做自动化控制postman_使用postman+newman+python做接口自动化测试

postman是一款API调试工具&#xff0c;可用于测试接口&#xff0c;相类似的工具还有jmeter、soupUI。通过postmannewmanpython可以批量运行调试接口&#xff0c;达到自动化测试的效果。1、PostMan安装共有两种方式&#xff0c;一种是chrome浏览器上的插件&#xff0c;一种是pos…

转3d视图快捷键_最全Solidworks快捷键,值得收藏!

SOLIDWORKS软件提供了很多实用的快捷键&#xff0c;如果我们熟练掌握这些快捷键无疑可以减轻工作强度和提高工作效率。当然我们也可以点击【工具】-【自定义】-【键盘】&#xff0c;自己定义一些快捷键。本文为大家整理一些常用的快捷键&#xff0c;多使用快捷键可以帮助我们节…

构建前端自动化工作流环境

目标&#xff1a;建一个自动化工作流环境 自动编译 自动合并 自动刷新 自动部署 工作流&#xff1a; 1 全局安装webpack 执行命令&#xff1a; npm install webpack webpack-cli -g 其中webpack-cil 是命令接口工具 2 初始化当前项目&#xff1a;npm init 然后一路回车…

大型布线:Java云应用程序缺少的技术

您是否曾经想过&#xff0c;为什么大多数Java框架中的依赖项注入仅用于本地进程内服务而不是分布式服务&#xff1f; 我最近在2013年EMC世界大会上遇到了Paul Maritz的主题演讲 &#xff08;跳至第32分钟&#xff09;&#xff0c;这使我在云平台的背景下思考了这个问题。 主题…