3.基础功能配置

3.1 Model实体对象与数据表映射

MyFurion.Model项目中,通过Nuget添加Furion、Furion.Extras.DatabaseAccessor.SqlSugar、Furion.Extras.ObjectMapper.Mapster、SqlSugarCore,同时添加对项目MyFurion.Unility的引用

创建实体基类BaseEntity

using SqlSugar;
using System.Text.Json.Serialization;
using MyFurion.Unility.Const;
namespace MyFurion.Model
    /// <summary>
    /// 实体基类
    /// </summary>
    public class BaseEntity
        /// <summary>
        /// </summary>
        public BaseEntity()
            CreateTime = DateTime.Now;
            IsDeleted = false;
            Id = SnowFlakeSingle.Instance.NextId();
        /// <summary>
        /// id
        /// </summary>
        [SugarColumn(IsPrimaryKey =true,DefaultValue ="主键")]
        public long Id { get; set; }
        /// <summary>
        /// 创建时间
        /// </summary>
        [SugarColumn(IsOnlyIgnoreUpdate = true,ColumnDescription = "创建时间")]
        public DateTime CreateTime { get; set; }
        /// <summary>
        /// 创建人id
        /// </summary>
        [SugarColumn(IsOnlyIgnoreUpdate = true,IsNullable =true, ColumnDescription = "创建人id")]
        [JsonIgnore]
        public string? CreateUserId { get; set; }
        /// <summary>
        /// 创建人
        /// </summary>
        [SugarColumn(IsOnlyIgnoreUpdate = true, IsNullable = true, ColumnDescription = "创建人")]
        [JsonIgnore]
        public string? CreateUser { get; set; }
        /// <summary>
        /// 创建单位id
        /// </summary>
        [SugarColumn(IsOnlyIgnoreUpdate = true, IsNullable = true, ColumnDescription = "创建单位id")]
        [JsonIgnore]
        public string? CreateOrgId { get; set; }
        /// <summary>
        /// 修改时间
        /// </summary>
        [SugarColumn(IsOnlyIgnoreInsert =true,IsNullable =true, ColumnDescription = "修改时间")]
        public DateTime? ModifyTime { get; set; }
        /// <summary>
        /// 修改人id
        /// </summary>
        [SugarColumn(IsOnlyIgnoreInsert = true, IsNullable = true, ColumnDescription = "修改人id")]
        [JsonIgnore]
        public string? ModifyUserId { get; set; }
        /// <summary>
        /// 修改人
        /// </summary>
        [SugarColumn(IsOnlyIgnoreInsert = true, IsNullable = true, ColumnDescription = "修改人")]
        [JsonIgnore]
        public string? ModifyUser { get; set; }
        /// <summary>
        /// 删除标识
        /// </summary>
        [SugarColumn(ColumnDescription ="删除标识")]
        public bool IsDeleted { get; set; }
        /// <summary>
        /// 删除时间
        /// </summary>
        [SugarColumn(IsOnlyIgnoreInsert = true, ColumnDescription = "删除时间",IsNullable =true)]
        [JsonIgnore]
        public DateTime? DeleteTime { get; set; }
        /// <summary>
        /// 删除原因
        /// </summary>
        [SugarColumn(IsOnlyIgnoreInsert = true, ColumnDescription = "删除原因", IsNullable = true)]
        public string? DeleteReason { get; set; }
        /// <summary>
        /// 删除人id
        /// </summary>
        [SugarColumn(IsOnlyIgnoreInsert = true, ColumnDescription = "删除人id", IsNullable = true)]
        [JsonIgnore]
        public string? DeleteUserId { get; set; }
        /// <summary>
        /// 删除人
        /// </summary>
        [SugarColumn(IsOnlyIgnoreInsert = true, ColumnDescription = "删除人", IsNullable = true)]
        [JsonIgnore]
        public string? DeleteUser { get; set; }
        /// <summary>
        /// 排序
        /// </summary>
        [SugarColumn(ColumnDescription ="排序")]
        public int SortNum { get; set; }
        /// <summary>
        /// 备注
        /// </summary>
        [SugarColumn(ColumnDescription = "备注", IsNullable =true,ColumnDataType =CommonConst.DB_STRING_MAX)]
        public string? Remark { get; set; }
        /// <summary>
        /// 多租户ID
        /// </summary>
        [SugarColumn(ColumnDescription = "多租户ID", DefaultValue = "0")]
        [JsonIgnore]
        public long TenantId { get; set; }

3.2 基类仓储及动态Api接口配置

MyFurion.Application项目中,通过Nuget添加SqlSugar.IOC,同时添加对MyFurion.Model项目的引用

新增Dtos、Repository、Controller三个文件夹,分别用于查询条件及输出信息的实体对象、业务代码、Api接口文件

在Dtos文件加下创建PageResult类(分页结果)及PageBaseInput(分页查询条件基类)

namespace MyFurion.Application.Dtos
    /// <summary>
    /// 分页数据信息
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class PageResult<T>
        /// <summary>
        /// 页码
        /// </summary>
        public int PageIndex { get; set; }
        /// <summary>
        /// 分页大小
        /// </summary>
        public int PageSize { get; set; }
        /// <summary>
        /// 页总数
        /// </summary>
        public int TotalPage { get; set; }
        /// <summary>
        /// 记录总数
        /// </summary>
        public int TotalCount { get; set; }
        /// <summary>
        /// 记录集合
        /// </summary>
        public List<T> Items { get; set; } = new();
namespace MyFurion.Application.Dtos
    /// <summary>
    /// 分页查询条件基类
    /// </summary>
    public class PageBaseInput
        /// <summary>
        /// 页码
        /// </summary>
        public int PageIndex { get; set; } = 1;
        /// <summary>
        /// 分页大小
        /// </summary>
        public int PageSize { get; set; } = 20;
        /// <summary>
        /// 开始日期
        /// </summary>
        public DateTime? StartTime { get; set; }
        /// <summary>
        /// 结束日期
        /// </summary>
        public DateTime? EndTime { get; set; }

 创建GlobalUsings.cs全局引用配置类

global using System.Reflection;
global using System.ComponentModel.DataAnnotations;
global using System.Linq.Expressions;
global using Microsoft.AspNetCore.Authorization;
global using Microsoft.AspNetCore.Http;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.CodeAnalysis;
global using Furion;
global using Furion.DataEncryption;
global using Furion.DataValidation;
global using Furion.DependencyInjection;
global using Furion.DynamicApiController;
global using Furion.Extensions;
global using Furion.FriendlyException;
global using Furion.Logging;
global using SqlSugar;
global using Mapster;
global using SqlSugar.IOC;
global using MyFurion.Model;
global using MyFurion.Application.Dtos;

创建仓储基类BaseRepository

namespace MyFurion.Application
    /// <summary>
    /// 仓储基类
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class BaseRepository<T> : SimpleClient<T> where T : BaseEntity, new()
        public ITenant itenant = null;//多租户事务
        public BaseRepository(ISqlSugarClient context = null) : base(context)
            //通过特性拿到ConfigId
            var configId = typeof(T).GetCustomAttribute<TenantAttribute>()?.configId;
            if (configId != null)
                Context = DbScoped.SugarScope.GetConnectionScope(configId);//根据类传入的ConfigId自动选择
                Context = context ?? DbScoped.SugarScope.GetConnectionScope(0);//没有默认db0
            //Context = DbScoped.SugarScope.GetConnectionScopeWithAttr<T>();
            itenant = DbScoped.SugarScope;//设置租户接口
        #region 基础业务
        /// <summary>
        /// 新增
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public async Task<bool> Add(T t)
                int rowsAffect = await Context.Insertable(t).IgnoreColumns(true).ExecuteCommandAsync();
                return rowsAffect > 0;
            catch (Exception ex)
                Log.Error($"新增失败:{ex.Message}");
                return false;
        /// <summary>
        /// 批量新增
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public async Task<bool> Insert(List<T> t)
                int rowsAffect = await Context.Insertable(t).ExecuteCommandAsync();
                return rowsAffect > 0;
            catch (Exception ex)
                Log.Error($"批量新增失败:{ex.Message}");
                return false;
        /// <summary>
        /// 插入设置列数据
        /// </summary>
        /// <param name="parm"></param>
        /// <param name="iClumns"></param>
        /// <param name="ignoreNull"></param>
        /// <returns></returns>
        public async Task<bool> Insert(T parm, Expression<Func<T, object>> iClumns = null, bool ignoreNull = true)
                int rowsAffect = await Context.Insertable(parm).InsertColumns(iClumns).IgnoreColumns(ignoreNullColumn: ignoreNull).ExecuteCommandAsync();
                return rowsAffect > 0;
            catch (Exception ex)
                Log.Error($"插入设置列数据失败:{ex.Message}");
                return false;
        /// <summary>
        /// 更新
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="ignoreNullColumns"></param>
        /// <returns></returns>
        public async Task<bool> Update(T entity, bool ignoreNullColumns = false)
                int rowsAffect = await Context.Updateable(entity).IgnoreColumns(ignoreNullColumns).ExecuteCommandAsync();
                return rowsAffect >= 0;
            catch (Exception ex)
                Log.Error($"更新失败:{ex.Message}");
                return false;
        /// <summary>
        /// 根据实体类更新指定列 eg:Update(dept, it => new { it.Status });只更新Status列,条件是包含
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="expression"></param>
        /// <param name="ignoreAllNull"></param>
        /// <returns></returns>
        public async Task<bool> Update(T entity, Expression<Func<T, object>> expression, bool ignoreAllNull = false)
                int rowsAffect = await Context.Updateable(entity).UpdateColumns(expression).IgnoreColumns(ignoreAllNull).ExecuteCommandAsync();
                return rowsAffect >= 0;
            catch (Exception ex)
                Log.Error($"根据实体类更新指定列失败:{ex.Message}");
                return false;
        /// <summary>
        /// 根据实体类更新指定列 eg:Update(dept, it => new { it.Status }, f => depts.Contains(f.DeptId));只更新Status列,条件是包含
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="expression"></param>
        /// <param name="where"></param>
        /// <returns></returns>
        public async Task<bool> Update(T entity, Expression<Func<T, object>> expression, Expression<Func<T, bool>> where)
                int rowsAffect = await Context.Updateable(entity).UpdateColumns(expression).Where(where).ExecuteCommandAsync();
                return rowsAffect >= 0;
            catch (Exception ex)
                Log.Error($"根据实体类更新指定列失败:{ex.Message}");
                return false;
        /// <summary>
        /// 更新指定列 eg:Update(w => w.NoticeId == model.NoticeId, it => new SysNotice(){ UpdateTime = DateTime.Now, Title = "通知标题" });
        /// </summary>
        /// <param name="where"></param>
        /// <param name="columns"></param>
        /// <returns></returns>
        public async Task<bool> Update(Expression<Func<T, bool>> where, Expression<Func<T, T>> columns)
                int rowsAffect = await Context.Updateable<T>().SetColumns(columns).Where(where).RemoveDataCache().ExecuteCommandAsync();
                return rowsAffect >= 0;
            catch (Exception ex)
                Log.Error($"更新指定列失败:{ex.Message}");
                return false;
        /// <summary>
        /// 事务 eg:var result = UseTran(() =>{SysRoleRepository.UpdateSysRole(sysRole);DeptService.DeleteRoleDeptByRoleId(sysRole.ID);DeptService.InsertRoleDepts(sysRole);});
        /// </summary>
        /// <param name="action"></param>
        /// <returns></returns>
        public bool UseTran(Action action)
                var result = Context.Ado.UseTran(() => action());
                return result.IsSuccess;
            catch (Exception ex)
                Context.Ado.RollbackTran();
                Log.Error($"事务执行失败:{ex.Message}");
                return false;
        /// <summary>
        /// 删除
        /// </summary>
        /// <param name="id">主键id</param>
        /// <param name="IsDelete">是否真删除</param>
        /// <returns></returns>
        public async Task<bool> DeleteById(long id, bool IsDelete = false)
            int rowsAffect = 0;
                if (IsDelete)
                    rowsAffect = await Context.Deleteable<T>().In(id).ExecuteCommandAsync();
                    //假删除 实体属性有isdelete或者isdeleted 请升级到5.0.4.9+,(5.0.4.3存在BUG)
                    rowsAffect = await Context.Deleteable<T>().In(id).IsLogic().ExecuteCommandAsync();
                return rowsAffect > 0;
            catch (Exception ex)
                Log.Error($"删除失败:{ex.Message}");
                return false;
        /// <summary>
        /// 根据查询条件删除
        /// </summary>
        /// <param name="where"></param>
        /// <param name="IsDelete"></param>
        /// <returns></returns>
        public async Task<bool> DeleteByWhere(Expression<Func<T, bool>> where, bool IsDelete = false)
            int rowsAffect = 0;
                if (IsDelete)
                    rowsAffect = await Context.Deleteable<T>().Where(where).ExecuteCommandAsync();
                    //假删除 实体属性有isdelete或者isdeleted 请升级到5.0.4.9+,(5.0.4.3存在BUG)
                    rowsAffect = await Context.Deleteable<T>().Where(where).IsLogic().ExecuteCommandAsync();
                return rowsAffect > 0;
            catch (Exception ex)
                Log.Error($"根据查询条件删除失败:{ex.Message}");
                return false;
        /// <summary>
        /// 根据id获取数据
        /// </summary>
        /// <param name="id">主键值</param>
        /// <returns>泛型实体</returns>
        public async Task<T> GetEntityById(long id)
            return await Context.Queryable<T>().FirstAsync(p => p.Id == id);
        /// <summary>
        /// 数据是否存在
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public async Task<bool> IsExists(Expression<Func<T, bool>> expression)
            return await Context.Queryable<T>().Where(expression).AnyAsync();
        /// <summary>
        /// 获取所有数据
        /// </summary>
        /// <returns></returns>
        public async Task<List<T>> GetAll()
            return await Context.Queryable<T>().ToListAsync();
        /// <summary>
        /// 根据查询条件获取数据
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public async Task<List<T>> GetListByWhere(Expression<Func<T, bool>> expression)
            return await Context.Queryable<T>().Where(expression).ToListAsync();
        /// <summary>
        /// 根据查询条件获取数据(动态表格拼接查询条件)
        /// </summary>
        /// <param name="conditions"></param>
        /// <returns></returns>
        public async Task<List<T>> GetListByWhere(List<IConditionalModel> conditions)
            return await Context.Queryable<T>().Where(conditions).ToListAsync();
        /// <summary>
        /// 根据查询条件获取数据
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="orderFiled">排序字段</param>
        /// <param name="orderEnum">排序方式</param>
        /// <returns></returns>
        public async Task<List<T>> GetList(Expression<Func<T, bool>> expression, Expression<Func<T, object>> orderFiled, OrderByType orderEnum = OrderByType.Desc)
            return await Context.Queryable<T>().Where(expression).OrderByIF(orderEnum == OrderByType.Asc, orderFiled, OrderByType.Asc).OrderByIF(orderEnum == OrderByType.Desc, orderFiled, OrderByType.Desc).ToListAsync();
        /// <summary>
        /// 获取分页数据
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public PageResult<T> GetPageList(Expression<Func<T, bool>> expression, int pageIndex, int pageSize)
            int totalCount = 0;
            var result = Context.Queryable<T>().Where(expression).ToPageList(pageIndex, pageSize, ref totalCount);
            var pageResult = new PageResult<T>();
            pageResult.Items = result;
            pageResult.TotalCount = totalCount;
            pageResult.TotalPage = (int)Math.Ceiling(totalCount / (double)pageSize);
            return pageResult;
        /// <summary>
        /// 获取分页数据
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public async Task<PageResult<T>> GetPageListAsync(Expression<Func<T, bool>> expression, int pageIndex, int pageSize)
            RefAsync<int> totalCount = 0;
            var result = await Context.Queryable<T>().Where(expression).ToPageListAsync(pageIndex, pageSize, totalCount);
            var pageResult = new PageResult<T>();
            pageResult.Items = result;
            pageResult.TotalCount = totalCount;
            pageResult.TotalPage = (int)Math.Ceiling(totalCount / (double)pageSize);
            return pageResult;
        /// <summary>
        /// 获取分页数据
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <param name="orderFiled"></param>
        /// <param name="orderEnum"></param>
        /// <returns></returns>
        public PageResult<T> GetPageList(Expression<Func<T, bool>> expression, int pageIndex, int pageSize, Expression<Func<T, object>> orderFiled, OrderByType orderEnum = OrderByType.Desc)
            int totalCount = 0;
            var result = Context.Queryable<T>().Where(expression).OrderByIF(orderEnum == OrderByType.Asc, orderFiled, OrderByType.Asc).OrderByIF(orderEnum == OrderByType.Desc, orderFiled, OrderByType.Desc)
                .ToPageList(pageIndex, pageSize, ref totalCount);
            var pageResult = new PageResult<T>();
            pageResult.Items = result;
            pageResult.TotalCount = totalCount;
            pageResult.TotalPage = (int)Math.Ceiling(totalCount / (double)pageSize);
            return pageResult;
        /// <summary>
        /// 获取分页数据
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <param name="orderFiled"></param>
        /// <param name="orderEnum"></param>
        /// <returns></returns>
        public async Task<PageResult<T>> GetPageListAsync(Expression<Func<T, bool>> expression, int pageIndex, int pageSize, Expression<Func<T, object>> orderFiled, OrderByType orderEnum = OrderByType.Desc)
            RefAsync<int> totalCount = 0;
            var result = await Context.Queryable<T>().Where(expression).OrderByIF(orderEnum == OrderByType.Asc, orderFiled, OrderByType.Asc).OrderByIF(orderEnum == OrderByType.Desc, orderFiled, OrderByType.Desc)
                .ToPageListAsync(pageIndex, pageSize, totalCount);
            var pageResult = new PageResult<T>();
            pageResult.Items = result;
            pageResult.TotalCount = totalCount;
            pageResult.TotalPage = (int)Math.Ceiling(totalCount / (double)pageSize);
            return pageResult;
        /// <summary>
        /// 获取分页数据
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <param name="orderFiled"></param>
        /// <param name="orderEnum"></param>
        /// <returns></returns>
        public async Task<PageResult<T>> GetOffsetPageListAsync(Expression<Func<T, bool>> expression, int pageIndex, int pageSize, Expression<Func<T, object>> orderFiled, OrderByType orderEnum = OrderByType.Desc)
            RefAsync<int> totalCount = 0;
            var result = await Context.Queryable<T>().Where(expression).OrderByIF(orderEnum == OrderByType.Asc, orderFiled, OrderByType.Asc).OrderByIF(orderEnum == OrderByType.Desc, orderFiled, OrderByType.Desc)
                .ToOffsetPageAsync(pageIndex, pageSize, totalCount);
            var pageResult = new PageResult<T>();
            pageResult.Items = result;
            pageResult.TotalCount = totalCount;
            pageResult.TotalPage = (int)Math.Ceiling(totalCount / (double)pageSize);
            return pageResult;
        #endregion
        #region 海量业务高性能
        /// <summary>
        /// 新增(对于海量数据并且性能要高的)
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public async Task<bool> BulkAdd(T t)
                int rowsAffect = await Context.Storageable(t).ToStorage().BulkCopyAsync();
                return rowsAffect > 0;
            catch (Exception ex)
                Log.Error($"新增失败:{ex.Message}");
                return false;
        /// <summary>
        /// 批量新增(对于海量数据并且性能要高的)
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public async Task<bool> BatchBulkAdd(List<T> t)
                int rowsAffect = await Context.Storageable(t).ToStorage().BulkCopyAsync();
                return rowsAffect > 0;
            catch (Exception ex)
                Log.Error($"批量新增失败:{ex.Message}");
                return false;
        /// <summary>
        /// 更新(对于海量数据并且性能要高的)
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        public async Task<bool> BulkUpdate(T entity)
                int rowsAffect = await Context.Storageable(entity).ToStorage().BulkUpdateAsync();
                return rowsAffect >= 0;
            catch (Exception ex)
                Log.Error($"更新失败:{ex.Message}");
                return false;
        /// <summary>
        /// 批量更新(对于海量数据并且性能要高的)
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public async Task<bool> BatchBulkUpdate(List<T> t)
                Context.QueryFilter = new QueryFilterProvider();//清空过滤器 否则会出现Parameter '@IsDelete0' must be defined错误
                int rowsAffect = await Context.Storageable(t).ToStorage().BulkUpdateAsync();
                return rowsAffect >= 0;
            catch (Exception ex)
                Log.Error($"更新失败:{ex.Message}");
                return false;
        /// <summary>
        /// 批量更新(对于海量数据并且性能要高的)
        /// </summary>
        /// <param name="t"></param>
        /// <param name="updateColumns"></param>
        /// <returns></returns>
        public async Task<bool> BatchBulkUpdate(List<T> t, string[] updateColumns)
                Context.QueryFilter = new QueryFilterProvider();//清空过滤器 否则会出现Parameter '@IsDelete0' must be defined错误
                int rowsAffect = await Context.Storageable(t).ToStorage().BulkUpdateAsync(updateColumns);
                return rowsAffect >= 0;
            catch (Exception ex)
                Log.Error($"更新失败:{ex.Message}");
                return false;
        #endregion
        #region 存储过程
        /// <summary>
        /// 存储过程
        /// </summary>
        /// <param name="procedureName"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public async Task<System.Data.DataTable> ProcedureQuery(string procedureName, object parameters)
            return await Context.Ado.UseStoredProcedure().GetDataTableAsync(procedureName, parameters);
        /// <summary>
        /// 存储过程
        /// </summary>
        /// <param name="procedureName"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public async Task<List<T>> ProcedureQueryList(string procedureName, object parameters)
            return await Context.Ado.UseStoredProcedure().SqlQueryAsync<T>(procedureName, parameters);
        #endregion
        #region Fastest
        /// <summary>
        /// 批量新增
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public async Task<bool> BatchFastestkAdd(List<T> t)
                int rowsAffect = await Context.Fastest<T>().BulkCopyAsync(t);
                return rowsAffect > 0;
            catch (Exception ex)
                Log.Error($"fastest批量新增失败:{ex.Message}");
                return false;
        /// <summary>
        /// 批量更新
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public async Task<bool> BatchFastestUpdate(List<T> t)
                Context.QueryFilter = new QueryFilterProvider();//清空过滤器 否则会出现Parameter '@IsDelete0' must be defined错误
                int rowsAffect = await Context.Fastest<T>().BulkUpdateAsync(t);
                return rowsAffect >= 0;
            catch (Exception ex)
                Log.Error($"fastest批量更新失败:{ex.Message}");
                return false;
        #endregion

 创建applicationsettings.json配置文件,用于配置api接口的风格及分组显示等

"$schema": "https://gitee.com/dotnetchina/Furion/raw/net6/schemas/v3/furion-schema.json", /*swagger文档描述配置*/ "SpecificationDocumentSettings": { "DocumentTitle": "MyFurion | 规范化接口", "DocExpansionState": "None", //文档展开方式 "GroupOpenApiInfos": [ "Group": "Default", "Title": "MyFurion API接口", "Description": "我的Furion", "Version": "1.0.0", "TermsOfService": "", "Contact": { "Name": "Furion", "Url": "", "Email": "" "License": { "Name": "Apache-2.0", "Url": "" /* controller 接口风格设置*/ "DynamicApiControllerSettings": { "KeepName": true, "KeepVerb": true, "LowercaseRoute": false, "AsLowerCamelCase": true, "UrlParameterization": true, "VerbToHttpMethods": [ //[ "getall", "HEAD" ], // => getall 会被复写为 `[HttpHead]` //[ "other", "PUT" ] // => 新增一条新规则,比如,一 `[other]` 开头会转换为 `[HttpPut]` 请求 PolicyName:跨域策略名,string 类型,必填,默认 App.Cors.Policy WithOrigins:允许跨域的域名列表,string[] 类型,默认 * WithHeaders:请求表头,没有配置则允许所有表头,string[] 类型 WithExposedHeaders:设置客户端可获取的响应标头,string[] 类型,默认 ["access-token", "x-access-token"] WithMethods:设置跨域允许请求谓词,没有配置则允许所有,string[] 类型 AllowCredentials:是否允许跨域请求中的凭据,bool 类型,默认值 true SetPreflightMaxAge:设置预检过期时间,int 类型,默认值 24小时 FixedClientToken:是否默认配置 WithExposedHeaders,bool 类型,默认 true SignalRSupport:是否启用 SignalR 跨域支持,bool 类型,默认 false "CorsAccessorSettings": { "SignalRSupport": true, //是否启用 SignalR 跨域支持,bool 类型,默认 false //设置客户端可获取的响应标头,string[] 类型,默认 ["access-token", "x-access-token"] "WithExposedHeaders": [ "access-token", "x-access-token", "environment", "Content-Disposition" ]

3.3 数据库IOC注册

在MyFurion.Start项目中,通过Nuget添加 AspNetCoreRateLimit、System.Linq.Dynamic.Core,同时添加对项目MyFurion.Application的引用

创建GlobalUsings类配置全局引用

global using System.Reflection;
global using System.Linq.Expressions;
global using Microsoft.Extensions.DependencyInjection;
global using Furion;
global using Furion.Logging;
global using SqlSugar;
global using SqlSugar.IOC;
global using System.Linq.Dynamic.Core;
global using MyFurion.Model;
global using MyFurion.Unility.Const;

创建SqlSugarSetup类,实现sqlsugar数据库IOC注册、CodeFirst、全局过滤器等功能的实现

namespace MyFurion.Start
    /// <summary>
    /// sqlsugarIOC注册
    /// </summary>
    public static class SqlSugarSetup
        public static void AddSqlsugarSetup(IServiceCollection services)
            List<IocConfig> iocConfigs = App.GetConfig<List<IocConfig>>("ConnectionConfigs");//获取数据库连接配置
            SugarIocServices.AddSqlSugar(iocConfigs);
            SugarIocServices.ConfigurationSugar(db =>
                foreach (var iocItem in iocConfigs)
                    SqlSugarProvider dbClient = db.GetConnection(iocItem.ConfigId);
                    SetQueryFilter(dbClient);
                    dbClient.Aop.OnLogExecuting = (sql, pars) =>
                        Log.Information(SqlProfiler.ParameterFormat(sql, pars));
                        Console.WriteLine(SqlProfiler.ParameterFormat(sql, pars));
                        Console.WriteLine();
                    var dbtype = dbClient.CurrentConnectionConfig.DbType;
                    dbClient.CurrentConnectionConfig.ConfigureExternalServices = new ConfigureExternalServices()
                        //自定义类型多库兼容
                        EntityService = (c, p) =>
                            if (p.DataType == CommonConst.DB_STRING_MAX)
                                if (dbtype == DbType.MySql)
                                    p.DataType = "longtext";
                                else if (dbtype == DbType.SqlServer)
                                    p.DataType = "nvarchar(max)";
            CreateTable(iocConfigs);
        /// <summary>
        /// 创建数据库表 codefirst
        /// </summary>
        private static void CreateTable(List<IocConfig> iocConfigs)
            foreach (var item in iocConfigs)
                string configId = item.ConfigId;
                ISqlSugarClient db = DbScoped.SugarScope.GetConnectionScope(configId);
                db.DbMaintenance.CreateDatabase();//没有数据库的时候创建数据库
                var tableLists = db.DbMaintenance.GetTableInfoList();
                var files = System.IO.Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "MyFurion.Model.dll");
                if (files.Length > 0)
                    Type[] types = Assembly.LoadFrom(files[0]).GetTypes().Where(it => it.BaseType == typeof(BaseEntity)).ToArray();
                    //Type[] types = Assembly.LoadFrom(files[0]).GetTypes().ToArray();
                    foreach (var entityType in types)
                        //创建数据表
                        string tableName = entityType.GetCustomAttribute<SugarTable>().TableName.ToLower();//根据特性获取表名称
                        var configid = entityType.GetCustomAttribute<TenantAttribute>()?.configId;//根据特性获取租户id
                        configid = configid == null ? "0" : configid.ToString();
                        if (!tableLists.Any(p => p.Name == tableName) && configId == configid.ToString())
                            //创建数据表包括字段更新
                            db.CodeFirst.InitTables(entityType);
                    db.Close();
        /// <summary>
        /// 添加全局过滤器
        /// </summary>
        /// <param name="provider"></param>
        private static void SetQueryFilter(SqlSugarProvider provider)
            //添加全局过滤器
            var files = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "MyFurion.Model.dll");
            if (files.Length > 0)
                Type[] types = Assembly.LoadFrom(files[0]).GetTypes().Where(it => it.BaseType == typeof(BaseEntity)).ToArray();
                foreach (var entityType in types)
                    //string tableName = entityType.GetCustomAttribute<SugarTable>().TableName;//根据特性获取表名称
                    var lambda = DynamicExpressionParser.ParseLambda( new[] { Expression.Parameter(entityType, "it") },typeof(bool), $"{nameof(BaseEntity.IsDeleted)} ==  @0",false);
                    provider.QueryFilter.Add(new TableFilterItem<object>(entityType, lambda, true)); //将Lambda传入过滤器
            //插入/更新过滤器,用于审计日志
            provider.Aop.DataExecuting = (oldValue, entityInfo) =>
                if (entityInfo.OperationType == DataFilterType.InsertByObject)
                    //if (entityInfo.PropertyName == "CreatedUId")
                    //    entityInfo.SetValue(CurrentUserInfo.UId.ToString());//CreatedUId
                    //if (entityInfo.PropertyName == "CreatedUName")
                    //    entityInfo.SetValue(CurrentUserInfo.Name);
                    //if (entityInfo.PropertyName == "CreateOrgId")
                    //    entityInfo.SetValue(CurrentUserInfo.OrgId.ToString());
                    //if (entityInfo.PropertyName == "CreateOrgName")
                    //    entityInfo.SetValue(CurrentUserInfo.OrgName.ToString());
                //update生效        
                if (entityInfo.OperationType == DataFilterType.UpdateByObject)
                    //if (entityInfo.PropertyName == "UpdatedTime")
                    //    entityInfo.SetValue(DateTimeOffset.Now);//修改UpdateTime字段
                    //if (entityInfo.PropertyName == "UpdatedUId")
                    //    entityInfo.SetValue(CurrentUserInfo.UId.ToString());//修改UpdateTime字段
                    //if (entityInfo.PropertyName == "UpdatedUName")
                    //    entityInfo.SetValue(CurrentUserInfo.Name);//修改UpdateTime字段

 3.4 Startup配置

在MyFurion.Start项目中创建Handlers文件夹,然后创建XnRestfulResultProvider类,自定义接口规范化输出数据格式

using Furion.DataValidation;
using Furion.FriendlyException;
using Furion.UnifyResult;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace MyFurion.Start
    /// <summary>
    /// 规范化RESTful风格返回值
    /// </summary>
    [UnifyModel(typeof(XnRestfulResult<>))]
    public class XnRestfulResultProvider : IUnifyResultProvider
        /// <summary>
        /// 异常返回值
        /// </summary>
        /// <param name="context"></param>
        /// <param name="metadata"></param>
        /// <returns></returns>
        public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata)
            return new JsonResult(new XnRestfulResult<object>
                Code = metadata.StatusCode,
                Success = false,
                Data = null,
                Message = context.Exception.Message,// metadata.Errors,
                //Extras = UnifyContext.Take(),
                //Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
        /// <summary>
        /// 成功返回值
        /// </summary>
        /// <param name="context"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public IActionResult OnSucceeded(ActionExecutedContext context, object data)
            return new JsonResult(new XnRestfulResult<object>
                Code = StatusCodes.Status200OK,// context.Result is EmptyResult ? StatusCodes.Status204NoContent : StatusCodes.Status200OK,  // 处理没有返回值情况 204
                Success = true,
                Data = data,
                Message = "请求成功",
                //Extras = UnifyContext.Take(),
                //Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
        /// <summary>
        /// 验证失败返回值
        /// </summary>
        /// <param name="context"></param>
        /// <param name="metadata"></param>
        /// <returns></returns>
        public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata)
            return new JsonResult(new XnRestfulResult<object>
                Code = StatusCodes.Status400BadRequest,
                Success = false,
                Data = null,
                Message = metadata.Message,
                //Extras = UnifyContext.Take(),
                //Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
        /// <summary>
        /// 处理输出状态码
        /// </summary>
        /// <param name="context"></param>
        /// <param name="statusCode"></param>
        /// <param name="unifyResultSettings"></param>
        /// <returns></returns>
        public async Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings)
            // 设置响应状态码
            UnifyContext.SetResponseStatusCodes(context, statusCode, unifyResultSettings);
            switch (statusCode)
                // 处理 401 状态码
                case StatusCodes.Status401Unauthorized:
                    await context.Response.WriteAsJsonAsync(new XnRestfulResult<object>
                        Code = StatusCodes.Status401Unauthorized,
                        Success = false,
                        Data = null,
                        Message = "401 登录已过期,请重新登录",
                        //Extras = UnifyContext.Take(),
                        //Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
                    }, App.GetOptions<JsonOptions>()?.JsonSerializerOptions);
                    break;
                // 处理 403 状态码
                case StatusCodes.Status403Forbidden:
                    await context.Response.WriteAsJsonAsync(new XnRestfulResult<object>
                        Code = StatusCodes.Status403Forbidden,
                        Success = false,
                        Data = null,
                        Message = "403 禁止访问,没有权限",
                        //Extras = UnifyContext.Take(),
                        //Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
                    }, App.GetOptions<JsonOptions>()?.JsonSerializerOptions);
                    break;
                default:
                    break;
    /// <summary>
    /// RESTful风格---XIAONUO返回格式
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class XnRestfulResult<T>
        /// <summary>
        /// 执行成功
        /// </summary>
        public bool Success { get; set; }
        /// <summary>
        /// 状态码
        /// </summary>
        public int? Code { get; set; }
        /// <summary>
        /// 错误信息
        /// </summary>
        public virtual string Message { get; set; } = String.Empty;
        /// <summary>
        /// 数据
        /// </summary>
        public T? Data { get; set; }
        / <summary>
        / 附加数据
        / </summary>
        //public  object Extras { get; set; }
        / <summary>
        / 时间戳
        / </summary>
        //public long Timestamp { get; set; }

在MyFurion.Start项目中创建Startup类,用于Service注册、日志、JSON序列化、Swagger等配置

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace MyFurion.Start
    /// <summary>
    /// </summary>
    public class Startup:AppStartup
        /// <summary>
        /// </summary>
        /// <param name="services"></param>
        public void ConfigureServices(IServiceCollection services)
            services.AddSensitiveDetection();//注册脱敏词汇检测服务
            services.AddControllers().AddNewtonsoftJson();//防止json数据类型转换失败
            services.AddControllers().AddInjectWithUnifyResult<XnRestfulResultProvider>();//规范化输出设置
            services.AddCorsAccessor();//配置跨域                 
            //统一日期类型返回
            services.AddControllersWithViews().AddNewtonsoftJson(options =>
                options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
            services.Configure<KestrelServerOptions>(options =>
                options.Limits.MaxRequestBodySize = int.MaxValue;
            //设置日志
            Array.ForEach(new[] { LogLevel.Information, LogLevel.Error }, logLevel =>
                services.AddFileLogging("Logs/{1}-{0:yyyy}-{0:MM}-{0:dd}-{0:HH}.log", options =>
                    options.FileNameRule = fileName => string.Format(fileName, DateTime.UtcNow, logLevel.ToString());
                    options.WriteFilter = logMsg => logMsg.LogLevel == logLevel;
                    options.Append = true;
                    //options.MessageFormat = (logMsg) =>
                    //    var stringBuilder = new System.Text.StringBuilder();
                    //    stringBuilder.Append(System.DateTime.Now.ToString("o"));
                    //    // 其他的。。。自己组装
                    //    return stringBuilder.ToString();
            SqlSugarSetup.AddSqlsugarSetup(services);        
        /// <summary>
        /// </summary>
        /// <param name="app"></param>
        /// <param name="env"></param>
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            if (env.IsDevelopment())
                app.UseDeveloperExceptionPage();
            //  NGINX 反向代理获取真实IP
            app.UseForwardedHeaders(new ForwardedHeadersOptions
                ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
            app.UseUnifyResultStatusCodes();// 添加状态码拦截中间件 添加规范化结果状态码        
            app.UseHttpsRedirection();// 强制https
            app.UseStaticFiles(); //启用静态文件
            app.UseRouting();
            app.UseCorsAccessor();//跨域中间件
            //开启身份认证
            //app.UseAuthentication();
            //app.UseAuthorization();
            app.UseInject("MyFurion");
            app.UseEndpoints(endpoints =>
                endpoints.MapControllers();

3.5 Swagger配置

 在项目MyFurion.Model、MyFurion.Application、MyFurion.Start三个项目Debug及Release模式下设置api XML文件输出

以MyFurion.Model为配置示例

然后在Startup中的Configure中添加注册Inject 

app.UseInject("MyFurion");//MyFurion swagger文档的路由前缀

配置项目默认启动页为Swagger

MyFurion.WebApi项目中,Properties/launchSettings.json配置文件中,将launchUrl修改为配置的Swagger路由地址 

3.6启动配置

MyFurion.WebApi项目 删除Controllers文件夹及WeatherForecast文件,卸载Nuget中对Swagger的引用,添加对项目MyFurion.Start的引用

Program.cs中的代码改为

Serve.Run(RunOptions.Default);

appSettings.json配置文件内容改为

"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information", "Microsoft.EntityFrameworkCore": "Information" "AllowedHosts": "*", /*数据库连接配置 ConnectionString:连接字符串 DbType:数据库类型 支持MySql = 0,SqlServer = 1,Sqlite = 2,Oracle = 3,PostgreSQL = 4,Dm = 5,Kdbndp = 6,Oscar = 7,MySqlConnector = 8,Access = 9,OpenGauss = 10,Custom = 900 ConfigId:租户id IsAutoCloseConnection:自动释放和关闭数据库连接,如果有事务事务结束时关闭,否则每次操作后关闭 AllowLoadLocalInfile:大数据写入是 mysql数据配置必须 "ConnectionConfigs": [ "ConnectionString": "Data Source=.;User ID=sa;Password=123456;Initial Catalog=MyFurionTest", "DbType": 1, "ConfigId": "0", "IsAutoCloseConnection": true "AppSettings": { "InjectSpecificationDocument": true //如果不需要线上环境开启 Swagger 功能,则设置为false 修改时需要重新发布

4.项目使用示例展示

在Furion.Model中创建Org实体对象,用于验证CodeFirst功能

namespace MyFurion.Model
    /// <summary>
    /// 组织机构信息
    /// </summary>
    [SugarTable("Sys_Org")]
    [Tenant(0)]
    public class OrgInfo:BaseEntity
        /// <summary>
        /// 机构编码
        /// </summary>
        [SugarColumn(IsNullable =true,ColumnDescription ="机构编码")]
        public string? OrgCode { get; set; }
        /// <summary>
        /// 机构名称
        /// </summary>
        [SugarColumn(IsNullable = true, ColumnDescription = "机构名称")]
        public string? OrgName { get; set; }

在MyFurion.Application项目中创建OrgRepository类,用于实现业务代码的仓储类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyFurion.Application
    /// <summary>
    /// 机构服务仓储
    /// </summary>
    public class OrgRepository:BaseRepository<OrgInfo>,ITransient
        //TODO

在MyFurion.Application项目中,创建Controller文件夹,存放接口文件

创建FurionTestController

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyFurion.Application.Controller
    /// <summary>
    /// furionTest
    /// </summary>
    [ApiDescriptionSettings(Name = "FurionTest", Order = 1)]
    [Route("api/furionTest")]
    public class FurionTestController:IDynamicApiController
        private readonly OrgRepository _orgRepository;
        public FurionTestController(OrgRepository orgRepository)
            _orgRepository = orgRepository;
        /// <summary>
        /// furionTestGet
        /// </summary>
        /// <returns></returns>
        [HttpGet("furionHello")]
        public string GetHello()
            return "Hello Furion";
        /// <summary>
        /// post test
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        [HttpPost("testPost")]
        public string TestPost(TestPostData data)
            return "Hello Post";
        /// <summary>
        /// 获取组织机构信息
        /// </summary>
        /// <returns></returns>
        [HttpGet("getOrgList")]
        public async Task<List<OrgInfo>> GetOrgList()
            return await _orgRepository.GetAll();
    public class TestPostData
        public string? DataValue { get; set; }
        public int TestTimes { get; set; }

数据库生成结果

 项目启动页

 最终项目架构

1、根据 Furion 框架搭建的 .net core 5.0 webapi demo 项目 2、demo 包含数据库以及创建的脚本,下载即可运行 3、EF 已经搭建配置好,如果有疑问,可以看我文章教程 4、demo中的Services和 Repository 做依赖注入已经搭建完成 5、EF 的配置 和 Services与Repository 依赖注入以及调用 可以参考
其中SqlSugar,也可以是EFcore,或者Dapper,或者其他ORM框架。其中mysql,也可以是SqlServer,或者oracle,或者其他数据库类型。1.首先使用vs2022建立.net6 web api2.增加SqlSugar和MySQL依赖项。Newtonsoft.Json是序列化 3. 根据官网说明进行注入SqlSugar.IOC/依赖注入 - SqlSugar 5x - .NET果糖网 其中ConnectString就是MySQL数据库的连接字符串 4. 建立实体
Genesys Source Framework is a full-stack .NET solution with Data Tier, Middle Tier and Presentation Tier projects that centralize your business objects in one reusable solution. Your framework C# objects then can be used in any app type that you may need - in a web site, in a web service, in a mobile app and in the database.
框架特色: 1、基于ASP.NET MVC4.0 + WebAPI + EasyUI + Knockout的架构设计开发 2、采用MVC的框架模式,具有耦合性低、重用性高、生命周期成本低、可维护性高、有利软件工程化管理等优点 3、采用WebAPI,客户端完全摆脱了代理和管道来直接进行交互 4、采用EasyUI前台UI界面插件,可轻松的打造出功能丰富并且美观的UI界面 5、采用Knockout,,提供了个数据模型与用户UI界面进行关联的高层次方式(采用行为驱动开发) 6、数据访问层采用强大的Fluentdata完美地支持多数据库操作 7、封装了大部分比较实用的控件和组件,如自动完成控件、弹出控件、拼音模糊输入控件、日期控件、导出组件等
ionic ValidationError: Invalid options object. Sass Loader has been initialized using an options obj 17330