Jexl表达式引擎(2)

JEXL是一个库,用于促进应用程序和框架中动态和脚本功能的实现。它提供了三种级别的API,包括动态调用、脚本表达式和模板表达式。JEXL支持多种表达式评估,如JexlExpression、JexlScript和JxltEngine.Expression。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JEXL是一个旨在促进应用程序和框架中动态和脚本功能实现的库。

Example:

在最简单的形式中,JEXL 在评估表达式时将JexlExpression 与 JexlContext 合并。使用JexlEngine.createExpression(java.lang.String)传递包含有效JEXL语法的String 创建表达式 。可以使用MapContext实例创建简单的JexlContext ; 可以通过其构造函数选择提供内部包装的变量映射。以下示例采用名为car的变量,并在属性engine上调用checkStatus()方法

        // Create a JexlEngine (could reuse one instead)
        JexlEngine jexl = new JexlBuilder().create();
        // Create an expression object equivalent to 'car.getEngine().checkStatus()':
        String jexlExp = "car.engine.checkStatus()";
        Expression e = jexl.createExpression( jexlExp );
        // The car we have to handle coming as an argument...
        Car car = theCarThatWeHandle;
        // Create a context and add data
        JexlContext jc = new MapContext();
        jc.set("car", car );
        // Now evaluate the expression, getting the result
        Object o = e.evaluate(jc);

使用JEXL
API由三个级别组成,以满足不同的功能需求:

  • 动态调用setter,getters,方法和构造函数
  • 脚本表达式称为JEXL表达式
  • JSP / JSF之类的表达式称为JXLT表达式

重要的提示:
公共API类位于2个包中:
org.apache.commons.jexl3
org.apache.commons.jexl3.introspection
以下软件包遵循“按您自己的维护成本使用”政策; 这些仅用于扩展JEXL。他们的类和方法不保证在后续版本中保持兼容。如果您认为需要直接使用他们的某些功能或方法,最好先通过邮件列表与社区联系。
org.apache.commons.jexl3.parser
org.apache.commons.jexl3.scripting
org.apache.commons.jexl3.internal
org.apache.commons.jexl3.internal.introspection

动态调用
这些功能与BeanUtils中的核心级实用程序非常接近 。对于基本的动态属性操作和方法调用,您可以使用以下方法集:

  • JexlEngine.newInstance(java.lang.Class<? extends T>, java.lang.Object...)
  • JexlEngine.setProperty(java.lang.Object, java.lang.String, java.lang.Object)
  • JexlEngine.getProperty(java.lang.Object, java.lang.String)
  • JexlEngine.invokeMethod(java.lang.Object, java.lang.String, java.lang.Object...)

以下示例说明了它们的用法:

           // test outer class
            public static class Froboz {
                int value;
                public Froboz(int v) { value = v; }
                public void setValue(int v) { value = v; }
                public int getValue() { return value; }
            }
            // test inner class
            public static class Quux {
                String str;
                Froboz froboz;
                public Quux(String str, int fro) {
                    this.str = str;
                    froboz = new Froboz(fro);
                }
                public Froboz getFroboz() { return froboz; }
                public void setFroboz(Froboz froboz) { this.froboz = froboz; }
                public String getStr() { return str; }
                public void setStr(String str) { this.str = str; }
            }
            // test API
            JexlEngine jexl = new JexlBuilder().create();
            Quux quux = jexl.newInstance(Quux.class, "xuuq", 100);
            jexl.setProperty(quux, "froboz.value", Integer.valueOf(100));
            Object o = jexl.getProperty(quux, "froboz.value");
            assertEquals("Result is not 100", new Integer(100), o);
            jexl.setProperty(quux, "['froboz'].value", Integer.valueOf(1000));
            o = jexl.getProperty(quux, "['froboz']['value']");
            assertEquals("Result is not 1000", new Integer(1000), o);

表达式和脚本
如果您的需求需要简单的表达式评估功能,核心JEXL功能很可能适合。主要方法是:

  • JexlEngine.createScript(org.apache.commons.jexl3.JexlInfo, java.lang.String, java.lang.String[])
  • JexlScript.execute(org.apache.commons.jexl3.JexlContext)
  • JexlEngine.createExpression(org.apache.commons.jexl3.JexlInfo, java.lang.String)
  • JexlExpression.evaluate(org.apache.commons.jexl3.JexlContext)

以下示例说明了它们的用法:

            JexlEngine jexl = new JexlBuilder().create();

            JexlContext jc = new MapContext();
            jc.set("quuxClass", quux.class);

            JexlExpression create = jexl.createExpression("quux = new(quuxClass, 'xuuq', 100)");
            JelxExpression assign = jexl.createExpression("quux.froboz.value = 10");
            JexlExpression check = jexl.createExpression("quux[\"froboz\"].value");
            Quux quux = (Quux) create.evaluate(jc);
            Object o = assign.evaluate(jc);
            assertEquals("Result is not 10", new Integer(10), o);
            o = check.evaluate(jc);
            assertEquals("Result is not 10", new Integer(10), o);

统一表达式和模板
如果您正在寻找类似JSP-EL和基本模板功能,可以使用JxltEngine中的Expression。

主要方法是:

  • JxltEngine.createExpression(java.lang.String)
  • JxltEngine.Expression.prepare(org.apache.commons.jexl3.JexlContext)
  • JxltEngine.Expression.evaluate(org.apache.commons.jexl3.JexlContext)
  • JxltEngine.createTemplate(org.apache.commons.jexl3.JexlInfo, java.lang.String, java.io.Reader, java.lang.String...)
  • JxltEngine.Template.prepare(org.apache.commons.jexl3.JexlContext)
  • JxltEngine.Template.evaluate(org.apache.commons.jexl3.JexlContext, java.io.Writer)

以下示例说明了它们的用法:

JexlEngine jexl = new JexlBuilder().create();
JxltEngine jxlt = jexl.createJxltEngine();
JxltEngine.Expression expr = jxlt.createExpression("Hello ${user}");
String hello = expr.evaluate(context).toString();

JexlExpression,JexlScript,表达式和模板:摘要

JexlExpression
这些是JexlEngine表达式的最基本形式,只允许执行单个命令并返回其结果。如果您尝试使用多个命令,它会忽略第一个分号后的所有内容,只返回第一个命令的结果。

还要注意表达式不是语句(这是脚本的组成),并且不允许使用流控制(if,while,for),变量或lambdas语法元素。

JexlScript
这些允许您使用多个语句,您可以使用变量赋值,循环,计算等。或多或少可以在Shell或JavaScript的基本级别实现。从脚本返回最后一个命令的结果。

JxltEngine.Expression
这些是生成“单行”文本的理想选择,就像toString()一样。要获得计算,请使用类似EL的语法,如$ {someVariable}。括号之间的表达式行为类似于JexlScript,而不是表达式。您可以使用分号来执行多个命令,并从脚本返回最后一个命令的结果。您还可以使用#{someScript}语法使用2-pass评估。

JxltEngine.Template
这些产生了文本文档。以'$$'开头的每一行(作为默认值)被视为JEXL代码,所有其他行被视为JxltEngine.Expression。可以将它们视为简单的Velocity模板。重写的MudStore初始Velocity示例如下所示:

    <html>
        <body>
            Hello ${customer.name}!
            <table>
    $$      for(var mud : mudsOnSpecial ) {
    $$          if (customer.hasPurchased(mud) ) {
                <tr>
                    <td>
                        ${flogger.getPromo( mud )}
                    </td>
                </tr>
    $$          }
    $$      }
    </table>
    </body>
    </html>

JEXL配置
JexlEngine可以通过一些参数进行配置,这些参数将驱动它在出现错误时的反应方式。这些配置方法是通过一个嵌入的JexlBuilder。

静态和共享配置
JexlEngine和JxltEngine都是线程安全的,他们的大部分内部领域都是最终的; 可以在不同线程之间共享相同的实例,并在关键区域(内省缓存)中实施适当的同步。

特别重要的是JexlBuilder.loader,它指示JexlEngine正在构建哪个类加载器用于解决类名; 这直接影响JexlEngine.newInstance和'new'脚本方法的运行方式。

在您依赖JEXL动态加载和调用应用程序插件的情况下,这也非常有用。为了避免在插件实现更改时重新启动服务器,您可以调用 JexlEngine.setClassLoader(java.lang.ClassLoader),通过此引擎实例创建的所有脚本将自动指向新加载的类。

您可以通过NoJexl 完全屏蔽JEXL内省的类和方法的注释来说明可以通过脚本操作的内容。限制JEXL的另一种可配置方式是使用一个 JexlSandbox允许更好地控制暴露内容的方法; 沙箱可以通过JexlBuilder.sandbox。

JexlBuilder.namespaces 通过将您自己的类注册为名称空间来扩展JEXL脚本,允许您自己的函数随意公开。

这可以用作:

            public static MyMath {
                public double cos(double x) {
                    return Math.cos(x);
                }
            }
            Map<String, Object> funcs = new HashMap<String, Object>();
            funcs.put("math", new MyMath());
            JexlEngine jexl = new JexlBuilder().namespaces(funcs).create();

            JexlContext jc = new MapContext();
            jc.set("pi", Math.PI);

            JexlExpression e = JEXL.createExpression("math:cos(pi)");
            o = e.evaluate(jc);
            assertEquals(Double.valueOf(-1),o);

如果命名空间是一个Class,并且该类声明了一个带有JexlContext(或扩展JexlContext的类)的构造函数,则在表达式中首次使用时会创建一个命名空间实例; 此实例生命周期仅限于表达式评估。

也可以配置JexlEngine和JxltEngine表达式缓存。如果您打算在应用程序中重复使用JEXL,那么这些值得配置,因为表达式解析非常繁重。请注意,JEXL创建的所有缓存都是通过SoftReference保存的; 在高内存压力下,GC将能够回收这些缓存,如果需要,JEXL将重建它们。默认情况下,JexlEngine会为“小”表达式创建缓存,而JxltEngine会为Expression创建一个缓存。

JexlBuilder.cache将设置JEXL引擎可以同时缓存多少个表达式。JxltEngine允许通过其构造函数定义缓存大小。

JexlBuilder.debug 使得JExlException携带的堆栈跟踪更有意义; 特别是,这些跟踪将携带表达式创建的确切调用者位置。

动态配置
这些配置选项可以在评估期间通过实现JexlContext 实现JexlEngine.Options带有评估选项的实现来覆盖 。测试包中存在这样的类的示例。

JexlBuilder.strict或者JexlEngine.Options.isStrict() 在JEXL在各种情况下将'null'视为错误时进行配置; 当面对一个不可引用的变量时,使用null作为算术运算符的参数或者无法调用方法或构造函数。宽松模式接近JEXL-1.1行为。

JexlBuilder.silent或JexlEngine.Options.isSilent() 配置JEXL如何对错误做出反应; 如果是静默的,引擎将不会抛出异常但会通过记录器发出警告并在出现错误时返回null。请注意,当非静默时,JEXL会抛出JexlException,这是未经检查的异常。

JexlContext.NamespaceResolver通过JexlContext 实现- 以 JexlEvalContext 为例 - 允许覆盖命名空间解析和通过定义的默认命名空间映射JexlBuilder.namespaces。

JEXL定制
这是JexlContext,JexlBuilder并且 JexlEngine.Options是您希望实现自定义的最可能的接口。由于它们暴露变量和选项,因此它们是主要目标。在此之前,请查看测试目录中的JexlEvalContext,ObjectContext这可能已经涵盖了您的一些需求。

JexlArithmetic 如果您需要更改运算符的行为方式或添加运算类型,则派生类。有3个入口点可以自定义创建的对象类型:

  • array literals: JexlArithmetic.arrayBuilder(int)
  • map literals: JexlArithmetic.mapBuilder(int)
  • set literals: JexlArithmetic.setBuilder(int)
  • range objects: JexlArithmetic.createRange(java.lang.Object, java.lang.Object)

您还可以重载运算符方法; 按照惯例,每个运算符都有一个与之关联的方法名称。如果在JexlArithmetic派生实现中重载某些方法,则当参数与方法签名匹配时,将调用这些方法。例如,如果你想要'+'来操作数组,就会出现这种情况; 你需要派生JexlArithmetic并实现'public Object add(Set x,Set y)'方法。但请注意,您无法更改运算符优先级。运算符/方法匹配列表如下:

OperatorMethod NameExample
+addadd(x, y)
-subtractsubtract(x, y)
*multiplymultiply(x, y)
/dividedivide(x, y)
%modmod(x, y)
&bitwiseAndbitwiseAnd(x, y)
IbitwiseOrbitwiseOr(x, y)
^bitwiseXorbitwiseXor(x, y)
!logicalNotlogicalNot(x)
-bitwiseComplementbitiwiseComplement(x)
==equalsequals(x, y)
<lessThanlessThan(x, y)
<=lessThanOrEquallessThanOrEqual(x, y)
>greaterThangreaterThan(x, y)
>=greaterThanOrEqualgreaterThanOrEqual(x, y)
-negatenegate(x)
sizesizesize(x)
emptyemptyempty(x)

您还可以添加方法来重载属性getter和setter操作符行为。名为propertyGet / propertySet / arrayGet / arraySet的JexlArithmetic实例的公共方法是在适当时调用的潜在替代。下表概述了语法形式与调用方法之间的关系,其中V是属性值类,O是对象类,P是属性标识符类(通常是String或Integer)。

ExpressionMethod Template
foo.propertypublic V propertyGet(O obj, P property);
foo.property = valuepublic V propertySet(O obj, P property, V value);
foo[property]public V arrayGet(O obj, P property, V value);
foo[property] = valuepublic V arraySet(O obj, P property, V value);

您还可以覆盖基本运算符方法,其参数为Object的方法可以为您提供完全控制。

扩展JEXL
如果您需要使JEXL以特殊方式处理某些对象或调整它对某些设置的反应,您可以获得其大部分内部工作。这些类和方法很少是私有的或最终的 - 只有当内部合同真正需要它时。但是,使用受保护的方法和内部包类意味着在发布新的JEXL版本时,可能必须重新调整代码。
Engine可以扩展为允许您捕获自己的配置默认值wrt缓存大小和各种标志。实现自己的缓存 - 而不是基于LinkedHashMap的基本缓存 - 将是另一种可能的扩展。

Interpreter 如果您需要向评估本身添加更多功能,那么该类是派生的类; 例如,您希望变量的前置和后置解析器或变量上下文的嵌套作用域。

Uberspect 如果需要为某些对象添加内省或反射功能,则可以派生类,例如将基于工厂的支持添加到“new”运算符。代码已经将公共字段反映为Java-beans约定之上的属性。

原文链接:http://commons.apache.org/proper/commons-jexl/apidocs/org/apache/commons/jexl3/package-summary.html#package.description

转载于:https://www.cnblogs.com/Jmmm/p/10610535.html

<think>我们正在讨论JEXL3表达式引擎中,当小数部分为.00时,在计算过程中自动转换为整数的问题。用户希望避免这种自动转换,保持小数格式。 首先,我们需要理解问题:在JEXL3中,如果一个数字的小数部分为0(如1.00),在计算过程中可能会被表示为整数(1)。但用户希望保留小数部分(即显示为1.00)。 注意:JEXL3是基于Java表达式语言引擎,其数字处理遵循Java的基本规则。在Java中,1.00和1在数值上是相等的,但在表示上,如果我们希望保留小数位数,通常需要格式化输出。 解决方案思路: 1.JEXL3表达式中,我们无法直接改变数字的内部表示,但可以在输出时进行格式化。 2. 使用JEXL3的内置函数或自定义函数来格式化数字,保留指定位数的小数。 具体步骤: a) 自定义一个JEXL函数,该函数接收一个数字和一个小数位数,返回格式化后的字符串(或BigDecimal,根据需求)。 b)表达式中,当我们需要保留小数时,调用这个自定义函数。 但是,用户可能希望在整个表达式的计算过程中都保持精确的小数,而不仅仅是输出。这涉及到使用BigDecimal来避免浮点数的精度问题。 更全面的解决方案: 1.JEXL3中,我们可以通过设置算术上下文(ArithmeticContext)来使用BigDecimal进行精确计算。这样,1.00就会一直保持为1.00(即BigDecimal类型,且scale为2)。 2. 同时,在输出时,我们可以直接输出BigDecimal,它会保留小数位数(包括尾随零)。 步骤: 1. 创建JexlEngine时,设置算术上下文为支持BigDecimal的模式。 2.表达式中,使用BigDecimal字面量(如`1.00`)或使用字符串构造BigDecimal(如`new('1.00')`)来确保数字具有所需的小数位数。 然而,JEXL3默认的算术工厂(Arithmetic)对于浮点数使用的是double,所以我们需要配置为使用BigDecimal算术。 示例配置: ```java JexlBuilder jexlBuilder = new JexlBuilder(); jexlBuilder.arithmetic(new BigDecimalArithmetic()); JexlEngine jexl = jexlBuilder.create(); ``` 这样,表达式中的数字字面量(如1.23)将被解释为BigDecimal。但是,注意:整数(如1)会被解释为Integer,而小数(如1.0)会被解释为BigDecimal。 对于用户的问题,我们希望1.00这样的数字在计算中不被转为整数,那么我们可以确保所有数字都使用BigDecimal表示。 在表达式中,我们可以这样写: 1.00 -> 这是一个BigDecimal,值为1.00(scale=2) 1.0 -> 这也是一个BigDecimal,值为1.0(scale=1) 1 -> 这是一个Integer,但当我们使用BigDecimalArithmetic时,在运算中会提升为BigDecimal。 因此,在计算中,如果我们有表达式`1.00 + 1`,结果将是2.00(BigDecimal,scale=2)。 但是,如果用户希望输出时保留两位小数,即使计算结果为整数(如2)也要显示为2.00,那么我们需要在输出时进行格式化。 我们可以使用以下方法: 在表达式中使用`String.format`或自定义的格式化函数。 例如,假设我们有一个自定义的格式化函数`format`,可以这样使用: format(2, 2) -> "2.00" 或者,在JEXL3中,我们可以使用Java的格式化方法: `String.format('%.2f', 2)` -> 但是注意,这个方法要求参数是浮点数,如果是整数,需要先转换为浮点数?实际上,整数也可以,但会格式化为2.00。 但是,在JEXL3表达式中直接使用String.format可能不太方便,因为需要处理异常和类型转换。 因此,建议自定义一个函数: ```java public static String formatNumber(Number number, int decimalPlaces) { // 使用BigDecimal来格式化 BigDecimal bd = (number instanceof BigDecimal) ? (BigDecimal) number : new BigDecimal(number.toString()); bd = bd.setScale(decimalPlaces, RoundingMode.HALF_UP); return bd.toPlainString(); } ``` 然后将这个函数注册到JEXL引擎中。 总结: 要避免小数.00被自动转换为整数,我们需要: 1. 配置JEXL3使用BigDecimal算术,这样小数部分为零的数仍然会保持为BigDecimal(例如1.00)。 2. 在需要输出时,使用格式化函数将数字格式化为指定小数位数的字符串,以确保尾随零的显示。 注意:在BigDecimal算术中,计算会保留小数位数和精度,但最终输出时,我们仍然需要格式化,因为BigDecimal的toString方法可能会省略尾随零(例如,1.0可能会显示为1,1.00可能会显示为1)。但是,我们可以使用`toPlainString`方法并设置scale来保留尾随零。 实际上,BigDecimal的toString方法对于整数部分不为0且小数部分全0的数会去掉尾随零,例如1.00会输出"1.00"吗?不会,它会输出"1"。所以我们需要使用`setScale`设置小数位数,然后调用`toPlainString`,这样就会输出带尾随零的字符串。 因此,在自定义的格式化函数中,我们使用setScale来设置小数位数,然后调用toPlainString,这样就会得到带尾随零的字符串。 示例: 输入:1.00,decimalPlaces=2 -> 输出"1.00" 输入:1.23,decimalPlaces=2 -> 输出"1.23" 输入:1.234,decimalPlaces=2 -> 输出"1.23"(四舍五入) 所以,用户可以在表达式中这样写: formatNumber(1.00, 2) // 返回字符串"1.00" formatNumber(1, 2) // 返回字符串"1.00"(因为1会被转换为BigDecimal(1),然后设置scale=2,变成1.00) 这样,就解决了输出时自动转换整数的问题。 但是,在计算过程中,如果希望中间结果也保持小数位数,我们需要在每一步计算中都注意操作数的类型和精度。使用BigDecimal算术会自动处理这些,但要注意,不同的操作(加减乘除)可能会有不同的精度规则。 因此,完整的解决方案包括: 1. 配置JEXL3使用BigDecimal算术。 2.表达式中使用BigDecimal字面量(如1.00)或通过字符串构造BigDecimal(如`new('1.00')`)来确保初始值具有正确的小数位数。 3. 在需要输出结果的地方,使用自定义的formatNumber函数来格式化数字。 下面是一个示例代码片段,展示如何配置JEXL3引擎和注册自定义函数: ```java // 创建JexlEngine,使用BigDecimal算术 JexlEngine jexl = new JexlBuilder().arithmetic(new BigDecimalArithmetic()).create(); // 创建JexlContext并设置变量 JexlContext context = new MapContext(); // 注册自定义函数(假设我们有一个工具类JexlUtils,其中包含formatNumber方法) jexl.setProperty(JexlEngine.FUNCTIONS, JexlUtils.class.getMethod("formatNumber", Number.class, int.class)); // 表达式 String expression = "formatNumber(1.00 + 2.00, 2)"; // 期望结果"3.00" // 执行表达式 Object result = jexl.createExpression(expression).evaluate(context); System.out.println(result); // 输出: 3.00 ``` 注意:BigDecimalArithmetic是JEXL3提供的一个类,位于`org.apache.commons.jexl3.arithmetic.BigDecimalArithmetic`。 另外,如果用户希望避免在表达式中每次都调用格式化函数,可以考虑在输出层统一处理,但这取决于具体的应用场景。 最后,需要提醒用户:使用BigDecimal算术可能会带来性能上的开销,但对于需要精确小数计算的场景是必要的。 相关问题: 1. 如何在JEXL3中配置使用BigDecimal进行精确计算? 2. 如何在JEXL3中注册自定义函数? 3. 如何避免BigDecimal计算中的精度损失? 4.JEXL3中,如何格式化数字输出?</think>### JEXL3 中小数自动转换为整数的问题及解决方法 在 JEXL3 表达式引擎中,当小数部分为 `.00` 时,系统会自动将其转换为整数(如 `1.00` 变为 `1`)。这源于 JEXL3 的数字处理机制:**所有数值字面量默认使用 Java 的 `Double` 类型存储**,而 `Double` 类型会自动省略无意义的小数尾零[^1]。以下是解决方案: --- #### 解决方法一:强制保留小数格式(推荐) 通过显式指定数字格式,确保输出保留两位小数: ```java import java.math.BigDecimal; import java.text.DecimalFormat; // 在 JEXL 表达式中调用格式化函数 String expression = "format(1.00)"; JexlEngine jexl = new JexlBuilder().arithmetic(new StrictArithmetic()).create(); JexlContext context = new MapContext(); context.set("format", (Function)(ctx, args) -> { double value = ((Number) args[0]).doubleValue(); return new DecimalFormat("0.00").format(value); // 格式化为两位小数 }); Object result = jexl.createExpression(expression).evaluate(context); System.out.println(result); // 输出 "1.00" ``` --- #### 解决方法二:使用 `BigDecimal` 精确计算 通过 `BigDecimal` 避免浮点数自动截断: ```java // 注册 BigDecimal 构造函数到 JEXL 上下文 context.set("BigDecimal", (Function)(ctx, args) -> new BigDecimal(args[0].toString())); // 表达式直接使用 BigDecimal String expression = "BigDecimal('1.00') + BigDecimal('2.00')"; Object result = jexl.createExpression(expression).evaluate(context); System.out.println(result); // 输出 "3.00" ``` --- #### 解决方法三:配置算术策略 修改 JEXL 引擎的算术策略,强制保留小数精度: ```java JexlEngine jexl = new JexlBuilder() .arithmetic(new StrictArithmetic() { @Override public Object promote(Object o) { if (o instanceof Double) { return BigDecimal.valueOf((Double) o); } return super.promote(o); } }) .create(); ``` --- #### 关键原因分析 | 现象 | 根本原因 | 解决方案核心 | |---------------------|----------------------------------|--------------------------| | `1.00` 输出为 `1` | `Double` 类型自动省略尾零 | 显式格式化或使用 `BigDecimal` | | 计算结果丢失小数位 | JEXL 默认浮点数运算 | 配置算术策略保留精度 | > **提示**:若需严格金融计算,优先使用 `BigDecimal` 方案;若仅需显示控制,选择格式化方案更简便[^2]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值