Dakua快速开发框架开发指南(Dakua.Common.CoreApp v6.2.1)

Dakua快速开发框架开发指南(Dakua.Common.CoreApp v6.2.1)

本文档提供了Dakua快速开发框架的相关开发指南,如有问题请联系:tonydai 13981808031

本框架亮点:

  • 基于.Net 6的跨平台设计(商业应用已长期运行在CentOSWindows平台下,以及Docker环境中)
  • 多数据库支持(本系统已长期稳定运行的数据库有MySQLSqlServerOraclePostgreSql
  • 多数据库ORM支持,数据库操作同时支持EFSqlSugar
  • 同时配套有Vue2 + ElementVue3 + TS + ElementPlus的前端框架
  • 配套有双前端及后端的全套代码生成器,自动生成适合的前端组件
  • 数据查询只需要编写查询DTO,可自动获取对应的查询表达式,不再需要依次手动写Where
  • 基础CRUD(包括软删除等)无须手写任何代码,由泛形服务自动实现
  • DDD领域驱动设计
    • 标准化的DDD分层模式设计
  • 全接口化设计,可以自行注入自己的实现类来替换框架功能
  • 读写分离设计,CQRS支持 (使用MediatR)
  • 标准模块化开发,继承IAcModule
  • 微服务/单体服务支持
    • 基于动态代理模块实现了微服务和单体服务的无缝开发及切换
    • 支持微服务/单服务负载均衡部署
    • 自带分布式微服务链路追踪
  • 智能动态代理模块
    • 基于接口实现了本地服务的API代理
    • 基于接口实现了外部服务的HTTP代理
    • 开发中可以无差别使用本地服务和外部服务,均是通过接口注入
  • 基于NACOS实现配置中心和服务注册中心UseNacosConfiguration(x => x.AddSerilog(Log.Logger))
    • 不用NACOS的,可选使用公用appsetting.json文件做为配置中心UseCommFileConfiguration()
  • 跨服务的消息队列
  • 注解化开发
    • NoRepeat实体字段标注:防重复,在新增或修改实体时进行字段的重复检查,会提示并记录重复的Id
    • KeyWord实体字段标注:关键字,用于关键字搜索
    • MemberGroup实体字段标注:对属性进行分组,用于分组更新实体指定字段
    • IgnoreMap实体字段标注:映射忽略分组,用于实体映射时要忽略指定的字段
    • DkEncode实体字段标注:数据库实体字段编码/加密,支持三种EncodeType:FuzzySearchMyBase64AES
    • AutoWired服务层类属性标注:可自动注入非私有属性
    • DbConnectionNameDbContext类标注:以获取对应的数据库连接
    • DynamicService接口层接口标:基于EMIT的动态编译,自动实现接口的本地实现或远程代理实现
    • DisableRoute接口标注:禁止外部请求接口(仅限内部服务使用),不暴露到网络中
    • DistributedCache接口标注:分布式接口缓存,减少数据库查询,建议应用于Query中
    • PreventDuplicateSubmit接口标注:分布式防重复提交,建议应用于UseCase中
    • Signature接口标注:对接口要求签名。可以使用SignatureHelper.SetSignature来为继承自ISignatureDto的类设置签名
    • BasicAuth接口标注:简易身份认证系统,需要标注AllowAnonymous后使用,用于swagger文档等
  • 工作单元及事务实现
  • API分层缓存
    • 分布式缓存
    • 本地内存缓存
  • 多租户支持,继承:ITenant
  • 部门支持,继承:IDepartment
  • 版本号控制(乐观锁),继承:IRowVersion
  • 自动记录历史版本及比较,继承:IHistoryRecord
  • 列属性扩展类支持,继承:IHasExtraProperties
  • 实体校验支持,继承:IEntityValidate
  • 增加,修改,删除相关属性自动记录,继承:ICreateAggregateRootIUpdateAggregateRootIFullAuditedAggregateRoot
  • 基于时间顺序的GUID生成
  • 自动EXCEL导入导出
  • 输出时字典翻译及枚举翻译
  • 全局错误处理
  • 实体映射转换(DTO互转Entity)时可指定忽略属性(用于部分属性输出)
  • HttpApi输出自带Gzip或Br压缩
  • 性能监控(可选开启)
  • 自动属性注入[AutoWired]
  • 自动延迟注入Lazy<T>
  • 基于OAuth2.0的SSO支持,独立的认证中心和登录中心
  • 密码登录使用RSA非对称加密(DES加密(密码+验证码))传输
  • 自定义Swagger文档路径,Swagger文档查看需要Basic认证,接口测试使用了基于OAuth2.0的SSO登录
  • 防AI识别的特殊验证码
  • 支持本地化及多语言

Dakua后台框架开发使用到的开发工具及技术要求

开发工具

  • Visual Studio 2022

后端使用技术

  • .NET 6
  • C# 10

中间件

  • Redis
  • NACOS
  • RabbitMQ
  • SEQ

前端工具

  • Visual Studio Code

前端框架

  • Vue2 + Element
  • Vue3 + TS + ElementPlus

开始一个新项目

安装开发工具

  • 安装VS2022,勾选ASP.NET 和 Web 开发单个组件-.NET 6.0运行时

新建(复制)项目

项目采用DDD(领域驱动)理念设计,通过新建/复制项目模板,得到以下项目结构:

  • *.Application,业务层
    • 引用本领域EntityFrameworkCore
    • 引用本领域.Shared
      用途:本领域的接口实现和服务
  • *.Domain,领域层
    • 引用Dakua.Common.EntityFrameworkCore
    • 引用本领域.Shared
      用途:本领域的Entity及Entity的验证规则,存储设施仓储接口
  • *.EntityFrameWorkCore,基础设施层
    • 引用本领域Domain
      用途:定义本领域的DbContext及仓储接口实现
  • *.HttpApi,API层
    • 引用本领域.Application
    • 引用Dakua.Common.NacosExtensions 【可选】
      用途:本领域的HttpApi,请注意参考StartupProgram的配置
  • *.Shared,其它领域分享层(DTO及接口)
    • 引用Dakua.Common.DynamicService
    • 引用Dakua.Common.CoreApp
      用途:本领域的DTO、接口、枚举、常量定义

开发指南

框架在启动时,会按顺序执行以下操作:

  • 扫描并读取配置信息,视情况扫描配置中心或公共配置文件
  • 配置Seq的Log服务
  • Config Service
  • Config APP
  • 扫描模块IAcModule,并初始化模块
    • 工作单元检查
    • 分级缓存扫描
    • 按排序初始化模块
      • 注入动态代理服务
      • 注入服务实例,需继承ISingletonDependency,IScopedDependency,ITransientDependency
      • 注入EF泛型Repository
      • 注入SqlSugar泛型Repository(如果有)
      • 其它模块初始化
  • 注册服务
  • 配置分级缓存
  • 启动WebHost,监听端口

数据库设计指南

  • 为了兼容其它数据库,方便数据迁移,数据库表名和字段名应该使用全小写+下划线的方式,如:admin_user 或 index_no、user_id等
  • ORM类应该使用驼峰方式,使用注解来映射表名和字段名,如:[Table("kd_admin_user")]来映射AdminUser 或 [Column("kd_index_no")]来映射IndexNo
  • 普通DbContext应该继承自IDbContext
  • 只读DbContext应该继承自IDbContextRead
  • 数据迁移如果不想创建外键约束,请使用[NotMapped],注意:NotMapped后无法Include

二次开发规范

  • 服务之间的调用,引入相应领域的Share项目即可实现基于Http的微服务调用接口实现,引入相应领域的Application项目即可直接接口实现
    服务之间使用HTTP请求接口,接口基于AccessToken授权验证,可以应用于内网或外网访问。

  • 对外restfull接口调用

    1. 全功能restfull接口,GET用于查询,POST用于新增,PUT用于修改,DELETE用于删除
    2. 除注册和登录接口或其它不需要验证用户权限的接口外,其余全部接口需要将AccessToken加入到Header的Authorization字段中Bearer {Token}或Query的URL中AccessToken={Token}
    3. 不验证身份权限的接口均需要对参数进行签名操作(接口标注:Signature),详情参考相关不同语言的签名方法
    • 后端签名方法,使用SignatureHelper.SetSignature来为继承自ISignatureDto的类设置签名

    • 后端JAVA签名方法

    /**
    * 签名生成算法
    * @@param HashMap<string,string>         params 请求参数集,所有参数必须已转换为字符串类型
    * @@param String secret 签名密钥
    * @@return 签名
    * @@throws IOException
    */
    public static String getSignature(HashMap<string,string>
        params, String secret) throws IOException
        {
        // 先将参数以其参数名的字典序升序进行排序
        Map<string, string>
            sortedParams = new TreeMap<string, string>
                (params);
                Set<entry<String, String>> entrys = sortedParams.entrySet();
    
         // 遍历排序后的字典,将所有参数按"key=value"格式拼接在一起
         StringBuilder basestring = new StringBuilder();
         for (Entry<String, String> param : entrys) {
             basestring.append(param.getKey()).append("=").append(param.getValue());
         }
         basestring.Append(",").Append(secret);
    
         // 使用MD5对待签名串求签
         byte[] bytes = null;
         try {
             MessageDigest md5 = MessageDigest.getInstance("MD5");
             bytes = md5.digest(basestring.toString().getBytes("UTF-8"));
         } catch (GeneralSecurityException ex) {
             throw new IOException(ex);
         }
    
         // 将MD5输出的二进制结果转换为小写的十六进制
         StringBuilder sign = new StringBuilder();
         for (int i = 0; i < bytes.length; i++) {
             String hex = Integer.toHexString(bytes[i] & 0xFF);
             if (hex.length() == 1) {
                 sign.append("0");
             }
             sign.append(hex);
         }
         return sign.toString().toUpperCase();
     }
    
    • 前端JS签名方法
    var getSignature = function (myParam, secret) {
    // 对参数名进行字典排序
    var array = new Array();
    for (var key in myParam) {
    array.push(key);
    }
    array.sort();
    // 拼接有序的参数名-值串
    var paramArray = new Array();
    for (var index in array) {
    var key = array[index];
    paramArray.push(key + "=" + myParam[key]);
    }
    paramArray.push("," + secret);
    // MD5签名,并转换成大写
    var shaSource = paramArray.join("");
    var sign = hex_md5(shaSource).toUpperCase();
    return sign;
    }
    //调用方法:
    // 定义申请获得的secret(signKey)
    var secret = "pethome";
    // 创建参数表
    var param = {};
    param["strWhere"] = "1=1";
    param["pageSize"] = "20";
    param["page"] = "1";
    param["orderFld"] = "id";
    param["isDesc"] = "true";
    param["timestamp"] = (new Date().getTime() / 1000).toFixed(0);//Unix时间戳
    var Signature = getSignature(param, secret);
    // 字符串连接示例
    // isDesc=trueorderFld=idpage=1pageSize=20strWhere=1=1timestamp=1450528907,mysecret
    // 签名示例
    // 41C3DEBCB735A4440DBFE2BF6E32BC61
    
  • 开发技术栈

    • 后端开发技术栈
      • 开发语言:C# 10.0
      • 服务注册与发现:Nacos 2.0.3
      • 服务配置:Nacos 2.0.3
      • 数据库ORM:EntityFrameworkCore 6.0.22 及 SqlSugar 5.1.4.102
      • Web框架:.Net 6.0.22
      • 主数据库:SQLServer 2019
      • 缓存服务:Redis 6.2
      • 消息队列:RabbitMQ 3.8
      • 日志服务:Seq 2021.4
      • 任务调度:Hangfire.Core 1.8.5
    • 前端开发技术栈
      • 前端开发框架:Vue3
      • 前端UI库:Element-Plus
      • 前端开发语言:TS

常用功能

  • 改写接口功能
    namespace Dakua.Admin.Application;
    
    /// <summary>
    /// 系统后台工具查询改写
    /// </summary>
    public class SystemToolQueryRewrite : SystemToolQuery
    {
        /**
         * 需要重新注入 ISystemToolQuery
         * services.AddScoped<ISystemToolQuery, SystemToolQueryRewrite>();
         */
    
        public SystemToolQueryRewrite(IServiceProvider sp) : base(sp) { }
    
        public new async ValueTask<ResultEntity<OutUploadFileDto>> FileUpLoad(IFormFile file)
        {
            return await base.FileUpLoad(file);
        }
    }
    
  • 数据库操作
    1. 通过EF操作
      1. 先注入DbContext,再注入EF泛型仓储(注:6.2.1版本后不需要此操作,会自动注入)
          //注入DbContext,推荐使用池化方式,代码参考模板...
          //------------------------
          //Repositories
          services.AddDefaultRepositories<AdminDbContext>();  //6.2.1版本后不需要此操作
      
      1. 使用方法
          //属性注入 + 延迟注入
          protected IEfRepository<Temp_FullAuditTable> TempFullAuditTableRepository => LazyTempFullAuditTableRepository.Value;
          [AutoWired] protected Lazy<IEfRepository<Temp_FullAuditTable>> LazyTempFullAuditTableRepository { get; set; }
      
          public async ValueTask<ResultEntity<bool>> CreateEFAsync(CreateTempFullAuditTableDto input)
          {
              var entity = input.MapTo<Temp_FullAuditTable>();
              var isOk = await TempFullAuditTableRepository.AddAsync(entity, true);
              return isOk.GetResultEntity();
          }
      
          public async ValueTask<ResultEntity<bool>> CreateEFUnitOfWorkAsync(CreateTempFullAuditTableDto input)
          {
              var entity = input.MapTo<Temp_FullAuditTable>();
              var isOk = await TempFullAuditTableRepository.AddAsync(entity);
              return isOk.GetResultEntity();
          }
      
    2. 通过SqlSugar操作
      1. 先注入DbContext,再注入EF泛型仓储(注:6.2.1版本不需要此操作,引扩Dakua的SqlSugar类库,即可自动注入)
          //注入DbContext,推荐使用池化方式,代码参考模板...
          //------------------------
          //SqlSugar
          services.AddDefaultSqlSugarRepositories<AdminDbContext>(AdminDbContext.ConnectionStringName, SqlSugar.DbType.SqlServer);  //6.2.1版本后不需要此操作
      
      1. 使用方法
          //属性注入 + 延迟注入
          protected ISqlSugarRepository<AdminDbContext> SqlSugarTempFullAuditTableRepository => LazySqlSugarTempFullAuditTableRepository.Value;
          [AutoWired] protected Lazy<ISqlSugarRepository<AdminDbContext>> LazySqlSugarTempFullAuditTableRepository { get; set; }
      
          public async ValueTask<ResultEntity<bool>> CreateSqlSugarAsync(CreateTempFullAuditTableDto input)
          {
              var entity = input.MapTo<Temp_FullAuditTable>();
              var isOk = await SqlSugarTempFullAuditTableRepository.AddAsync(entity);
              return isOk.GetResultEntity();
          }
      
    3. 【已弃用】通过Dapper操作
      1. 先注入DbContext,再注入EF泛型仓储(注:6.2.1版本已弃用Dapper,改用SqlSugar)
          //注入DbContext,推荐使用池化方式,代码参考模板...
          //------------------------
          //Repositories
          services.AddDefaultDapperRepositories<AdminDbContext>();
      
      1. 使用方法
          /// <summary>
          /// Admin_Areas构造方法
          /// </summary>
          public AdminAreasUseCase(IDapperRepository<AdminDbContext> commonDapperRepository) : base(repository)
          {
              _commonDapperRepository = commonDapperRepository;
          }
      
          /// <summary>
          /// 新增
          /// </summary>
          /// <param name="input">dto</param>
          /// <returns></returns>
          public async ValueTask<ResultEntity<bool>> CreateAsync(CreateAdminAreasDto input)
          {
              var entity = input.MapTo<Admin_Areas>();
              await _commonDapperRepository.InsertAsync(entity).ConfigureAwait(false);    //Dapper无法使用工作单元
              return GetResultEntity(true);
          }
      
  • 对象映射,默认为深度复制(如:DTO与实体Entity,支持class到Dictionary映射)

    src.MapTo<Destination>(); //直接映射
    src.MapTo(entity); //覆盖映射
    src.MapTo(entity, new List<string>() { "Name" });//覆盖时忽略指定列
    
  • 使用带时间顺序的GUID生成器

    IGuidGenerate guidGenerator //注入IGuidGenerate 即可
    
  • 获取HttpContextAccessorHttpContext

    IHttpContextAccessor httpContextAccessor //注入IHttpContextAccessor
    httpContextAccessor.HttpContext //获取HttpContext
    
  • 通过HTTP访问外部服务(如:上报数据)

    IDakuaHttpClient dakuaHttpClient //注入IDakuaHttpClient
    dakuaHttpClient.GetClient()     //获取HttpClient
    dakuaHttpClient.PostData<T>(...)    //post json
    dakuaHttpClient.GetData<T>(...)     //get url
    dakuaHttpClient.PostFormData<T>(...) //post form
    dakuaHttpClient.RequestData<T>(...)  //底层方法
    
  • DTO输出时的加密处理,使用[JsonConverter(typeof(JsonEncryptConverter))]标注

        /// <summary>
        /// 输出加密用户信息
        /// </summary>
        [JsonConverter(typeof(JsonEncryptConverter))]
        public string UserInfo { get; set; }
    
  • DTO输出时的隐藏机密信息,使用[JsonConverter(typeof(JsonHideConverter)...]标注

        /// <summary>
        /// 输出从第3位开始隐藏4位的手机号
        /// </summary>
        [JsonConverter(typeof(JsonHideConverter), 3, 4)]
        public string Phone { get; set; }
    
  • DTO输出时的枚举翻译,使用枚举字段.GetDescription()即可

        /// <summary>
        /// 字典类型
        /// </summary>
        public DictionaryTypeEnum DictionaryType { get; set; } = 0;
        /// <summary>
        /// 字典类型文本
        /// </summary>
        public string DictionaryTypeTxt => DictionaryType.GetDescription();//枚举转换
    
  • DTO输出时的字典翻译,使用字典Code字段.GetDictNameByCode(字典编码)即可

        /// <summary>
        /// Code
        /// </summary>
        [MaxLength(20)]
        [NoRepeat]
        public string Code { get; set; }
        /// <summary>
        /// Gets the code text.
        /// </summary>
        public string CodeTxt => Code.GetDictNameByCode("DictCode");//字典转换
    
  • DTO输出时不输出指定的属性(输出null),只需要在MapTo时指定忽略的列属性即可,或者标注[JsonIgnore]

        entity.MapTo<TOutputDto>(q => new { q.Id });//不转换Id
        //批量转换
        //dataList.MapTo<List<TOutputDto>>();
        dataList.Select(q => q.MapTo<TOutputDto>(w => w.Id)).ToList();        
    
  • 实体属性JSON内容转CLASS功能

    • 定义一个扩展子类,继承自IHasExtraProperties,必须有一个[Key]注解
      /// <summary>
      /// 测试Json子类
      /// </summary>
      public class AdminAreasExtJson: IHasExtraProperties
      {
          [Key]
          public bool IsOK { get; set; } = true;
          public string Tips { get; set; } = "";
      }
      
    • 在父类中使用该扩展类为属性类型
      public AdminAreasExtJson ExtJson { get; set; }
      
  • 内置控制器/动态代理接口注解功能

    [PreventDuplicateSubmit]        //防止一段时间内的重复提交
    [DynamicService(ConstAuthPermission.ServiceName)]   //指定动态代理服务
    [ApiExplorer("AdminManage")]    //指定Swagger分组
    [UnitOfWork]    //指定使用工作单元包裹方法
    public interface ITempFullAuditTableUseCase : IDynamicService
    {
        /// <summary>
        /// 新增
        /// </summary>
        /// <param name="input">dto</param>
        /// <returns></returns>
        [HttpPost]
        [Route("/api/biz/TempFullAuditTable")]
        [ApiAuth(ConstAuthPermission.BaseAdmin, RoleAction.Create)]
        ValueTask<ResultEntity<bool>> CreateAsync(CreateTempFullAuditTableDto input);
    }
    
  • 内置实体注解实体功能

        /// <summary>
        /// 编码
        /// </summary>
        [MaxLength(50)]     //实体验证配置
        [Required]          //不能为null
        [KeyWord]           //关键字搜索
        [NoRepeat("编码")]    //不允许重复,提示名称为‘编码’
        [MemberGroup("Update|ABC")]//指定可以通过update和ABC来进行分组检索字段
        [IgnoreMap("Out|MiniOut")]//指定可以通过Out和MiniOut来忽略MapTo时的字段映射
        public string DictionaryCode { get; set; }
    
  • 内置实体可继承接口功能

    • BaseAggregateRootBaseEntityBaseAggregateRootCrossBaseEntityCross,默认有CreateByCreateTimeSetNewId()GetMemberListByMemberGroup()GetMemberNameListByMemberGroup()
    • ICreateAggregateRoot,默认有CreateByCreateTime
    • IUpdateAggregateRoot,默认有ICreateAggregateRoot所有属性加上UpdateByUpdateTime
    • IFullAuditedAggregateRoot,默认有IUpdateAggregateRoot所有属性加上DeleteByDeleteTimeIsDelete
    • IDepartment用户及部门数据权限支持,默认有UserIdDepartId
    • ITenant多租户支持,默认有TenantId
    • IRowVersion更新时版本号检查(乐观锁),默认有RowVersion
    • IHasExtraProperties扩展列接口标识(将JSON转成Class,限EF),先将子类继承于本接口(子类必须有一个属性为[Key]),再在父类中使用子类为属性类型即可
    • IHistoryRecord自动历史记录,将在历史记录表中创建数据,要在前端查看则需要返回附加数据
  • 自动注入

    • 在属性上标注[AutoWired]即可,publicinternalprotected均可注入,建议使用internal
    • 注意:仅会注入在初始化方法后仍然未初始化的属性,已经被初始化方法赋值为非null的属性不会再次被注入
  • 延迟注入

    • 注入时使用Lazy<T>即可
        [AutoWired] protected Lazy<SystemPermissionQuery> LazySystemPermissionQuery { get; set; }
        protected SystemPermissionQuery SystemPermissionQuery => LazySystemPermissionQuery.Value;
    
        [AutoWired] protected Lazy<SystemPermissionUseCase> LazySystemPermissionUseCase { get; set; }
        private SystemPermissionUseCase SystemPermissionUseCase => LazySystemPermissionUseCase.Value;
    
  • 数据库内容加密(仅适用于string类型,目前仅自动处理EF的新增/修改/查询),EncodeType支持三种:FuzzySearch自动模糊搜索,MyBase64乱序Base64,AES对称加密

    • 在实体的属性上标注[DkEncode]即可
        [DkEncode(encodeType)]
        public string Name { get; set; }
    
  • 缓存使用

    //分布式缓存(Redis)
    protected IRedisHelper RedisHelper
        
    //内存缓存
    protected IMemoryCache MemoryCache
    
  • 分布式消息队列,需要引用Nuget包Dakua.Common.CAP,然后注入ICAPHelper capHelper后使用

    //发送消息队列
     await _capHelper.RecordAndPublishMQ<AdminDbContext>(sendDto.MqEvent, sendDto.DataJson, sendDto.BizNo, (dataId) => new Dictionary<string, string>()
     {
      //MQRecordId
      ["SendMqId"] = dataId.ToString(),
      //失败重试次数(通过定时服务)
      ["FailedRetryCount"] = "2",
      //失败重试间隔(分)
      ["FailedRetryInterval"] = "5",
     });
    
    /// <summary>
    /// 分布式消息处理服务
    /// </summary>
    public class MyCapService : ICapSubscribe
    {
        private readonly ICAPHelper _capHelper;
    
        /// <summary>
        /// Initializes a new instance of the <see cref="MyCapService"/> class.
        /// </summary>
        /// <param name="capHelper">The cap helper.</param>
        public MyCapService(ICAPHelper capHelper)
        {
            _capHelper = capHelper;
        }
    
        /// <summary>
        /// 接收指定为[mqEvent]的消息,并处理
        /// </summary>
        /// <param name="eventData">The event data.</param>
        [CapSubscribe("mqEvent")]
        public async void MyEventTodo(CapEventData<object> eventData)
        {
            await _capHelper.UpdateAndRunMq<AdminDbContext>(eventData, async (record) =>
            {
                //业务处理
                var isOk = await todoBiz(record);
                return new CapActionResult() { IsOK = isOk, Msg = isOk ? "执行成功" : "执行失败" };
            });
        }
    }
    
  • JSON序列化,使用Newtonsoft.Json,需要:using Dakua.Common;

    • 实体到JSON字符串:class.ToJsonString()
    • JSON字符串到实体类:"jsonString".ToClassByJson<class>();
  • 应用程序级消息,参考使用MediatR

  • 工作单元提交,基于注解[UnitOfWork]

    • 仅能标注到动态代理的接口Controller类及方法上
    • 如果类上标注了[UnitOfWork],可以方法上标注[NoUnitOfWork]取消本方法使用工作单元提交
  • 认证授权与权限菜单

    • 默认所有Api接口均受认证保护,都需要登录后方可访问
    • 如部分方法不需要登录访问,请在方法上标注[AllowAnonymous]
    • 指定验证权限需要标注[ApiAuth(ConstAuthPermission.BaseAdmin, RoleAction.Create)],分别为权限字符串及权限动作。
    • 权限字符串需要在后台管理中增加权限菜单,然后通过账号及角色授权到权限
  • 实体校验

    1. 通用校验,基于System.ComponentModel.DataAnnotations
      /// <summary>
      /// 排序号
      /// </summary>
      [Required]
      public int IndexNo { get; set; } = 0;
      /// <summary>
      /// 扩展信息
      /// </summary>
      [MaxLength(8000)]
      public string ExtJson { get; set; }
      
    2. 框架自定义校验,需要继承自IEntityValidate<EntityClassName>
      //实体验证
      public void ValidateAdd(ValidateContext<EntityClassName> validateContext)
      {
          ValidateCommon(validateContext, true);
      }
      
      public void ValidateUpdate(ValidateContext<EntityClassName> validateContext)
      {
          ValidateCommon(validateContext);
      }
      
      private static void ValidateCommon(ValidateContext<EntityClassName> validateContext, bool isAdd = false)
      {
          //请求实体不能为null,否则直接中断校验
          validateContext.MustNotNull().IfFailThenExit();
          //必须不为0
          //validateContext.RuleFor(i => i.Level).MustNotEqualTo(0);
          //不能为空且长度在1-4之间
          //validateContext.RuleFor(i => i.FullName).MustNotNullOrEmptyOrWhiteSpace().MustLengthInRange(2, 20);
      }
      
  • DTO校验

    1. 通用校验,基于System.ComponentModel.DataAnnotations
      /// <summary>
      /// 排序号
      /// </summary>
      [Required]
      public int IndexNo { get; set; } = 0;
      /// <summary>
      /// 扩展信息
      /// </summary>
      [MaxLength(8000)]
      public string ExtJson { get; set; }
      
    2. 框架自定义校验,需要继承自IDtoValidate<DtoClassName>
      public void Validate(ValidateContext<DtoClassName> validateContext)
      {
          //请求实体不能为null,否则直接中断校验
          validateContext.MustNotNull().IfFailThenExit();
          //必须不为string
          //validateContext.RuleFor(i => i.Province).MustNotEqualTo("string");
      }
      
  • 自动CRUD泛型服务,需要继承自AutoUseCaseApplicationServiceAutoQueryApplicationServiceAutoFullQueryApplicationService

    public virtual async ValueTask<ResultEntity<bool>> CreateAsync(TCreateInput input);
    
    public virtual async ValueTask<ResultEntity<bool>> UpdateAsync(TUpdateInput input);
    
    public virtual async ValueTask<ResultEntity<bool>> DeleteAsync(Guid id);
    
    public virtual async ValueTask<ResultEntity<bool>> DeleteBatchAsync(string ids);
    
     /// <summary>
     /// 获取单个
     /// </summary>
     /// <param name="id">Id</param>
     /// <returns></returns>
     public async ValueTask<ResultEntity<TGetOutputDto>> GetAsync(Guid id);
    
     /// <summary>
     /// 获取翻页列表
     /// </summary>
     /// <param name="input">翻页数据</param>
     /// <returns></returns>
     public async ValueTask<ResultEntity<List<TGetListOutputDto>>> GetListAsync(TGetListInput input);
    
     /// <summary>
     /// 回收站(AutoFullQueryApplicationService 专有)
     /// </summary>
     /// <param name="input">翻页数据</param>
     /// <returns></returns>
     public async ValueTask<ResultEntity<List<TGetListOutputDto>>> GetListRecycleAsync(TGetListInput input);
    
  • 本地化及多语言支持,依赖注入IStringLocalizerIStringLocalizer<Class>即可,Class最好标注LocalizationResourceName来指定查找路径

    //service中配置
    services.AddJsonLocalization(options => options.ResourcesPath = "Resources"); //在/Resources目录中配置语言
    
    //Configure中配置支持的多语言版本(建议从配置文件读取)
    app.UseRequestLocalization("en-US", "fr-FR");
    
    
    //MVC的view使用需要配置
        services.AddMvc()
                .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
                .AddDataAnnotationsLocalization(options =>
                {
                    options.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(RegisterViewModel));
                });
    /***
    * 使用:
    * 1、在Resources目录中给出对应的语言,如en.json的内容为{ "你好":"Hello","操作异常,请稍候再试": "Abnormal operation, please try again later",... }
    * 2、注入IStringLocalizer或者继承自BaseApplicationService后使用L["你好"]
    ***/
    
  • 基础服务继承可使用的属性,需要继承自BaseApplicationService或他的子类

    
    //多语言支持,如:L["你好"]
    protected IStringLocalizer L;
    
    //时间顺序GUID工具类
    protected IGuidGenerate GuidGenerator;
    
    //当前认证账号
    protected ICurrentIdentifyUser CurrentUser;
    
    //HttpContextAccessor 
    protected IHttpContextAccessor HttpContextAccessor;
    
    //Redis工具类
    protected IRedisHelper RedisHelper;
    
    //Excel工具类
    protected IExcelHelper ExcelHelper;
    
    //Mediator
    protected IMediator Mediator;
    
    //字典转换类
    protected ITranslateDict TranslateDict;
    
    //全局配置类
    protected IGlobalConfig GlobalConfig;
    
    //用户工具类
    protected IAdminUserHelper AdminUserHelper;
    
    //租户工具类
    protected ITenantHelper TenantHelper;
    
    //Entity工具类
    protected IEntityHelper EntityHelper;
    
    //当前用户Id
    protected Guid CurrentUserId => CurrentUser.Id;
    
    //当前用户信息
    protected RedisAdminUserDto CurrentAdminUser;
    
    //当前用户是否超级管理员
    protected bool IsSuperAdmin;
    
    //当前租户Id
    protected Guid CurrentTenantId;
    
    //当前租户信息
    protected AdminTenantDto CurrentTenant;
    
    //当前用户权限部门(不包括子部门,仅包括选择部门)
    protected List<AdminUserDepartment> CurrentUserDepartments;
    
    //当前用户登录选择的权限部门(包括子部门继承)
    protected List<AdminUserDepartment> CurrentUserAllDepartments;
    
    //执行事务提交,包含仓储未保存的修改
    protected async ValueTask<int> CommitRepositoryAsync(params IEFCoreRepository[] repositories);
    
    //执行事务提交,包含仓储未保存的修改,同时执行SQL
    protected async ValueTask<int> CommitRepositoryAsync(List<CommitSQLDto> commitSqlList, params IEFCoreRepository[] repositories);
    
    // 雪花Id
    protected long NewSnowFlakeID();
    
    // 时间顺序的GUID
    protected Guid NewTimeGuid();
    
        /// <summary>
        /// 获取模糊匹配查询表达式(根据查询DTO)
        /// </summary>
        /// <typeparam name="TDto">The type of the dto.</typeparam>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="dto">查询dto</param>
        /// <param name="exp">现有需要附加的表达式</param>
        /// <param name="noExp">不处理的字段表达式,如:q => new { q.Name }</param>
        /// <param name="isAnd">表达式之间是否使用And连接,为false时将使用Or连接</param>
        /// <returns></returns>
        public static Expression<Func<TEntity, bool>> GetExpressionByQueryDto<TDto, TEntity>(TDto dto
            , Expression<Func<TEntity, bool>> exp = null
            , Expression<Func<TDto, object>> noExp = null
            , bool isAnd = true)
            where TEntity : class, new() 
            where TDto : class, new()
            => GetExpressionEqualOrContainsByQueryDto(dto, exp, noExp, isAnd);
    
        /// <summary>
        /// 获取精确匹配查询表达式(根据查询DTO)
        /// </summary>
        /// <typeparam name="TDto">The type of the dto.</typeparam>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="dto">查询dto</param>
        /// <param name="exp">现有需要附加的表达式</param>
        /// <param name="noExp">不处理的字段表达式,如:q => new { q.Name }</param>
        /// <param name="isAnd">表达式之间是否使用And连接,为false时将使用Or连接</param>
        /// <returns></returns>
        public static Expression<Func<TEntity, bool>> GetExpressionEqualByQueryDto<TDto, TEntity>(TDto dto
            , Expression<Func<TEntity, bool>> exp = null
            , Expression<Func<TDto, object>> noExp = null
            , bool isAnd = true)
            where TEntity : class, new() 
            where TDto : class, new()
            => GetExpressionEqualOrContainsByQueryDto(dto, exp, noExp, isAnd, false);
    

常见问题:

  • SqlServer的QueryDto中的日期查询发生错误, EF获取翻页列表发生错误:从字符串转换日期和/或时间时,转换失败。
    原因是SqlServer中的日期字段类型为datetime,修改为datetime2即可

last update 2023-11-10, by www.dakua.net

dakua.net

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容