springaop实现相关功能(事务、异常处理、记录日志)

server/2024/12/22 3:50:39/

springaop_0">springaop的底层实现(代理模式)

  • 原理设计

    • 代理模式:
      Spring AOP使用了代理模式,为目标对象创建一个代理对象。当调用目标对象的方法时,实际上是调用代理对象的方法。代理对象在调用目标方法前后,可以插入额外的代码(即切面的通知),从而实现AOP的功能。
    • 字节码操作:
      Spring AOP使用字节码操作技术来创建代理对象。具体地,它会为目标对象生成一个子类(使用CGLIB代理)或接口的实现类(使用JDK动态代理),并在该类中重写目标方法,以插入额外的代码。
  • JDK动态代理

    • 如果目标对象实现了至少一个接口,Spring AOP会使用JDK动态代理。JDK动态代理通过反射机制为目标接口创建一个代理类,并调用InvocationHandler接口的invoke方法来执行目标方法。
    • JDK动态代理主要基于Java的反射机制,它要求被代理的对象必须实现至少一个接口。代理类在运行时动态生成,并实现了与被代理对象相同的接口。当调用代理对象的方法时,实际上是通过反射机制调用被代理对象的相应方法。
      • JDK动态代理优点:

        • 简单易用:JDK动态代理使用Java标准库中的Proxy类和InvocationHandler接口,无需额外引入第三方库,使用相对简单。
        • 灵活性:能够动态地创建代理类,并可以灵活地添加额外的功能,如日志记录、性能监控等。
        • 通用性:由于基于接口代理,因此可以很方便地对不同的接口进行代理,实现通用的代理逻辑。
      • JDK动态代理缺点:

        • 只能代理接口:JDK动态代理要求目标对象必须实现至少一个接口,如果目标类没有实现任何接口,则无法使用JDK动态代理进行代理。

        • 性能开销:由于JDK动态代理基于反射机制实现,因此在调用代理方法时可能存在一定的性能开销。

        • 举例,假设我们有一个接口MyInterface和一个实现了该接口的类MyClass:

          public interface MyInterface {  void doSomething();  
          }  public class MyClass implements MyInterface {  @Override  public void doSomething() {  System.out.println("Doing something in MyClass");  }  
          }
          
        • 我们可以使用JDK动态代理为MyClass创建一个代理对象:

          import java.lang.reflect.InvocationHandler;  
          import java.lang.reflect.Method;  
          import java.lang.reflect.Proxy;  public class MyInvocationHandler implements InvocationHandler {  private Object target;  public MyInvocationHandler(Object target) {  this.target = target;  }  @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  System.out.println("Before method invocation");  Object result = method.invoke(target, args);  System.out.println("After method invocation");  return result;  }  public static void main(String[] args) {  MyInterface myClass = new MyClass();  MyInterface proxy = (MyInterface) Proxy.newProxyInstance(  MyClass.class.getClassLoader(),  myClass.getClass().getInterfaces(),  new MyInvocationHandler(myClass)  );  proxy.doSomething();  // 输出:Before method invocation, Doing something in MyClass, After method invocation  }  
          }
          
        • 在这个例子中,我们创建了一个实现了InvocationHandler接口的类MyInvocationHandler,并在其中添加了额外的逻辑(在方法调用前后打印消息)。然后,我们使用Proxy.newProxyInstance方法创建了一个代理对象,该对象实现了与MyClass相同的接口。当我们调用代理对象的doSomething方法时,实际上会先调用MyInvocationHandlerinvoke方法,然后再调用MyClassdoSomething方法。

  • CGLIB代理

    • 如果目标对象没有实现任何接口,Spring AOP会使用CGLIB代理。CGLIB是一个强大的高性能的代码生成库,它可以在运行时扩展Java类与实现Java接口。CGLIB通过继承目标类来创建代理类,并重写目标方法。

    • 与JDK动态代理不同,CGLIB代理是通过继承被代理类来创建代理对象的。因此,它不需要被代理对象实现任何接口。CGLIB代理在运行时动态生成被代理类的子类,并重写其中的方法。当调用代理对象的方法时,实际上是调用CGLIB生成的子类中的相应方法。

      • CGLIB代理优点:
        • 可以代理任意类:CGLIB代理通过继承目标类来创建代理对象,因此无需目标类实现接口,可以代理任意类。
        • 性能较好:CGLIB代理通过直接操作字节码生成新的类,避免了使用反射的开销,因此在性能方面通常优于JDK动态代理。
        • 提供更多控制:CGLIB代理提供了更多的控制选项,如方法拦截、方法回调等,可以实现更复杂的代理逻辑。
      • CGLIB代理缺点:
        • 无法代理final类和方法:由于CGLIB代理是通过继承目标类实现的,因此无法代理被标记为final的类和方法。

        • 使用相对复杂:CGLIB代理需要引入第三方库,并且其实现逻辑相对复杂,需要一定的编程经验和技能。

        • 性能开销:虽然CGLIB代理在性能方面通常优于JDK动态代理,但由于它涉及到字节码操作,因此在创建代理类的过程中可能会存在一定的性能开销。

        • 使用CGLIB代理,需要添加CGLIB库的依赖。然后,可以创建一个实现了MethodInterceptor接口的类来定义代理逻辑:

          import net.sf.cglib.proxy.MethodInterceptor;  
          import net.sf.cglib.proxy.MethodProxy;  
          import java.lang.reflect.Method;  
          import java.lang.reflect.Object;  public class MyMethodInterceptor implements MethodInterceptor {  @Override  public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {  System.out.println("Before method invocation");  Object result = proxy.invokeSuper(obj, args);  // 调用父类(即被代理类)的方法  System.out.println("After method invocation");  return result;  }  
          }
          

          接下来,我们可以使用CGLIB来创建MyClass的代理对象:

          import net.sf.cglib.proxy.Enhancer;  public class Main {  public static void main(String[] args) {  Enhancer enhancer = new Enhancer();  enhancer.setSuperclass(MyClass.class);  // 设置被代理类  enhancer.setCallback(new MyMethodInterceptor());  // 设置回调(即代理逻辑)  MyClass proxy = (MyClass) enhancer.create();  // 创建代理对象  proxy.doSomething();  // 输出:Before method invocation, Doing something in MyClass, After method invocation  }  
          }
          

          在这个例子中,创建了一个实现了MethodInterceptor接口的类MyMethodInterceptor,并在其中添加了额外的逻辑。然后,我们使用CGLIB的Enhancer类来创建MyClass的代理对象,并设置回调为MyMethodInterceptor的实例。当我们调用代理对象的doSomething方法时,实际上会先调用MyMethodInterceptorintercept方法,然后再调用MyClass的方法

    1. 代理类的生成:
      Spring AOP使用AspectJ的weaver组件来生成代理类的字节码。weaver组件负责解析切面定义、切点表达式,并生成相应的代理类。
    2. 通知的执行:
      当代理对象的方法被调用时,Spring AOP会根据切点表达式匹配相应的通知。然后,按照通知的类型(前置、后置、环绕等)执行相应的代码。
    3. 事务管理:
      Spring AOP的事务管理是一个重要的应用场景。通过配置事务管理器、事务属性(如传播行为、隔离级别等),Spring AOP可以在方法执行前后自动开启和提交/回滚事务。

异常处理

  • 定义一个切面来捕获和处理方法执行过程中抛出的异常:
    1. 准备:AOP 的依赖,如果你使用 Maven,可以在 pom.xml 文件中添加以下依赖:

      <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-aop</artifactId>  
      </dependency>
      
    2. 创建一个异常处理切面 ExceptionHandlingAspect,使用 @AfterThrowing 注解来定义在方法抛出异常后执行的通知:

      import org.aspectj.lang.JoinPoint;  
      import org.aspectj.lang.annotation.AfterThrowing;  
      import org.aspectj.lang.annotation.Aspect;  
      import org.springframework.stereotype.Component;  @Aspect  
      @Component  
      public class ExceptionHandlingAspect {  @AfterThrowing(pointcut = "execution(* com.example.myapp.service.*.*(..))", throwing = "exception")  public void handleException(JoinPoint joinPoint, Throwable exception) {  // 处理异常逻辑  String methodName = joinPoint.getSignature().getName();  String className = joinPoint.getTarget().getClass().getName();  System.err.println("Exception occurred in method " + methodName + " of class " + className);  exception.printStackTrace();  // 你可以在这里记录异常到日志、发送邮件通知、执行回滚操作等  }  
      }
      
    3. 如上代码@Aspect 注解标记这个类为一个切面

      • @Component 注解使得 Spring 能够自动发现并注册这个 bean。
      • @AfterThrowing 注解定义了一个异常通知,它会在匹配 pointcut 表达式的方法抛出异常后执行。
      • throwing = "exception" 指定了通知方法的参数名,用于接收抛出的异常。
    4. 在 Spring 配置中启用 AOP 支持,Spring Boot,可以通过添加 @EnableAspectJAutoProxy 注解来启用 AOP:

      import org.springframework.boot.SpringApplication;  
      import org.springframework.boot.autoconfigure.SpringBootApplication;  
      import org.springframework.context.annotation.EnableAspectJAutoProxy;  @SpringBootApplication  
      @EnableAspectJAutoProxy  
      public class MyApplication {  public static void main(String[] args) {  SpringApplication.run(MyApplication.class, args);  }  
      }
      
    5. 当你的应用程序中的 com.example.myapp.service 包下的任意方法抛出异常时,ExceptionHandlingAspect 切面中的 handleException 方法将被自动调用,处理异常逻辑。

    6. 注意,还可以使用 @Around 注解来在方法执行前后进行更精细的控制,包括在异常发生时执行特定的逻辑。

      import org.aspectj.lang.ProceedingJoinPoint;  
      import org.aspectj.lang.annotation.Around;  
      import org.aspectj.lang.annotation.Aspect;  
      import org.springframework.stereotype.Component;  @Aspect  
      @Component  
      public class ExceptionHandlingAspect {  @Around("execution(* com.example.myapp.service.*.*(..))")  public Object handleExceptions(ProceedingJoinPoint joinPoint) throws Throwable {  try {  // 在目标方法执行之前执行一些操作(可选)  System.out.println("Before method execution: " + joinPoint.getSignature());  // 继续执行目标方法  Object result = joinPoint.proceed();  // 在目标方法执行之后执行一些操作(可选)  System.out.println("After method execution: " + joinPoint.getSignature());  return result;  } catch (Exception ex) {  // 处理异常  System.out.println("Exception occurred: " + ex.getMessage());  // 这里可以记录日志、发送通知等  // 可以选择抛出异常或者返回一个默认值、错误响应等  throw new RuntimeException("An error occurred during method execution", ex);  }  }  
      }
      

springmvc_224">springmvc拦截器实现异常处理

  • Spring MVC中使用拦截器和@ControllerAdvice与@ExceptionHandler来处理异常。

    1. 创建一个简单的拦截器来记录请求信息:
    import javax.servlet.http.HttpServletRequest;  
    import javax.servlet.http.HttpServletResponse;  import org.springframework.web.servlet.HandlerInterceptor;  
    import org.springframework.web.servlet.ModelAndView;  public class LoggingInterceptor implements HandlerInterceptor {  @Override  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)  throws Exception {  // 在请求处理之前记录日志  System.out.println("Pre-Handle: " + request.getRequestURI());  return true; // 继续处理请求  }  @Override  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,  ModelAndView modelAndView) throws Exception {  // 在请求处理之后但在视图渲染之前记录日志  System.out.println("Post-Handle: " + request.getRequestURI());  }  @Override  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)  throws Exception {  // 在请求处理完成之后(包括视图渲染)记录日志  System.out.println("After-Completion: " + request.getRequestURI());  // 注意:这里虽然可以访问到异常,但不建议在这里处理异常  // 因为此时请求已经处理完成,并且可能已经将异常信息发送给了客户端  if (ex != null) {  System.out.println("Exception occurred: " + ex.getMessage());  }  }  
    }
    
    1. 配置拦截器:
    import org.springframework.beans.factory.annotation.Autowired;  
    import org.springframework.context.annotation.Configuration;  
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;  
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;  @Configuration  
    public class WebConfig implements WebMvcConfigurer {  @Autowired  private LoggingInterceptor loggingInterceptor;  @Override  public void addInterceptors(InterceptorRegistry registry) {  registry.addInterceptor(loggingInterceptor);  }  
    }
    
    1. 创建一个@ControllerAdvice类来处理异常:
    import org.springframework.http.HttpStatus;  
    import org.springframework.http.ResponseEntity;  
    import org.springframework.web.bind.annotation.ControllerAdvice;  
    import org.springframework.web.bind.annotation.ExceptionHandler;  @ControllerAdvice  
    public class GlobalExceptionHandler {  @ExceptionHandler(value = Exception.class)  public ResponseEntity<Object> handleException(Exception e) {  // 这里处理异常,比如记录日志、返回错误信息等  ErrorDto errorDto = new ErrorDto("Error", e.getMessage());  return new ResponseEntity<>(errorDto, HttpStatus.INTERNAL_SERVER_ERROR);  }  // ErrorDto 是一个简单的类,用于封装错误信息  // ...  
    }
    
    1. 测试在控制器中,抛出一个异常来测试异常处理是否生效:
    import org.springframework.web.bind.annotation.GetMapping;  
    import org.springframework.web.bind.annotation.RestController;  @RestController  
    public class MyController {  @GetMapping("/testException")  public String testException() {  throw new RuntimeException("Test Exception");  }  
    }
    
  • 访问/testException端点时,会触发异常,并且GlobalExceptionHandler中的handleException方法会被调用,返回一个包含错误信息的ResponseEntity对象。同时,拦截器的preHandlepostHandleafterCompletion方法也会被调用,你可以在afterCompletion方法中看到异常信息(如果有的话)。

  • 注意:不要在afterCompletion方法中处理异常,因为它通常用于清理资源等后续操作。

springaop_333">springaop实现日志

import org.aspectj.lang.ProceedingJoinPoint;  
import org.aspectj.lang.annotation.Around;  
import org.aspectj.lang.annotation.Aspect;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.stereotype.Component;  @Aspect  
@Component  
public class LoggingAspect {  private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);  @Around("execution(* com.example.myapp.service.*.*(..))")  public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {  long startTime = System.currentTimeMillis();  // 记录方法执行前的日志  logger.info("Entering method: " + joinPoint.getSignature());  Object result = joinPoint.proceed(); // 继续执行目标方法  // 记录方法执行后的日志  long endTime = System.currentTimeMillis();  logger.info("Exiting method: " + joinPoint.getSignature() + " took " + (endTime - startTime) + "ms");  return result;  }  
}

springaop_367">springaop实现事务

  • 关于事务管理,Spring AOP通过@Transactional注解和PlatformTransactionManager接口来实现。以下是如何使用Spring AOP实现事务管理的步骤:

    1. 配置事务管理器:

      • 首先,你需要在Spring配置文件中配置一个事务管理器。事务管理器的类型取决于你使用的数据库和连接池。如果你使用的是MyBatis和JDBC,你可能会使用DataSourceTransactionManager

        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  <property name="dataSource" ref="dataSource"/>  
        </bean>
        
    2. 启用事务注解:

      • 在Spring配置中启用对@Transactional注解的支持。这可以通过<tx:annotation-driven/>标签来完成。
      <tx:annotation-driven transaction-manager="transactionManager"/>
      
      • Java配置:
      @Configuration  
      @EnableTransactionManagement  
      public class AppConfig {  // ... 其他bean配置 ...  
      }
      
    3. 使用@Transactional注解:
      使用@Transactional注解来声明这些方法需要在事务上下文中执行。Spring会自动为你管理这些事务。

      @Service  
      public class MyService {  @Autowired  private MyRepository myRepository;  @Transactional  public void doSomethingInTransaction() {  // 这里执行一些数据库操作,如果发生异常,所有操作都会被回滚  myRepository.save(new MyEntity());  }  
      }
      
      • 事务属性:
        @Transactional注解还允许你指定事务的属性,比如传播行为、隔离级别、超时和只读标志等。例如:

        @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)  
        public void doSomethingWithCustomTransactionAttributes() {  // ...  
        }
        
    4. 异常处理:
      默认情况下,Spring会在运行时异常(RuntimeException及其子类)发生时回滚事务。如果你希望在其他类型的异常(如检查型异常)发生时也回滚事务,你可以使用rollbackFor属性来指定。

springaop_433">springaop实现权限认证

  • 步骤:

    1. 定义切面:创建一个切面类,用于拦截需要进行权限认证的方法。
    2. 定义切入点:在切面类中,使用AspectJ的切入点表达式定义哪些方法需要被拦截。
    3. 实现通知方法:在切面类中,实现一个或多个通知方法(如@Before、@Around等),在这些方法中实现权限认证逻辑。
  • 示例

  1. 定义一些需要权限认证的服务接口和实现类:

    // 服务接口  
    public interface MyService {  void secureMethod(String userId);  
    }  // 服务实现类  
    @Service  
    public class MyServiceImpl implements MyService {  @Override  public void secureMethod(String userId) {  // 业务逻辑  System.out.println("Executing secure method for user: " + userId);  }  
    }
    
  2. 创建一个切面类用于权限认证:

    @Aspect  
    @Component  
    public class AuthorizationAspect {  // 假设有一个权限检查服务  @Autowired  private PermissionCheckService permissionCheckService;  // 切入点表达式,拦截MyService接口的所有方法  @Before("execution(* com.example.myapp.service.MyService.*(..))")  public void checkPermission(JoinPoint joinPoint) {  // 获取方法参数  Object[] args = joinPoint.getArgs();  if (args.length > 0 && args[0] instanceof String) {  String userId = (String) args[0];  // 检查用户是否有权限执行该方法  if (!permissionCheckService.hasPermission(userId, joinPoint.getSignature().getName())) {  throw new AccessDeniedException("Access denied for user: " + userId);  }  }  }  
    }
    
  3. 接下来,定义一个简单的PermissionCheckService接口和实现类,用于模拟权限检查逻辑:

    public interface PermissionCheckService {  boolean hasPermission(String userId, String methodName);  
    }  @Service  
    public class PermissionCheckServiceImpl implements PermissionCheckService {  @Override  public boolean hasPermission(String userId, String methodName) {  // 这里只是示例,实际应用中需要根据业务逻辑和权限配置来判断  return "admin".equals(userId);  }  
    }
    
  4. 在Spring Boot项目中启用AOP支持,可以通过在启动类上添加@EnableAspectJAutoProxy注解来实现:

    @SpringBootApplication  
    @EnableAspectJAutoProxy  
    public class MyApplication {  public static void main(String[] args) {  SpringApplication.run(MyApplication.class, args);  }  
    }
    
  • 总述:当调用MyServicesecureMethod方法时,AuthorizationAspect切面会自动拦截该方法,并执行权限认证逻辑。如果用户没有权限,则会抛出异常。

springMVC_526">springMVC使用拦截器实现权限认证&日志记录

  • 创建一个拦截器类,实现 HandlerInterceptor 接口。
  • 在 preHandle 方法中,检查当前用户是否有权限访问目标资源。
    • 如果用户没有权限,则设置响应状态码或重定向到错误页面。
    • 如果用户有权限,则继续处理请求。
public class AuthenticationInterceptor implements HandlerInterceptor {  @Autowired  private AuthenticationService authenticationService; // 假设的权限服务  @Override  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  // 在Controller处理之前调用  // 可以在这里执行权限检查、记录日志等  // 如果返回false,则请求处理到此为止,不会调用Controller  // 获取当前用户(这里假设可以从请求中获取)  User user = ...;  // 检查用户是否有权限访问目标资源(这里假设 handler 是 HandlerMethod)  if (handler instanceof HandlerMethod) {  HandlerMethod handlerMethod = (HandlerMethod) handler;  if (!authenticationService.isAuthenticated(user, handlerMethod.getBeanType(), handlerMethod.getMethod().getName())) {  response.sendError(HttpServletResponse.SC_FORBIDDEN); // 发送 403 Forbidden 响应  return false; // 阻止后续处理  }  }  // 如果有权限,则继续处理请求  return true;  }  @Override  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {  // 在Controller处理请求完成之后调用,但在视图渲染之前  // 可以在这里进行模型属性的修改等  System.out.println("MyInterceptor - postHandle");  }  @Override  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {  // 在整个请求结束之后调用,即在视图渲染之后  // 通常用于清理资源、记录执行时间等  System.out.println("MyInterceptor - afterCompletion");  }  }
  • 拦截器注册
java
import org.springframework.context.annotation.Configuration;  
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;  
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;  @Configuration  
public class WebConfig implements WebMvcConfigurer {  @Override  public void addInterceptors(InterceptorRegistry registry) {  registry.addInterceptor(new MyInterceptor());  // 还可以添加多个拦截器,并为它们指定路径模式  /** registry.addInterceptor(anotherInterceptor()).addPathPatterns("/somePath/**").excludePathPatterns("/static/**");;  }  
}

http://www.ppmy.cn/server/26878.html

相关文章

C++入门基础(二)

目录 缺省参数缺省参数概念缺省参数分类全缺省参数半缺省参数声明与定义分离 缺省参数的应用 函数重载函数重载概念例子1 参数类型不同例子2 参数的个数不同例子3 参数的顺序不同 C支持函数重载的原理--名字修饰(name Mangling) 感谢各位大佬对我的支持,如果我的文章对你有用,欢…

tauri2 riscv wasm leptos debian

目前 riscv 相关的 debian 里的库与 x86 不太兼容&#xff0c;不能像 arm 那样方便&#xff0c;tauri 要在 x86 上交叉编译到 riscv 有点麻烦&#xff0c;主要问题就是没有资料和编译慢&#xff0c;要用模拟器 sudo apt install mmdebstrap qemu-user-static binfmt-support s…

最近邻回归(概念+实例)

目录 前言 一、基本概念 1. KNN回归的原理 2. KNN回归的工作原理举例 3. KNN回归的参数 4. KNN回归的优缺点 5. KNN回归的应用场景 二、实例 前言 最近邻回归&#xff08;K-nearest neighbors regression&#xff0c;简称KNN回归&#xff09;是一种简单而又直观的非参数…

MT3608B 航天民芯代理 1.2Mhz 24V输入 升压转换器

深圳市润泽芯电子有限公司为航天民芯一级代理商 技术支持欢迎试样~Tel&#xff1a;18028786817 简述 MT3608B是恒定频率的6针SOT23电流模式升压转换器&#xff0c;用于小型、低功耗应用。MT3608B开关频率为1.2MHz&#xff0c;允许使用微小、低电平成本电容器和电感器高度不…

Gateway服务网关!!!

一、为什么需要服务网关&#xff1a; 两大特性&#xff1a;高可用和高性能 1、高性能&#xff1a;采用异步的方式调用服务。 2、高可用 二、网关包含三大属性&#xff1a; 三、基本配置 <dependency><groupId>org.springframework.boot</groupId><artif…

拉索回归(Lasso)算法原理讲解

拉索回归&#xff08;Lasso Regression&#xff09;是机器学习中的一种线性回归方法&#xff0c;它在回归问题中加入了L1正则化项&#xff0c;有助于进行特征选择和模型稀疏化。下面是对拉索回归算法原理的讲解&#xff1a; 线性回归基础&#xff1a; 首先&#xff0c;我们先回…

OSS 文件下载-Excel

发起请求网址如果是 www.baidu.com&#xff0c;跨域下载 Google cdn 的资源 Excel 文件 背景&#xff1a; Excel 模板存储在 OSS 上&#xff0c;提供的一个链接&#xff0c;需要支持 用户点击下载 方案&#xff1a; V1 问题&#xff1a;跨域 a标签 download 属性失效 &l…

【分治算法】【Python实现】整数划分问题

文章目录 [toc]问题描述分治算法Python实现 个人主页&#xff1a;丷从心 系列专栏&#xff1a;分治算法 学习指南&#xff1a;Python学习指南 问题描述 将正整数 n n n表示成一系列正整数之和&#xff0c; n n 1 n 2 ⋯ n k ( n 1 ≥ n 2 ≥ ⋯ ≥ n k ≥ 1 , k ≥ 1 ) …