IOC、DI<4> Unity、AOP、MVCAOP、UnityAOP 区别

IOC():控制反转,把程序上层对下层的依赖,转移到第三方的容器来装配
是程序设计的目标,实现方式包含了依赖注入和依赖查找(.net里面只有依赖注入)
DI:依赖注入,是IOC的实习方式。
在这里插入图片描述

在这里插入图片描述
注册服务:将接口映射到实现类,并可能指定生命周期。
解析服务:根据注册信息创建并返回服务实例。
管理生命周期:根据指定的生命周期策略创建、缓存和销毁对象实例。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Unity;
using ZEN.Interface;
using ZEN.Service;

namespace UnityIOC
{
    class Program
    {
        static void Main(string[] args)
        {
            //定义一个IOC容器
            IUnityContainer container = new UnityContainer();
            //添加映射关系
            container.RegisterType<ICar, TeslaCar>();
            //获取服务
            var car = container.Resolve<ICar>();
            car.GetName();
            car.GetPrice();
            car.GetMaxSpeed();
            Console.ReadKey();
        }
    }
}

在这里插入图片描述
在这里插入图片描述
》》当用RegisterType注册映射关系时,如果没有指定LifetimeManager,默认是使用一个瞬态的Lifetime Manager。即每次通过Unity容器获取对象的实例时都会重新创建一个新的实例。也就说,容器不存在一个到该对象的引用
在这里插入图片描述

走指定构造函数

就不需要加特性啦
这样既可完成对象注册的同时对构造函数参数进行注入,此时还有另外一个需求,就是虽然在注册的时候已经对构造函数参数进行了初始化
在Unity中,已经帮我们解决了这个问题,我们可以通过ParameterOverride和ParameterOverrides来实现,其中ParameterOverride是针对一个参数,而ParameterOverrides是针对参数列表,有关注册参数初始化及参数重载的全部代码如下:

在这里插入图片描述
》》如果初始化是三个参数的构造函数, Resolve解析,是2个参数,只会替换对应的两个参数。
用了集合初始化器 赋值
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

》》配置文件
在这里插入图片描述

对IUnityContainer 进行封装
using System;
using System.Collections.Generic;
using System.Linq;
using Unity;
using System.Configuration;
using System.IO;
using Microsoft.Practices.Unity.Configuration;
using Unity.Resolution;

namespace IOC.Common.IOC_AOP
{
    public class ZenUnityContainerHelper
    {
        private IUnityContainer container;
        public IUnityContainer Container {
            private set {
                container = value;
            }
            get {
                return Container;
            }
        }
        public ZenUnityContainerHelper()
        {
            this.container = new UnityContainer() ;
            ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
            fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\\Unity.Config");//找配置文件的路径
            Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
            UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);           
            section.Configure(container, "testContainer");
        }

        public T GetServer<T>()
        {
            return container.Resolve<T>();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="configName">配置文件中指定的name,对于同一个接口,多个实现情况</param>
        /// <returns></returns>
        public T GetServer<T>(string configName)
        {
            return container.Resolve<T>(configName);
        }
        /// <summary>
        /// 返回构结函数带参数
        /// </summary>
        /// <typeparam name="T">依赖对象</typeparam>
        /// <param name="parameterList">参数集合(参数名,参数值)</param>
        /// <returns></returns>
        public T GetServer<T,M>(Dictionary<string, object> parameterList)
        {
            var list = new ParameterOverrides();
            foreach (KeyValuePair<string, object> item in parameterList)
            {
                list.Add(item.Key, item.Value);
            }
            return container.Resolve<T>(list.OnType<M>());
        }
        /// <summary>
        /// 返回构结函数带参数
        /// </summary>
        /// <typeparam name="T">依赖对象</typeparam>
        /// <param name="ConfigName">配置文件中指定的文字(没写会报异常)</param>
        /// <param name="parameterList">参数集合(参数名,参数值)</param>
        /// <returns></returns>
        public T GetServer<T,M>(string configName, Dictionary<string, object> parameterList)
        {
           

            var list = new ParameterOverrides();
            foreach (KeyValuePair<string, object> item in parameterList)
            {
                list.Add(item.Key, item.Value);
               
            }
           
            return container.Resolve<T>(configName,
               list.OnType<M>());

        }
    }
}

》》》 使用封装

 ZenUnityContainerHelper zenContainer= new  ZenUnityContainerHelper();

                var tesla_car = zenContainer.GetServer<ICar>("tesla");
                tesla_car.GetName();               
                var xiaomi_car = zenContainer.GetServer<ICar>("xiaomi" );
                xiaomi_car.GetName();
                Dictionary<string, object> parm = new Dictionary<string, object>();
                parm.Add("age", "一百");
                parm.Add("policy", "遥遥领先");
                var xiaomi_car1 = zenContainer.GetServer<ICar,XiaoMICar>("xiaomi2", parm);
                xiaomi_car1.GetName();
                Console.WriteLine("-----------------------------------");
一个接口多个实现进行注册

如: 接口 IPhone
实现华为手机 继承 IPhone
实现苹果手机 继承 IPhone

IUnityContainer container = new UnityContainer();//1、定义一个空容器
container.RegisterType<IPhone, HuaweiPhone>(“huawei”);//2、注册类型,表示遇到IDbInterface的类型,创建DbMSSQL的实例
container.RegisterType<IPhone, ApplePhone>(“apple”);//表示遇到IDbInterface的类型,创建DbMSSQL的实例
var huawei= container.Resolve(“huawei”);
var apple= container.Resolve(“apple”);
Console.WriteLine(huawei.xxx());
Console.WriteLine(apple.xxx());

在这里插入图片描述
在这里插入图片描述

以上还是依赖细节,完全脱离细节,需要用配置文件,跟autofac一样

在这里插入图片描述
这样每次生成,才会把配置文件生成到bin目录下

在这里插入图片描述

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/27471038b94244c0920e55d1b8db4e4b.png

会发现,如果改成使用配置文件的方式实现的话,代码里面就不会依赖于细节了,只要一个接口类型。既然没有细节了,那么对项目进行如下的改造:把引用里面对细节的引用都去掉(ZEN.Service),然后Debug文件夹里面没有这个DLL了,但是这时需要把这个DLL复制到Debug目录下面,否则程序运行的时候会找不到具体实现的类型。这样就意味着程序架构只依赖于接口。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Unity.Configuration" />
  
  </configSections>
  
  <unity>
   <typeAliases>
	     <typeAlias  alias="IPhone" type="ZEN.Interface.IPhone,ZEN.Interface"></typeAlias >
	     <typeAlias  alias="IWork" type="ZEN.Interface.IWork,ZEN.Interface"></typeAlias >
	     <typeAlias  alias="IPower" type="ZEN.Interface.IPower,ZEN.Interface"></typeAlias >
	     <typeAlias  alias="ICar" type="ZEN.Interface.ICar,ZEN.Interface"></typeAlias >
	     <typeAlias  alias="Phone" type="ZEN.Service.Phone,ZEN.Service"></typeAlias >
	     <typeAlias  alias="Work" type="ZEN.Service.Work,ZEN.Service"></typeAlias >
	     <typeAlias  alias="Power" type="ZEN.Service.Power,ZEN.Service"></typeAlias >
	     <typeAlias  alias="Car" type="ZEN.Service.TeslaCar,ZEN.Service"></typeAlias >
    </typeAliases>
    <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration" />
    <containers>
      <container name="testContainer">
         <register type="IPhone" mapTo="Phone" />
         <register type="IPower" mapTo="Power" />
         <register type="ICar" mapTo="Car" name="tesla" >
            <property name="work" dependencyType="IWork" />
            <property name="phone" dependencyType="IPhone" />
         </register>
         <register type="IWork" mapTo="Work" />     
       
       
      </container>
    </containers>
  </unity>
</configuration>
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Unity;

using ZEN.Interface;
using System.Configuration;
using Microsoft.Practices.Unity.Configuration;
using Unity.Interception;
using Unity.Interception.Interceptors.InstanceInterceptors.InterfaceInterception;
using Microsoft.Practices.Unity;

namespace UnityIOC
{
    class Program
    {
        static void Main(string[] args)
        {
            //{ //定义一个IOC容器
            //    IUnityContainer container = new UnityContainer();
            //    //添加映射关系      
            //    container.RegisterType<ICar, TeslaCar>("tesla");
            //    container.RegisterType<ICar, XiaoMICar>("xiaomi");
            //    container.RegisterType<IWork, Work>();
            //    container.RegisterType<IPower, Power>();
            //    container.RegisterType<IPhone, Phone>();
            //    //获取服务
            //    var car = container.Resolve<ICar>("tesla");
            //    car.GetName();
            //    car.GetPrice();
            //    car.GetMaxSpeed();
            //}
            {
                //ExeConfigurationFileMap 要引入 System.Configuration;
                ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
                fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\\Unity.Config");//找配置文件的路径
                Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
                UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
                IUnityContainer container = new UnityContainer();
                section.Configure(container, "testContainer");
               
                var car = container.Resolve<ICar>("tesla");
                car.GetName();
                car.GetPrice();
                car.GetMaxSpeed();
                //
            }
            Console.ReadKey();
        }
    }
}

在这里插入图片描述

构造函数注入、属性注入、方法注入 配置文件

在这里插入图片描述

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Unity.Configuration" />
  
  </configSections>
  
  <unity>
    <typeAliases>
    <typeAlias  alias="IPhone" type="ZEN.Interface.IPhone,ZEN.Interface"></typeAlias >
     <typeAlias  alias="IWork" type="ZEN.Interface.IWork,ZEN.Interface"></typeAlias >
     <typeAlias  alias="IPower" type="ZEN.Interface.IPower,ZEN.Interface"></typeAlias >
    <typeAlias  alias="ICar" type="ZEN.Interface.ICar,ZEN.Interface"></typeAlias >
  <typeAlias  alias="Phone" type="ZEN.Service.Phone,ZEN.Service"></typeAlias >
     <typeAlias  alias="Work" type="ZEN.Service.Work,ZEN.Service"></typeAlias >
     <typeAlias  alias="Power" type="ZEN.Service.Power,ZEN.Service"></typeAlias >
          <typeAlias  alias="Car" type="ZEN.Service.TeslaCar,ZEN.Service"></typeAlias >
  </typeAliases>
    <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration" />
    <containers>
      <container name="testContainer">
         <register type="IPhone" mapTo="Phone" />
         <register type="IPower" mapTo="Power" />
        <register type="ICar" mapTo="Car" name="tesla" >
            <property name="work" dependencyType="IWork" />
            <property name="phone" dependencyType="IPhone" />
          <method name="InitIphone">
            <param name="_power" type="IPower" />
            <param name="val" type="int" value="33"/>
          </method>
        </register>
         <register type="IWork" mapTo="Work" />     
       
       
      </container>
    </containers>
  </unity>
</configuration>
  ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
                fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\\Unity.Config");//找配置文件的路径
                Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
                UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
                IUnityContainer container = new UnityContainer();
                section.Configure(container, "testContainer");
               
                var car = container.Resolve<ICar>("tesla");
                car.GetName();
                car.GetPrice();
                car.GetMaxSpeed();

在这里插入图片描述
unity IOC 源码

Unity AOP 自动拦截

AOP(Aspect-Oriented Programming,面向切面的编程),它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。它是一种新的方法论,它是对传统OOP编程的一种补充。 OOP是关注将需求功能划分为不同的并且相对独立,封装良好的类,并让它们有着属于自己的行为,依靠继承和多态等来定义彼此的关系;AOP是希望能够将通用需求功能从不相关的类当中分离出来,能够使得很多类共享一个行为,一旦发生变化,不必修改很多类,而只需要修改这个行为即可。 AOP是使用切面(aspect)将横切关注点模块化,OOP是使用类将状态和行为模块化。在OOP的世界中,程序都是通过类和接口组织的,使用它们实现程序的核心业务逻辑是十分合适。但是对于实现横切关注点(跨越应用程序多个模块的功能需求)则十分吃力,比如日志记录,验证等

Unity默认提供了三种拦截器:TransparentProxyInterceptor、InterfaceInterceptor、VirtualMethodInterceptor。

TransparentProxyInterceptor:代理实现基于.NET Remoting技术,它可拦截对象的所有函数。缺点是被拦截类型必须派生于MarshalByRefObject。
InterfaceInterceptor:只能对一个接口做拦截,好处时只要目标类型实现了指定接口就可以拦截。
VirtualMethodInterceptor:对virtual函数进行拦截。缺点是如果被拦截类型没有virtual函数则无法拦截,这个时候如果类型实现了某个特定接口可以改用

InterfaceInterceptor。

》》Unity.InterceptionExtension.IInterceptionBehavior
在这里插入图片描述

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Unity.Configuration" />
  
  </configSections>
  
  <unity>
    <typeAliases>
    <typeAlias  alias="IPhone" type="ZEN.Interface.IPhone,ZEN.Interface"></typeAlias >
     <typeAlias  alias="IWork" type="ZEN.Interface.IWork,ZEN.Interface"></typeAlias >
     <typeAlias  alias="IPower" type="ZEN.Interface.IPower,ZEN.Interface"></typeAlias >
    <typeAlias  alias="ICar" type="ZEN.Interface.ICar,ZEN.Interface"></typeAlias >
  <typeAlias  alias="Phone" type="ZEN.Service.Phone,ZEN.Service"></typeAlias >
     <typeAlias  alias="Work" type="ZEN.Service.Work,ZEN.Service"></typeAlias >
     <typeAlias  alias="Power" type="ZEN.Service.Power,ZEN.Service"></typeAlias >
          <typeAlias  alias="Car" type="ZEN.Service.TeslaCar,ZEN.Service"></typeAlias >
  </typeAliases>
    <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration" />
    <containers>
      <container name="testContainer">
        <!AOP  需要添加这个节点 
          <extension type="Interception"/>
          -->
         <extension type="Interception"/>
         <register type="IPhone" mapTo="Phone" />
         <register type="IPower" mapTo="Power" />
        <register type="ICar" mapTo="Car" name="tesla" >
            <property name="work" dependencyType="IWork" />
            <property name="phone" dependencyType="IPhone" />
          <method name="InitIphone">
            <param name="_power" type="IPower" />
            <param name="val" type="int" value="33"/>
          </method>
          <!-- 拦截类型 
        TransparentProxyInterceptor
        InterfaceInterceptor
        VirtualMethodInterceptor-->
          <interceptor type="InterfaceInterceptor"/>
          <interceptionBehavior type="IOC.Common.IOC_AOP.AOP, IOC.Common"/>
          
        </register>
         <register type="IWork" mapTo="Work" />     
       
       
      </container>
    </containers>
  </unity>
</configuration>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Unity.Interception.InterceptionBehaviors;
using Unity.Interception.PolicyInjection.Pipeline;

namespace IOC.Common.IOC_AOP
{

    /// <summary>
    /// 不需要特性
    /// </summary>
    public class AOP : IInterceptionBehavior
    {
        /// <summary>
        /// 获取一个<see cref="Boolean"/>值,该值表示当前拦截行为被调用时,是否真的需要执行
        /// 某些操作。
        /// </summary>
        public bool WillExecute
        {
            get { return true; }
        }
        /// <summary>
        /// 获取当前行为需要拦截的对象类型接口。
        /// </summary>
        /// <returns>所有需要拦截的对象类型接口。</returns>
        public IEnumerable<Type> GetRequiredInterfaces()
        {
            return Type.EmptyTypes;
        }
      /// <summary>
        /// 通过实现此方法来拦截调用并执行所需的拦截行为。
        /// </summary>
        /// <param name="input">调用拦截目标时的输入信息。</param>
        /// <param name="getNext">通过行为链来获取下一个拦截行为的委托。</param>
        /// <returns>从拦截目标获得的返回信息。</returns>
        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            Console.WriteLine("IOC-AOP触发");
             #region 参数部分
            Console.WriteLine("-------------参数内容---------------");
            for (int i = 0; i < input.Arguments.Count; i++)
            {
                var parameter = input.Arguments[i];
                Console.WriteLine(string.Format("第{0}个参数值为:{1}", i+1, parameter.ToString()));
            }
            Console.WriteLine("------------------------------------");
            #endregion
            return getNext().Invoke(input, getNext);
        }
    }
}


Unity AOP 显示拦截

Unity AOP 容器的生命周期

TransientLifetimeManager:每次请求时都会创建新的实例。
ContainerControlledLifetimeManager(默认):整个容器中只创建一个实例,所有的请求都会得到这个相同的实例。
HierarchicalLifetimeManager:在父容器和子容器中共享同一个实例,直到子容器被销毁。
PerResolveLifetimeManager:每次解析请求时都会创建一个新的实例,但在同一解析操作内请求相同的实例会返回同一个对象。
PerThreadLifetimeManager:每个线程中只创建一个实例,所有同一线程的请求都会得到这个相同的实例。

》》非配置文件方式

//RegisterType   ContainerControlledLifetimeManager
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Unity;
using ZEN.Interface;
using ZEN.Service;

namespace UnityIOC
{
    class Program
    {
        static void Main(string[] args)
        {
            //定义一个IOC容器
            IUnityContainer container = new UnityContainer(new ContainerControlledLifetimeManager());
            //添加映射关系
            container.RegisterType<ICar, TeslaCar>();
            //获取服务
            var car = container.Resolve<ICar>();
            var car1 = container.Resolve<ICar>();
            //car.GetHashCode.ToString() ==car1.GetHashCode.ToString() 
            //car1、car是相同的实例了
            car.GetName();
            car.GetPrice();
            car.GetMaxSpeed();
            Console.ReadKey();
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Unity;
using ZEN.Interface;
using ZEN.Service;

namespace UnityIOC
{
    class Program
    {
        static void Main(string[] args)
        {
            //定义一个IOC容器
            IUnityContainer container = new UnityContainer();
            //添加映射关系
            container.RegisterType<ICar, TeslaCar>();
            ICar tesla=new TeslaCar();
            container.RegisterInstance<ICar>(tesla);
            //获取服务
            var car = container.Resolve<ICar>();
            var car1 = container.Resolve<ICar>();
            //car.GetHashCode.ToString() ==car1.GetHashCode.ToString() 
            //car1、car是相同的实例了
            car.GetName();
            car.GetPrice();
            car.GetMaxSpeed();
            Console.ReadKey();
        }
    }
}

》》配置文件形式

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Unity.Configuration" />
  
  </configSections>
  
  <unity>
    <typeAliases>
    <typeAlias  alias="IPhone" type="ZEN.Interface.IPhone,ZEN.Interface"></typeAlias >
     <typeAlias  alias="IWork" type="ZEN.Interface.IWork,ZEN.Interface"></typeAlias >
     <typeAlias  alias="IPower" type="ZEN.Interface.IPower,ZEN.Interface"></typeAlias >
    <typeAlias  alias="ICar" type="ZEN.Interface.ICar,ZEN.Interface"></typeAlias >
  <typeAlias  alias="Phone" type="ZEN.Service.Phone,ZEN.Service"></typeAlias >
     <typeAlias  alias="Work" type="ZEN.Service.Work,ZEN.Service"></typeAlias >
     <typeAlias  alias="Power" type="ZEN.Service.Power,ZEN.Service"></typeAlias >
          <typeAlias  alias="Car" type="ZEN.Service.TeslaCar,ZEN.Service"></typeAlias >
  </typeAliases>
    <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration" />
    <containers>
      <container name="testContainer">
        <!AOP  需要添加这个节点,<extension type="Interception"/>/>-->
         <extension type="Interception"/>
         <register type="IPhone" mapTo="Phone" />
         <register type="IPower" mapTo="Power" />
         <register type="ICar" mapTo="Car" name="tesla" >
              <!---生命周期------>
              <lifetimeManager type='transient'/>
              <property name="work" dependencyType="IWork" />
              <property name="phone" dependencyType="IPhone" />
	          <method name="InitIphone">
	            <param name="_power" type="IPower" />
	            <param name="val" type="int" value="33"/>
	          </method>
	            <!-- 拦截类型 
        TransparentProxyInterceptor
        InterfaceInterceptor
        VirtualMethodInterceptor-->
          <interceptor type="InterfaceInterceptor"/>
          <interceptionBehavior type="IOC.Common.IOC_AOP.AOP, IOC.Common"/>
          
         </register>
         <register type="IWork" mapTo="Work" />    
      </container>
    </containers>
  </unity>
</configuration>

MVC中的Filter过滤器也起到AOP功能,但与unity的AOP

有所不同
mvc中的过滤器是针对action的,action前,action后
unity框架的AOP 可以针对 方法

Filter可以在action前 后 异常都扩展逻辑 AOP—针对action完整方法
Unity容器的AOP也是需要的, AOP—针对方法里面的业务层扩展的

  • 32
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值