作者简介:博主是一位.Net开发者,同时也是RPA和低代码平台的践行者。
个人主页:会敲键盘的肘子
系列专栏:SqlSugar ORM
专栏简介:SqlSugar是一款来自未来的ORM,拥有超前的理念,需求领跑第一线,在设计理念上就算不更新几年都不会过时,是一款真正用了功能齐全的ORM框架。博主在工作中学习和实战SqlSugar,形成本专栏,希望可以帮助到您解决问题。
座右铭:总有一天你所坚持的会反过来拥抱你。
前言
随着华为、中兴事务,国产数据库市场相信是未来是趋势走向,纵观 .net core 整个圈子FreeSql和SqlSugar(排名不分先后)都已早早支持国产数据库,比如人大金仓、神通、达梦,针对不同的项目要求支持适配不同的国产数据库。今天我们使用 SqlSugar ORM 的工作单元模式来连接多个国产数据库。
SqlSugar是一款来自未来的ORM,开箱即用,拥有超前的理念,需求领跑第一线,在设计理念上就算不更新几年都不会过时,是一款真正用了功能齐全的ORM框架。
感谢果糖大数据科技团队对SqlSugar的维护和更新。
本文关键字:国产数据库、SqlSugar、ORM、人大金仓、达梦、翰高
文章目录
- 前言
- Sqlsugar基本介绍
- ♈ 基本介绍
- ⭐ 功能介绍
- ⭐ 数据库支持
- ♉ Nuget安装
- ⭐ 打开Nuget
- ⭐ 安装SqlSugar
- ⭐ SqlSugar源码
- 工作单元模式/IUnitOfWorK
- ♊ 工作单元模式/IUnitOfWorK
- ⭐ 数据库枚举
- ⭐ 连接字符串(多数据库)
- ♉ 实现
- ⭐ UnitOfWork.cs 工作单元实现
- ⭐ KdbndpOfWork.cs(人大金仓数据库)
- ⭐ DMOfWork.cs(达梦数据库)
- ⭐ MysqlOfWork.cs(MySQL数据库)
- ⭐ OracleOfWork.cs(Oracle数据库)
- ⭐ HGdbOfWork.cs(瀚高数据库)
- 仓储
- ♊ 定义
- ⭐ 仓储接口
- ⭐ 通用实体
- ⭐ 用户表实体
- ♉ 各个仓储实现
- ⭐ 用户表仓储接口(人大金仓)
- ⭐ 用户表仓储(人大金仓)
- ⭐ 用户表仓储接口(达梦)
- ⭐ 用户表仓储(达梦)
- ⭐ 用户表仓储接口(翰高)
- ⭐ 用户表仓储(翰高)
- ⭐ 多表查询
- 结语
Sqlsugar基本介绍
♈ 基本介绍
SqlSugar 是一款 老牌 .NET 开源ORM框架,由果糖大数据科技团队维护和更新 ,开箱即用最易上手的ORM框架 ,51Job和Boss直招简历数超过 国外框架 Nhibernate PetaPoco, 仅次于Dapper和EF Core , 占Dapper 40% 。
⭐ 功能介绍
- 相比EF Core【学习成本低一天学会】,多库支持更好 ,在国内拥有不逊色EF Core 的【开源生态】。
- 支持 .NET 百万级【大数据】写入和更新、分表和几十亿查询和统计等 拥有成熟方案。
- 支持 完整的SAAS一套应用 跨库查询 、租户分库 、租户分表 和 租户数据隔离。
- 支持【低代码】+工作流 (无实体多库兼容CRUD & JSON TO SQL )。
- 语法最爽的ORM、优美的表达式、仓储、UnitOfWork、DbContext、AOP。
- 支持 DbFirst、CodeFirst和【WebFirst】 3种模式开发。
- 简单易用、功能齐全、高性能、轻量级、服务齐全、官网教程文档、有专业技术支持一天18小时服务。
⭐ 数据库支持
关系型数据库 | MySql、SqlServer、Sqlite、Oracle 、 postgresql、达梦、人大金仓、神通数据库、瀚高、Access 、MySqlConnector、华为 GaussDB 、南大通用 GBase 、Odbc、自定义 |
---|---|
时序数据库 | QuestDb (适合几十亿数据分析,模糊查询,自动分表存储 ,缺点不支持删除) |
列式存储库 | Clickhouse(适用于商业智能领域(BI),缺点大小写必须和库一样,不支持事务) |
即将上线 | Mongodb(mongodb.entities)TDengine、Sybase… |
♉ Nuget安装
⭐ 打开Nuget
点击项目引用 >> 右键 Nuget管理
⭐ 安装SqlSugar
.Net Core 3 & 5 & 6 &7
⭐ SqlSugar源码
博主这里因为需要对SqlSugar做一些修改,所以用了源码,结构如下,
工作单元模式/IUnitOfWorK
♊ 工作单元模式/IUnitOfWorK
⭐ 数据库枚举
public enum DbType{ MySql , SqlServer, Sqlite, Oracle, PostgreSQL, Dm, Kdbndp // 人大金仓 只支持.NET CORE}
⭐ 连接字符串(多数据库)
appsettings.json配置
"ConnectionStrings": { "kdbndp": "data source=127.0.0.1;database=test;Port=54321;UID=SYSTEM;PWD=system", //测试版本用人大金仓数据库 "Oracledb": "data source=127.0.0.1/mes;user id=mes_open;password=123456", //Oracle数据库 "MySqldb": "data source=127.0.0.1;database=production;uid=root;pwd=123456",//MySql数据库 "DMdb":"PORT=5236;DATABASE=DAMENG;HOST=127.0.0.1;PASSWORD=SYSDBA;USER ID=SYSDBA",//DM数据库 "HGdb":"PORT=5432;DATABASE=SqlSugar4xTest;HOST=localhost;PASSWORD=haosql;USER ID=postgres",//瀚高数据库 },
♉ 实现
⭐ UnitOfWork.cs 工作单元实现
using Microsoft.AspNetCore.Http;using Microsoft.Extensions.Configuration;using SqlSugar;using System;namespace Sys.Infrastructure.Data{ /// /// 工作单元实现 /// public abstract class UnitOfWork : IUnitOfWork { public UnitOfWork(IConfiguration configuration, IHttpContextAccessor accessor) { Configuration = configuration; HttpContextAccessor = accessor; } protected IConfiguration Configuration { get; set; } public IHttpContextAccessor HttpContextAccessor { get; set; } protected DateTime StartTime { get; set; } protected DateTime EndTime { get; set; } public SqlSugarClient Db { get; protected set; } public void BeginTran() => Db.BeginTran(); public void CommitTran() { try { Db.CommitTran(); } catch (Exception ex) { Db.RollbackTran(); throw ex; } } public void RollbackTran() => Db.RollbackTran(); }}
⭐ KdbndpOfWork.cs(人大金仓数据库)
using Microsoft.AspNetCore.Http;using Microsoft.Extensions.Configuration;using Newtonsoft.Json;using Serilog;using SqlSugar;using System;using System.IO;using System.Linq;namespace Sys.Infrastructure.Data{ public class KdbndpOfWork : UnitOfWork, IUnitOfWork { public KdbndpOfWork(IConfiguration configuration, IHttpContextAccessor accessor) : base(configuration, accessor) { Db = new SqlSugarClient(new ConnectionConfig { ConnectionString = configuration.GetConnectionString("kdbndpdb"), //数据库连接在 appsettings.json 中配置 DbType = DbType.Kdbndp, IsAutoCloseConnection = true, IsShardSameThread = true, AopEvents = new AopEvents { OnLogExecuted = OnLogExecuted, OnLogExecuting = OnLogExecuting, OnError = OnError } }); } /// /// 当数据库操作执行出错时 /// /// private static void OnError(SqlSugarException exception) { } /// /// 当数据库操作执行时 /// /// /// private static void OnLogExecuting(string sql, SugarParameter[] parameters) { //StartTime = DateTime.Now; } /// /// 当数据库操作执行完毕后 /// /// /// private static void OnLogExecuted(string sql, SugarParameter[] parameters) { // EndTime = DateTime.Now; //CreateHttpSqlLog(sql, parameters); } }}
⭐ DMOfWork.cs(达梦数据库)
using Microsoft.AspNetCore.Http;using Microsoft.Extensions.Configuration;using Newtonsoft.Json;using Serilog;using SqlSugar;using System;using System.IO;using System.Linq;namespace Sys.Infrastructure.Data{ public class DMOfWork : UnitOfWork, IUnitOfWork { public DMOfWork(IConfiguration configuration, IHttpContextAccessor accessor) : base(configuration, accessor) { Db = new SqlSugarClient(new ConnectionConfig { ConnectionString = configuration.GetConnectionString("DMdb"), //数据库连接在 appsettings.json 中配置 DbType = DbType.DM, IsAutoCloseConnection = true, IsShardSameThread = true, AopEvents = new AopEvents { OnLogExecuted = OnLogExecuted, OnLogExecuting = OnLogExecuting, OnError = OnError } }); } /// /// 当数据库操作执行出错时 /// /// private static void OnError(SqlSugarException exception) { } /// /// 当数据库操作执行时 /// /// /// private static void OnLogExecuting(string sql, SugarParameter[] parameters) { //StartTime = DateTime.Now; } /// /// 当数据库操作执行完毕后 /// /// /// private static void OnLogExecuted(string sql, SugarParameter[] parameters) { // EndTime = DateTime.Now; //CreateHttpSqlLog(sql, parameters); } }}
⭐ MysqlOfWork.cs(MySQL数据库)
using Microsoft.AspNetCore.Http;using Microsoft.Extensions.Configuration;using Newtonsoft.Json;using Serilog;using SqlSugar;using Supcon.APS.Common;using Supcon.APS.Domain.Core;using Supcon.APS.Domain.Entities;using Supcon.APS.Domain.Entities.Sys;using Supcon.APS.Domain.IRepository;using System;using System.IO;using System.Linq;namespace Sys.Infrastructure.Data{ public class MysqlOfWork : UnitOfWork, IUnitOfWork { public MysqlOfWork(IConfiguration configuration, IHttpContextAccessor accessor) : base(configuration, accessor) { Db = new SqlSugarClient(new ConnectionConfig { ConnectionString = configuration.GetConnectionString("Mysqldb"), //数据库连接在 appsettings.json 中配置 DbType = DbType.MySQL, IsAutoCloseConnection = true, IsShardSameThread = true, AopEvents = new AopEvents { OnLogExecuted = OnLogExecuted, OnLogExecuting = OnLogExecuting, OnError = OnError } }); } /// /// 当数据库操作执行出错时 /// /// private static void OnError(SqlSugarException exception) { } /// /// 当数据库操作执行时 /// /// /// private static void OnLogExecuting(string sql, SugarParameter[] parameters) { //StartTime = DateTime.Now; } /// /// 当数据库操作执行完毕后 /// /// /// private static void OnLogExecuted(string sql, SugarParameter[] parameters) { // EndTime = DateTime.Now; //CreateHttpSqlLog(sql, parameters); } }}
⭐ OracleOfWork.cs(Oracle数据库)
using Microsoft.AspNetCore.Http;using Microsoft.Extensions.Configuration;using Newtonsoft.Json;using Serilog;using SqlSugar;using Supcon.APS.Common;using Supcon.APS.Domain.Core;using Supcon.APS.Domain.Entities;using Supcon.APS.Domain.Entities.Sys;using Supcon.APS.Domain.IRepository;using System;using System.IO;using System.Linq;namespace Sys.Infrastructure.Data{ public class OracleOfWork : UnitOfWork, IUnitOfWork { public OracleOfWork(IConfiguration configuration, IHttpContextAccessor accessor) : base(configuration, accessor) { Db = new SqlSugarClient(new ConnectionConfig { ConnectionString = configuration.GetConnectionString("Oracledb"), //数据库连接在 appsettings.json 中配置 DbType = DbType.Oracle, IsAutoCloseConnection = true, IsShardSameThread = true, AopEvents = new AopEvents { OnLogExecuted = OnLogExecuted, OnLogExecuting = OnLogExecuting, OnError = OnError } }); } /// /// 当数据库操作执行出错时 /// /// private static void OnError(SqlSugarException exception) { } /// /// 当数据库操作执行时 /// /// /// private static void OnLogExecuting(string sql, SugarParameter[] parameters) { //StartTime = DateTime.Now; } /// /// 当数据库操作执行完毕后 /// /// /// private static void OnLogExecuted(string sql, SugarParameter[] parameters) { // EndTime = DateTime.Now; //CreateHttpSqlLog(sql, parameters); } }}
⭐ HGdbOfWork.cs(瀚高数据库)
using Microsoft.AspNetCore.Http;using Microsoft.Extensions.Configuration;using Newtonsoft.Json;using Serilog;using SqlSugar;using Supcon.APS.Common;using Supcon.APS.Domain.Core;using Supcon.APS.Domain.Entities;using Supcon.APS.Domain.Entities.Sys;using Supcon.APS.Domain.IRepository;using System;using System.IO;using System.Linq;namespace Sys.Infrastructure.Data{ public class HGdbOfWork : UnitOfWork, IUnitOfWork { public HGdbOfWork(IConfiguration configuration, IHttpContextAccessor accessor) : base(configuration, accessor) { Db = new SqlSugarClient(new ConnectionConfig { ConnectionString = configuration.GetConnectionString("HGdb"), //数据库连接在 appsettings.json 中配置 DbType = SqlSugar.DbType.PostgreSQL, IsAutoCloseConnection = true, MoreSettings=new ConnMoreSettings() { PgSqlIsAutoToLower=false //数据库存在大写字段的 ,需要把这个设为false ,并且实体和字段名称要一样 }, AopEvents = new AopEvents { OnLogExecuting = (sql, p) => { Console.WriteLine(sql); Console.WriteLine(string.Join(",", p" /> /// 当数据库操作执行出错时 /// /// private static void OnError(SqlSugarException exception) { } /// /// 当数据库操作执行时 /// /// /// private static void OnLogExecuting(string sql, SugarParameter[] parameters) { //StartTime = DateTime.Now; } /// /// 当数据库操作执行完毕后 /// /// /// private static void OnLogExecuted(string sql, SugarParameter[] parameters) { // EndTime = DateTime.Now; //CreateHttpSqlLog(sql, parameters); } }}
仓储
♊ 定义
仓储有一套自带的数据库操作方法,比起 db.xx.xxx来说可能更简便些满足一些常用需求, 复杂的功能还是用db.xxx.xxx。
⭐ 仓储接口
public interface IRepository : IRepository where TEntity : ApsEntity, new() { }/// /// 人大金仓仓储接口/// public interface IKdbndpRepository : IRepository where TEntity : SupplyChanEntity, new() { }/// ///达梦仓储接口/// public interface IDMRepository : IRepository where TEntity : SupplyChanEntity, new() { }/// /// 翰高仓储接口/// public interface IHGRepository : IRepository where TEntity : SupplyChanEntity, new() { }/// /// MySQL仓储接口/// public interface IMySQLRepository : IRepository where TEntity : SupplyChanEntity, new() { }/// /// Oracle仓储接口/// public interface IOracleRepository : IRepository where TEntity : SupplyChanEntity, new() { } public interface IRepository where TEntity : Entity, new() where TUnitOfWork : IUnitOfWork { #region 查询 /// /// 是否存在满足指定条件的数据 /// /// 条件表达式 /// bool IsExist(Expression<Func> whereExpression); /// /// 统计数据条数 /// /// 条件表达式 /// int Count(Expression<Func> whereExpression = null); /// /// 获取一条数据 /// /// 条件表达式 /// Task SingleAsync(Expression<Func> whereExpression); /// /// 查询 /// /// Task<List> QueryAsync(); /// /// 查询 /// /// 查询字符串 /// Task<List> QueryAsync(string strWhere); /// /// 查询 /// /// 条件表达式 /// Task<List> QueryAsync(Expression<Func> whereExpression); /// /// 查询 /// /// 查询字符串 /// 排序字段,如:Name Asc, Age Desc /// Task<List> QueryAsync(string strWhere, string strOrderByFileds); /// /// 查询 /// /// 条件表达式 /// 排序字段,如:Name Asc, Age Desc /// Task<List> QueryAsync(Expression<Func> whereExpression, string strOrderByFileds); /// /// 查询 /// /// 条件表达式 /// 排序表达式 /// 是否为升序 /// Task<List> QueryAsync(Expression<Func> whereExpression, Expression<Func> orderByExpression, bool isAsc = true); /// /// 查询前 N 条数据 /// /// 条件表达式 /// 记录数 /// 排序字段,如:Name Asc, Age Desc /// Task<List> TopAsync( Expression<Func> whereExpression, int count, string strOrderByFileds); /// /// 查询前 N 条数据 /// /// 条件字符串 /// 记录数 /// 排序字段,如:Name Asc, Age Desc /// Task<List> TopAsync( string strWhere, int count, string strOrderByFileds); /// /// 分页查询 /// /// 条件字符串 /// 当前页数 /// 分页大小 /// 排序字段,如:Name Asc, Age Desc /// 总记录数 /// List Pages(string strWhere, int pageIndex, int pageSize, string strOrderByFileds, out int totalCount); /// /// 分页查询 /// /// 条件表达式 /// 当前页数 /// 分页大小 /// 排序表达式 /// 是否为升序 /// 总记录数 /// List Pages(Expression<Func> whereExpression, int pageIndex, int pageSize, Expression<Func> orderByExpression, bool isAsc, out int totalCount); /// /// 多表查询 /// /// 实体1 /// 实体2 /// 返回类型 /// 关联表达式 (join1, join2) => new object[] { JoinType.Left, join1.UserNo == join2.UserNo } /// 返回表达式 (s1, s2) => new { Id =s1.UserNo, Id1 = s2.UserNo } /// 查询表达式 (w1, w2) => w1.UserNo == "") /// Task<List> QueryMuchAsync( Expression<Func> joinExpression, Expression<Func> selectExpression, Expression<Func> whereLambda = null) where T1 : class, new(); /// ///多表查询 /// Task<List> QueryMuchAsync( Expression<Func> joinExpression, Expression<Func> selectExpression, Expression<Func> whereLambda = null) where T1 : class, new(); /// ///多表查询 /// Task<List> QueryMuchAsync( Expression<Func> joinExpression, Expression<Func> selectExpression, Expression<Func> whereLambda = null) where T1 : class, new(); Task<List> QueryMuchAsync( Expression<Func> joinExpression, Expression<Func> selectExpression, Expression<Func> whereLambda = null) where T1 : class, new(); Task<List> QueryMuchAsync( Expression<Func> joinExpression, Expression<Func> selectExpression, Expression<Func> whereLambda = null) where T1 : class, new(); #endregion #region 新增 /// /// 新增 /// /// 实体(集合) /// Task<List> AddAsync(params TEntity[] entities); /// /// 新增 /// /// 实体 /// 指定要插入的列 /// Task AddAsync(TEntity entity, Expression<Func> insertColumns); /// /// 新增 /// /// 实体集合 /// 指定要插入的列 /// Task<List> AddAsync(TEntity[] entities, Expression<Func> insertColumns); #endregion #region 更新 /// /// 更新 /// /// 实体 /// 指定要更新的列 /// 指定要忽略的列 /// Task UpdateAsync(TEntity entity, Expression<Func> updateColumns = null, Expression<Func> ignoreColumns = null); /// /// 更新 /// /// 实体集合 /// 指定要更新的列 /// 指定要忽略的列 /// Task UpdateAsync(TEntity[] entities, Expression<Func> updateColumns = null, Expression<Func> ignoreColumns = null); /// /// 更新 /// /// 条件表达式 /// 要更新的值(匿名对象) /// Task UpdateAsync(Expression<Func> whereExpression, dynamic anonymous); #endregion #region 删除 /// /// 删除(Support IPhantomEntity) /// /// 实体(集合) /// Task DeleteAsync(params TEntity[] entities); /// /// 删除(Support IPhantomEntity) /// /// 主键(集合) /// Task DeleteAsync(params TKey[] ids); / / 删除(Support IPhantomEntity) / / 条件表达式 / //Task DeleteAsync(Expression<Func> whereExpression); / / 删除(Support IPhantomEntity) / / 条件字符串 / //Task DeleteAsync(string strWhere); #endregion #region 更新或新增 /// /// 更新或插入 /// http://www.codeisbug.com/Doc/8/1172 /// /// 实体 /// 指定要更新或插入的列 /// 指定要忽略的列 /// TEntity UpsertAsync(TEntity entity, Expression<Func> upsertColumns = null, Expression<Func> ignoreColumns = null); /// /// 更新或插入 /// http://www.codeisbug.com/Doc/8/1172 /// /// 实体集合 /// 指定要更新或插入的列 /// 指定要忽略的列 /// Task<List> UpsertAsync(List entities, Expression<Func> upsertColumns = null, Expression<Func> ignoreColumns = null); /// /// 更新或插入 /// /// 实体 /// 指定要插入的列 /// 指定要忽略的插入列 /// 指定要更新的列 /// 指定要忽略的更新列 /// TEntity UpsertAsync(TEntity entity, Expression<Func> insertColumns = null, Expression<Func> ignoreInsertColumns = null, Expression<Func> updateColumns = null, Expression<Func> ignoreUpdateColumns = null); /// /// 更新或插入 /// /// 实体集合 /// 指定要插入的列 /// 指定要忽略的插入列 /// 指定要更新的列 /// 指定要忽略的更新列 /// Task<List> UpsertAsync(List entities, Expression<Func> insertColumns = null, Expression<Func> ignoreInsertColumns = null, Expression<Func> updateColumns = null, Expression<Func> ignoreUpdateColumns = null); #endregion }
⭐ 通用实体
public abstract class Entity { [SugarColumn(IsPrimaryKey = true)] public virtual TKey Id { get; set; } } public abstract class KdbndpEntity : Entity, IPhantomEntity { public KdbndpEntity() { Id = GuidGenerator.Current.Create(); } /// /// 创建时间 /// [SugarColumn(ColumnName = "create_time", IsOnlyIgnoreInsert = true, IsOnlyIgnoreUpdate = true)] public virtual DateTime CreateTime { get; set; } = DateTime.Now; /// /// 更新时间 /// [SugarColumn(ColumnName = "update_time", IsNullable = true, IsOnlyIgnoreInsert = true, IsOnlyIgnoreUpdate = true)] public virtual DateTime? UpdateTime { get; set; } [SugarColumn(ColumnName = SqlSugarConst.PhantomColumnName)] public bool IsDeleted { get; set; } public virtual string GetMainKey() { return this.Id.ToString(); } }
⭐ 用户表实体
/// /// 用户表 /// [SugarTable("sys_user")] public class User : KdbndpEntity { /// /// 用户名 /// public string Username { get; set; } /// /// 昵称 /// public string Nickname { get; set; } /// /// 手机 /// public string Phone { get; set; } /// /// 邮箱 /// public string Email { get; set; } /// /// 密码 /// public string Password { get; set; } }
♉ 各个仓储实现
⭐ 用户表仓储接口(人大金仓)
/// /// 用户表仓储接口 /// public interface IUserRepository : IKdbndpRepository { }
⭐ 用户表仓储(人大金仓)
public class UserRepository : Repository, IUserRepository { public UserRepository(KdbndpOfWork unitOfWork) : base(unitOfWork) { } }
⭐ 用户表仓储接口(达梦)
/// /// 用户表仓储接口 /// public interface IUserRepository : IDMRepository { }
⭐ 用户表仓储(达梦)
public class UserRepository : Repository, IUserRepository { public UserRepository(DMOfWork unitOfWork) : base(unitOfWork) { } }
⭐ 用户表仓储接口(翰高)
/// /// 用户表仓储接口 /// public interface IUserRepository : IHGRepository { }
⭐ 用户表仓储(翰高)
public class UserRepository : Repository, IUserRepository { public UserRepository(HGOfWork unitOfWork) : base(unitOfWork) { } }
⭐ 多表查询
public List Query(UserQuery query) { return Db.Queryable((p, userRole) => p.RoleId == userRole.Id) .WhereIF(!IsNullOrEmpty(query.Username), p => p.Username.Contains(query.Username, StringComparison.OrdinalIgnoreCase)) .WhereIF(!IsNullOrEmpty(query.Nickname), p => p.Nickname == query.Nickname) .WhereIF(!IsNullOrEmpty(query.RoleName), (p, userRole) => userRole.Id.Contains(query.Id, StringComparison.OrdinalIgnoreCase)) .Select((p, unitType) => new UserModel() { Username = p.Username, Nickname = p.Nickname, CreateTime = p.CreateTime, Phone = p.Phone, Password = p.Password, Id = p.Id, RoleId = userRole.Id.ToString(), UpdateTime = p.UpdateTime, Email=p.Email } ).ToList(); }
结语
本文主要介绍了SqlSugar使用工作单元模式操作多个数据库的实战,包括多类型MySQL、人大金仓、达梦等数据,或者同一种数据库的多个库。
文章中出现的任何错误请大家批评指出,一定及时修改。
希望写在这里的小伙伴能给个三连支持!