java type chain_基于Struts2 Result Type为chain 的Action之间数据传递

本文为原创,欢迎转载,转载请注明出处BlogJava。

chain:基本用途是构造成一条动作链。前一个Action将控制权转交给后一个Action,而前一个Action的状态在后一个Action里仍然保持着。

我现在有一个场景,FirstAction 通过chain的方式,将控制权交给 SecondAction。FirstAction对应的页面代码为first.ftl,SecondAction对应的页面代码为second.ftl。

假设我们的FirstAction如下定义:

1fa987a29c6482f53d401256f96355eb.png

ca75c07623e1b494fee67e8f316fc310.gifpublicclassFirstActionextendsActionSupport9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.pngprivateString input1=null;//由first.ftl页面输入值d18c02628675d0a2c816449d98bda930.pngprivateString input2=null;//由first.ftl页面输入值d18c02628675d0a2c816449d98bda930.pngprivateCustomUser user=null;//并不在first.ftl页面上有相关元素绑定d18c02628675d0a2c816449d98bda930.png97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifpublicString execute()throwsException9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png//做一些事情,生成了CustomUserd18c02628675d0a2c816449d98bda930.pngsetCustomUser(ret);

d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.pngreturn"toSecond";

ecedf933ec37d714bd4c2545da43add2.png    }d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.png//getter setter8f1ba5b45633e9678d1db480c16cae3f.png}

意思很明确了,通过first.ftl的输入,到DB中或其他,生成了我们的CustomUser对象,这个CustomUser对象将要在SecondAction使用。

于是我们想到了要配置FirstAction 的 name为toSecond的 Result type为 chain,将 生成的CustomUser对象传递到 SecondAction中,

我们也这样做了,但是 经过调试,发现在SecondAction中没有得到 FirstAction中的CustomUser对象。

SecondAction是这样实现的:

1fa987a29c6482f53d401256f96355eb.png

ca75c07623e1b494fee67e8f316fc310.gifpublicclassSecondActionextendsActionSupport9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.pngprivateCustomUser user=null;

d18c02628675d0a2c816449d98bda930.png

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifpublicString execute()throwsException9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png//利用user做事情或显示在页面上ecedf933ec37d714bd4c2545da43add2.png}d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.png//getter setter8f1ba5b45633e9678d1db480c16cae3f.png}4f1150b881333f12a311ae9ef34da474.png

看一下ChainingInterceptor.java的实现,发现有这样的注释:

4f1150b881333f12a311ae9ef34da474.pngAn interceptor that copies all the properties of every object in the value stack to the currently executing object

9b8a8a44dd1c74ae49c20a7cd451974e.png.

在 FirstAction 中CustomUser user 并没有在 value stack 中,所以没有拷贝到SecondAction中。

知道了问题所在,就要解决。首先是想换一种方式去做,将我们要传递的参数通过 其他 Result type 如redirectAction去传递。

例如:

4f1150b881333f12a311ae9ef34da474.png4f1150b881333f12a311ae9ef34da474.pngSecondAction4f1150b881333f12a311ae9ef34da474.pngexecute4f1150b881333f12a311ae9ef34da474.png${user}4f1150b881333f12a311ae9ef34da474.png

但这样做的缺点是,

1.我们要在浏览器上看到很长很乱的URL(如果超过URL长度限制那就更悲剧了)。

2.暴露这些参数总感觉很不爽。

3.自定义的对象不能用这种方式传递,要么传String、或JsonObject等。

另外一个解决办法:

因为Result type为chain时,在执行SecondAction时,它的上一个Action,也就是FirstAction的实例并没有被销毁,FirstAction的实例被加入到了ValueStack中。

所以,实现的思路就是,增加一个拦截器,在执行Actioin前判断一下,当前Action是否需要从前面的Action实例中获取数据。

这个可以通过注解的方式告诉拦截器,当前的action需要什么样的对象。

思路明确了,来看看代码:

注解类:ChainTransParam.java

4f1150b881333f12a311ae9ef34da474.pngimportjava.lang.annotation.Documented;

4f1150b881333f12a311ae9ef34da474.pngimportjava.lang.annotation.ElementType;

4f1150b881333f12a311ae9ef34da474.pngimportjava.lang.annotation.Retention;

4f1150b881333f12a311ae9ef34da474.pngimportjava.lang.annotation.RetentionPolicy;

4f1150b881333f12a311ae9ef34da474.pngimportjava.lang.annotation.Target;

4f1150b881333f12a311ae9ef34da474.png

4f1150b881333f12a311ae9ef34da474.png@Target(ElementType.FIELD)

4f1150b881333f12a311ae9ef34da474.png@Retention(RetentionPolicy.RUNTIME)

4f1150b881333f12a311ae9ef34da474.png@Documented

1fa987a29c6482f53d401256f96355eb.png

ca75c07623e1b494fee67e8f316fc310.gifpublic@interfaceChainTransParam9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png    String fieldName()default"";

8f1ba5b45633e9678d1db480c16cae3f.png}

拦截器实现:ChainParameterInterceptor.java

1fa987a29c6482f53d401256f96355eb.png

ca75c07623e1b494fee67e8f316fc310.gif/** *//**d18c02628675d0a2c816449d98bda930.png * Result type 为chain时 可通过注解的方式实现参数传递 此参数为前置Action的成员变量、并提供getter方法

d18c02628675d0a2c816449d98bda930.png * 此参数并不要求一定要在值栈中

d18c02628675d0a2c816449d98bda930.png * 

d18c02628675d0a2c816449d98bda930.png *@authorliming

8f1ba5b45633e9678d1db480c16cae3f.png*/1fa987a29c6482f53d401256f96355eb.png

ca75c07623e1b494fee67e8f316fc310.gifpublicclassChainParameterInterceptorextendsAbstractInterceptor9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.pngprivatestaticfinallongserialVersionUID=-8279316685527646358L;

d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.png    @Override

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifpublicString intercept(ActionInvocation invocation)throwsException9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png        ValueStack stack=invocation.getStack();

d18c02628675d0a2c816449d98bda930.png        CompoundRoot root=stack.getRoot();

d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.png//值栈不为null 且已经有前置Action

d18c02628675d0a2c816449d98bda930.png//栈最顶层(index = 0)为当前Action、紧接着(index = 1) 为前置Action97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifif(root==null||root.size()<=2)9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.pngreturninvocation.invoke();

ecedf933ec37d714bd4c2545da43add2.png        }d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.png//当前Action对象d18c02628675d0a2c816449d98bda930.pngObject target=invocation.getAction();

d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.png        Field[] fields=target.getClass().getDeclaredFields();

d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.png//遍历此Action对象的属性 是否有RecieveData注解97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.giffor(Field field : fields)9b8a8a44dd1c74ae49c20a7cd451974e.png{

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifif(field.isAnnotationPresent(ChainTransParam.class))9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png                ChainTransParam rData=field.getAnnotation(ChainTransParam.class);

d18c02628675d0a2c816449d98bda930.png//取得源数据字段名d18c02628675d0a2c816449d98bda930.pngString fromName=rData.fieldName();

d18c02628675d0a2c816449d98bda930.png                fromName=StringUtils.isEmpty(fromName)?field.getName() : fromName;

d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.png//取得最近的前置Actiond18c02628675d0a2c816449d98bda930.pngObject srcAction=root.get(1);

d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.png//取得对应字段的值d18c02628675d0a2c816449d98bda930.pngObject value=ReflectionUtils.getFieldValue(srcAction, srcAction.getClass(), field.getName());

d18c02628675d0a2c816449d98bda930.png//设定值d18c02628675d0a2c816449d98bda930.pngReflectionUtils.setFieldValue(target, field.getName(), field.getType(), value);

ecedf933ec37d714bd4c2545da43add2.png            }ecedf933ec37d714bd4c2545da43add2.png        }d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.pngreturninvocation.invoke();

ecedf933ec37d714bd4c2545da43add2.png    }d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.png    @SuppressWarnings("unused")

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifprivateObject findFieldValue(CompoundRoot root, Field field)9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png        Object value=null;

d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.pngintsize=root.size();

d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.png//按顺序遍历前置Action97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.giffor(intindex=1; index{

d18c02628675d0a2c816449d98bda930.png            Object srcAction=root.get(index);

d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.png            Object tmp=ReflectionUtils.getFieldValue(srcAction, srcAction.getClass(), field.getName());

d18c02628675d0a2c816449d98bda930.png//取得对应字段的值 则返回

d18c02628675d0a2c816449d98bda930.png//问题:如果前置Action中该字段本身就为null 则无法处理97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifif(tmp!=null)9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.pngbreak;

ecedf933ec37d714bd4c2545da43add2.png            }ecedf933ec37d714bd4c2545da43add2.png        }d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.pngreturnvalue;

ecedf933ec37d714bd4c2545da43add2.png    }8f1ba5b45633e9678d1db480c16cae3f.png}

在拦截器的实现中,我是只取得前一个Action中的数据,并没有迭代寻找整个ValueStack的Action,也是可以这样实现的,请看我的findFieldValue方法的实现,但这个方法在此拦截器中并没有使用上。因为我不想这样做。

代码完毕之后,配置好拦截器,

我们只要在 SecondAction中 这样定义即可:

1fa987a29c6482f53d401256f96355eb.png

ca75c07623e1b494fee67e8f316fc310.gifpublicclassSecondActionextendsActionSupport9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png    @ChainTransParam

d18c02628675d0a2c816449d98bda930.pngprivateCustomUser user=null;

d18c02628675d0a2c816449d98bda930.png

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifpublicString execute()throwsException9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png//利用user做事情或显示在页面上ecedf933ec37d714bd4c2545da43add2.png}d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.png//getter setter8f1ba5b45633e9678d1db480c16cae3f.png}

当在执行SecondAction之前,拦截器会去查找FirstAction,是否有 user 对象,有则将值拷贝到 SecondAction 中。

ChainTransParam 注解 允许输入参数名,没有输入则默认根据变量名去查找。

注:Struts2 Reference里的意思是不提倡使用Result Type Chain。

另:ReflectionUtils.java 实现:

4f1150b881333f12a311ae9ef34da474.pngimportjava.lang.reflect.Field;

4f1150b881333f12a311ae9ef34da474.pngimportjava.lang.reflect.InvocationTargetException;

4f1150b881333f12a311ae9ef34da474.pngimportjava.lang.reflect.Method;

4f1150b881333f12a311ae9ef34da474.png

4f1150b881333f12a311ae9ef34da474.pngimportorg.apache.commons.lang.StringUtils;

4f1150b881333f12a311ae9ef34da474.pngimportorg.apache.commons.logging.Log;

4f1150b881333f12a311ae9ef34da474.pngimportorg.apache.commons.logging.LogFactory;

4f1150b881333f12a311ae9ef34da474.png

1fa987a29c6482f53d401256f96355eb.png

ca75c07623e1b494fee67e8f316fc310.gifpublicabstractclassReflectionUtils9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.pngprivatestaticfinalLog logger=LogFactory.getLog(ReflectionUtils.class);

d18c02628675d0a2c816449d98bda930.png

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifpublicstaticvoidsetFieldValue(Object target, String fname, Class>ftype, Object fvalue)9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png        setFieldValue(target, target.getClass(), fname, ftype, fvalue);

ecedf933ec37d714bd4c2545da43add2.png    }d18c02628675d0a2c816449d98bda930.png

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifpublicstaticvoidsetFieldValue(Object target, Class>clazz, String fname, Class>ftype, Object fvalue)9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.pngif(target==null||fname==null||"".equals(fname)

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gif||(fvalue!=null&&!ftype.isAssignableFrom(fvalue.getClass())))9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.pngreturn;

ecedf933ec37d714bd4c2545da43add2.png        }d18c02628675d0a2c816449d98bda930.png

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.giftry9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png            Method method=clazz.getDeclaredMethod(

d18c02628675d0a2c816449d98bda930.png"set"+Character.toUpperCase(fname.charAt(0))+fname.substring(1), ftype);

d18c02628675d0a2c816449d98bda930.png//if (!Modifier.isPublic(method.getModifiers())) {d18c02628675d0a2c816449d98bda930.pngmethod.setAccessible(true);

d18c02628675d0a2c816449d98bda930.png//}d18c02628675d0a2c816449d98bda930.pngmethod.invoke(target, fvalue);

d18c02628675d0a2c816449d98bda930.png

ecedf933ec37d714bd4c2545da43add2.png        }97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifcatch(Exception me)9b8a8a44dd1c74ae49c20a7cd451974e.png{

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifif(logger.isDebugEnabled())9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png                logger.debug(me);

ecedf933ec37d714bd4c2545da43add2.png            }d18c02628675d0a2c816449d98bda930.png

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.giftry9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png                Field field=clazz.getDeclaredField(fname);

d18c02628675d0a2c816449d98bda930.png//if (!Modifier.isPublic(field.getModifiers())) {d18c02628675d0a2c816449d98bda930.pngfield.setAccessible(true);

d18c02628675d0a2c816449d98bda930.png//}d18c02628675d0a2c816449d98bda930.pngfield.set(target, fvalue);

ecedf933ec37d714bd4c2545da43add2.png            }97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifcatch(Exception fe)9b8a8a44dd1c74ae49c20a7cd451974e.png{

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifif(logger.isDebugEnabled())9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png                    logger.debug(fe);

ecedf933ec37d714bd4c2545da43add2.png                }ecedf933ec37d714bd4c2545da43add2.png            }ecedf933ec37d714bd4c2545da43add2.png        }ecedf933ec37d714bd4c2545da43add2.png    }d18c02628675d0a2c816449d98bda930.png

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifpublicstaticObject getFieldValue(Object target, String fname)9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.pngreturngetFieldValue(target, target.getClass(), fname);

ecedf933ec37d714bd4c2545da43add2.png    }d18c02628675d0a2c816449d98bda930.png

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifpublicstaticObject getFieldValue(Object target, Class>clazz, String fname)9b8a8a44dd1c74ae49c20a7cd451974e.png{

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifif(target==null||fname==null||"".equals(fname))9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.pngreturnnull;

ecedf933ec37d714bd4c2545da43add2.png        }d18c02628675d0a2c816449d98bda930.png

d18c02628675d0a2c816449d98bda930.pngbooleanexCatched=false;

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.giftry9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png            String methodname="get"+StringUtils.capitalize(fname);

d18c02628675d0a2c816449d98bda930.png            Method method=clazz.getDeclaredMethod(methodname);

d18c02628675d0a2c816449d98bda930.png//if (!Modifier.isPublic(method.getModifiers())) {d18c02628675d0a2c816449d98bda930.pngmethod.setAccessible(true);

d18c02628675d0a2c816449d98bda930.png//}d18c02628675d0a2c816449d98bda930.pngreturnmethod.invoke(target);

ecedf933ec37d714bd4c2545da43add2.png        }97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifcatch(NoSuchMethodException e)9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png            exCatched=true;

ecedf933ec37d714bd4c2545da43add2.png        }97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifcatch(InvocationTargetException e)9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png            exCatched=true;

ecedf933ec37d714bd4c2545da43add2.png        }97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifcatch(IllegalAccessException e)9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png            exCatched=true;

ecedf933ec37d714bd4c2545da43add2.png        }d18c02628675d0a2c816449d98bda930.png

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifif(exCatched)9b8a8a44dd1c74ae49c20a7cd451974e.png{

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.giftry9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png                Field field=clazz.getDeclaredField(fname);

d18c02628675d0a2c816449d98bda930.png//if (!Modifier.isPublic(field.getModifiers())) {d18c02628675d0a2c816449d98bda930.pngfield.setAccessible(true);

d18c02628675d0a2c816449d98bda930.png//}d18c02628675d0a2c816449d98bda930.pngreturnfield.get(target);

ecedf933ec37d714bd4c2545da43add2.png            }97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifcatch(Exception fe)9b8a8a44dd1c74ae49c20a7cd451974e.png{

97e794c86028c5f5b5461ae5ef440a4c.png

3c6cafce68eb941a00f1998f1d3d3aa6.gifif(logger.isDebugEnabled())9b8a8a44dd1c74ae49c20a7cd451974e.png{

d18c02628675d0a2c816449d98bda930.png                    logger.debug(fe);

ecedf933ec37d714bd4c2545da43add2.png                }ecedf933ec37d714bd4c2545da43add2.png            }ecedf933ec37d714bd4c2545da43add2.png        }d18c02628675d0a2c816449d98bda930.pngreturnnull;

ecedf933ec37d714bd4c2545da43add2.png    }d18c02628675d0a2c816449d98bda930.png

8f1ba5b45633e9678d1db480c16cae3f.png}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值