SpringBoot实现过滤器、拦截器与切片源码分析

SpringBoot 全面深入学习指南 本文系统介绍了SpringBoot的全栈知识体系,涵盖基础原理、Web开发、数据访问、系统集成、微服务、安全监控等10大领域。重点内容包括:SpringBoot自动配置机制和Starter原理;Web开发中的RESTful API设计、异常处理和响应式编程;多种数据访问技术集成;消息队列和任务调度实现;微服务架构下的服务治理方案;以及安全认证、监控运维等生产级实践。本指南不仅包含技术使用,还深入原理分析和最佳实践,为开发者提供从入门到精通的完整学习路径,适合构建高性能、可扩展的企业级应用。 阅读详情

过滤器Filter

过滤器概念

Filter是J2E中来的,可以看做是Servlet的一种“加强版”,它主要用于对用户请求进行预处理和后处理,拥有一个典型的处理链。Filter也可以对用户请求生成响应,这一点与Servlet相同,但实际上很少会使用Filter向用户请求生成响应。使用Filter完整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行预处理并生成响应,最后Filter再对服务器响应进行后处理。

过滤器作用

在JavaDoc中给出了几种过滤器的作用

 * Examples that have been identified for this design are<br>
 * 1) Authentication Filters, 即用户访问权限过滤
 * 2) Logging and Auditing Filters, 日志过滤,可以记录特殊用户的特殊请求的记录等
 * 3) Image conversion Filters
 * 4) Data compression Filters <br>
 * 5) Encryption Filters <br>
 * 6) Tokenizing Filters <br>
 * 7) Filters that trigger resource access events <br>
 * 8) XSL/T filters <br>
 * 9) Mime-type chain Filter <br>

对于第一条,即使用Filter作权限过滤,其可以这么实现:定义一个Filter,获取每个客户端发起的请求URL,与当前用户无权限访问的URL列表(可以是从DB中取出)作对比,起到权限过滤的作用。

过滤器实现方式

自定义的过滤器都必须实现javax.Servlet.Filter接口,并重写接口中定义的三个方法:

  1. void init(FilterConfig config)

  2. 用于完成Filter的初始化。

  3. void destory()

  4. 用于Filter销毁前,完成某些资源的回收。

  5. void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)

  6. 实现过滤功能,即对每个请求及响应增加的额外的预处理和后处理。,执行该方法之前,即对用户请求进行预处理;执行该方法之后,即对服务器响应进行后处理。值得注意的是,chain.doFilter()方法执行之前为预处理阶段,该方法执行结束即代表用户的请求已经得到控制器处理。因此,如果再doFilter中忘记调用chain.doFilter()方法,则用户的请求将得不到处理。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
// 必须添加注解,springmvc通过web.xml配置
@Component
public class TimeFilter implements Filter {
 private static final Logger LOG = LoggerFactory.getLogger(TimeFilter.class);
 @Override
 public void init(FilterConfig filterConfig) throws ServletException {
 LOG.info("初始化过滤器:{}", filterConfig.getFilterName());
 }
 @Override
 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 LOG.info("start to doFilter");
 long startTime = System.currentTimeMillis();
 chain.doFilter(request, response);
 long endTime = System.currentTimeMillis();
 LOG.info("the request of {} consumes {}ms.", getUrlFrom(request), (endTime - startTime));
 LOG.info("end to doFilter");
 }
 @Override
 public void destroy() {
 LOG.info("销毁过滤器");
 }
 private String getUrlFrom(ServletRequest servletRequest){
 if (servletRequest instanceof HttpServletRequest){
 return ((HttpServletRequest) servletRequest).getRequestURL().toString();
 }
 return "";
 }
}

从代码中可看出,类Filter是在javax.servlet.*中,因此可以看出,过滤器的一个很大的局限性在于,其不能够知道当前用户的请求是被哪个控制器(Controller)处理的,因为后者是spring框架中定义的。

在SpringBoot中注册第三方过滤器

对于SpringMvc,可以通过在web.xml中注册过滤器。但在SpringBoot中不存在web.xml,此时如果引用的某个jar包中的过滤器,且这个过滤器在实现时没有使用@Component标识为Spring Bean,则这个过滤器将不会生效。此时需要通过java代码去注册这个过滤器。以上面定义的TimeFilter为例,当去掉类注解@Component时,注册方式为:

@Configuration
public class WebConfig {
 /**
 * 注册第三方过滤器
 * 功能与spring mvc中通过配置web.xml相同
 * @return
 */
 @Bean
 public FilterRegistrationBean thirdFilter(){
 ThirdPartFilter thirdPartFilter = new ThirdPartFilter();
 FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean() ;
 filterRegistrationBean.setFilter(thirdPartFilter);
 List<String > urls = new ArrayList<>();
 // 匹配所有请求路径
 urls.add("/*");
 filterRegistrationBean.setUrlPatterns(urls);
 return filterRegistrationBean;
 }
}

相比使用@Component注解,这种配置方式有个优点,即可以自由配置拦截的URL。

拦截器Interceptor

拦截器概念

拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截,然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。

拦截器作用

  1. 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等

  2. 权限检查:如登录检测,进入处理器检测检测是否登录

  3. 性能监控:通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间。(反向代理,如apache也可以自动记录);

  4. 通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。

拦截器实现

通过实现HandlerInterceptor接口,并重写该接口的三个方法来实现拦截器的自定义:

  1. preHandler(HttpServletRequest request, HttpServletResponse response, Object handler)

  2. 方法将在请求处理之前进行调用。SpringMVC中的Interceptor同Filter一样都是链式调用。每个Interceptor的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor中的preHandle方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值Boolean 类型的,当它返回为false时,表示请求结束,后续的Interceptor和Controller都不会再执行;当返回值为true时就会继续调用下一个Interceptor 的preHandle 方法,如果已经是最后一个Interceptor 的时候就会是调用当前请求的Controller 方法。

  3. postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)

  4. 在当前请求进行处理之后,也就是Controller 方法调用之后执行,但是它会在DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller 处理之后的ModelAndView 对象进行操作。

  5. afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex)

  6. 该方法也是需要当前对应的Interceptor的preHandle方法的返回值为true时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的。

@Component
public class TimeInterceptor implements HandlerInterceptor {
 private static final Logger LOG = LoggerFactory.getLogger(TimeInterceptor.class);
 @Override
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 LOG.info("在请求处理之前进行调用(Controller方法调用之前)");
 request.setAttribute("startTime", System.currentTimeMillis());
 HandlerMethod handlerMethod = (HandlerMethod) handler;
 LOG.info("controller object is {}", handlerMethod.getBean().getClass().getName());
 LOG.info("controller method is {}", handlerMethod.getMethod());
 // 需要返回true,否则请求不会被控制器处理
 return true;
 }
 @Override
 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
 LOG.info("请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后),如果异常发生,则该方法不会被调用");
 }
 @Override
 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
 LOG.info("在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)");
 long startTime = (long) request.getAttribute("startTime");
 LOG.info("time consume is {}", System.currentTimeMillis() - startTime);
 }

与过滤器不同的是,拦截器使用@Component修饰后,还需要通过实现WebMvcConfigurer手动注册:

// java配置类
@Configuration
public class WebConfig implements WebMvcConfigurer {
 @Autowired
 private TimeInterceptor timeInterceptor;
 @Override
 public void addInterceptors(InterceptorRegistry registry){
 registry.addInterceptor(timeInterceptor);
 }
}

切片Aspect

切片概述

相比过滤器,拦截器能够知道用户发出的请求最终被哪个控制器处理,但是拦截器还有一个明显的不足,即不能够获取request的参数以及控制器处理之后的response。所以就有了切片的用武之地了。

切片实现

切片的实现需要注意@Aspect,@Component以及@Around这三个注解的使用,详细查看官方文档:传送门

@Aspect
@Component
public class TimeAspect {
 private static final Logger LOG = LoggerFactory.getLogger(TimeAspect.class);
 @Around("execution(* me.ifight.controller.*.*(..))")
 public Object handleControllerMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
 LOG.info("切片开始。。。");
 long startTime = System.currentTimeMillis();
 // 获取请求入参
 Object[] args = proceedingJoinPoint.getArgs();
 Arrays.stream(args).forEach(arg -> LOG.info("arg is {}", arg));
 // 获取相应
 Object response = proceedingJoinPoint.proceed();
 long endTime = System.currentTimeMillis();
 LOG.info("请求:{}, 耗时{}ms", proceedingJoinPoint.getSignature(), (endTime - startTime));
 LOG.info("切片结束。。。");
 return null;
 }
}

过滤器、拦截器以及切片的调用顺序

如下图,展示了三者的调用顺序Filter->Intercepto->Aspect->Controller。相反的是,当Controller抛出的异常的处理顺序则是从内到外的。因此我们总是定义一个注解@ControllerAdvice去统一处理控制器抛出的异常。如果一旦异常被@ControllerAdvice处理了,则调用拦截器的afterCompletion方法的参数Exception ex就为空了。

实际执行的调用栈也说明了这一点:

而对于过滤器和拦截器详细的调用顺序如下图:

过滤器和拦截器的区别

最后有必要再说说过滤器和拦截器二者之间的区别:

Filter Interceptor 实现方式 过滤器是基于函数回调 基于Java的反射机制的 规范 Servlet规范 Spring规范 作用范围 对几乎所有的请求起作用 只对action请求起作用 除此之外,相比过滤器,拦截器能够“看到”用户的请求具体是被Spring框架的哪个控制器所处理。

持续更新-使用 Maven Module 搭建spring boot项目(整合Spring Security、Spring Social、spring OAuth)第一篇 使用 Maven Module 搭建spring boot项目(整合Spring Security、Spring Social、spring OAuth)一 使用 Maven Module 搭建spring boot项目(整合Spring Security、Spring Social、spring OAuth)一 文章說明 简介 项目环境、组件及目标 基本概念 Spring Security... 阅读详情

相关推荐

SpringBoot拦截器原理解析及使用方法

主要介绍了SpringBoot拦截器原理解析及使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

Spring Boot 使用 @Aspect 注解实现 AOP

AOP(Aspect Oriented Programming,面向切面编程)是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 在Spring...

wuychn 1万+

DSP配置I2C通讯(非IO口软件模拟时序)

dsp即数字信号处理器,是一种广泛应用的嵌入式处理器,主要应用是实时快速地实现各种数字信号处理算法,目前,国际主要的dsp供应商是ti公司,其tms32系列产品占据了dsp市场近一半的份额,本文以TMS320F28377D为例,使用ti公司dsp开发工具ccs10.1配置i2c模块。 写这篇文章的缘由,因为市面上很多DSP其实内部是具有硬件I2C接口的,但由于网络上大部分资料和例程都是利用IO口模拟I2C时序进而实现I2C通讯的。给很多想学习硬件I2C通讯的小伙伴带来了困难。 下面先介绍一下配置步骤 1.E

weixin_44387442的博客 5546

SpringBoot2 | SpingBoot FilterRegistrationBean 注册组件 | FilterChain 责任链源码分析(九)

微信公众号:吉姆餐厅ak 学习更多源码知识,欢迎关注。 SpringBoot2 | SpringBoot启动流程源码分析(一) SpringBoot2 | SpringBoot启动流程源码分析(二) SpringBoot2 | @SpringBootApplication注解 自动化配置流程源码分析(三) SpringBoot2 | SpringBoot Environment源码分析(四...

程序员阿康 1万+

高速PCB差分对布线仿真实战案例

差分对布线是高速PCB设计的关键环节,直接影响信号完整性与EMI性能。本文结合实际项目,详解等长控制、阻抗匹配、间距优化等核心技巧,并通过仿真工具验证眼图、串扰和时序裕量,助力工程师规避常见高速pcb设计陷阱。

weixin_29092787的博客 350

脑影像分析软件推荐 | JuSpace

JuSpace是一种允许基于 MRI 测量与核成像衍生估计的跨模态关联的工具包,它涵盖各种神经递质系统,包括多巴胺能、血清素能、去甲肾上腺素能和 GABA(γ-氨基丁酸) 神经传递。JuSpace 由一组 Matlab 功能以及涵盖各种受体系统的 PET 受体图谱组成。

NHKJ_ZF的博客 1710

Spring Security 源码分析(一):过滤器链

实例 调用apply方法获取中的配置,并调用buildinit();/*** 内部配置初始化*//*** 配置逻辑*/可以看到构建操作为将通过apply方法应用进来的配置分别初始化和构建,链条为。中的(认证管理器生成配置)、(过滤器管理器生成配置)、(过滤器生成配置) 都是继承通过这个链条生成目标对象,这 3 个配置也是的配置核心。

ywb201314的专栏 776

Uds诊断自动化(CANoe.diva应用)

(7)其他配置选择可以保持默认(一般情况下,DTC以及31服务、下载服务需要另外测试)。(1)打开CANoe.Diva软件,并新建Diva工程。(2)设置工程名和保存位置;在"Specification file"(3)测试案例打开后的界面如下,可以对要进行测试的案例进行勾选,(可查看下角的进度条)后;(3)等待DIva工程的生成;(4)接下来是修改Diva工程选项,如下图点击。,如用例间的时间间隔、复位等待时间等。,即可打开导入的测试工程。,即可生成测试用例。(1)在CANoe中。

LIUJIAAAAA的博客 3445

Spring Boot Filter 顺序源码分析

前言 笔者写的spring boot项目的filter,使用@WebFilter不能自定义顺序,使用Bean注入又不能定义拦截路径。一定要定义FilterRegistrationBean的Bean才能同时生效,本章从源码的角度分析为什么。 1. 使用ServletComponentScan启动分析 在SpringBoot启动后,会扫描jar的包 可以看到扫描了Se...

fenglllle的博客 1646

「免费开源」从 OCR 识别到结构化输出:合同文本高效比对的完整解决方案!

本文将系统拆解这一解决方案的技术逻辑、实施步骤与落地价值,为企业合同管理数字化转型提供实操指南。字段提取:自动识别并提取预设的关键字段(如 “甲方”“乙方”“合同金额”“履约期限” 等),若遇到模糊表述(如 “付款时间为合同签订后 30 个工作日内”),系统可自动解析为 “履约期限:合同签订日 + 30 工作日”;预设结构化字段:根据合同类型(如采购合同、服务合同、租赁合同),定义需提取的关键字段(如 “甲方名称”“乙方名称”“合同金额”“有效期起 / 止日期”“履约地点”“违约金比例” 等);

qxsz_yyh的博客 836

【Word提效 No.010】使用AI一键复制格式刷应用格式

本文介绍利用AI工具WorkBuddy实现Word文档格式批量复制的自动化解决方案。通过简单指令即可将指定段落的格式一键应用到多个目标段落,解决传统格式刷操作繁琐、易出错的问题。方案包含三步操作:准备文档、输入指令模板、验收效果,并提供多种场景的现成指令示例。该工具支持字体、段落等格式属性的批量复制,且不会覆盖原文件,适用于论文、手册等文档的格式统一需求。

u011186532的专栏 78

springboot的切面应用(注解Aspect )

spring boot 拦截的方式 过滤器filter: 可以获取http、http请求和响应,但无法获取与spring框架相关的信息,如哪个control处理,哪个方法处理,有哪些参数,这些都是无法获取的。主要用于内容上的过滤,敏感字替换成*等,也可用于非登入状态的非法请求过滤。 拦截器interceptor: 除了获取http、http请求和响应对象,还可以获取请求的类名、方法名,但拦截器无法获取请求参数的值,从DispatcherServlet类源码分析。主要用于对公共的一些拦截获取,例如请求

weixin_44632065的博客 6239

修改yolov8使支持单通道图像输入

默认支持BGR三通道图像输入,但是在很多工业场景下,存在很多灰度图像的场景,因此对yolov8代码做一些修改以支持单通道图像输入,满足项目实际需求。训练时,只需要在 data 的 yaml 文件中,添加 ch=1 即可。

mingo_敏 5779

springboot 拦截Restful服务三种方式(filter、interceptor、aspect)

1、将过滤器加入项目的方式: (1)采用@Component 注解,实现过滤器bean注入 (2)针对第三方过滤器,即无@Component 注解,需要在@Configuration配置文件进行注册: package com.mall.config; import java.util.ArrayList; import java.util.List; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.

lzf2284466的博客 1075

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

备忘-20190812

JDK动态代理和CGLIB动态代理 JDK动态代理-超详细源码分析 过滤器、拦截器、切片区别 Spring boot 单元测试 转载于:https://my.oschina.net/...

155

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

干货合集 | 500 篇Java技术文章整理

今天,给大家推荐一位 Java 架构领域做得非常好的朋友:师长。师长的公众号:Java进阶架构师这3年来累积了200篇优质原创文章,及各类优质技术文章共500篇。其号内专注于 原...

公众号:Java后端 7149

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

干货合集 | 500篇Java技术文章整理 | 建议打开

今天,给大家推荐一位Java架构领域做得非常好的朋友:师长。师长的公众号:Java进阶架构师这3年来累积了200篇优质原创文章,及各类优质技术文章共500篇。其号内专注于原理、架构设...

Java团长的博客 829

全面了解SpringBoot拦截器

拦截器(Interceptor)是一种特殊的组件,它可以在请求处理的过程中对请求和响应进行拦截和处理。拦截器可以在请求到达目标处理器之前、处理器处理请求之后以及视图渲染之前执行特定的操作。拦截器的主要目的是在不修改原有代码的情况下,实现对请求和响应的统一处理。本文详细介绍了SpringBoot中的拦截器,包括拦截器的概念、作用、实现方式、执行顺序、生命周期以及高级应用。我们还探讨了拦截器的性能优化策略和常见问题。希望本文能帮助您更好地理解和使用SpringBoot中的拦截器。

万猫学社 2万+

Spring Boot之拦截器原理与使用场景

1.什么是拦截器? 拦截器的主要是基于Java的反射机制,属于面向切面编程(AOP)的一种运用,就是在Service或者一个方法前调用一个方法,或者在方法后调用一个方法,甚至在抛出异常的时候做业务逻辑的操作。 拦截器的作用类似于Servlet中的Filter,都可以用于对处理器进行预处理和后处理。在Spring MVC 与Spring Boot 中使用拦截器一般是实现HandlerInterceptor接口。 HandlerInterceptor接口提供了三...

花&败 3077

【Spring从入门到实战】第 5 讲:SpringBoot 实现拦截器及其原理

【Spring从入门到实战】第 5 讲:SpringBoot实现拦截器及其原理

执梗的博客 3218

preHandle、postHandle与afterCompletion

preHandle 调用时间:Controller方法处理之前 执行顺序:链式Intercepter情况下,Intercepter按照声明的顺序一个接一个执行 若返回false,则中断执行,注意:不会进入afterCompletion postHandle 调用前提:preHandle返回true 调用时间:Controller方法处理完之后,DispatcherServlet进行视图的渲染之前,也就是说在这个方法中你可以对ModelAndView进行操作 执行顺序:链式Intercepter情况下,Int

weixin_45657091的博客 1159

Spring Boot 拦截器原理

1、HandlerInterceptor 接口 import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.ht

weixin_40423235的博客 1156

【拦截器Interceptor】springboot拦截器的使用和原理

拦截器就是用来拦截指定的请求,在请求前、请求处理后做一些响应的业务逻辑处理,或者在请求完成之后做一些资源释放。拦截器最常用的使用场景就是认证,在请求开始之前,对当前请求进行权限校验,如果当前请求用户具备操作当前请求的权限,就对当前请求放行,允许执行业务逻辑;否则拦截当前请求,直接返回。拦截器的功能通过网关也都是可以实现的,但是一些单体架构还是需要使用拦截器。要在SpringBoot中实现拦截器,首先需要创建一个类并实现HandlerInterceptor接口。

weixin_44823875的博客 2646

【Springboot】Filter 过滤器的使用

过滤器 Filter 作为 Java 三大器之一,在 Java Web 的使用中有很高的地位。所谓过滤器,就是实现了 javax.servlet.Filter 接口的服务器端程序,就是对事物进行过滤的,在 Web 中的过滤器,当然就是对请求进行过滤,我们使用过滤器,就可以对请求进行拦截,然后做相应的处理,实现许多特殊功能。如登录控制,权限管理,过滤敏感词汇等。

yuxiangdeming的博客 2650
上一篇: SpringBoot项目启动Stopping service [Tomcat]
下一篇: org.apache.thrift.TApplicationException: Invalid method name问题解决
coding_1994
博客等级 码龄9年 636粉丝 40原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值