vs合并项目_.Net Core2.2 + EF Core + DI,三层框架项目搭建教程

笔记:

  近两年.Net Core发展的很快,目前最新版为3.0预览版,之前在网上买了一本1.1版书籍都还没来得及看呢,估计现在拿出来看也毫无意义了。已多年.net工作经验,看书不如直接实际上手来得快,遇到问题再度娘吧。正好最近公司不忙时,抽空亲手搭建.Net Core项目熟悉一下,说起.net那最自豪的就是VS编译器了,强大的辅助功能很多中小型项目只需要下一步就可以创建完成。这里我们还需要简单封装一下,使用仓储模式对数据访问层封装和Service层封装,通过.net自带DI依赖注入进行创建对象。对于初学者的我只能简单的封装一下,接下来我会一一讲解框架的思路,如有更好的方案或不明的地方欢迎留言。转载请备注来源:https://www.cnblogs.com/han1982/p/11058788.html

下面是已搭建好的框架结构:

5b0132cf8b696b3d8889e22db01d5cc6.png

第一步:创建解决方案

使用Visual Studio 2019编译器创建解决方案,默认安装vs2019自带的.NET Core 2.1,创建.NET Core 2.2版需要下载SDK安装。

https://dotnet.microsoft.com/download/visual-studio-sdks?utm_source=getdotnetsdk&utm_medium=referral

接下来可以创建项目了,首先创建的是数据访问层,我们命名为common.Core,另外给他创建一个接口层common.Interface。

 8a1dcfd972854d172b55516522b75114.png

(注意所有程序集创建必须为.Net Core版,为以后发布垮平台考虑)

第二步:创建Model层

封装仓储层之前先来创建数据Model层,Nuget添加EF Core相关引用,工具 - NuGet包管理器 - 程序包管理器控制台,默认项目选择Model程序集依次安装以下组件包。

Install-Package Microsoft.EntityFrameworkCore -version 2.2.4

Install-Package Microsoft.EntityFrameworkCore.SqlServer -version 2.2.4

Install-Package Microsoft.EntityFrameworkCore.Tools -version 2.2.4

dbe5765b1624765a76435fbe0104ea9b.png

也可以在项目中找到依赖项,右键管理NuGet管理包方式进行添加。

e541b0916dc9c014df023b34c4470a88.png

Microsoft.EntityFrameworkCore.Tools中包含了Microsoft.EntityFrameworkCore.Design依赖包,不需要单独安装了。

这里我使用的是Database First模式,使用工具Scaffold-DbContext(数据库上下文脚手架)来生成model类文件和DbContext。

执行以下命令:-o (OutputDir) 指定用于输出类的目录  -f (Force) 生成时覆盖现有文件 -Context 指定生成的DbContext类的名称,省略的话按数据库名称生成DbContext类文件。

Scaffold-DbContext "server=.;database=ConCard;uid=sa;pwd=123123;" Microsoft.EntityFrameworkCore.SqlServer -O Models -F

673af760cccb385a760122d988f09443.png

出现错误:VS2019有个小小BUG,默认项目选中以后最终执行的不是被选中程序集,这里需要把model程序集设为启动项目,再次执行。

35f9e8e06d6186d6ca36c20d4f254f10.png

自动生成所有类模型文件,ConCardContext.cs数据库上下文也都帮你创建好了,这样就省去了我们手动写DBSet时间。

第三步:封装数据访问层

数据访问层主要封装仓储Repository和工作单元UnitOfWork,我把这两个合并到一个类中实现,通过简单工厂方式创建实例对象。

我们直接把ConCardContext.cs这个类复制到common.Core程序集中,把命名空间修改为common.Core。

这时应该报错,因为common.Core项目中没有引用EFCore依赖包,按之前Model层添加一样,使用Install-Package命令为common.Core添加依赖包,Tools可以不用安装。

20503f9a6696f168754515c72ccafa43.png

依赖项中,右键添加引用,把Model和common.Interface项目引用,ConCardContext.cs中using model就不会报错了。

接下来修改下ConCardContext:

重写SaveChanges()方法

public override int SaveChanges()
{return base.SaveChanges(true);
}

删除OnConfiguring()方法,因为我们不需要在这里配置数据库连接,后面通过读取配置方式设置。

49f8f55446628e002272aa955658609d.png

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("server=.;database=ConCard;uid=sa;pwd=123123;");
}
}

49f8f55446628e002272aa955658609d.png

common.Interface程序集中创建IconcardContext接口,ConCardContext类中继承自这个接口。(主要用来后期使用DI依赖注入使用,不用接口也可以用DbContext代替)

ConCardContext类:

d80e1c7bc05b6ee970ae6968685f7f6e.png

49f8f55446628e002272aa955658609d.png

using System;using com.Synjones.Model.Models;using common.Interface;using Microsoft.EntityFrameworkCore;using Microsoft.EntityFrameworkCore.Metadata;namespace common.Core
{public partial class ConCardContext : DbContext, IconcardContext
{public ConCardContext()
{
}public ConCardContext(DbContextOptions options)
: base(options)
{
}public override int SaveChanges()
{return base.SaveChanges(true);
}public virtual DbSet Admin { get; set; }public virtual DbSet User { get; set; }protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasAnnotation("ProductVersion", "2.2.4-servicing-10062");
modelBuilder.Entity(entity =>
{
entity.Property(e => e.PassWord).HasMaxLength(50);
entity.Property(e => e.UserId).HasMaxLength(50);
});
modelBuilder.Entity(entity =>
{
entity.Property(e => e.Name).HasMaxLength(50);
entity.Property(e => e.Phone).HasMaxLength(50);
});
}
}
}

49f8f55446628e002272aa955658609d.png

开始继续创建Repository.cs仓储类,它是一个泛型类,并且拥有一个带有参数的构造方法,通过构造方法获得当前DbContext上下文对象,泛型类为指定Model类型,通过DbContext.Set()方法最终得到相应的DbSet对象来操作工作单元。

当然我们也要给他定义一个接口IRepository接口:

d80e1c7bc05b6ee970ae6968685f7f6e.png

49f8f55446628e002272aa955658609d.png

using System;using System.Collections.Generic;using System.Data;using System.Linq;using System.Linq.Expressions;using System.Text;namespace common.Interface
{public interface IRepository : IDisposable where T : class
{/// /// 显式开启数据上下文事务/// /// 指定连接的事务锁定行为void BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.Unspecified);/// /// 提交事务的更改/// void Commit();/// /// 显式回滚事务,仅在显式开启事务后有用/// void Rollback();/// /// 提交当前单元操作的更改/// int SaveChanges();/// /// 获取 当前实体类型的查询数据集,数据将使用不跟踪变化的方式来查询,当数据用于展现时,推荐使用此数据集,如果用于新增,更新,删除时,请使用数据集///
IQueryable Entities { get; }/// /// 获取 当前实体类型的查询数据集,当数据用于新增,更新,删除时,使用此数据集,如果数据用于展现,推荐使用数据集///
IQueryable TrackEntities { get; }/// /// 插入 - 通过实体对象添加/// /// 实体对象/// 是否执行/// ///
T Add(T entity, bool isSave = true);/// /// 批量插入 - 通过实体对象集合添加/// /// 实体对象集合/// 是否执行void AddRange(IEnumerable entitys, bool isSave = true);/// /// 删除 - 通过实体对象删除/// /// 实体对象/// 是否执行void Delete(T entity, bool isSave = true);/// /// 批量删除 - 通过实体对象集合删除/// /// 实体对象集合/// 是否执行void Delete(bool isSave = false, params T[] entitys);/// /// 删除 - 通过主键ID删除/// /// 主键ID/// 是否执行void Delete(object id, bool isSave = true);/// /// 批量删除 - 通过条件删除/// /// 过滤条件/// 是否执行void Delete(Expressionbool>> @where, bool isSave = true);/// /// 修改 - 通过实体对象修改/// /// 实体对象/// void Update(T entity, bool isSave = true);/// /// 批量修改 - 通过实体对象集合修改/// /// 实体对象集合/// void Update(bool isSave = true, params T[] entitys);/// /// 是否满足条件/// /// 过滤条件/// bool Any(Expressionbool>> @where);/// /// 返回总条数/// /// int Count();/// /// 返回总条数 - 通过条件过滤/// /// 过滤条件/// int Count(Expressionbool>> @where);/// /// 返回第一条记录/// /// 过滤条件///
T FirstOrDefault(Expressionbool>> @where);/// /// 返回第一条记录 - 通过条件过滤/// /// 排序约束/// 过滤条件/// 排序条件/// 排序方式///
T FirstOrDefault(Expressionbool>> @where, Expression> order, bool isDesc = false);/// /// 去重查询/// /// 过滤条件///
IQueryable Distinct(Expressionbool>> @where);/// /// 条件查询/// /// 过滤条件///
IQueryable Where(Expressionbool>> @where);/// /// 条件查询 - 支持排序/// /// 排序约束/// 过滤条件/// 排序条件/// 排序方式///
IQueryable Where(Expressionbool>> @where, Expression> order, bool isDesc = false);/// /// 条件分页查询 - 支持排序/// /// 排序约束/// 过滤条件/// 排序条件/// 当前页码/// 每页记录条数/// 返回总条数/// 是否倒序///
IEnumerable Where(Funcbool> @where, Func order, int pageIndex, int pageSize, out int count, bool isDesc = false);/// /// 条件分页查询 - 支持排序 - 支持Select导航属性查询/// /// 排序约束/// 过滤条件/// 排序条件/// 当前页码/// 每页记录条数/// 返回总条数/// 是否倒序///
IQueryable Where(Expressionbool>> @where, Expression> order, int pageIndex, int pageSize, out int count, bool isDesc = false);/// /// 获取所有数据/// ///
IQueryable GetAll();/// /// 获取所有数据 - 支持排序/// /// 排序约束/// 排序条件/// 排序方式///
IQueryable GetAll(Expression> order, bool isDesc = false);/// /// 根据ID查询/// /// 字段类型/// 主键ID///
T GetById(Ttype id);/// /// 获取最大值/// /// 字段类型/// 字段条件///
Ttype Max(Expression> column);/// /// 获取最大值/// /// 字段类型/// 字段条件/// 过滤条件///
Ttype Max(Expression> column, Expressionbool>> @where);/// /// 获取最小值/// /// 字段类型/// 字段条件///
Ttype Min(Expression> column);/// /// 获取最小值/// /// 字段类型/// 字段条件/// 过滤条件///
Ttype Min(Expression> column, Expressionbool>> @where);/// /// 获取总数/// /// 字段类型/// 字段条件/// 过滤条件///
TType Sum(Expression> selector, Expressionbool>> @where) where TType : new();
}
}

49f8f55446628e002272aa955658609d.png

Repository类,实现了CRUD基本功能的封装:

d80e1c7bc05b6ee970ae6968685f7f6e.png

49f8f55446628e002272aa955658609d.png

using common.Interface;using Microsoft.EntityFrameworkCore;using System;using System.Collections.Generic;using System.Data;using System.Data.SqlClient;using System.Linq;using System.Linq.Expressions;using System.Text;namespace common.Core
{public class Repository : IRepositorywhere T : class
{private ConCardContext _dbContext;private readonly DbSet _dbSet;private readonly string _connStr;public Repository(IconcardContext mydbcontext)
{this._dbContext = mydbcontext as ConCardContext;this._dbSet = _dbContext.Set();this._connStr = _dbContext.Database.GetDbConnection().ConnectionString;
}public void BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.Unspecified)
{if (this._dbContext.Database.CurrentTransaction == null)
{this._dbContext.Database.BeginTransaction(isolationLevel);
}
}public void Commit()
{var transaction = this._dbContext.Database.CurrentTransaction;if (transaction != null)
{try
{
transaction.Commit();
}catch (Exception)
{
transaction.Rollback();throw;
}
}
}public void Rollback()
{if (this._dbContext.Database.CurrentTransaction != null)
{this._dbContext.Database.CurrentTransaction.Rollback();
}
}public int SaveChanges()
{return this._dbContext.SaveChanges();
}public IQueryable Entities
{get { return this._dbSet.AsNoTracking(); }
}public IQueryable TrackEntities
{get { return this._dbSet; }
}public T Add(T entity, bool isSave = true)
{this._dbSet.Add(entity);if (isSave)
{this.SaveChanges();
}return entity;
}public void AddRange(IEnumerable entitys, bool isSave = true)
{this._dbSet.AddRange(entitys);if (isSave)
{this.SaveChanges();
}
}public void Delete(T entity, bool isSave = true)
{this._dbSet.Remove(entity);if (isSave)
{this.SaveChanges();
}
}public void Delete(bool isSave = true, params T[] entitys)
{this._dbSet.RemoveRange(entitys);if (isSave)
{this.SaveChanges();
}
}public void Delete(object id, bool isSave = true)
{this._dbSet.Remove(this._dbSet.Find(id));if (isSave)
{this.SaveChanges();
}
}public void Delete(Expressionbool>> @where, bool isSave = true)
{
T[] entitys = this._dbSet.Where(@where).ToArray();if (entitys.Length > 0)
{this._dbSet.RemoveRange(entitys);
}if (isSave)
{this.SaveChanges();
}
}public void Update(T entity, bool isSave = true)
{var entry = this._dbContext.Entry(entity);if (entry.State == EntityState.Detached)
{
entry.State = EntityState.Modified;
}if (isSave)
{this.SaveChanges();
}
}public void Update(bool isSave = true, params T[] entitys)
{var entry = this._dbContext.Entry(entitys);if (entry.State == EntityState.Detached)
{
entry.State = EntityState.Modified;
}if (isSave)
{this.SaveChanges();
}
}public bool Any(Expressionbool>> @where)
{return this._dbSet.AsNoTracking().Any(@where);
}public int Count()
{return this._dbSet.AsNoTracking().Count();
}public int Count(Expressionbool>> @where)
{return this._dbSet.AsNoTracking().Count(@where);
}public T FirstOrDefault(Expressionbool>> @where)
{return this._dbSet.AsNoTracking().FirstOrDefault(@where);
}public T FirstOrDefault(Expressionbool>> @where, Expression> order, bool isDesc = false)
{if (isDesc)
{return this._dbSet.AsNoTracking().OrderByDescending(order).FirstOrDefault(@where);
}else
{return this._dbSet.AsNoTracking().OrderBy(order).FirstOrDefault(@where);
}
}public IQueryable Distinct(Expressionbool>> @where)
{return this._dbSet.AsNoTracking().Where(@where).Distinct();
}public IQueryable Where(Expressionbool>> @where)
{return this._dbSet.Where(@where);
}public IQueryable Where(Expressionbool>> @where, Expression> order, bool isDesc = false)
{if (isDesc)
{return this._dbSet.Where(@where).OrderByDescending(order);
}else
{return this._dbSet.Where(@where).OrderBy(order);
}
}public IEnumerable Where(Funcbool> @where, Func order, int pageIndex, int pageSize, out int count, bool isDesc = false)
{
count = Count();if (isDesc)
{return this._dbSet.Where(@where).OrderByDescending(order).Skip((pageIndex - 1) * pageSize).Take(pageSize);
}else
{return this._dbSet.Where(@where).OrderBy(order).Skip((pageIndex - 1) * pageSize).Take(pageSize);
}
}public IQueryable Where(Expressionbool>> @where, Expression> order, int pageIndex, int pageSize, out int count, bool isDesc = false)
{
count = Count();if (isDesc)
{return this._dbSet.Where(@where).OrderByDescending(order).Skip((pageIndex - 1) * pageSize).Take(pageSize);
}else
{return this._dbSet.Where(@where).OrderBy(order).Skip((pageIndex - 1) * pageSize).Take(pageSize);
}
}public IQueryable GetAll()
{return this._dbSet.AsNoTracking();
}public IQueryable GetAll(Expression> order, bool isDesc = false)
{if (isDesc)
{return this._dbSet.AsNoTracking().OrderByDescending(order);
}else
{return this._dbSet.AsNoTracking().OrderBy(order);
}
}public T GetById(Ttype id)
{return this._dbSet.Find(id);
}public Ttype Max(Expression> column)
{if (this._dbSet.AsNoTracking().Any())
{return this._dbSet.AsNoTracking().Max(column);
}return default(Ttype);
}public Ttype Max(Expression> column, Expressionbool>> @where)
{if (this._dbSet.AsNoTracking().Any(@where))
{return this._dbSet.AsNoTracking().Where(@where).Max(column);
}return default(Ttype);
}public Ttype Min(Expression> column)
{if (this._dbSet.AsNoTracking().Any())
{return this._dbSet.AsNoTracking().Min(column);
}return default(Ttype);
}public Ttype Min(Expression> column, Expressionbool>> @where)
{if (this._dbSet.AsNoTracking().Any(@where))
{return this._dbSet.AsNoTracking().Where(@where).Min(column);
}return default(Ttype);
}public TType Sum(Expression> selector, Expressionbool>> @where) where TType : new()
{object result = 0;if (new TType().GetType() == typeof(decimal))
{
result = this._dbSet.AsNoTracking().Where(where).Sum(selector as Expressiondecimal>>);
}if (new TType().GetType() == typeof(decimal?))
{
result = this._dbSet.AsNoTracking().Where(where).Sum(selector as Expressiondecimal?>>);
}if (new TType().GetType() == typeof(double))
{
result = this._dbSet.AsNoTracking().Where(where).Sum(selector as Expressiondouble>>);
}if (new TType().GetType() == typeof(double?))
{
result = this._dbSet.AsNoTracking().Where(where).Sum(selector as Expressiondouble?>>);
}if (new TType().GetType() == typeof(float))
{
result = this._dbSet.AsNoTracking().Where(where).Sum(selector as Expressionfloat>>);
}if (new TType().GetType() == typeof(float?))
{
result = this._dbSet.AsNoTracking().Where(where).Sum(selector as Expressionfloat?>>);
}if (new TType().GetType() == typeof(int))
{
result = this._dbSet.AsNoTracking().Where(where).Sum(selector as Expressionint>>);
}if (new TType().GetType() == typeof(int?))
{
result = this._dbSet.AsNoTracking().Where(where).Sum(selector as Expressionint?>>);
}if (new TType().GetType() == typeof(long))
{
result = this._dbSet.AsNoTracking().Where(where).Sum(selector as Expressionlong>>);
}if (new TType().GetType() == typeof(long?))
{
result = this._dbSet.AsNoTracking().Where(where).Sum(selector as Expressionlong?>>);
}return (TType)result;
}public void Dispose()
{this._dbContext.Dispose();
}
}
}

49f8f55446628e002272aa955658609d.png

这样仓储模式就创建好了,接下来想办法通过DI创建实例,而不是直接在Service层new一个实例,但是Repository是泛型类,通过DI创建需要设置,所有不同model类都要声明一遍,这里只能使用简单工厂来处理下。

添加RepositoryFactory类和IRepositoryFactory接口,接口中定义IRepository CreateRepository(IconcardContext mydbcontext) where T : class; 通过实现CreateRepository方法来创建不同数据模型的工作单元。

IRepositoryFactory接口:

d80e1c7bc05b6ee970ae6968685f7f6e.png

49f8f55446628e002272aa955658609d.png

using System;using System.Collections.Generic;using System.Text;namespace common.Interface
{public interface IRepositoryFactory
{
IRepository CreateRepository(IconcardContext mydbcontext) where T : class;
}
}

49f8f55446628e002272aa955658609d.png

RepositoryFactory类:

d80e1c7bc05b6ee970ae6968685f7f6e.png

49f8f55446628e002272aa955658609d.png

using common.Interface;using System;using System.Collections.Generic;using System.Text;namespace common.Core
{public class RepositoryFactory : IRepositoryFactory
{public IRepository CreateRepository(IconcardContext mydbcontext) where T : class
{return new Repository(mydbcontext);
}
}
}

49f8f55446628e002272aa955658609d.png

5a21e220430880f6d7dfdf3ffb3b7d64.png

 第四步:创建Service层:

 老规矩,先添加新建项目Service和IService,一个是定义Service接口,另一个是它的实现,他们都需要引入Model层和Interface层,Service要引入IService层。

2574f7c72d6d65a5ca2c96664b6d01d8.png

添加BaseService类和IBaseService接口,接口中定义IRepository CreateService() where T : class, new();

IBaseService接口:

d80e1c7bc05b6ee970ae6968685f7f6e.png

49f8f55446628e002272aa955658609d.png

using common.Interface;using System;using System.Collections.Generic;using System.Text;namespace com.Synjones.IService
{public interface IBaseService
{
IRepository CreateService() where T : class, new();
}
}

49f8f55446628e002272aa955658609d.png

BaseService类:

d80e1c7bc05b6ee970ae6968685f7f6e.png

49f8f55446628e002272aa955658609d.png

using com.Synjones.IService;using common.Interface;using System;using System.Collections.Generic;using System.Text;namespace com.Synjones.Service
{public class BaseService : IBaseService
{private IRepositoryFactory _repositoryFactory;private IconcardContext _mydbcontext;public BaseService(IRepositoryFactory repositoryFactory, IconcardContext mydbcontext)
{this._repositoryFactory = repositoryFactory;this._mydbcontext = mydbcontext;
}public IRepository CreateService() where T : class, new()
{return _repositoryFactory.CreateRepository(_mydbcontext);
}
}
}

49f8f55446628e002272aa955658609d.png

这里说明一下,BaseService类也是泛型类,也不需要通过DI方式创建,Service层中根据每个模块添加一个Service类,并且继承BaseService类,DI依赖注入模块Service中直接获取到指定模型的仓储进行操作。

添加User模块UserService类和IUserService接口,UserService类继承父类BaseService,生成构造函数。

5115ab383bd3147cbe80f23fdfac50e2.png

public UserService(IRepositoryFactory repositoryFactory, IconcardContext mydbcontext) : base(repositoryFactory, mydbcontext)
{
}

下面我们简单举例对user表读取操作的业务层实现,定义接口List GetUsers(),实现GetUsers()方法,读取所有user表数据。

IUserService接口:

d80e1c7bc05b6ee970ae6968685f7f6e.png

49f8f55446628e002272aa955658609d.png

using com.Synjones.Model.Models;using System;using System.Collections.Generic;using System.Text;namespace com.Synjones.IService
{public interface IUserService
{
List GetUsers();
}
}

49f8f55446628e002272aa955658609d.png

UserService类:

d80e1c7bc05b6ee970ae6968685f7f6e.png

49f8f55446628e002272aa955658609d.png

using com.Synjones.IService;using com.Synjones.Model.Models;using common.Interface;using System;using System.Collections.Generic;using System.Text;using System.Linq;namespace com.Synjones.Service
{public class UserService : BaseService, IUserService
{public UserService(IRepositoryFactory repositoryFactory, IconcardContext mydbcontext) : base(repositoryFactory, mydbcontext)
{
}public List GetUsers()
{var service = this.CreateService();return service.GetAll().ToList();
}
}
}

49f8f55446628e002272aa955658609d.png

 5910c3d04be5e4e492697a4314e55ec0.png

第五步:UI创建并调用Service接口返回数据。

我这里创建了WebApi项目,依赖项中添加引用所有其他项目。

ed8311b0963e28519e0b491eeb3aba42.png

配置数据库连接字符串,打开appsettings.json,添加

"ConnectionStrings": {"SchoolConnection": "server=.;database=ConCard;uid=sa;pwd=123123;"
}

配置EF服务注册:

打开Startup.cs,ConfigureServices方法中添加services.AddDbContext指定数据库连接配置项,通过services.AddScoped添加DI依赖注入配置。

49f8f55446628e002272aa955658609d.png

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);// 配置EF服务注册
services.AddDbContext(options =>
options.UseSqlServer(Configuration.GetConnectionString("SchoolConnection")));
services.AddScoped();
services.AddScoped();
services.AddScoped();
}

49f8f55446628e002272aa955658609d.png

修改下ValuesController控制器,添加构造函数和Get方法。

14e9fb7ac1d47293fc8cc5da10d1bc57.png

WebApi项目设为启动项目,运行看结果。

 89e218f88d4269cfea175632b971e371.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值