Dakua快速开发框架开发指南(Dakua.Common.CoreApp v6.2.1)
本文档提供了Dakua快速开发框架的相关开发指南,如有问题请联系:tonydai 13981808031
本框架亮点:
- 基于
.Net 6的跨平台设计(商业应用已长期运行在CentOS及Windows平台下,以及Docker环境中) - 多数据库支持(本系统已长期稳定运行的数据库有
MySQL、SqlServer、Oracle、PostgreSql) - 多数据库ORM支持,数据库操作同时支持
EF、SqlSugar - 同时配套有
Vue2 + Element及Vue3 + TS + ElementPlus的前端框架 - 配套有双前端及后端的全套代码生成器,自动生成适合的前端组件
- 数据查询只需要编写查询DTO,可自动获取对应的查询表达式,不再需要依次手动写Where
- 基础CRUD(包括软删除等)无须手写任何代码,由泛形服务自动实现
- DDD领域驱动设计
- 标准化的DDD分层模式设计
- 全接口化设计,可以自行注入自己的实现类来替换框架功能
- 读写分离设计,CQRS支持 (使用
MediatR) - 标准模块化开发,继承
IAcModule - 微服务/单体服务支持
- 基于动态代理模块实现了微服务和单体服务的无缝开发及切换
- 支持微服务/单服务负载均衡部署
- 自带分布式微服务链路追踪
- 智能动态代理模块
- 基于接口实现了本地服务的API代理
- 基于接口实现了外部服务的HTTP代理
- 开发中可以无差别使用本地服务和外部服务,均是通过接口注入
- 基于NACOS实现配置中心和服务注册中心
UseNacosConfiguration(x => x.AddSerilog(Log.Logger))- 不用NACOS的,可选使用公用appsetting.json文件做为配置中心
UseCommFileConfiguration()
- 不用NACOS的,可选使用公用appsetting.json文件做为配置中心
- 跨服务的消息队列
- 注解化开发
-
NoRepeat实体字段标注:防重复,在新增或修改实体时进行字段的重复检查,会提示并记录重复的Id -
KeyWord实体字段标注:关键字,用于关键字搜索 -
MemberGroup实体字段标注:对属性进行分组,用于分组更新实体指定字段 -
IgnoreMap实体字段标注:映射忽略分组,用于实体映射时要忽略指定的字段 -
DkEncode实体字段标注:数据库实体字段编码/加密,支持三种EncodeType:FuzzySearch、MyBase64、AES -
AutoWired服务层类属性标注:可自动注入非私有属性 -
DbConnectionNameDbContext类标注:以获取对应的数据库连接 -
DynamicService接口层接口标:基于EMIT的动态编译,自动实现接口的本地实现或远程代理实现 -
DisableRoute接口标注:禁止外部请求接口(仅限内部服务使用),不暴露到网络中 -
DistributedCache接口标注:分布式接口缓存,减少数据库查询,建议应用于Query中 -
PreventDuplicateSubmit接口标注:分布式防重复提交,建议应用于UseCase中 -
Signature接口标注:对接口要求签名。可以使用SignatureHelper.SetSignature来为继承自ISignatureDto的类设置签名 -
BasicAuth接口标注:简易身份认证系统,需要标注AllowAnonymous后使用,用于swagger文档等
-
- 工作单元及事务实现
- API分层缓存
- 分布式缓存
- 本地内存缓存
- 多租户支持,继承:
ITenant - 部门支持,继承:
IDepartment - 版本号控制(乐观锁),继承:
IRowVersion - 自动记录历史版本及比较,继承:
IHistoryRecord - 列属性扩展类支持,继承:
IHasExtraProperties - 实体校验支持,继承:
IEntityValidate - 增加,修改,删除相关属性自动记录,继承:
ICreateAggregateRoot,IUpdateAggregateRoot,IFullAuditedAggregateRoot - 基于时间顺序的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,请注意参考Startup和Program的配置
- 引用本领域
-
*.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接口调用
- 全功能restfull接口,GET用于查询,POST用于新增,PUT用于修改,DELETE用于删除
- 除注册和登录接口或其它不需要验证用户权限的接口外,其余全部接口需要将AccessToken加入到Header的Authorization字段中
Bearer {Token}或Query的URL中AccessToken={Token} - 不验证身份权限的接口均需要对参数进行签名操作(接口标注:
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); } } - 数据库操作
- 通过EF操作
- 先注入DbContext,再注入EF泛型仓储(注:6.2.1版本后不需要此操作,会自动注入)
//注入DbContext,推荐使用池化方式,代码参考模板... //------------------------ //Repositories services.AddDefaultRepositories<AdminDbContext>(); //6.2.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(); } - 通过SqlSugar操作
- 先注入DbContext,再注入EF泛型仓储(注:6.2.1版本不需要此操作,引扩Dakua的SqlSugar类库,即可自动注入)
//注入DbContext,推荐使用池化方式,代码参考模板... //------------------------ //SqlSugar services.AddDefaultSqlSugarRepositories<AdminDbContext>(AdminDbContext.ConnectionStringName, SqlSugar.DbType.SqlServer); //6.2.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(); } - 【已弃用】通过Dapper操作
- 先注入DbContext,再注入EF泛型仓储(注:6.2.1版本已弃用Dapper,改用SqlSugar)
//注入DbContext,推荐使用池化方式,代码参考模板... //------------------------ //Repositories services.AddDefaultDapperRepositories<AdminDbContext>();- 使用方法
/// <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); }
- 通过EF操作
-
对象映射,默认为深度复制(如:DTO与实体Entity,支持class到Dictionary映射)
src.MapTo<Destination>(); //直接映射 src.MapTo(entity); //覆盖映射 src.MapTo(entity, new List<string>() { "Name" });//覆盖时忽略指定列 -
使用带时间顺序的GUID生成器
IGuidGenerate guidGenerator //注入IGuidGenerate 即可 -
获取
HttpContextAccessor或HttpContextIHttpContextAccessor 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; } -
内置实体可继承接口功能
-
BaseAggregateRoot、BaseEntity、BaseAggregateRootCross、BaseEntityCross,默认有CreateBy、CreateTime、SetNewId()、GetMemberListByMemberGroup()、GetMemberNameListByMemberGroup() -
ICreateAggregateRoot,默认有CreateBy、CreateTime -
IUpdateAggregateRoot,默认有ICreateAggregateRoot所有属性加上UpdateBy、UpdateTime -
IFullAuditedAggregateRoot,默认有IUpdateAggregateRoot所有属性加上DeleteBy、DeleteTime、IsDelete -
IDepartment用户及部门数据权限支持,默认有UserId、DepartId -
ITenant多租户支持,默认有TenantId -
IRowVersion更新时版本号检查(乐观锁),默认有RowVersion -
IHasExtraProperties扩展列接口标识(将JSON转成Class,限EF),先将子类继承于本接口(子类必须有一个属性为[Key]),再在父类中使用子类为属性类型即可 -
IHistoryRecord自动历史记录,将在历史记录表中创建数据,要在前端查看则需要返回附加数据
-
-
自动注入
- 在属性上标注
[AutoWired]即可,public、internal、protected均可注入,建议使用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>();
- 实体到JSON字符串:
应用程序级消息,参考使用MediatR
-
工作单元提交,基于注解
[UnitOfWork]- 仅能标注到
动态代理的接口和Controller类及方法上 - 如果类上标注了
[UnitOfWork],可以方法上标注[NoUnitOfWork]取消本方法使用工作单元提交
- 仅能标注到
-
认证授权与权限菜单
- 默认所有Api接口均受认证保护,都需要登录后方可访问
- 如部分方法不需要登录访问,请在方法上标注
[AllowAnonymous] - 指定验证权限需要标注
[ApiAuth(ConstAuthPermission.BaseAdmin, RoleAction.Create)],分别为权限字符串及权限动作。 - 权限字符串需要在后台管理中
增加权限菜单,然后通过账号及角色授权到权限。
-
实体校验
- 通用校验,基于
System.ComponentModel.DataAnnotations/// <summary> /// 排序号 /// </summary> [Required] public int IndexNo { get; set; } = 0; /// <summary> /// 扩展信息 /// </summary> [MaxLength(8000)] public string ExtJson { get; set; } - 框架自定义校验,需要继承自
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校验
- 通用校验,基于
System.ComponentModel.DataAnnotations/// <summary> /// 排序号 /// </summary> [Required] public int IndexNo { get; set; } = 0; /// <summary> /// 扩展信息 /// </summary> [MaxLength(8000)] public string ExtJson { get; set; } - 框架自定义校验,需要继承自
IDtoValidate<DtoClassName>public void Validate(ValidateContext<DtoClassName> validateContext) { //请求实体不能为null,否则直接中断校验 validateContext.MustNotNull().IfFailThenExit(); //必须不为string //validateContext.RuleFor(i => i.Province).MustNotEqualTo("string"); }
- 通用校验,基于
-
自动CRUD泛型服务,需要继承自
AutoUseCaseApplicationService和AutoQueryApplicationService、AutoFullQueryApplicationServicepublic 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); -
本地化及多语言支持,依赖注入
IStringLocalizer或IStringLocalizer<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
