IOC

using BLLExtention;
using DAL;
using IBLL;
using IDAL;
using Microsoft.Practices.Unity.Configuration;
using MyContainer;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Unity;
using Unity.Lifetime;

namespace MyIOC
{
    class Program
    {
        static void Main(string[] args)
        {
            //分层UI——BLL(业务逻辑)——DAL(数据访问):划分边界,独立演化;职责清晰,方便分工合作;提高代码复用
            //依赖倒置原则DIP:系统架构时,高层模块不应该依赖低层模块,两者通过抽象来依赖。依赖抽象而不应该依赖细节

            //控制反转:把上端对下端的依赖转移到第三方容器(工厂+配置+反射)中去
            //DI依赖注入:创建对象时的依赖的另外对象,可以通过自动初始化并传入(比如构造A时需要传入B,实现B自动构造并传入A的过程叫做 依赖注入)
            //            依赖注入的三种方式:构造函数注入--属性注入--方法注入(按三者执行循序依次注入) 
            //            使用最多的是构造函数注入:默认找参数最多的构造函数,而且可以省略特性,这样被注入对象可以省略对容器的依赖
            //IOC是最终目标,DI是实现这个目标的手段

            {
                抽象+反射+配置   把实例化对象的细节放入工厂,工厂再通过配置实现  但是如果创建对象的时候有依赖,还是得先把依赖创建,手动可以创建,但是又更好的框架Unity   
                //IPlant pine = (IPlant)Fctory.CreatePlant();
                //pine.Photosynthesis();
            }
            {
                //Unity实现控制反转
                //1.添加nuget包
                //2.容器三部曲
                //3.注意各个项目的Unity版本一致,可以从解决方案菜单统一管理

                {
                    使用容器
                    //IUnityContainer container = new UnityContainer();//1实例化容器
                    container.RegisterType<IPlant, Pine>();//2.注册类型
                    //container.RegisterInstance<IPlant>(new Pine());//通过子类实例来注册
                    container.RegisterType<IPlant, Willow>();//再次用相同接口/父类注册会覆盖之前注册的
                    //container.RegisterType<AbstractTree, Cypress>();//也可以通过抽象类注册对象
                    //                                                //container.RegisterType<Cypress, ChineseJuniper>();//也可以通过父类注册对象  但是父类不能在之前被注册(不能出现在泛型括号的右边),会导致获取对象取到孙子对象
                    //IPlant pine = container.Resolve<IPlant>();//3.获取实例
                    //AbstractTree cypress = container.Resolve<AbstractTree>();
                    //Cypress chineseJuniper = container.Resolve<Cypress>();
                }
                {
                    带依赖的注册   实现依赖注入
                    //IUnityContainer container = new UnityContainer();//1实例化容器
                    //container.RegisterType<AbstractTree, Poplar>();
                    //container.RegisterType<IBranch, Branch>();
                    //container.RegisterType<ILeaves, Leaves>();
                    //AbstractTree poplar = container.Resolve<AbstractTree>();//默认找无参构造函数,可以通过特性来指定构造函数
                    poplar.Photosynthesis();
                }
                {
                    #region 自己实现容器
                    自己实现容器
                    //ISanSanContainer container = new SanSanContainer();//1实例化容器
                    //container.RegisterType<AbstractTree, Poplar>(LifeTime.Singleton);
                    container.RegisterType<IBranch, Branch>();
                    container.RegisterType<ILeaves, Leaves>();
                    //AbstractTree poplar = container.Resolve<AbstractTree>();//默认找无参构造函数,可以通过特性来指定构造函数

                    //AbstractTree poplar1 = null;
                    //AbstractTree poplar2 = null;
                    //AbstractTree poplar3 = null;

                    //Action action1 = new Action(() =>
                    //{
                    //    poplar1 = container.Resolve<AbstractTree>();
                    //    Console.WriteLine($"poplar1线程ID{Thread.CurrentThread.ManagedThreadId}");
                    //});
                    //var rst1 = action1.BeginInvoke(null, null);
                    //Action action2 = new Action(() =>
                    //{
                    //    poplar2 = container.Resolve<AbstractTree>();
                    //    Console.WriteLine($"poplar2线程ID{Thread.CurrentThread.ManagedThreadId}");
                    //});
                    //var rst2 = action2.BeginInvoke(t =>
                    //{
                    //    poplar3 = container.Resolve<AbstractTree>();
                    //    Console.WriteLine($"poplar3线程ID{Thread.CurrentThread.ManagedThreadId}");
                    //    Console.WriteLine($"object.ReferenceEquals(poplar2, poplar3)={object.ReferenceEquals(poplar2, poplar3)}");
                    //}, null);
                    //action1.EndInvoke(rst1);
                    //action2.EndInvoke(rst2);

                    //Console.WriteLine($"object.ReferenceEquals(poplar1, poplar2)={object.ReferenceEquals(poplar1, poplar2)}");
                    #endregion

                    #region 基于配置实现容器
                    //1.未做配置中带别命的,思路:存类型的字典的键值不能是父类Type了,得把别名信息也加上
                    //  创建时根据别命和父类Type共同查找该实例化哪个子类

                    //2.指定参数值未作 思路:获取了构造参数的参数值,来和配置中的比对
                    //  如果就是配置中的参数,值类型直接赋值,引用类型按照配置的类型去实例化

                    //3.泛型 type.GetGenericTypeDefinition()获取原始泛型类型  
                    //  获取泛型类型typeof(IBLL.IGeneric<>);
                    Type ty1 = typeof(IGeneric<string>);
                    Type ty2 = typeof(IBLL.IGeneric<>);
                    bool iseq = ty1.GetGenericTypeDefinition() == ty2;


                    ISanSanContainer container = new SanSanContainer();
                    string jsonPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"CfgFiles\SanSanContainer.json");
                    SanSanConfigurationSection sanSanConfig = new SanSanConfigurationSection(jsonPath);
                    sanSanConfig.Configure(container, "testContainer1");
                    AbstractTree poplar = container.Resolve<AbstractTree>();
                    poplar.Photosynthesis();
                    IGeneric<string> generic = container.Resolve<IGeneric<string>>();
                    generic.Show();
                    #endregion
                }
                {
                    #region 生命周期
                    //可以做一些扩展,生命周期管理
                    //1.瞬时生命周期:每次都是构造一个新的,每次生成一个新对象
                    //IUnityContainer container = new UnityContainer();
                    瞬时生命周期:
                    //container.RegisterType<AbstractTree, Poplar>();//默认就是瞬时
                    //container.RegisterType<AbstractTree, Poplar>(new TransientLifetimeManager());//瞬时
                    //2.全局单例:全局只构造这一个对象实例
                    //非强制性单例,只有通过容器获取才是单例;项目中一般都通过这种方式生成单例,避免自己写
                    //container.RegisterType<AbstractTree, Poplar>(new SingletonLifetimeManager());

                    //3.线程单例:同一个线程是同一个实例,不同线程不同
                    //container.RegisterType<AbstractTree, Poplar>(new PerThreadLifetimeManager());

                    //AbstractTree poplar1 = null;
                    //AbstractTree poplar2 = null;
                    //AbstractTree poplar3 = null;

                    //Action action1 = new Action(() =>
                    //{
                    //    poplar1 = container.Resolve<AbstractTree>();
                    //    Console.WriteLine($"poplar1线程ID{Thread.CurrentThread.ManagedThreadId}");
                    //});
                    //var rst1 = action1.BeginInvoke(null, null);
                    //Action action2 = new Action(() =>
                    //{
                    //    poplar2 = container.Resolve<AbstractTree>();
                    //    Console.WriteLine($"poplar2线程ID{Thread.CurrentThread.ManagedThreadId}");
                    //});
                    //var rst2 = action2.BeginInvoke(t =>
                    //{
                    //    poplar3 = container.Resolve<AbstractTree>();
                    //    Console.WriteLine($"poplar3线程ID{Thread.CurrentThread.ManagedThreadId}");
                    //    Console.WriteLine($"object.ReferenceEquals(poplar2, poplar3)={object.ReferenceEquals(poplar2, poplar3)}");
                    //}, null);
                    //action1.EndInvoke(rst1);
                    //action2.EndInvoke(rst2);

                    //Console.WriteLine($"object.ReferenceEquals(poplar1, poplar2)={object.ReferenceEquals(poplar1, poplar2)}");

                    //4.分级容器单例,每个子容器构造的实例是同一个
                    //var childContainer = container.CreateChildContainer();
                    //外部可释放单例
                    //循环引用
                    #endregion
                }
                {
                    #region Unity实现AOP+IOC
                    //上面的容器注册类型时还是会依赖细节,要避免依赖的话,反射+配置
                    //1.一个接口多个不同类型实例,配置中加别命
                    //2.构造时需要传入具体值,配置中构造函数的参数值写死(写死好像没办法),param配置
                    //  需要灵活,就不能在构造参数时传入具体值,可以实现在类中写个属性,构造参数使用该属性替换参数,实例化后再对该属性赋值,实现可变
                    //3.泛型注册  配置类型名称+`1

                    //容易一般全局只有一个,初始化时创建一个容器供全局使用
                    //ABP??
                    //需要AOP时配置中增加相应节点,一定要引用Unity.Interception.Configuration类库,否则报读配置错误,

                    #region 读配置固定写法,不用关心
                    ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();//
                    fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"CfgFiles\Unity.config");
                    Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);

                    UnityConfigurationSection configSection = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
                    #endregion
                    //声明容器
                    IUnityContainer container = new UnityContainer();//
                    //这个是配置中容器的名字<container>标签
                    configSection.Configure(container, "testContainer1");

                    AbstractTree poplar = container.Resolve<AbstractTree>();
                    poplar.Photosynthesis();
                    //传入别名,实现相同父类注册不同子类
                    AbstractTree cypress = container.Resolve<AbstractTree>("Cypress");
                    cypress.Photosynthesis();
                    IGeneric<string> generic = container.Resolve<IGeneric<string>>();
                    generic.Show();
                    #endregion
                }
            }
        }
    }
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="unity"
             type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, 
                               Unity.Configuration" />
  </configSections>
  <unity>
    <!--type前面是完整类型名称,后面是Dll名称-->
    <sectionExtension
      type ="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension,
      Unity.Interception.Configuration"/>
    <containers>
      <!--可以添加多个container来实现多个接口的AOP-->
      <!--容器名称,代码中是要用这个名称的-->
      <container name ="testContainer1">
        <extension type ="Interception"/>
        <!--容器支持AOP,得加此节点-->
        <!--前面的写法都是固定的-->
        <!--type前面是接口名称,后面是dll名称;mapTo是要映射的类的名称,后面是dll名称-->
        <register type ="BLLExtention.AbstractTree,BLLExtention" mapTo="BLLExtention.Poplar,BLLExtention"/>
        <!--同一个父对象需要注册多个子类,加个别命-->
        <register type ="BLLExtention.AbstractTree,BLLExtention" mapTo="BLLExtention.Cypress,BLLExtention" name ="Cypress">
          <!--指定构造函数中的参数-->
          <constructor>
            <param name="leaves" type="IDAL.ILeaves,IDAL"/>
            <param name="i" type="System.Int32" value="3"/>
          </constructor>
        </register>
        <register type ="IDAL.IBranch,IDAL" mapTo="DAL.Branch,DAL"/>
        <register type ="IDAL.ILeaves,IDAL" mapTo="BLLExtention.Leaves,BLLExtention"/>
        <register type ="IBLL.IGeneric`1,IBLL" mapTo="BLL.MyGeneric`1,BLL">
          <!--IOC+AOP-->
          <interceptor type ="InterfaceInterceptor"/>
          <interceptionBehavior type="MyAop.UnityWay.LogBeforeBehavior,MyAop"/>
          <interceptionBehavior type="MyAop.UnityWay.CachingBehavior,MyAop"/>
          <interceptionBehavior type="MyAop.UnityWay.ExceptionLoggingBehavior,MyAop"/>
          <interceptionBehavior type="MyAop.UnityWay.LogAfterBehavior,MyAop"/>
          <interceptionBehavior type="MyAop.UnityWay.MonitorBehavior,MyAop"/>
        </register>
      </container>
      <!--<container name ="testContainer">
        <extension type ="Interception"/>
        -->
      <!--前面的写法都是固定的-->
      <!--
        -->
      <!--type前面是接口名称,后面是dll名称;mapTo是要映射的类的名称,后面是dll名称-->
      <!--
        <register type ="MyAop.UnityWay.IUserProcessor,MyAop" mapTo="MyAop.UnityWay.UserProcessor,MyAop">
          -->
      <!--指定扩展类型是基于接口的扩展(还有基于方法的、基于类的)-->
      <!--
          <interceptor type ="InterfaceInterceptor"/>
          <interceptionBehavior type="MyAop.UnityWay.LogBeforeBehavior,MyAop"/>
          <interceptionBehavior type="MyAop.UnityWay.ParameterCheckBehavior,MyAop"/>
          <interceptionBehavior type="MyAop.UnityWay.CachingBehavior,MyAop"/>
          <interceptionBehavior type="MyAop.UnityWay.ExceptionLoggingBehavior,MyAop"/>
          <interceptionBehavior type="MyAop.UnityWay.LogAfterBehavior,MyAop"/>
          <interceptionBehavior type="MyAop.UnityWay.MonitorBehavior,MyAop"/>
        </register>
      </container>-->
    </containers>
  </unity>


</configuration>

实现容器:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyContainer
{
    public interface ISanSanContainer
    {
        Dictionary<Type, RegisterInfo> DicRegisterType { set; get; }

        void RegisterType<TFrom, TTo>(LifeTime lifeTime = LifeTime.Transient) where TTo : TFrom;

        T Resolve<T>();
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading.Tasks;

namespace MyContainer
{
    public class SanSanContainer : ISanSanContainer
    {
        public Dictionary<Type, RegisterInfo> DicRegisterType { set; get; } = new Dictionary<Type, RegisterInfo>();

        private Dictionary<Type, object> DicSingletonVal = new Dictionary<Type, object>();

        private static readonly object ObjLock = new object();

        public void RegisterType<TFrom, TTo>(LifeTime lifeTime = LifeTime.Transient) where TTo : TFrom
        {
            if (DicRegisterType.Keys.Contains(typeof(TFrom)))
            {
                DicRegisterType[typeof(TFrom)] = new RegisterInfo()
                {
                    RegisterType = typeof(TTo),
                    RegisterLifeTime = lifeTime
                };
            }
            else
            {
                DicRegisterType.Add(typeof(TFrom), new RegisterInfo()
                {
                    RegisterType = typeof(TTo),
                    RegisterLifeTime = lifeTime
                });
            }
        }

        public T Resolve<T>()
        {
            try
            {
                T instance = (T)InjectionPara(typeof(T));
                return instance;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                return default(T);
            }
        }

        private object InjectionPara(Type tFrom)
        {
            object objRst = null;
            Type type = tFrom;
            LifeTime lifeTime = LifeTime.Transient;
            if (type.IsGenericType)//判断是否是泛型
            {
                Type genericParent = type.GetGenericTypeDefinition();
                if (DicRegisterType.ContainsKey(genericParent))
                {
                    Type[] types = type.GenericTypeArguments;
                    type = DicRegisterType[genericParent].RegisterType.MakeGenericType(types); ;
                }
            }
            else
            {
                if (DicRegisterType.ContainsKey(tFrom))
                {
                    type = DicRegisterType[tFrom].RegisterType;
                    lifeTime = DicRegisterType[tFrom].RegisterLifeTime;
                }
            }
            if (lifeTime.Equals(LifeTime.Singleton))
            {
                if (DicSingletonVal.ContainsKey(type))
                    return DicSingletonVal[type];
            }
            else if (lifeTime.Equals(LifeTime.PerThread))
            {
                //线程槽
                object oValue = CallContext.GetData(type.FullName);
                if (oValue != null)
                {
                    return oValue;
                }
            }

            #region 构造函数注入
            //构造函数注入
            ConstructorInfo[] ctorArr = type.GetConstructors()
                .Where(t => t.IsDefined(typeof(SanSanInjectionConstructorAttribute)))
                .OrderByDescending(t => t.GetParameters().Count()).ToArray();
            ConstructorInfo ctor = null;
            if (ctorArr == null || ctorArr.Count() <= 0)
            {
                ctor = type.GetConstructors().OrderByDescending(t => t.GetParameters().Count()).First();
                //return Activator.CreateInstance(type);
            }
            else
            {
                ctor = ctorArr.First();
            }
            List<object> paraObjList = new List<object>();
            foreach (var parameter in ctor.GetParameters())
            {
                Type paraType = parameter.ParameterType;
                object paraObj = InjectionPara(paraType);
                paraObjList.Add(paraObj);
            }
            if (paraObjList.Count() > 0)
            {
                objRst = Activator.CreateInstance(type, paraObjList.ToArray());
            }
            else
            {
                objRst = Activator.CreateInstance(type);
            }
            #endregion

            #region 属性注入
            //属性注入
            PropertyInfo[] propertyArr = type.GetProperties()
                .Where(t => t.IsDefined(typeof(SanSanDependencyAttribute))).ToArray();
            if (propertyArr != null && propertyArr.Count() > 0)
            {
                foreach (PropertyInfo property in propertyArr)
                {
                    property.SetValue(objRst, InjectionPara(property.PropertyType));
                }
            }
            #endregion

            #region 方法注入
            //方法注入
            MethodInfo[] methodArr = type.GetMethods()
                .Where(t => t.IsDefined(typeof(SanSanInjectionMethodAttribute))).ToArray();
            if (methodArr != null && methodArr.Count() > 0)
            {
                foreach (MethodInfo method in methodArr)
                {
                    List<object> paraObjListMethod = new List<object>();
                    foreach (var parameter in method.GetParameters())
                    {
                        Type paraType = parameter.ParameterType;
                        object paraObj = InjectionPara(paraType);
                        paraObjListMethod.Add(paraObj);
                    }
                    method.Invoke(objRst, paraObjListMethod.ToArray());
                }
            }
            #endregion

            if (lifeTime.Equals(LifeTime.Singleton))
            {
                lock (ObjLock)
                {
                    if (!DicSingletonVal.ContainsKey(type))
                        DicSingletonVal.Add(type, objRst);
                    else
                        return DicSingletonVal[type];
                }
            }
            else if (lifeTime.Equals(LifeTime.PerThread))
            {
                //线程槽
                CallContext.SetData(type.FullName, objRst);
            }
            return objRst;
        }

    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyContainer
{
    public class RegisterInfo
    {
        public Type RegisterType { set; get; }
        public LifeTime RegisterLifeTime { set; get; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyContainer
{
    public enum LifeTime
    {
        /// <summary>
        /// 瞬时
        /// </summary>
        Transient,
        /// <summary>
        /// 全局单例
        /// </summary>
        Singleton,
        /// <summary>
        /// 线程单例
        /// </summary>
        PerThread,
    }
}
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace MyContainer
{
    public class SanSanConfigurationSection
    {
        private string JsonPath { set; get; }
        public SanSanConfigurationSection(string path)
        {
            JsonPath = path;
        }

        public void Configure(ISanSanContainer container, string containerName)
        {
            ContainerConfigMode containerConfigMode = JsonConfigToContainerObj();
            if (containerConfigMode == null)
                throw new Exception("未找到配置文件");
            var containerMode = containerConfigMode.listContainer.Where(t => t.ContainerName.Equals(containerName)).FirstOrDefault();
            if (containerMode == null)
                throw new Exception("未找到对应容器");
            containerMode.listRegister.ForEach(t =>
            {
                Type typeParent = GetTypeFromConfig(t.Type);
                Type typeChild = GetTypeFromConfig(t.MapTo);
                if (!container.DicRegisterType.ContainsKey(typeParent))
                    container.DicRegisterType.Add(typeParent, new RegisterInfo
                    {
                        RegisterLifeTime = LifeTime.Transient,
                        RegisterType = typeChild
                    });
            });
        }

        private Type GetTypeFromConfig(string path)
        {
            string[] arrParent = path.Split(',');
            Assembly assembly = Assembly.Load(arrParent[1]);
            Type typeParent = assembly.GetType(arrParent[0]);
            return typeParent;
        }

        private ContainerConfigMode JsonConfigToContainerObj()
        {
            if (!File.Exists(JsonPath))
            {
                return null;
            }
            string strJson = File.ReadAllText(JsonPath);
            return JsonConvert.DeserializeObject<ContainerConfigMode>(strJson);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyContainer
{
    internal class ContainerConfigMode
    {
        public List<ContainerMode> listContainer { set; get; }
    }

    internal class ContainerMode
    {
        public string ContainerName { set; get; }
        public List<Register> listRegister { set; get; }
    }

    internal class Register
    {
        public string Type { set; get; }
        public string MapTo { set; get; }
    }
}

特性标记注入方式

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyContainer
{
    [AttributeUsage(AttributeTargets.Constructor)]
    public class SanSanInjectionConstructorAttribute : Attribute
    {

    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyContainer
{
    [AttributeUsage(AttributeTargets.Method)]
    public class SanSanInjectionMethodAttribute : Attribute
    {

    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyContainer
{
    [AttributeUsage(AttributeTargets.Property)]
    public class SanSanDependencyAttribute : Attribute
    {
    }
}

配置

{
  "listContainer": [
    {
      "ContainerName": "testContainer1",
      "listRegister": [
        {
          "MapTo": "BLLExtention.Poplar,BLLExtention",
          "Type": "BLLExtention.AbstractTree,BLLExtention"
        },
        {
          "MapTo": "BLLExtention.Cypress,BLLExtention",
          "Type": "BLLExtention.AbstractTree,BLLExtention"
        },
        {
          "MapTo": "DAL.Branch,DAL",
          "Type": "IDAL.IBranch,IDAL"
        },
        {
          "MapTo": "BLLExtention.Leaves,BLLExtention",
          "Type": "IDAL.ILeaves,IDAL"
        },
        {
          "MapTo": "BLL.MyGeneric`1,BLL",
          "Type": "IBLL.IGeneric`1,IBLL"
        }
      ]
    }
  ]
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值