艾伟_转载:使用AOP动态调用WebService

    在网上搜了一下“动态调用WebService”相信都能搜出上千篇文章,但是都出自同一个版本:使用ServiceDescriptionImporter导入wsdl然后进行动态编译,再调用相应的Method返回值。这种方法不足之处就是编译的时候可能会有些慢,毕竟是编译整个WebService,而且前台都是使用同一个方法传入调用的方法来进行调用的。再者,如果使用了Model,引用了WebService后的Model并非此Model,而且如果是List的话,那更差之千里了,返回的只能是数组。
    本人经过思考,用AOP的原理实现了WebService的动态调用,实际上,是调用接口类的方法,然后使用反射得到该方法的返回值,参数等,然后再构造一个WebService的代理类,动态编译后调用返回值。接下来将一一介绍。
    首先定义一个WebService如下。其中使用了FaibClass.Data数据框架。

Code
 1 using System;
 2 using System.Web;
 3 using System.Web.Services;
 4 using System.Web.Services.Protocols;
 5 using System.Xml.Serialization;
 6 using Test.Model;
 7 using Test.DA;
 8 using FaibClass.Data;
 9 
10 [WebService(Namespace = "http://tempuri.org/")]
11 [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
12 public class Service : System.Web.Services.WebService
13 {
14     public Service () {
15     }
16     [WebMethod]
17     public TCompanyType ATest_GetCompanyType()
18     {
19         ATCompanyType da = new ATCompanyType();
20         da.AccessOptions = AccessOptions.SubEntityList;
21         //排除引用实体属性
22         da.PropertyFilter = new DebarredAttributes(typeof(ReferenceEntityAttribute));
23         //列出分类
24         return da.Get("Name='大类'", (string[])null);
25     }
26     [WebMethod]
27     public TCompany ATest_GetFirstCompany()
28     {
29         return new ATCompany().Get(null);
30     }
31     [WebMethod]
32     public TCompanies ATest_GetCompanies()
33     {
34         return new ATCompany().Select();
35     }
36     [WebMethod]
37     [XmlInclude(typeof(TCompany))]
38     public bool ATest_Insert(TCompany info)
39     {
40         return true;
41     }
42     [WebMethod]
43     [XmlInclude(typeof(TCompanies))]
44     public bool ATest_InsertAll(TCompanies list)
45     {
46         return true;
47     }
48     [WebMethod]
49     public void ATest_TestNull()
50     {
51     }
52     private void ATest_ListSubType(TCompanyTypes list)
53     {
54         if (list == nullreturn;
55         foreach (TCompanyType type in list)
56         {
57             //该分类下的公司
58             ATest_ListSubCompany(type.Companies);
59             //该分类下的子类
60             ATest_ListSubType(type.SubCompanyTypes);
61         }
62     }
63 
64     //列出分类公司下面的子公司
65     private void ATest_ListSubCompany(TCompanies companies)
66     {
67         if (companies == nullreturn;
68         foreach (TCompany company in companies)
69         {
70             ATest_ListSubCompany(company.SubCompanies);
71         }
72     }
73 }

    客户端也定义一个与之相似的类,暂将它称为接口类,因为它并不实现操作,只是为AOP调用提供方法信息,但是返回值都为null,即不操作。

Code
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Text;
 4 using FaibClass.Data;
 5 using Test.Model;
 6 
 7 using FaibClass.Dynamic.WebService;
 8 
 9 namespace Test
10 {
11     [DynamicWebService1("ATest_{0}")]
12     public class ATest : DynamicWebService
13     {
14         public TCompanyType GetCompanyType()
15         {
16             return null;
17         }
18         public TCompany GetFirstCompany()
19         {
20             return null;
21         }
22         public TCompanies GetCompanies()
23         {
24             return null;
25         }
26         public bool Insert(TCompany info)
27         {
28             return false;
29         }
30         public bool InsertAll(TCompanies list)
31         {
32             return true;
33         }
34         public void TestNull()
35         {
36         }
37     }
38 }

    前台调用如下:

Code
 1             ATest test = new ATest();
 2             test.TestNull();
 3             TCompanyType type = test.GetCompanyType();
 4             ListSubType(type.SubCompanyTypes);
 5             Console.WriteLine(test.GetFirstCompany().Name);
 6             Console.WriteLine(test.GetCompanies()[0].Name);
 7             TCompany a = new TCompany();
 8             a.Name = "dfdf";
 9             TCompanies list = new TCompanies();
10             list.Add(a);
11             Console.WriteLine(test.InsertAll(list));
12 

    下面将一一对每个类进行说明。
    一、自定义代理属性 DynamicWebServiceAttribute。 

Code
 1 //*******************************************************************
 2 // 模块:动态WebService属性的基类
 3 // 日期:2009-8-23 0:24
 4 // 作者:Faib
 5 // 版权:Copyright Faib Studio 2009
 6 // 官网:http://www.faib.net.cn
 7 // 邮箱:faib920@126.com
 8 // 备注:
 9 //*******************************************************************
10 using System;
11 using System.Runtime.Remoting.Proxies;
12 using System.Runtime.Remoting;
13 using System.Runtime.Remoting.Contexts;
14 using System.Runtime.Remoting.Activation;
15 
16 using FaibClass.Dynamic.Configuration;
17 
18 namespace FaibClass.Dynamic.WebService
19 {
20     /// 
21     /// 动态WebService属性的基类。
22     /// 
23     [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
24     public class DynamicWebServiceAttribute : ProxyAttribute
25     {
26         string m_match;
27 
28         public DynamicWebServiceAttribute(string match)
29         {
30             m_match = match;
31         }
32 
33         /// 
34         /// 创建实例。
35         /// 
36         /// 
37         /// 
38         public override MarshalByRefObject CreateInstance(Type serverType)
39         {
40             DynamicWebServiceSettings setting = DynamicWebServiceConfiguration.Settings[this.GetType().FullName];
41             //创建初始对象
42             MarshalByRefObject mobj = base.CreateInstance(serverType);
43             if (setting != null)
44             {
45                 RealProxy realProxy = new AspectDynamicWebServiceProxy(setting, m_match, serverType, mobj);
46                 //经过代理后返回透明代理
47                 return realProxy.GetTransparentProxy() as MarshalByRefObject;
48             }
49             return mobj;
50         }
51 
52         /// 
53         /// 匹配符。
54         /// 
55         public string Match
56         {
57             get { return m_match; }
58             set { m_match = value; }
59         }
60     }
61 }
62 

    客户端还要为每一个WebService定义一个DynamicWebServiceAttribute的继承类,如

Code
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Text;
 4 
 5 using FaibClass.Dynamic.WebService;
 6 
 7 namespace Test
 8 {
 9     internal class DynamicWebService1 : DynamicWebServiceAttribute
10     {
11         public DynamicWebService1(string match) : base(match){}
12     }
13 }
14 

    就是ATest上的那个特性,该类再在app.config里定义相应的webservice调用参数,后面再介绍。这里的Match你可能发现了,就是webservice里方法名与ATest里的匹配方式。
    二、代理处理类 AspectDynamicWebServiceProxy 核心就在这里了

Code
  1 //*******************************************************************
  2 // 模块:动态调用WebService的代理处理类
  3 // 日期:2009-8-23 0:35
  4 // 作者:Faib
  5 // 版权:Copyright Faib Studio 2009
  6 // 官网:http://www.faib.net.cn
  7 // 邮箱:faib920@126.com
  8 // 备注:
  9 //*******************************************************************
 10 using System;
 11 using System.Text;
 12 using System.Collections;
 13 using System.Collections.Generic;
 14 using System.Runtime.Remoting.Proxies;
 15 using System.Runtime.Remoting.Messaging;
 16 using System.Runtime.Remoting;
 17 using System.Runtime.Remoting.Activation;
 18 using System.Runtime.Remoting.Services;
 19 using System.Web.Services.Description;
 20 using System.Xml;
 21 using System.CodeDom;
 22 using System.CodeDom.Compiler;
 23 using Microsoft.CSharp;
 24 using System.Reflection;
 25 
 26 using FaibClass.Data;
 27 using FaibClass.Dynamic.Configuration;
 28 
 29 namespace FaibClass.Dynamic.WebService
 30 {
 31     /// 
 32     /// 动态调用WebService的代理处理类。
 33     /// 
 34     internal class AspectDynamicWebServiceProxy : RealProxy
 35     {
 36         MarshalByRefObject target;
 37         DynamicWebServiceSettings m_setting;
 38         string m_match;
 39 
 40         /// 
 41         /// 构造代理类。
 42         /// 
 43         /// 
 44         public AspectDynamicWebServiceProxy(Type myType)
 45             : base(myType)
 46         {
 47         }
 48         /// 
 49         /// 构造代理类。
 50         /// 
 51         /// 
 52         /// 
 53         /// 
 54         /// 
 55         public AspectDynamicWebServiceProxy(DynamicWebServiceSettings setting, string match, Type myType, MarshalByRefObject obj)
 56             : base(myType)
 57         {
 58             this.m_setting = setting;
 59             this.m_match = match;
 60             target = obj;
 61         }
 62 
 63         public override IMessage Invoke(IMessage msg)
 64         {
 65             IMessage retMsg = msg;
 66             if (msg is IConstructionCallMessage)
 67             {
 68                 IConstructionCallMessage ccm = (IConstructionCallMessage)msg;
 69                 RemotingServices.GetRealProxy(target).InitializeServerObject(ccm);
 70                 ObjRef oRef = RemotingServices.Marshal(target);
 71                 RemotingServices.Unmarshal(oRef);
 72                 retMsg = EnterpriseServicesHelper.CreateConstructionReturnMessage
 73                     (ccm, (MarshalByRefObject)this.GetTransparentProxy());
 74             }
 75             else if (msg is IMethodCallMessage)
 76             {
 77                 IMethodCallMessage mcm = (IMethodCallMessage)msg;
 78                 MethodInfo minfo = mcm.MethodBase as MethodInfo;
 79                 //调用WebService返回值
 80                 object result = DyamicCallWebService(minfo, mcm.Args);
 81                 if (result == null)
 82                     retMsg = RemotingServices.ExecuteMessage(target, mcm);
 83                 else
 84                     retMsg = new ReturnMessage(result, null0null, mcm);
 85             }
 86             return retMsg;
 87         }
 88 
 89         /// 
 90         /// 动态调用WebService中的方法。
 91         /// 
 92         /// 方法名。
 93         /// 方法参数。
 94         /// 返回值。
 95         private object DyamicCallWebService(MethodInfo method, object[] args)
 96         {
 97             Type agentType;
 98             object agent = null;
 99             string[] assemblies = m_setting.Assemblies.Split('|');
100             //调用的方法名
101             string methodName = method.Name;
102             if (!string.IsNullOrEmpty(m_match))
103             {
104                 methodName = string.Format(m_match, methodName);
105             }
106             //缓存键
107             string key = DataCacheKeyManager.GetServiceKey(m_setting.Type, method);
108             if (DataCache<object>.ContainsKey(key))
109             {
110                 agent = DataCache<object>.Get(key);
111                 agentType = agent.GetType();
112             }
113             else
114             {
115 
116                 CSharpCodeProvider icc = new CSharpCodeProvider();
117                 CompilerParameters cp = new CompilerParameters();
118                 //引用程序集
119                 cp.ReferencedAssemblies.Add("mscorlib.dll");
120                 cp.ReferencedAssemblies.Add("System.dll");
121                 cp.ReferencedAssemblies.Add("System.Web.Services.dll");
122                 cp.ReferencedAssemblies.Add("System.Xml.dll");
123                 cp.ReferencedAssemblies.Add("FaibClass.Data2.dll");
124                 cp.ReferencedAssemblies.Add("FaibClass.Dynamic.dll");
125                 foreach (string ass in assemblies)
126                 {
127                     cp.ReferencedAssemblies.Add(ass.Split(',')[1].Trim() + ".dll");
128                 }
129 
130                 StringBuilder classSource = new StringBuilder();
131                 //加入引用
132                 classSource.Append("using System;\n");
133                 classSource.Append("using System.Text;\n");
134                 classSource.Append("using System.Web.Services;\n");
135                 classSource.Append("using System.Web.Services.Protocols;\n");
136                 classSource.Append("using System.Web.Services.Description;\n");
137                 classSource.Append("using System.Xml.Serialization;\n");
138                 classSource.Append("using System.Diagnostics;\n");
139                 classSource.Append("using System.ComponentModel;\n");
140                 classSource.Append("using System.CodeDom.Compiler;\n");
141                 classSource.Append("using FaibClass.Data;\n");
142                 classSource.Append("using FaibClass.Dynamic.WebService;\n");
143                 foreach (string ass in assemblies)
144                 {
145                     classSource.Append("using " + ass.Split(',')[0].Trim() + ";\n");
146                 }
147                 classSource.Append("\n");
148                 //定义特性
149                 classSource.Append("[WebServiceBinding(Name = \"ServiceSoap\", Namespace = \"http://tempuri.org/\"), XmlInclude(typeof(MarshalByRefObject)), DesignerCategory(\"code\"), DebuggerStepThrough, GeneratedCode(\"Test\", \"1.0.0.0\")]\n");
150                 classSource.Append("public class DynamicWebService : DynamicSoapHttpClientProtocol\n");
151                 classSource.Append("{\n\tpublic DynamicWebService(){ base.Url = \"" + m_setting.Url + "\"; }");
152                 //方法开始=======
153                 classSource.Append("\n\t[SoapDocumentMethod(\"http://tempuri.org/" + methodName + "\", RequestNamespace = \"http://tempuri.org/\", ResponseNamespace = \"http://tempuri.org/\", Use=SoapBindingUse.Literal, ParameterStyle=SoapParameterStyle.Wrapped)]");
154                 classSource.Append("\n\tpublic ");
155                 //方法返回类型
156                 if (method.ReturnType.FullName == "System.Void")
157                     classSource.Append("void");
158                 else
159                     classSource.Append(method.ReturnType.Name);
160                 //方法参数
161                 classSource.Append(" " + methodName + "(");
162                 bool first = true;
163                 ParameterInfo[] pinfos = method.GetParameters();
164                 foreach (ParameterInfo pinfo in pinfos)
165                 {
166                     if (!first) classSource.Append(",");
167                     else first = false;
168                     classSource.Append(pinfo.ParameterType.Name + " " + pinfo.Name);
169                 }
170                 //================
171                 classSource.Append(")\n\t{\n\t\t");
172                 if (method.ReturnType.FullName != "System.Void")
173                     classSource.Append("return (" + method.ReturnType.Name + ")");
174                 classSource.Append("base.Invoke(\"" + methodName + "\"");
175                 if (pinfos.Length == 0)
176                     classSource.Append("new object[0]");
177                 else
178                 {
179                     classSource.Append("new object[]{");
180                     first = true;
181                     foreach (ParameterInfo pinfo in pinfos)
182                     {
183                         if (!first) classSource.Append(",");
184                         else first = false;
185                         classSource.Append(pinfo.Name);
186                     }
187                     classSource.Append("}");
188                 }
189                 classSource.Append(")");
190                 if (method.ReturnType.FullName != "System.Void")
191                     classSource.Append("[0]");
192                 classSource.Append(";\n\t}\n}");
193 
194                 CompilerResults cr = icc.CompileAssemblyFromSource(cp, classSource.ToString());
195                 int c = cr.Errors.Count;
196                 agentType = cr.CompiledAssembly.GetTypes()[0];
197                 agent = Activator.CreateInstance(agentType);
198                 DataCache<object>.Add(key, agent);
199             }
200 
201             if (agent != null)
202             {
203                 return agentType.GetMethod(methodName).Invoke(agent, args);
204             }
205             return null;
206         }
207     }
208 }
209 

    在invoke中,拦截了ATest的调用方法,DyamicCallWebService进行分析并构造WebServicw的代理类代码,这里使用了缓存,第一次调用 方法都要经过编译,以后就不用了。
    三、配置类
DynamicWebServiceSectionHandler

Code
 1 //*******************************************************************
 2 // 模块:WebService配置节的访问
 3 // 日期:2009-8-12 9:22:24
 4 // 作者:Faib
 5 // 版权:Copyright Faib Studio 2009
 6 // 官网:http://www.faib.net.cn
 7 // 邮箱:faib920@126.com
 8 // 备注:
 9 //*******************************************************************
10 using System;
11 using System.Configuration;
12 using System.Xml;
13 
14 namespace FaibClass.Dynamic.Configuration
15 {
16     /// 
17     /// 处理WebService配置节的访问。
18     /// 
19     public sealed class DynamicWebServiceSectionHandler : IConfigurationSectionHandler
20     {
21         /// 
22         /// 创建动态WebService节处理程序
23         /// 
24         /// 父对象。
25         /// 上下文件对象。
26         /// Xml节点。
27         /// 
28         public object Create(object parent, object configContext, XmlNode section)
29         {
30             return new DynamicWebServiceConfiguration(section);
31         }
32     }
33 }
34 
DynamicWebServiceConfiguration
Code
 1 //*******************************************************************
 2 // 模块:动态WebService
 3 // 日期:2009-8-23 0:51
 4 // 作者:Faib
 5 // 版权:Copyright Faib Studio 2009
 6 // 官网:http://www.faib.net.cn
 7 // 邮箱:faib920@126.com
 8 // 备注:
 9 //*******************************************************************
10 using System;
11 using System.Xml;
12 using System.Configuration;
13 
14 namespace FaibClass.Dynamic.Configuration
15 {
16     /// 
17     /// 动态WebService配置。
18     /// 
19     public sealed class DynamicWebServiceConfiguration
20     {
21         DynamicWebServiceDictionary m_dic;
22         static DynamicWebServiceDictionary m_dic1;
23 
24         /// 
25         /// 由XmlNode生成实例配置集合。
26         /// 
27         /// 
28         internal DynamicWebServiceConfiguration(XmlNode section)
29         {
30             if (section == nullthrow new ArgumentNullException();
31 
32             m_dic = new DynamicWebServiceDictionary();
33             foreach (XmlNode node in section.SelectNodes("webservice"))
34             {
35                 string type = node.Attributes["type"!= null ? node.Attributes["type"].Value : "";
36                 string url = node.Attributes["url"!= null ? node.Attributes["url"].Value : "";
37                 string assemblies = node.Attributes["assemblies"!= null ? node.Attributes["assemblies"].Value : "";
38                 if (!string.IsNullOrEmpty(type) && !string.IsNullOrEmpty(url))
39                 {
40                     m_dic.Add(type, new DynamicWebServiceSettings(type, url, assemblies));
41                 }
42             }
43         }
44 
45         internal DynamicWebServiceDictionary settings
46         {
47             get { return m_dic; }
48         }
49 
50         public static DynamicWebServiceDictionary Settings
51         {
52             get
53             {
54                 if (m_dic1 == null)
55                 {
56                     object obj = ConfigurationManager.GetSection("faibclass.dynamic.webService");
57                     if (obj != null)
58                     {
59                         m_dic1 = ((DynamicWebServiceConfiguration)obj).settings;
60                     }
61                 }
62                 return m_dic1;
63             }
64         }
65     }
66 }
67 
DynamicWebServiceDictionary
Code
 1 //*******************************************************************
 2 // 模块:动态WebService配置字典
 3 // 日期:2009-8-12 9:21:05
 4 // 作者:Faib
 5 // 版权:Copyright Faib Studio 2009
 6 // 官网:http://www.faib.net.cn
 7 // 邮箱:faib920@126.com
 8 // 备注:
 9 //*******************************************************************
10 using System;
11 using System.Collections;
12 
13 namespace FaibClass.Dynamic.Configuration
14 {
15     /// 
16     /// 动态WebService配置字典。
17     /// 
18     public class DynamicWebServiceDictionary : DictionaryBase
19     {
20         internal void Add(string key, DynamicWebServiceSettings setting)
21         {
22             Dictionary.Add(key, setting);
23         }
24 
25         /// 
26         /// 获取本字典内的键集合。
27         /// 
28         public ICollection Keys
29         {
30             get { return Dictionary.Keys; }
31         }
32 
33         /// 
34         /// 由键名返回对应的配置信息。
35         /// 
36         /// 
37         /// 
38         public DynamicWebServiceSettings this[string key]
39         {
40             get 
41             {
42                 if (Dictionary.Contains(key))
43                 {
44                     return (DynamicWebServiceSettings)Dictionary[key];
45                 }
46                 return null;
47             }
48         }
49     }
50 }
51 
DynamicWebServiceSettings
Code
 1 //*******************************************************************
 2 // 模块:动态WebService配置信息
 3 // 日期:2009-8-23 0:12
 4 // 作者:Faib
 5 // 版权:Copyright Faib Studio 2009
 6 // 官网:http://www.faib.net.cn
 7 // 邮箱:faib920@126.com
 8 // 备注:
 9 //*******************************************************************
10 using System;
11 using System.Collections.Generic;
12 using System.Text;
13 
14 namespace FaibClass.Dynamic.Configuration
15 {
16     /// 
17     /// 动态WebService配置信息。
18     /// 
19     public class DynamicWebServiceSettings
20     {
21         /// 
22         /// 处理类型
23         /// 
24         public string Type;
25         /// 
26         /// WebService地址
27         /// 
28         public string Url;
29         /// 
30         /// 引用程序集
31         /// 
32         public string Assemblies;
33 
34         public DynamicWebServiceSettings(string type, string url, string assemblies)
35         {
36             Type = type;
37             Url = url;
38             Assemblies = assemblies;
39         }
40     }
41 }
42 

    ap.config配置如下

Code
xml version="1.0" encoding="utf-8" ?>
<configuration>
  
<configSections>
    
<section name="faibclass.data.instance" type="FaibClass.Data.Configuration.DataInstanceSectionHandler, FaibClass.Data2" />
    
<section name="faibclass.dynamic.webService" type="FaibClass.Dynamic.Configuration.DynamicWebServiceSectionHandler, FaibClass.Dynamic"/>
  
configSections>
  
<faibclass.dynamic.webService>
    
<webservice type="Test.DynamicWebService1" url="http://localhost:1574/WebService/Service.asmx" assemblies="Test.Model, Model" />
  
faibclass.dynamic.webService>
  
<faibclass.data.instance defaultInstance="sqlite">
    

    
<file name="access1">
      
<instanceType>FaibClass.Data.OleDb, FaibClass.Data2instanceType>
      
<filename>{APP}\1.xmlfilename>
      
<path>//config/connectionpath>
    
file>
    

    
<file name="access2">
      
<instanceType>FaibClass.Data.OleDb, FaibClass.Data2instanceType>
      
<interfaceType>SysXmlinterfaceType>
      
<filename>1.xmlfilename>
      
<path>//config/connectionpath>
    
file>
    

    
<file name="access3">
      
<instanceType>FaibClass.Data.OleDb, FaibClass.Data2instanceType>
      
<interfaceType>AppXmlinterfaceType>
      
<path>constrpath>
    
file>
    

    
<reg name="sqlserver">
      
<instanceType>FaibClass.Data.SqlServer, FaibClass.Data2instanceType>
      
<registryKey>CurrentUserregistryKey>
      
<subKey>Software\MicrosoftsubKey>
      
<key>ckey>
    
reg>
    

    
<file name="sqlserver2005">
      
<instanceType>FaibClass.Data.SqlServer, FaibClass.Data2instanceType>
      
<interfaceType>BinaryinterfaceType>
      
<filename>{APP}\connection.binfilename>
      
<path>Chinapath>
    
file>
    

    
<string name="sqlite">
      
<instanceType>FaibClass.Data.DataDriverConverter, FaibClass.Data2instanceType>
      
<dataDriver>
        
<assemblyName>System.Data.SQLiteassemblyName>
        
<connectionType>System.Data.SQLite.SQLiteConnectionconnectionType>
        
<commandType>System.Data.SQLite.SQLiteCommandcommandType>
        
<parameterType>System.Data.SQLite.SQLiteParameterparameterType>
        
<dataAdapterType>System.Data.SQLite.SQLiteDataAdapterdataAdapterType>
        
<parameterPrefix>@parameterPrefix>
      
dataDriver>
      
<connectionString>Data source={App}BitracDB.db3;Pooling=TrueconnectionString>
    
string>
  
faibclass.data.instance>
  
<appSettings>
    
<add key="constr" value="Provider=Microsoft.Jet.OLEDB.4.0;Data Source={App}\test.mdb">add>
  
appSettings>
configuration>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值