SpringBoot中使用注解来实现 Redis 分布式锁

SpringBoot 八种实现异步的方法 异步的八种实现方式 阅读详情

Springboot 中异步线程的使用

在过往的后台开发中,我们往往使用java自带的线程或线程池,来进行异步的调用。这对于效果来说没什么,甚至可以让开发人员对底层的状况更清晰,但是对于代码的易读性和可维护性却非常的差。

开发人员在实际使用过程中,应该更多的将精力放置在业务代码的书写过程中,而不是系统代码的维护中。你需要懂,但是不需要你直接维护去写,这才是编程语言的风向标。(这也是为什么spring在目前的java开发中,占用比重如此之大的原因之一)

下面来看使用Springboot 来实现异步调用的集中场景

一、简易注解,无需额外配置

1、添加@EnableAsync 到启动类(或者线程池配置类中)

2、添加@Async到需要异步执行的方法中

代码如下:

启动类

1 @EnableAsync
2 @SpringBootApplication
3 public class DemoLearnSpringbootApplication {
4 
5     public static void main(String[] args) {
6         SpringApplication.run(DemoLearnSpringbootApplication.class, args);
7     }
8 }

调用类

1 @Component
 2 public class SimpleAsyncDemo {
 3     @Autowired
 4     private SimpleTaskHandler simpleTaskHandler;
 5 
 6 
 7     @PostConstruct
 8     public void execTaskHandler1() {
 9         try {
10             simpleTaskHandler.handle1(2);
11             simpleTaskHandler.handle2(2);
12             simpleTaskHandler.handle3(2);
13             simpleTaskHandler.handle1(2);
14             simpleTaskHandler.handle2(2);
15             simpleTaskHandler.handle3(2);
16             simpleTaskHandler.handle1(2);
17             simpleTaskHandler.handle2(2);
18             simpleTaskHandler.handle3(2);
19         } catch (InterruptedException e) {
20             e.printStackTrace();
21         }
22     }
23   
24 }

被异步调用的类

 1 @Component
 2 public class SimpleTaskHandler {
 3 
 4     public void printCurrentTime(String key) {
 5         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 6         System.out.println(format.format(new Date()) + "***" + key + "****" + Thread.currentThread().getName());
 7     }
 8 
 9     @Async
10     public void handle1(int time) throws InterruptedException {
11         TimeUnit.SECONDS.sleep(time);
12         printCurrentTime("handle1");
13     }
14 
15     @Async
16     public void handle2(int time) throws InterruptedException {
17         TimeUnit.SECONDS.sleep(time);
18         printCurrentTime("handle2");
19     }
20 
21     @Async
22     public void handle3(int time) throws InterruptedException {
23         TimeUnit.SECONDS.sleep(time);
24         printCurrentTime("handle3");
25     }
26 
27 
28 }

执行结果

handle1、handle2、handle3的执行结果为乱序,不可预估。这样最简易的通过2个注解即完成异步线程的调用了。

细心的同学已经发现了,连续调用9次异步线程后,最后一次的线程名称就会与之前的重复。这是由于默认的线程池配置的结果。

默认配置如下

 

# 核心线程数
spring.task.execution.pool.core-size=8  
# 最大线程数
spring.task.execution.pool.max-size=16
# 空闲线程存活时间
spring.task.execution.pool.keep-alive=60s
# 是否允许核心线程超时
spring.task.execution.pool.allow-core-thread-timeout=true
# 线程队列数量
spring.task.execution.pool.queue-capacity=100
# 线程关闭等待
spring.task.execution.shutdown.await-termination=false
spring.task.execution.shutdown.await-termination-period=
# 线程名称前缀
spring.task.execution.thread-name-prefix=task-

二、自定义线程池

只通过注解来完成异步线程调用,简单明了,对应的异步线程来自springboot 默认生成的异步线程池。但是有些场景却并不满足。所以我们需要针对业务需要定义自己的线程池配置文件

1、在application.properties中定义我们自己的线程池配置

2、在springboot项目中,添加对应的线程池bean对象

3、添加@EnableAsync 到启动类(或者线程池配置类中)

4、添加@Async到需要异步执行的方法中

代码如下:

application.properties配置文件

task.pool.demo.corePoolSize= 5
task.pool.demo.maxPoolSize= 10
task.pool.demo.keepAliveSeconds= 300
task.pool.demo.queueCapacity= 50

调用类

1 @Component
 2 public class SimpleAsyncDemo {
 3 
 4     @Autowired
 5     private PoolTaskHandler poolTaskHandler;
 6 
 7 
 8     @PostConstruct
 9     public void execTaskHandler2() {
10         try {
11             poolTaskHandler.handle1(2);
12             poolTaskHandler.handle2(2);
13             poolTaskHandler.handle3(2);
14             poolTaskHandler.handle1(2);
15             poolTaskHandler.handle2(2);
16             poolTaskHandler.handle3(2);
17             poolTaskHandler.handle1(2);
18             poolTaskHandler.handle2(2);
19             poolTaskHandler.handle3(2);
20         } catch (InterruptedException e) {
21             e.printStackTrace();
22         }
23     }
24 
25 }

异步线程池的配置类

 1 @Configuration
 2 public class ThreadPoolConfig {
 3 
 4     @Value("${task.pool.demo.corePoolSize}")
 5     private int corePoolSize;
 6     @Value("${task.pool.demo.maxPoolSize}")
 7     private int maxPoolSize;
 8     @Value("${task.pool.demo.queueCapacity}")
 9     private int queueCapacity;
10     @Value("${task.pool.demo.keepAliveSeconds}")
11     private int keepAliveSeconds;
12 
13 
14     @Bean("handleAsync")
15     public TaskExecutor taskExecutor() {
16         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
17         // 设置核心线程数
18         executor.setCorePoolSize(corePoolSize);
19         // 设置最大线程数
20         executor.setMaxPoolSize(maxPoolSize);
21         // 设置队列容量
22         executor.setQueueCapacity(queueCapacity);
23         // 设置线程活跃时间(秒)
24         executor.setKeepAliveSeconds(keepAliveSeconds);
25         // 设置默认线程名称前缀
26         executor.setThreadNamePrefix("Thread-ABC-");
27         // 设置拒绝策略
28         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
29         // 等待所有任务结束后再关闭线程池
30         executor.setWaitForTasksToCompleteOnShutdown(true);
31         return executor;
32     }
33 }

被异步调用的类

1 @Component
 2 public class PoolTaskHandler {
 3 
 4     public void printCurrentTime(String key) {
 5         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 6         System.out.println(format.format(new Date()) + "***" + key + "****" + Thread.currentThread().getName());
 7     }
 8 
 9     @Async("handleAsync")
10     public void handle1(int time) throws InterruptedException {
11         TimeUnit.SECONDS.sleep(time);
12         printCurrentTime("handle-1");
13     }
14 
15     @Async("handleAsync")
16     public void handle2(int time) throws InterruptedException {
17         TimeUnit.SECONDS.sleep(time);
18         printCurrentTime("handle-2");
19     }
20 
21     @Async("handleAsync")
22     public void handle3(int time) throws InterruptedException {
23         TimeUnit.SECONDS.sleep(time);
24         printCurrentTime("handle-3");
25     }
26 
27 
28 }

执行结果如下

与上例类似,我们发现请求线程变成了每5个一批,这与我们在配置文件中的配置互相印证

调用类

 1 @Component
 2 public class SimpleAsyncDemo {
 3 
 4     @Autowired
 5     private ReturnTaskHandler returnTaskHandler;
 6 
 7     @PostConstruct
 8     public void execTaskHandler3() {
 9         try {
10             String a1 = returnTaskHandler.handle1(2);
11             String a2 = returnTaskHandler.handle2(2);
12             String a3 = returnTaskHandler.handle3(2);
13             String a4 = returnTaskHandler.handle1(2);
14             String a5 = returnTaskHandler.handle2(2);
15             String a6 = returnTaskHandler.handle3(2);
16             String a7 = returnTaskHandler.handle1(2);
17             String a8 = returnTaskHandler.handle2(2);
18             String a9 = returnTaskHandler.handle3(2);
19             int c = 1;
20         } catch (InterruptedException e) {
21             e.printStackTrace();
22         }
23     }
24 
25 }

 被调用类

 1 @Component
 2 public class ReturnTaskHandler {
 3 
 4     public void printCurrentTime(String key) {
 5         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 6         System.out.println(format.format(new Date()) + "***" + key + "****" + Thread.currentThread().getName());
 7     }
 8 
 9     @Async("handleAsync")
10     public String handle1(int time) throws InterruptedException {
11         TimeUnit.SECONDS.sleep(time);
12         printCurrentTime("handle-1");
13         return "result1";
14     }
15 
16     @Async("handleAsync")
17     public String handle2(int time) throws InterruptedException {
18         TimeUnit.SECONDS.sleep(time);
19         printCurrentTime("handle-2");
20         return "result2";
21     }
22 
23     @Async("handleAsync")
24     public String handle3(int time) throws InterruptedException {
25         TimeUnit.SECONDS.sleep(time);
26         printCurrentTime("handle-3");
27         return "result3";
28     }
29 
30 }

其余代码继续我们使用上文中的其他代码

结果如下

所有结果返回都是null值。

如果想要拿到正确的执行结果,我们需要使用future接口类看来帮忙接住异步线程的返回结果(关于future等接口类的内容我会在后边的文章中讲解)

其余代码继续我们使用上文中的其他代码,改动的代码如下:

被调用类

 1 @Component
 2 public class ReturnSuccTaskHandler {
 3 
 4     public void printCurrentTime(String key) {
 5         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 6         System.out.println(format.format(new Date()) + "***" + key + "****" + Thread.currentThread().getName());
 7     }
 8 
 9     @Async("handleAsync")
10     public Future<String> handle1(int time) throws InterruptedException {
11         TimeUnit.SECONDS.sleep(time);
12         printCurrentTime("handle-1");
13         return new AsyncResult<>("result1");
14     }
15 
16     @Async("handleAsync")
17     public Future<String> handle2(int time) throws InterruptedException {
18         TimeUnit.SECONDS.sleep(time);
19         printCurrentTime("handle-2");
20         return new AsyncResult<>("result2");
21     }
22 
23     @Async("handleAsync")
24     public Future<String> handle3(int time) throws InterruptedException {
25         TimeUnit.SECONDS.sleep(time);
26         printCurrentTime("handle-3");
27         return new AsyncResult<>("result3");
28     }
29 
30 
31 }

 调用类

 1 @Component
 2 public class SimpleAsyncDemo {
 3 
 4 
 5     @Autowired
 6     private ReturnSuccTaskHandler returnSuccTaskHandler;
 7 
 8 
 9 
10     @PostConstruct
11     public void execTaskHandler4() {
12         try {
13             Future<String> a1 = returnSuccTaskHandler.handle1(2);
14             Future<String> a2 = returnSuccTaskHandler.handle2(2);
15             Future<String> a3 = returnSuccTaskHandler.handle3(2);
16             Future<String> a4 = returnSuccTaskHandler.handle1(2);
17             Future<String> a5 = returnSuccTaskHandler.handle2(2);
18             Future<String> a6 = returnSuccTaskHandler.handle3(2);
19             Future<String> a7 = returnSuccTaskHandler.handle1(2);
20             Future<String> a8 = returnSuccTaskHandler.handle2(2);
21             Future<String> a9 = returnSuccTaskHandler.handle3(2);
22             while (true){
23                 // 如果任务都做完就执行如下逻辑
24                 if (a1.isDone() &&
25                         a2.isDone()&&
26                         a3.isDone()&&
27                         a4.isDone()&&
28                         a5.isDone()&&
29                         a6.isDone()&&
30                         a7.isDone()&&
31                         a8.isDone()&&
32                         a9.isDone()){
33                     SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
34                     System.out.println(format.format(new Date()) + "async task end.");
35                     System.out.println("async result:"+a1.get());
36                     System.out.println("async result:"+a2.get());
37                     System.out.println("async result:"+a3.get());
38                     System.out.println("async result:"+a3.get());
39                     System.out.println("async result:"+a4.get());
40                     System.out.println("async result:"+a5.get());
41                     System.out.println("async result:"+a6.get());
42                     System.out.println("async result:"+a7.get());
43                     System.out.println("async result:"+a8.get());
44                     System.out.println("async result:"+a9.get());
45                     break;
46                 }
47             }
48         } catch (InterruptedException | ExecutionException e) {
49             e.printStackTrace();
50         }
51     }
52 
53 
54 }

输出结果如下,我们可以发现 ,1、可以拿到返回结果,2、在最后一个子任务执行完成后,即立刻拿到结果。

Spring Boot异步请求处理框架 ​ 在Spring Boot项目中,经常会遇到处理时间过长,导致出现HTTP请求超时问题,状态码:502。​ 例如一个文件导入接口需要导入一个Excel文件的学员记录,原来是针对一个班的学员,最多500条记录,1分钟的HTTP超时时长之内基本可以响应。现在将很多班级的学员混在一起,放在一个Excel文件中(这样可以提高操作人员的工作效率),比如5万条学员记录,于是就出现HTTP请求超时问题。​ ​解决方案有:1)Ajax异步请求方式;2)WebSocket方式;3)异步请求处理方式:请求+轮询。 阅读详情

相关推荐

配置@Async异步任务的线程池

上一篇我们介绍了如何使用@Async注解来创建异步任务,我可以用这种方法来实现一些并发操作,以加速任务的执行效率。但是,如果只是如前文那样直接简单的创建来使用,可能还是会碰到一些问题。存在有什么问题呢?先来思考下,下面的这个接口,通过异步任务加速执行的实现,是否存在问题或风险呢? @RestController public class HelloController { @Autowired private AsyncTasks asyncTasks; @G

q1472750149的博客 1349

java中的异步请求

线程池 多线程

qq_44718662的博客 2471

SpringBoot+自定义注解+Redis实现分布式锁

SpringBoot+自定义注解+Redis实现分布式锁1. 自定义注解+动态代理:日志2. 自定义注解+AOP:分布式锁2.1 Redis分布式锁理论2.2 Redis整合 封装服务类2.3 自定义注解2.4 Aop实现注解环绕通知、获取注解参数、加锁解锁2.5 ThreadLocal实现可重入锁 1. 自定义注解+动态代理:日志 @Target(ElementType.METHOD) //该注解只能作用在方法上 @Retention(RetentionPolicy.RUNTIME) //运行时可被

Hello, New World! 1017

SpringBoot 8种异步实现方式

什么是异步?在同步操作中,执行到发送短信的时候,我们必须等待这个方法彻底执行完才能执行赠送积分这个操作,如果赠送积分这个动作执行时间较长,发送短信需要等待,这就是典型的同步场景。实际上,发送短信和赠送积分没有任何的依赖关系,通过异步,我们可以实现赠送积分和发送短信这两个操作能够同时进行,比如:这就是所谓的异步,是不是非常简单,下面就说说异步的几种实现方式吧。/**

qq_21305943的专栏 6608

手把手教你用Simulink搭建PMSM FOC控制模型(附完整仿真文件下载)

本文手把手教你使用Simulink搭建完整的永磁同步电机(PMSM)磁场定向控制(FOC)仿真模型。内容涵盖从电机与逆变器建模、核心的坐标变换(Clark/Park变换)实现、电流环与速度环PID整定,到SVPWM调制与系统集成调试的全过程,并提供关键参数设置与实用调试技巧,帮助工程师快速掌握FOC的工程实现。

silver的专栏 863

机器学习-02 模型的评估与选择

2.1 误差与过拟合 我们将学习器对样本的实际预测结果与样本的真实值之间的差异成为:误差(error)。定义: 在训练集上的误差称为训练误差(training error)或经验误差(empirical error)。 在测试集上的误差称为测试误差(test error)。 学习器在所有新样本上的误差称为泛化误差(generalization error)。 显然,我们希望得到的是在新样本上表现得很好的学习器,即泛化误差小的学习器。因此,我们应该让学习器尽可能地从训练集中学出普适性的“一般特征”,这

一个小白 1706

基于springboot自定义注解+redis实现分布式锁

1、描述 ava多线程编程的时候,锁是一个很重要也很基础的概念,锁可以看做是多线程情况下访问共享资源的一种线程同步机制。这是对于单进程应用而言的,即所有线程都在同一个JVM进程里的时候,使用Java语言提供的锁机制可以起到对共享资源进行同步的作用。如果分布式环境下多个不同线程需要对共享资源进行同步,那么用Java的锁机制就无法实现了,这个时候就必须借助分布式锁来解决分布式环境下共享资源的同步问题。本文将以Sping核心aop+自定义注解@AspectJ 来实现与介绍分布式锁。 Sping的aop即面向切面编

JustMyLive的博客 1093

Ubuntu22.04实战:基于VLLM高效部署Qwen3系列模型并构建Dify智能体

本文详细介绍了在Ubuntu22.04系统上使用VLLM高效部署Qwen3系列模型并集成Dify平台的实战指南。从环境配置、Conda管理到VLLM优化部署,再到Dify智能体构建,提供了全面的技术方案和性能调优建议,帮助开发者快速实现AI模型的高效部署与应用。

weixin_28323057的博客 163

Spring Boot 加一个注解,轻松实现 Redis 分布式锁

对于耗时业务和核心数据,不能让重复的请求同时操作数据,避免数据的不正确,所以要使用分布式锁来对它们进行保护。再来梳理一下设计流程:新建注解 @interface,在注解里设定入参标志增加 AOP 切点,扫描特定注解建立 @Aspect 切面任务,注册 bean 和拦截特定方法特定方法参数 ProceedingJoinPoint,对方法 pjp.proceed() 前后进行拦截切点前进行加锁,任务执行后进行删除 key本次学习是通过Review。

2501_92606851的博客 853

VTK学习(入门级教程,包括安装和使用)~持续更新中

说明:研究QT+VTK有段时间了,准备把学到的东西分享给大家,这篇博客以VS+QT+VTK为主进行展开学习的。

HXX904的博客 4759

Ubuntu 22.04 局域网安装GitLab

下面直接给你一份。

u013250861的博客 669

SpringBoot 自定义注解+AOP+Redis 实现分布式锁做幂等

SpringBoot 自定义注解+AOP+Redis 实现分布式锁做幂等一、maven依赖二、自定义注解类1、DistributedLockApi.java(API分布式锁注解)2、DistributedLockTask.java(任务分布式锁注解)三、切面类DistributedLockAspect.java(分布式锁切面)四、测试类1、TaskService.java(测试任务类)2、Main.java(测试执行任务类)3、ApiController.java(测试ApiController类)五、测试

摸鱼佬的博客 1437

NPU性能榨取手把手教程:RK3588上YOLOv5s多线程部署与120FPS极限探索

RK3588 YOLOv5s多线程部署优化指南 本教程深入解析RK3588芯片上YOLOv5s模型的高性能多线程部署方案。RK3588作为瑞芯微旗舰AIoT芯片,内置三核NPU(6TOPS算力)和硬件加速器(RGA/VPU),为AI推理提供强大支持。通过线程池技术,我们将视频处理的读取、推理、后处理等环节解耦并行,显著提升系统吞吐量。教程详细介绍了从PyTorch到RKNN格式的模型转换流程、INT8量化方法,以及基于RKNN C++ SDK的YOLOv5推理核心实现,帮助开发者最大化发挥RK3588的AI

LXY_LOVE36的博客 973

SpringBoot基于自定义注解简洁方便地实现Redis分布式锁

需求描述 控制某个service的方法,当有线程调用了该方法后,其他线程不可再调用该方法 基于自定义注解实现,使用时只需要在被调用的方法上添加注解标记即可 redis的锁键(lockKey)通过自定义注解的属性传递进去 客户端如果在当前时间不可执行该方法,可以得到及时的反馈 转载请备注出处:SpringBoot基于自定义注解简洁方便地实现Redis分布式锁 技能点 使用spring-inte...

阿呆的专栏 2215

定位分析RCU stall问题

在编译内核时打开或者在启动参数中增加这样统计信息是在采样周期中收集的。其中number行分别是发生stalled的cpu上的硬中断,软中断,和上下文的切换次数cputime行的前三列分别是硬中断,软中断和任务消耗的cpu时间,单位为ms.最后一个数字是统计的周期时间(ms).因为用户态的任务通常不会引起RCU CPU stalls,所以任务通常是内核任务,这也是为什么只统计system cpu的原因.

happyAnger6的专栏 1994

在虚拟机中对阿克曼转向车进行导航及避障的仿真测试

一、    在我使用rosdep install teb_local_planner去安装teb_local_planner的时候,报错:Command ‘rosdep’ not found, but can be installed with:sudo apt install python3-rosdep2    我花大半个月好不容易建立起的一系列ROS环境,被python3-rosdep2这条万恶的语句连同ROS一起给我删了。。。悲伤来的那么猝不及防    随着ROS版本的迭代,相关资料很多都不再适用

慕羽★的博客 3810

完美,SpringBoot中使用注解来实现 Redis 分布式锁

对于耗时业务和核心数据,不能让重复的请求同时操作数据,避免数据的不正确,所以要使用分布式锁来对它们进行保护。有些业务请求,属于耗时操作,需要加锁,防止后续的并发操作,同时对数据库的数据进行操作,需要避免对之前的业务造成影响。

qq_51033936的博客 333

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

SpringBoot模块化开发入门——注解驱动实现自定义包装Redis分布式锁

本文结合 SpringBoot 模块化开发,从 配置文件、注解、API 三个方面实现Redis分布式锁,并加深对 SpringBoot 模块化背后的理解

birth2001的博客 288

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

SpringBoot 自定义注解+AOP+Redis 实现分布式锁

前言 分布式环境下多个不同线程需要对共享资源进行同步,那么用Java的锁机制就无法实现了,这个时候就必须借助分布式锁来解决分布式环境下共享资源的同步问题。 aop 通过设置切面,当切面设置的目标类的方法被调用时,aop 框架会拦截此次调用,源码中 pointCut 类里有两个核心属性,即 ClassFilter 类过滤器与MethodMatcher 方法匹配器,aop基于其两个核心来进行拦截,拦截之后aop机制会通过jdk或cglib生成动态代理对象,调用增强类的增强方法进行功能织入。 @AspectJ 是

liuerchong的博客 1287

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

@transaction使自定义注解失效_SpringBoot中使用注解来实现 Redis 分布式锁

阅读本文大概需要 7.5 分钟。作者:jingQhttps://www.sevenyuan.cn/一、业务背景有些业务请求,属于耗时操作,需要加锁,防止后续的并发操作,同时对数据库的数据进行操作,需要避免对之前的业务造成影响。二、分析流程使用 Redis 作为分布式锁,将锁的状态放到 Redis 统一维护,解决集群中单机 JVM 信息不互通的问题,规定操作顺序,保护用户的数据正确。梳理设计流程新建...

weixin_34453486的博客 201
上一篇: Java多线程下——各类锁的详解
下一篇: Spring 源码阅读 04:BeanFactory 初始化
java_lujj
博客等级 码龄4年 18粉丝 152原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值