Struts源码学习 RequestProcessor

Struts基本执行流程 struts 阅读详情

     Struts的执行

      详细介绍struts在初始化之后是如何处理一个请求,并返回数据的。这里最核心的类是RequestProcessor以及 RequestUtils。RequestProcessor类通过RequestDispatcher实现页面的跳转,而 RequestProcessor负责处理request中传来的请求信息,存放到FormBeanConfig中,以及对要跳转的url进行处理。

 

     struts 在初始化完成之后,会根据请求调用doGet(...)或者doPost(...)方法,这两个方法直接
调用process(request, response)方法。process(...)方法首先判断当前的request属于
哪一个ModuleConfig,然后生成与这个ModuleConifg相对应的RequestProcessor,最后调用这个
RequestProcessor的process(...)方法,执行request的请求。

一、RequestUtils.selectModule(String prefix, HttpServletRequest,ServletContext)方法:
    这个方法,根据prefix,从ServletContext中选择相应的ModuleConfig,然后把这个ModuleConfig保存到 request中。ServletContext对应的key值为Globals.MODULE_KEY + prefix,保存到request中使用的key值为Globals.MODULE_KEY。如果在ServletContext中不存在这样的一个 ModuleConfig,那么调用request.removeAttribute(Globals.MODULE_KEY)方法。然后以同样的方法查找、保存MessageResources对象。当prefix为空时,会调用下面的方法选择ModuleConfig。

二、RequestUtils.selectModule(HttpServletRequest, ServletContext)
    这个方发首先使用getModuleName(HttpServletRequest, ServletContext)获取相应的path,然后通过调用getModuleName(String matchPath, ServletContext)获取相应的prefix。以这prefix为参数调用(一)中的selectModule(...)选择 ModuleConfig。获取path的过程为:首先从request中查找名称为 INCLUDE_SERVLET_PATH(javax.servlet.include.servlet_path)的属性,如果为空,就调用 request.getServletPath()方法获取servletPath。

    获取prefix的过程为:它首先调用getModulePrefixes(ServletContext),获取所有已存在的module前缀。然后通过分析上面获取的path来判断当前的url属于哪一个module,方法是截取path中最后一个"/"字符前面的字符串,然后与有上面方法中获取的prefixes[]逐个对比,如果prefixes[]中存在这样的一个值,则返回这个截取的字符串,否则继续截取path最后面的"/"前面的字符串,然后对比。如果始终找不到,则返回""。

    getModulePrefixes(ServletContext)的执行过程是:首先通过 context.getAttribute(PREFIXES_KEY)查找是否存在这样的一个保存所有的prefix的string array,如果存在,就说明已经解析过一次了,就直接返回这个string array,否则遍历ServletContext中所有的attribute name,查找以MODULE_KEY(org.apache.struts.action.MODULE)开头的name,然后截取这个 atrribute name中MODULE_KEY后面的字符串,作为一个prefix. 最后把通过这个这个方式获取的所有的prefix作为一个ArrayList存储到ServletContext中,属性key值为 PREFIXES_KEY(org.apache.struts.util.PREFIXES),这样下次再次查找的时候就可以直接从这个 attribute中获取了。

三、getModuleConfig(HttpServletRequest)
    这个方法就是获取上面的selectModule(...)方法所得到的ModuleConfig。如果找不到这样的ModuleConfig,那么就把 ServletContext中缺省的ModuleConfig返回(调用 getServletContext().getAttribute(Globals.MODULE_KEY))

四、getRequestProcessor(ModuleConfig config)
    这个方法从根据ModuleConfig的prefix作为key,从ServletContext中获取RequestProcessor。这个key 值为Globals.REQUEST_PROCESSOR_KEY + config.getPrefix()。如果ServletContext中不存在这样的一个RequestProcessor,那么就生成一个新的 RequestProcessor的实例,完成初始化(保存ActionServlet以及ModuleConfig到这个新的实例中)后将其保存到 ServletContext中。

五、RequestProcessor.process(HttpServletRequest, HttpServletResponse)
    这是真正执行HttpServletRequst请求的方法。这个方法首先判断但前的HttpServletRequest是否是Multipart类型的request,也就是说当前的request是否有字段是"file"类型。这样做的原因是这种类型的request中getParameter() 等相类似的方法都是无法执行的,所以要区分对待。如果是Multipart类型的,那么把这个request包装成 MultipartRequestWrapper类。MultipartRequestWrapper 与 HttpServletRequest不同的就是重新实现了setParameter(String name, String value),getParameter(String name),getParameterNames()以及getParameterValues(String name)方法。这些方法的思想是所有的变量名、值对都保存到一个HashMap里:key为变量名,value为变量值,但是注意value都是 String[]格式的。当有一个新的值加入的时候,通过setParameter(...)方法,把值加到数组的最后。在 setParameter(...)中有一个比较少见的保存值的方法,记录如下:
    public void setParameter(String name, String value) {
        String[] mValue = (String[]) parameters.get(name);
        if (mValue == null) {
            mValue = new String[0];
        }
        String[] newValue = new String[mValue.length + 1];
        System.arraycopy(mValue, 0, newValue, 0, mValue.length);
        newValue[mValue.length] = value;
       
        parameters.put(name, newValue);
    }

    然后是调用processPath(HttpServletRequest, HttpServletResponse)获取地址,以这个地址作为从ModuleConfig获取ActionMapping的Key。这里首先查询 request.getAttribute(INCLUDE_PATH_INFO)中是否有这样的一个值,如果为空就调用 request.getPathInfo()方法获取path。如果这样还是获取不了,就从 request.getAttribute(INCLUDE_SERVLET_PATH)方法中获取path,找不到就使用 request.getServletPath()得到的path进行分析。分析过程如下:

    然后如果这个path不是属于当前的ModuleConfig的话,直接返回null。截取prefix后面的字符串,如果这个字符串以.XX结尾,那么就截取"."前面的字符,比如http://localhost:8080/servlet/where/go.do 。where为module的名称(prefix),那么我们获取的path值为/go。

    然后是通过processLocale(HttpServletRequest ,HttpServletResponse)为当前用户设定一个Local对象,它查找的顺序是:moduleConfig.getControllerConfig().getLocale(),session.getAttribute(Globals.LOCALE_KEY),request.getLocale()。你可以在config XML文件中通过<controller><set-property..../></controller>执行相关的定义。

    调用processContent(HttpServletRequest, HttpServletResponse)为response设定contentType。这个contentType是从 moduleConfig.getControllerConfig().getContentType()获取的。你可以在config XML文件中通过<controller><set-property..../></controller>执行相关的定义。

    通过processNoCache(HttpServletRequest, HttpServletResponse)方法设置response的缓存。你可以在config XML文件中通过<controller><set-property..../></controller>执行相关的定义。

    processPreprocess(HttpServletRequest, HttpServletResponse)预处理request,这个方法是预留的,用户可以根据自己的需要加一些预处理的程序。

    通过processMapping(HttpServletRequest, HttpServletResponse, String path)以processPath(...)的返回值为key从ModuleConfig中查找ActionMapping 对象。如果找到了,那么保存这个Mapping到request中,key值为Globals.MAPPING_KEY。如果不存在,那么遍历 ModuleConfig中所有的ActionMapping,查找哪一个是缺省的ActionMapping。然后把它保存到request中,key 值为Globals.MAPPING_KEY。

    通过processRoles(HttpServletRequest,HttpServletResponse,ActionMapping)检查当前用户是否有权限执行这个请求。如果request.isUserInRole(roles[i])返回true,则代表有。

    通过processActionForm(HttpServletRequest, HttpServletResponse, ActionMapping)生成一个ActionForm,然后根据ActionMapping所属的scope,保存到request或者 session中。key值为这个ActionMapping中的attribute属性。ActionForm是通过 RequestUtils.createActionForm(...)方法获取的,在下一篇将会对这个方法进行详细的说明。这里只说明 ActionForm与FormBeanConfig以及FormPropertyConfig之间的区别。每个FormPropertyConfig代表Form表单的一个字段,表单中的所有FormPropertyConfig以一个HashMap保存到FormBeanConfig中。而 FormBeanConfig是在Actionservet初始化的时候生成的:
    <form-beans>
      <form-bean name="一个名称,作为action选择的key" type="实际对应的ActionForm类"/>
    <form-beans>
    名称用来在MoudleConfig中的formBeans HashMap中查找相应的FormBeanConfig,而FormBeanConfig中有一个type,它保存了上面XML文件中定义的值,用来生成ActionForm。
   
    通过processPopulate(HttpServletRequest, HttpServletResponse, ActionForm,ActionMapping)方法初始化ActionForm 以及 FormBean,这个方法首先会设定这个ActionForm所属于的ActionServlet,然后对这个ActionForm进行初始化。判断当前的reqest是否Multipart类型,如果是就把相应的MultipartClass类全名保存到request中,key值为 Globals.MULTIPART_KEY。调用RequestUtils.populate(...)对request中的参数进行处理,并保存到相应的FormBean中。在下一篇将会对这个方法进行详细的说明。最后根据request的parameter判断当前的请求是否是被取消,然后把相关信息保存到request中:
    if ((request.getParameter(Constants.CANCEL_PROPERTY) != null) ||
         (request.getParameter(Constants.CANCEL_PROPERTY_X) != null)) {
        request.setAttribute(Globals.CANCEL_KEY, Boolean.TRUE);
    }

六、下面说明这个request是如何真正被处理的,下面的方法都是在RequestProcessor中定义
    通过processValidate(HttpServletRequest, HttpServletResponse, ActionForm,ActionMapping)判断request中的parameter是否合法。如果合法就返回true,否则取消这次请求,并且返回到指定的输入页面。它判断合法的过程是:如果这次的请求被取消,那么直接返回true;如果没有要求对request中的parameter进行合法性检验,也直接返回true;最后通过调用form.validate(mapping, request)执行检验,如果返回的ActionErrors为null,那么代表通过检验,返回true,否则取消这次request。取消的过程是:如果这个请求是Multipart类型的,那么要对这个MultipartRequestHandler进行回滚;而后,获取当前的 ActionMapping的input值,即在config XML 中<action.../>定义的input属性。如果ActionMapping没有这个input值,那么调用 response.sendError(...)方法,然后返回false;如果存在,就保存验证出错信息到request中,key为 Globals.ERROR_KEY,跳转到input所指定的地址中。

    processForward(HttpServletRequest, HttpServletResponse, ActionMapping)以及processInclude(HttpServletRequest, HttpServletResponse, ActionMapping)分别通过执行RequestDispatcher.forward(request, response) 以及 RequestDispatcher.include(request, response)实现对页面的跳转。
   
    但是如果当前的请求还有其它操作要执行,那么ActionMapping中的include或者forward属性值就会为空。这时需要通过调用 processActionCreate(HttpServletRequest, HttpServletResponse, ActionMapping)方法根据config XML文件的配置信息生成一个Action类,然后通过processActionPerform(...)调用生成的Action中的 execute(...)方法。最后根据execute(...)方法返回的ActionForword类,执行跳转。在整个过程中有一个url的计算方法,这个将在下一篇中说明。

七、对ActionMaping结构的说明:
    在ActionConfig为保存Moudle Action属性的一个javabean,它有以下属性:
    * boolean configured 这个对象的所有属性是否已经被配置完。如果已经完成,那么在改变其中任何属性都会抛出IllegalStateException("Configuration is frozen")
    * ModuleConfig moduleConfig 本ActionConfig类所属于的ModuleConfig。
    * String attribute request范围 或者 session范围 的属性名称,我们将通过这个属性名称来获取相应的form bean,注意,这个属性与form bean中定义的名称是不一样的。注意以下的方法:
      public String getAttribute() {
          if (this.attribute == null) {
              return (this.name);
          } else {
              return (this.attribute);
          }
      }
    * String forward 调用RequestDispatcher.forward()方法时所需要的上下文相关的地址。
    * String include 调用RequestDispatcher.include()方法时所需要的上下文相关的地址。
    * String type Action类的类全名,如果上面的两个属性都没有定义的话,就会用它来处理request。
    * String input 如果输入数据没有通过验证,将会以这个值为地址,跳转到相应的页面。
    * String multipartClass MultipartRequestHandler实现类的全名
    * String name 与本类相关的 form bean 的名称。
    * String parameter 用于struts的扩展,存储其它的配置信息。struts自己不会用到这个属性。
    * String path 上下文相关的地址,这个地址为下一个将会进入的地址,这个地址必须要以"/"开头。如果使用了extension mapping的话,这个地址将不会带有扩展名。
    * String roles 一个以","分隔的角色名称。这些角色将会有权限访问这个request。
    * String scope 缺省为session。
    * String prefix,String suffix后缀和前缀。
    * boolean unknown 标志当前的ActionConfig类是否是为了未知的request path而准备的,ActionMappings将会根据这个标志返回这个ActionConfig。
    * boolean validate 是否调用validate()方法进行数据校验。   

    ActionMapping 继承自 ActionConfig,它增加了从ModuleConfig查找ActionForword的方法。同时它还提供了从ModuleConfig查找 ExceptionConfig的方法,如果找不到,会通过type.getSuperclass()方法,根据父类的类名称继续查找,直到最后。

八、关于request.getServletPath()的解释:
    request.getServletPath() 的返回值就是在下面url中定义的值,比如如果按照下面的定义
    <url-pattern>
      *.do
    </url-pattern>
    那么:
    http://localhost:8080/servlet/go.do ---------->/go.do
    http://localhost:8080/servlet/where/go.do ---------->/where/go.do

    如果
    <url-pattern>
      /where/*.do
    </url-pattern>
    那么地址中只有http://localhost:8080/servlet/where/*.do 有效(tomcat 4.0)
  
   如果
   <url-pattern>
      /where/*
   </url-pattern>
   那么:
    http://localhost:8080/servlet/where/go/ ---------->servletPath为/where, pathInfo为/go/

【SSH进阶之路】深入源码,详解Struts基本实现流程(七) 通过一步步的封装我们实现了Struts,我们解决了Struts怎么实现MVC的问题,我们现在仅仅有了Struts的基础,所以对Struts的学习才刚刚开始,这篇我们要通过对比MVC的执行流程来理解Struts的执行流程,最后深入Struts的源码,一看究竟。 MVC M:业务逻辑,业务数据可以重复使用,其实struts没有实现业务层,也无法实现 V:显示逻辑,同一份数据,对应多中显示方法,J 阅读详情

相关推荐

Struts与Tomcat源码深度解析实战

接口定义了三个核心方法,分别用于处理请求前、请求中和请求后的行为:init():拦截器初始化方法,通常用于加载配置信息或初始化资源。:拦截器的核心逻辑方法,接收对象,用于调用下一个拦截器或目标 Action。destroy():拦截器销毁方法,用于释放资源。其中,方法是拦截器的核心,它通过控制拦截流程的继续执行。Struts 框架本身提供了一些内置拦截器,但在实际项目中,往往需要根据业务需求编写自定义拦截器。

weixin_28681379的博客 288

Struts源代码阅读RequestProcessor

第二篇 struts的执行 本篇详细介绍struts在初始化之后是如何处理一个请求,并返回数据的。这里最核心的类是RequestProcessor以及 RequestUtils。RequestProcessor类通过RequestDispatcher实现页面的跳转,而 RequestProcessor负责处理request中传来的请求信息,存放到FormBeanConfig中,以及对要跳转的u...

samson_g_q_su的博客 235

struts1整合spring 通过代理RequestProcessor实现

这只是一个最简单的demo,只有一个action, 通过代理RequestProcessor实现

Strust组件—RequestProcessor类详解

Struts框架只允许应用中存在一个ActionServlet类,但是可以存在多个客户化的RequestProcessor类,每个子应用模块都可以有单独的RequestProcessor类, ActionServlet主要负责初始化,以及介绍请求并找到合适的RequestRrocessor,之后真正干活的是RequestProecssor和Action. 上回说到ActionServlet...

花香蝶自来-学无止境 313

全球最大光伏电池缺陷异常检测数据集PVELAD【发布】

下载地址 1:GitHub - binyisu/PVEL-AD 下载地址 2:http://aihebut.com/col.jsp?id=118 下载地址 3:NAVEDownload 竞赛测试系统:Photovoltaic cell anomaly detection | Kaggle 一、简介 PVEL-AD 数据集又叫做EL2021数据集是用于对光伏电池异常缺陷检测方法进行基准测试的数据集,重点是光伏电池工业检测。PVEL-AD包含 36,543 张具有各种内部缺陷和异构背...

苏打水的杯子的博客 2万+

Linux下自动化解析q7签名文件的有效期与时间戳

本文详细介绍了在Linux环境下自动化解析q7签名文件有效期与时间戳的方法。通过编写高效脚本,开发者可以批量提取签名时间信息,验证签名有效性,并集成到CI/CD流程中。文章提供了完整的脚本实现,包括错误处理、批量处理功能,并解决了常见环境问题,特别适用于国产操作系统如UOS的开发场景。

weixin_30872499的博客 517

Struts控制器组件---RequestProcessor类

RequestProcessor类Struts框架只允许应用中存在一个ActionServlet类,但是可以存在多个客户化的RequestProcessor类,每个子应用模块都可以拥有单独的RequestProcessor类,如果像修改RequestProcessor类的一些默认功能,可以覆盖RequestProcessor基类中的相关方法。下面我们来介绍其中的process()方法:pubic

mikechu的专栏 5773

基于图像分割的LIDC-IDRI 数据集预处理之数据下载篇

近期接触到了一个关于图像分割实验项目,关于数据预处理部分在此予以记录,和大家分享。

qq_67696694的博客 1902

RequestProcessor处理请求源码剖析

RequestProcessor处理请求源码剖析RequestProcessor结构1、PrepRequestProcessor剖析2、SyncRequestProcessor剖析3、FinalRequestProcessor剖析 在上一次分析ZK网络通信源码的时候我们追溯到了ZookeeperServer.processPacket()就没有再深入了。 本次就要来介绍具体业务处理的流程。入口为ZookeeperServer.processPacket()中的submitRequest(si); 概览图

Hello World 719

保姆级教程:用Python spatialdata框架搞定10x Xenium空间转录组数据(从下机到高级分析)

本教程详细介绍了如何使用Python spatialdata框架处理10x Xenium空间转录组数据,从数据获取到高级分析的全流程。涵盖环境配置、数据读取、质量控制、降维聚类、细胞注释及空间共定位分析等关键步骤,特别适合生物信息学研究者快速掌握Xenium数据分析技术。

weixin_30300225的博客 478

求助:有没有基于pscad的锂电池仿真模型

求助:有没有基于pscad的锂电池仿真模型 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。 新的改变没有 我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写...

qq_38922523的博客 1220

Struts——三大组件(二)RequestProcessor

RequestProcessor                   ActionServlet是Struts框架里唯一一个Servlet,它负责处理所有请求。它无论何时收到一个请求,都会首先试着为现有请求找到一个子应用。一旦子应用被找到,它会为其生成一个RequestProcessor对象,并调用传入HttpServletRequest和HttpServletResponse为参数的p

康 3545

VUE3如何使得大屏自适应的几种方法?

16:9。

weixin_42711805的博客 1万+

java struts1_Struts1.1源码解析

Struts1.1b3部分源代码分析.作者: 文章来源:访问次数: 次 加入时间:2006-05-12Struts1.1部分源代码分析一:说明本文针对Struts1.1b3做分析,主要希望通过对源代码的分析阐述Struts1.1的工作方式。本文不适合初学者参考,适合具有一定基于Struts开发的程序员参考。下面的描述;里面将会对ActionServlet,RequestProcess...

weixin_31119221的博客 265

Agent 智能体学习指南:从入门到求职的完整路线

AI Agent 不是更长的 prompt,而是一类系统设计——模型在受控环境里观察状态、选择动作、调用工具、接收反馈。本文系统梳理 Agent 的 5 个核心概念、7 个关键模块(Goal/State/Context/Tools/Loop/Guardrails/Eval)、三条学习路线(工程落地/研究算法/求职作品)、框架选型策略(LangGraph/OpenAI Agents SDK/Pydantic AI 等场景推荐),以及可执行的 7 天最小项目计划(从概念到简历 bullet)。

program哲学 229

基于Django+Vue3的目标检测系统设计与实现,Web前后端分离,Vue3前台系统+Django后台管理系统开发,YOLOv11 Web目标检测,实现图片、视频和摄像头检测等功能,全网独发

基于Django+Vue3的智能目标检测系统设计与实现,Web前后端分离,YOLOv11 Web目标检测,Vue3前台系统+Django后台管理系统开发,实现图片检测、视频检测、摄像头检测、登录、注册和个人中心功能

在职AI算法工程师,擅长计算机视觉,YOLO目标检测、分割等,擅长web、pyqt界面可视化,好内容持续更新中,来这里跟大家一起学习,共同进步 3663

struts ModuleConfig类加载初始化的过程以及RequestProcessor类源码分析

简要的对struts ActionServlet init()方法以及 struts RequestProcessor类做源码分析 首先要确定的是Servlet至始至终只有一个对象以及init()方法会在Servlet第一次加载时被执行一次也是唯 一的一次,所以Servlet的初始化工作一般在init()方法进行 一、解析ModuleConfig ModuleConfig封装了struts

shenglam的专栏 912

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

RequestProcessor及其在struts-config.xml中的配置

RequestProcessor及其在struts-config.xml中的配置 &lt;controller processorClass="cn.ourbooks.common.MyRequestProcessor" /&gt; 通常来讲,好象不常用到。 但如果想从源头抓头对Web层的控制,就非得做一个RequestProcessor的过滤器。   在这里说一个最明显示的例子: 在...

爱java,爱生活 291

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

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

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Struts源代码 --阅读RequestProcessor

本篇详细介绍struts在初始化之后是如何处理一个请求,并返回数据的。这里最核心的类是RequestProcessor以及 RequestUtils。RequestProcessor类通过RequestDispatcher实现页面的跳转,而 RequestProcessor负责处理request中传来的请求信息,存放到FormBeanConfig中,以及对要跳转的url进行处理。 第二

啊恒的专栏 1658

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

RequestProcessor

RequestProcessor及其在struts-config.xml中的配置通常来讲,好象不常用到。但如果想从源头抓头对Web层的控制,就非得做一个RequestProcessor的过滤器。 在这里说一个最明显示的例子:在使用Struts的过程中,对Session Timeout或者客户是否登录的判断往往是放到一个Action基类(继承于Struts的Action)中处

vc_asp的专栏 1111

struts1源码分析

struts1源码分析

清水雨潭 1773

Struts源码学习 — ActionServlet的初始化

struts

bearyb1982的专栏 513

struts源码分析

一:说明本文针对Struts1.1b3做分析,主要希望通过对源代码的分析阐述Struts1.1的工作方式。本文不适合初学者参考,适合具有一定基于Struts开发的程序员参考。下面的描述;里面将会对ActionServlet,RequestProcessor,ModuleConfig等几个类做一些说明。以注释源代码的方式,说明取工作流程。特别申明:Struts1.1代码版权属于Apache遵循The

cool_rain_man的专栏 1445

自学SSH框架之---struts(三):源码分析struts1如何实现Action单例模式

<br />      今天想说的是struts1和struts2的一个区别,就是Struts1 Action是单例模式并且必须是线程安全的,而Struts2 Action对象为每一个请求产生一个实例,因此没有线程安全问题。我们就简单分析源码看看struts1是如何实现Action单例模式的。<br />       我们借助于自学SSH框架之---struts(二)中的例子来简单分析下。先看看web.xml的配置,如下:<br /><?xml version="1.0" encoding="UTF-8"?

fengg5241的专栏 2564

struts+spring+hibernate整合出现的问题:TilesPlugin : Specified RequestProcessor not comp

在整合ssh过程中出现的问题和解决方法: 一、 严重: Servlet /loginssh threw load() exception javax.servlet.UnavailableException: Parsing error processing resource path /WEB-INF/struts-config.xml 分析在struts-config....

Air100的专栏 323
上一篇: struts的初始化
下一篇: Liferay学习
myjava_024
博客等级 码龄20年 59粉丝 45原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值