😎同步开源代码仓库

This commit is contained in:
zuohuaijun 2024-11-25 16:27:14 +08:00
parent f75abc9d4a
commit 369bb601b5
265 changed files with 5213 additions and 7741 deletions

View File

@ -47,7 +47,7 @@
<PackageReference Include="SSH.NET" Version="2024.2.0" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.4.9" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1127" />
<PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1128" />
<PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
</ItemGroup>

View File

@ -44,12 +44,12 @@ public class DictAttribute : ValidationAttribute, ITransient
var dictDataList = sysDictDataServiceProvider.GetDataList(DictTypeCode).Result;
// 使用HashSet来提高查找效率
var dictCodes = new HashSet<string>(dictDataList.Select(u => u.Code));
var valueList = (value?.GetType().IsEnum ?? DictTypeCode.EndsWith("Enum")) ? dictDataList.Select(u => u.Name) : dictDataList.Select(u => u.Code);
var dictHash = new HashSet<string>(valueList);
if (!dictCodes.Contains(valueAsString))
if (!dictHash.Contains(valueAsString))
return new ValidationResult($"提示:{ErrorMessage}|字典【{DictTypeCode}】不包含【{valueAsString}】!");
else
return ValidationResult.Success;
return ValidationResult.Success;
}
/// <summary>

View File

@ -11,6 +11,11 @@ namespace Admin.NET.Core;
/// </summary>
public class ClaimConst
{
/// <summary>
/// 应用Id
/// </summary>
public const string AppId = "AppId";
/// <summary>
/// 用户Id
/// </summary>

View File

@ -71,6 +71,11 @@ public class ConfigConst
/// </summary>
public const string SysDomainLogin = "sys_domain_login";
/// <summary>
/// 租户域名隔离登录验证
/// </summary>
public const string SysTenantHostLogin = "sys_tenant_host_login";
/// <summary>
/// 数据校验日志
/// </summary>

View File

@ -26,8 +26,18 @@ public class SqlSugarConst
/// </summary>
public const string PrimaryKey = "Id";
/// <summary>
/// 默认应用Id
/// </summary>
public const long DefaultAppId = 1300000000001;
/// <summary>
/// 默认租户Id
/// </summary>
public const long DefaultTenantId = 1300000000001;
/// <summary>
/// 默认租户域名
/// </summary>
public const string DefaultTenantHost = "gitee.com";
}

View File

@ -0,0 +1,82 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 系统应用表
/// </summary>
[SysTable]
[SugarTable(null, "系统应用表")]
public partial class SysApp : EntityBase
{
/// <summary>
/// 名称
/// </summary>
[SugarColumn(ColumnDescription = "名称", Length = 32), Required, MaxLength(32)]
public virtual string Name { get; set; }
/// <summary>
/// 图标
/// </summary>
[SugarColumn(ColumnDescription = "图标", Length = 256), Required, MaxLength(256)]
public virtual string? Logo { get; set; }
/// <summary>
/// 标题
/// </summary>
[SugarColumn(ColumnDescription = "标题", Length = 32), MaxLength(32)]
public string Title { get; set; }
/// <summary>
/// 副标题
/// </summary>
[SugarColumn(ColumnDescription = "副标题", Length = 32), MaxLength(32)]
public string ViceTitle { get; set; }
/// <summary>
/// 副描述
/// </summary>
[SugarColumn(ColumnDescription = "副描述", Length = 64), MaxLength(64)]
public string? ViceDesc { get; set; }
/// <summary>
/// 水印
/// </summary>
[SugarColumn(ColumnDescription = "水印", Length = 32), MaxLength(32)]
public string? Watermark { get; set; }
/// <summary>
/// 版权信息
/// </summary>
[SugarColumn(ColumnDescription = "版权信息", Length = 64), MaxLength(64)]
public string? Copyright { get; set; }
/// <summary>
/// ICP备案号
/// </summary>
[SugarColumn(ColumnDescription = "ICP备案号", Length = 32), MaxLength(32)]
public string? Icp { get; set; }
/// <summary>
/// 排序
/// </summary>
[IgnoreUpdateSeedColumn]
[SugarColumn(ColumnDescription = "排序")]
public int OrderNo { get; set; } = 100;
/// <summary>
/// 备注
/// </summary>
[SugarColumn(ColumnDescription = "备注", Length = 256), MaxLength(256)]
public string? Remark { get; set; }
/// <summary>
/// 应用租户
/// </summary>
[Navigate(NavigateType.OneToMany, nameof(SysTenant.AppId))]
public List<SysTenant> TenantList { get; set; }
}

View File

@ -0,0 +1,41 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 系统应用菜单表
/// </summary>
[SugarTable(null, "系统应用菜单表")]
[SysTable]
public partial class SysAppMenu : EntityBaseId
{
/// <summary>
/// 应用Id
/// </summary>
[SugarColumn(ColumnDescription = "应用Id")]
public long AppId { get; set; }
/// <summary>
/// 应用
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[Navigate(NavigateType.OneToOne, nameof(AppId))]
public SysApp SysApp { get; set; }
/// <summary>
/// 菜单Id
/// </summary>
[SugarColumn(ColumnDescription = "菜单Id")]
public long MenuId { get; set; }
/// <summary>
/// 菜单
/// </summary>
[Navigate(NavigateType.OneToOne, nameof(MenuId))]
public SysMenu SysMenu { get; set; }
}

View File

@ -85,6 +85,13 @@ public partial class SysCodeGen : EntityBase
[MaxLength(128)]
public string? BusName { get; set; }
/// <summary>
/// 表唯一字段配置
/// </summary>
[SugarColumn(ColumnDescription = "表唯一字段配置", Length = 128)]
[MaxLength(128)]
public string? TableUniqueConfig { get; set; }
/// <summary>
/// 是否生成菜单
/// </summary>
@ -134,4 +141,10 @@ public partial class SysCodeGen : EntityBase
/// </summary>
[Navigate(NavigateType.OneToMany, nameof(SysCodeGenTemplateRelation.CodeGenId))]
public List<SysCodeGenTemplateRelation> CodeGenTemplateRelations { get; set; }
/// <summary>
/// 表唯一字段列表
/// </summary>
[SugarColumn(IsIgnore = true)]
public virtual List<TableUniqueConfigItem> TableUniqueList => string.IsNullOrWhiteSpace(TableUniqueConfig) ? null : JSON.Deserialize<List<TableUniqueConfigItem>>(TableUniqueConfig);
}

View File

@ -130,5 +130,5 @@ public partial class SysMenu : EntityBase
/// 菜单子项
/// </summary>
[SugarColumn(IsIgnore = true)]
public List<SysMenu> Children { get; set; } = new List<SysMenu>();
public List<SysMenu> Children { get; set; } = new ();
}

View File

@ -13,6 +13,20 @@ namespace Admin.NET.Core;
[SysTable]
public partial class SysTenant : EntityBase
{
/// <summary>
/// 应用Id
/// </summary>
[SugarColumn(ColumnDescription = "应用Id", DefaultValue = "1300000000001")]
public long? AppId { get; set; }
/// <summary>
/// 应用
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[Navigate(NavigateType.OneToOne, nameof(AppId))]
public SysApp SysApp { get; set; }
/// <summary>
/// 用户Id
/// </summary>
@ -26,9 +40,9 @@ public partial class SysTenant : EntityBase
public long OrgId { get; set; }
/// <summary>
/// 主机
/// 域名
/// </summary>
[SugarColumn(ColumnDescription = "主机", Length = 128)]
[SugarColumn(ColumnDescription = "域名", Length = 128)]
[MaxLength(128)]
public string? Host { get; set; }

View File

@ -30,6 +30,12 @@ public class SysUserLdap : EntityTenant
[Required]
public string Account { get; set; }
/// <summary>
/// 域用户名
/// </summary>
[SugarColumn(ColumnDescription = "域用户名", Length = 32)]
public string UserName { get; set; }
/// <summary>
/// 对应EmployeeId(用于数据导入对照)
/// </summary>
@ -41,4 +47,34 @@ public class SysUserLdap : EntityTenant
/// </summary>
[SugarColumn(ColumnDescription = "组织代码", Length = 64)]
public string? DeptCode { get; set; }
/// <summary>
/// 最后设置密码时间
/// </summary>
[SugarColumn(ColumnDescription = "最后设置密码时间")]
public DateTime? PwdLastSetTime { get; set; }
/// <summary>
/// 邮箱
/// </summary>
[SugarColumn(ColumnDescription = "组织代码", Length = 64)]
public string? Mail { get; set; }
/// <summary>
/// 检查账户是否已过期
/// </summary>
[SugarColumn(ColumnDescription = "检查账户是否已过期")]
public bool AccountExpiresFlag { get; set; } = false;
/// <summary>
/// 密码设置是否永不过期
/// </summary>
[SugarColumn(ColumnDescription = "密码设置是否永不过期")]
public bool DontExpiresFlag { get; set; } = false;
/// <summary>
/// DN
/// </summary>
[SugarColumn(ColumnDescription = "DN", Length = 512)]
public string Dn { get; set; }
}

View File

@ -15,7 +15,7 @@ public enum CultureLevelEnum
/// <summary>
/// 其他
/// </summary>
[Description("其他")]
[Description("其他"), Theme("info")]
Level0 = 0,
/// <summary>

View File

@ -15,7 +15,7 @@ public enum DataOpTypeEnum
/// <summary>
/// 其它
/// </summary>
[Description("其它")]
[Description("其它"), Theme("info")]
Other,
/// <summary>

View File

@ -223,6 +223,12 @@ public enum ErrorCodeEnum
[ErrorCodeItemMetadata("开放接口绑定租户禁止删除")]
D1031,
/// <summary>
/// 手机号已存在
/// </summary>
[ErrorCodeItemMetadata("手机号已存在")]
D1032,
/// <summary>
/// 父机构不存在
/// </summary>
@ -319,6 +325,18 @@ public enum ErrorCodeEnum
[ErrorCodeItemMetadata("字典状态错误")]
D3005,
/// <summary>
/// 字典编码不能以Enum结尾
/// </summary>
[ErrorCodeItemMetadata("字典编码不能以Enum结尾")]
D3006,
/// <summary>
/// 禁止修改枚举类型的字典编码
/// </summary>
[ErrorCodeItemMetadata("禁止修改枚举类型的字典编码")]
D3007,
/// <summary>
/// 菜单已存在
/// </summary>
@ -535,6 +553,12 @@ public enum ErrorCodeEnum
[ErrorCodeItemMetadata("租户从库配置错误")]
D1302,
/// <summary>
/// 已存在同名的租户域名
/// </summary>
[ErrorCodeItemMetadata("已存在同名的租户域名")]
D1303,
/// <summary>
/// 该表代码模板已经生成过
/// </summary>
@ -631,6 +655,24 @@ public enum ErrorCodeEnum
[ErrorCodeItemMetadata("已存在同名功能或同名程序及插件")]
D1900,
/// <summary>
/// 禁止删除存在关联租户的应用
/// </summary>
[ErrorCodeItemMetadata("禁止删除存在关联租户的应用")]
A1001,
/// <summary>
/// 禁止删除存在关联菜单的应用
/// </summary>
[ErrorCodeItemMetadata("禁止删除存在关联菜单的应用")]
A1002,
/// <summary>
/// 找不到系统应用
/// </summary>
[ErrorCodeItemMetadata("找不到系统应用")]
A1000,
/// <summary>
/// 已存在同名或同编码项目
/// </summary>

View File

@ -15,12 +15,12 @@ public enum FinishStatusEnum
/// <summary>
/// 已完成
/// </summary>
[Description("已完成")]
[Description("已完成"), Theme("success")]
Finish = 1,
/// <summary>
/// 未完成
/// </summary>
[Description("未完成")]
[Description("未完成"), Theme("danger")]
UnFinish = 0,
}

View File

@ -15,7 +15,7 @@ public enum GenderEnum
/// <summary>
/// 未知的性别
/// </summary>
[Description("未知的性别")]
[Description("未知的性别"), Theme("info")]
Unknown = 0,
/// <summary>
@ -33,6 +33,6 @@ public enum GenderEnum
/// <summary>
/// 未说明的性别
/// </summary>
[Description("未说明的性别")]
[Description("未说明的性别"), Theme("info")]
Unspecified = 9
}

View File

@ -15,7 +15,7 @@ public enum JobCreateTypeEnum
/// <summary>
/// 内置
/// </summary>
[Description("内置")]
[Description("内置"), Theme("info")]
BuiltIn = 0,
/// <summary>

View File

@ -15,7 +15,7 @@ public enum MenuTypeEnum
/// <summary>
/// 目录
/// </summary>
[Description("目录")]
[Description("目录"), Theme("warning")]
Dir = 1,
/// <summary>
@ -27,6 +27,6 @@ public enum MenuTypeEnum
/// <summary>
/// 按钮
/// </summary>
[Description("按钮")]
[Description("按钮"), Theme("info")]
Btn = 3
}

View File

@ -15,24 +15,24 @@ public enum MessageTypeEnum
/// <summary>
/// 普通信息
/// </summary>
[Description("消息")]
[Description("消息"), Theme("info")]
Info = 0,
/// <summary>
/// 成功提示
/// </summary>
[Description("成功")]
[Description("成功"), Theme("success")]
Success = 1,
/// <summary>
/// 警告提示
/// </summary>
[Description("警告")]
[Description("警告"), Theme("warning")]
Warning = 2,
/// <summary>
/// 错误提示
/// </summary>
[Description("错误")]
[Description("错误"), Theme("danger")]
Error = 3
}

View File

@ -15,7 +15,7 @@ public enum NoticeStatusEnum
/// <summary>
/// 草稿
/// </summary>
[Description("草稿")]
[Description("草稿"), Theme("info")]
DRAFT = 0,
/// <summary>

View File

@ -21,6 +21,6 @@ public enum NoticeUserStatusEnum
/// <summary>
/// 已读
/// </summary>
[Description("已读")]
[Description("已读"), Theme("info")]
READ = 1
}

View File

@ -15,12 +15,12 @@ public enum StatusEnum
/// <summary>
/// 启用
/// </summary>
[Description("启用")]
[Description("启用"), Theme("success")]
Enable = 1,
/// <summary>
/// 停用
/// </summary>
[Description("停用")]
[Description("停用"), Theme("danger")]
Disable = 2,
}

View File

@ -9,6 +9,7 @@ namespace Admin.NET.Core;
/// <summary>
/// 事件类型-系统用户操作枚举
/// </summary>
[SuppressSniffer]
[Description("事件类型-系统用户操作枚举")]
public enum SysUserEventTypeEnum
{

View File

@ -15,12 +15,12 @@ public enum YesNoEnum
/// <summary>
/// 是
/// </summary>
[Description("是")]
[Description("是"), Theme("success")]
Y = 1,
/// <summary>
/// 否
/// </summary>
[Description("否")]
[Description("否"), Theme("danger")]
N = 2
}

View File

@ -23,4 +23,26 @@ public static class ListExtensions
await action(value);
}
}
public static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> consumer)
{
foreach (T item in enumerable)
{
consumer(item);
}
}
public static void AddRange<T>(this IList<T> list, IEnumerable<T> items)
{
if (list is List<T> list2)
{
list2.AddRange(items);
return;
}
foreach (T item in items)
{
list.Add(item);
}
}
}

View File

@ -155,7 +155,7 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public async Task ClientsSendMessagetoAll(MessageInput message)
public async Task ClientsSendMessageToAll(MessageInput message)
{
await _sysMessageService.SendAllUser(message);
}
@ -165,7 +165,7 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public async Task ClientsSendMessagetoOther(MessageInput message)
public async Task ClientsSendMessageToOther(MessageInput message)
{
await _sysMessageService.SendOtherUser(message);
}
@ -175,7 +175,7 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public async Task ClientsSendMessagetoUsers(MessageInput message)
public async Task ClientsSendMessageToUsers(MessageInput message)
{
await _sysMessageService.SendUsers(message);
}

View File

@ -14,8 +14,8 @@ namespace Admin.NET.Core;
public class EnumToDictJob : IJob
{
private readonly IServiceScopeFactory _scopeFactory;
private const string DefaultTagType = null;
private const int OrderOffset = 10;
private const string DefaultTagType = "info";
public EnumToDictJob(IServiceScopeFactory scopeFactory)
{
@ -24,48 +24,46 @@ public class EnumToDictJob : IJob
public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
{
var originColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"【{DateTime.Now}】系统枚举转换字典");
using var serviceScope = _scopeFactory.CreateScope();
var sysEnumService = serviceScope.ServiceProvider.GetRequiredService<SysEnumService>();
// 获取数据库连接
var db = serviceScope.ServiceProvider.GetRequiredService<ISqlSugarClient>().CopyNew();
// 获取枚举类型列表
var enumTypeList = sysEnumService.GetEnumTypeList();
var enumCodeList = enumTypeList.Select(u => u.TypeName);
// 查询数据库中已存在的枚举类型代码
//var exp = Expressionable.Create<SysDictType, SingleColumnEntity<string>>().And((t1, t2) => t1.Code == t2.ColumnName).ToExpression();
//var sysDictTypeList = await db.Queryable<SysDictType>().Includes(t1 => t1.Children).BulkListQuery(exp, enumCodeList, stoppingToken);
var sysDictTypeList = await db.Queryable<SysDictType>().Includes(u => u.Children)
.Where(u => enumCodeList.Contains(u.Code)).ToListAsync(stoppingToken);
// 更新的枚举转换字典
var updatedEnumCodes = sysDictTypeList.Select(u => u.Code);
var updatedEnumType = enumTypeList.Where(u => updatedEnumCodes.Contains(u.TypeName)).ToList();
var sysDictTypeDict = sysDictTypeList.ToDictionary(u => u.Code, u => u);
var (updatedDictTypes, updatedDictDatas, newSysDictDatas) = GetUpdatedDicts(updatedEnumType, sysDictTypeDict);
var sysEnumService = serviceScope.ServiceProvider.GetRequiredService<SysEnumService>();
var sysDictTypeList = GetDictByEnumType(sysEnumService.GetEnumTypeList());
// 新增的枚举转换字典
var newEnumType = enumTypeList.Where(u => !updatedEnumCodes.Contains(u.TypeName)).ToList();
var (newDictTypes, newDictDatas) = GetNewSysDicts(newEnumType);
// 校验枚举类命名规范,字典相关功能中需要通过后缀判断是否为枚举类型
Console.ForegroundColor = ConsoleColor.Red;
foreach (var dictType in sysDictTypeList.Where(x => !x.Code.EndsWith("Enum")))
Console.WriteLine($"【{DateTime.Now}】系统枚举转换字典的枚举类名称必须以Enum结尾: {dictType.Code} ({dictType.Name})");
sysDictTypeList = sysDictTypeList.Where(x => x.Code.EndsWith("Enum")).ToList();
// 执行数据库操作
await SyncEnumToDictInfoAsync(db, sysDictTypeList);
Console.ForegroundColor = ConsoleColor.Yellow;
try
{
await db.BeginTranAsync();
var storageable1 = await db.Storageable(sysDictTypeList)
.SplitUpdate(it => it.Any())
.SplitInsert(_ => true)
.ToStorageAsync();
await storageable1.BulkCopyAsync();
await storageable1.BulkUpdateAsync();
if (updatedDictTypes.Count > 0)
await db.Updateable(updatedDictTypes).ExecuteCommandAsync(stoppingToken);
Console.WriteLine($"【{DateTime.Now}】系统枚举类转字典类型数据: 插入{storageable1.InsertList.Count}条, 更新{storageable1.UpdateList.Count}条, 共{storageable1.TotalList.Count}条。");
if (updatedDictDatas.Count > 0)
await db.Updateable(updatedDictDatas).ExecuteCommandAsync(stoppingToken);
var storageable2 = await db.Storageable(sysDictTypeList.SelectMany(u => u.Children).ToList())
.WhereColumns(u => new { u.DictTypeId, u.Code })
.SplitUpdate(u => u.Any())
.SplitInsert(_ => true)
.ToStorageAsync();
await storageable2.BulkCopyAsync();
await storageable2.BulkUpdateAsync(nameof(SysDictData.Value), nameof(SysDictData.Code), nameof(SysDictData.Name));
if (newSysDictDatas.Count > 0)
await db.Insertable(newSysDictDatas).ExecuteCommandAsync(stoppingToken);
if (newDictTypes.Count > 0)
await db.Insertable(newDictTypes).ExecuteCommandAsync(stoppingToken);
if (newDictDatas.Count > 0)
await db.Insertable(newDictDatas).ExecuteCommandAsync(stoppingToken);
Console.WriteLine($"【{DateTime.Now}】系统枚举项转字典值数据: 插入{storageable2.InsertList.Count}条, 更新{storageable2.UpdateList.Count}条, 共{storageable2.TotalList.Count}条。");
await db.CommitTranAsync();
}
@ -75,131 +73,63 @@ public class EnumToDictJob : IJob
Log.Error($"系统枚举转换字典操作错误:{error.Message}\n堆栈跟踪{error.StackTrace}", error);
throw;
}
var originColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"【{DateTime.Now}】系统枚举转换字典");
Console.ForegroundColor = originColor;
finally
{
Console.ForegroundColor = originColor;
}
}
/// <summary>
/// 获取需要新增的字典列表
/// 用于同步枚举转字典旧数据(后期可删除)
/// </summary>
/// <param name="addEnumType"></param>
/// <returns>
/// 一个元组,包含以下元素:
/// <list type="table">
/// <item><term>SysDictTypes</term><description>字典类型列表</description></item>
/// <item><term>SysDictDatas</term><description>字典数据列表</description></item>
/// </list>
/// </returns>
private (List<SysDictType>, List<SysDictData>) GetNewSysDicts(List<EnumTypeOutput> addEnumType)
/// <param name="db"></param>
/// <param name="list"></param>
//[Obsolete]
private static async Task SyncEnumToDictInfoAsync(SqlSugarClient db, List<SysDictType> list)
{
var newDictType = new List<SysDictType>();
var newDictData = new List<SysDictData>();
if (addEnumType.Count <= 0)
return (newDictType, newDictData);
// 新增字典类型
newDictType = addEnumType.Select(u => new SysDictType
var codeList = list.Select(u => u.Code).ToList();
foreach (var dbDictType in await db.Queryable<SysDictType>().Where(x => codeList.Contains(x.Code)).ToListAsync() ?? new())
{
Id = YitIdHelper.NextId(),
Code = u.TypeName,
Name = u.TypeDescribe,
Remark = u.TypeRemark,
Status = StatusEnum.Enable
}).ToList();
var enumDictType = list.First(u => u.Code == dbDictType.Code);
if (enumDictType.Id == dbDictType.Id) continue;
// 新增字典数据
newDictData = addEnumType.Join(newDictType, t1 => t1.TypeName, t2 => t2.Code, (t1, t2) => new
// 数据不一致则删除
_ = db.Deleteable<SysDictData>().Where(u => u.DictTypeId == dbDictType.Id).ExecuteCommandAsync();
_ = db.Deleteable<SysDictType>().Where(u => u.Id == dbDictType.Id).ExecuteCommandAsync();
Console.WriteLine($"【{DateTime.Now}】删除字典数据: {dbDictType.Name}-{dbDictType.Code}");
}
}
/// <summary>
/// 枚举信息转字典
/// </summary>
/// <param name="enumTypeList"></param>
/// <returns></returns>
private List<SysDictType> GetDictByEnumType(List<EnumTypeOutput> enumTypeList)
{
var orderNo = 1;
var list = new List<SysDictType>();
foreach (var type in enumTypeList)
{
Data = t1.EnumEntities.Select(u => new SysDictData
var dictType = new SysDictType
{
Id = YitIdHelper.NextId(),
DictTypeId = t2.Id,
Name = u.Describe,
Value = u.Value.ToString(),
Code = u.Name,
Remark = t2.Remark,
Id = 900000000000 + CommonUtil.GetFixedHashCode(type.TypeName),
Code = type.TypeName,
Name = type.TypeDescribe,
Remark = type.TypeRemark
};
dictType.Children = type.EnumEntities.Select(u => new SysDictData
{
Id = dictType.Id + orderNo++,
DictTypeId = dictType.Id,
Name = u.Name,
Value = u.Describe,
Code = u.Value.ToString(),
OrderNo = u.Value + OrderOffset,
TagType = u.Theme != "" ? u.Theme : DefaultTagType,
}).ToList()
}).SelectMany(x => x.Data).ToList();
return (newDictType, newDictData);
}
/// <summary>
/// 获取需要更新的字典列表
/// </summary>
/// <param name="updatedEnumType"></param>
/// <param name="sysDictTypeDict"></param>
/// <returns>
/// 一个元组,包含以下元素:
/// <list type="table">
/// <item><term>SysDictTypes</term><description>更新字典类型列表</description>
/// </item>
/// <item><term>SysDictDatas</term><description>更新字典数据列表</description>
/// </item>
/// <item><term>SysDictDatas</term><description>新增字典数据列表</description>
/// </item>
/// </list>
/// </returns>
private (List<SysDictType>, List<SysDictData>, List<SysDictData>) GetUpdatedDicts(List<EnumTypeOutput> updatedEnumType, Dictionary<string, SysDictType> sysDictTypeDict)
{
var updatedSysDictTypes = new List<SysDictType>();
var updatedSysDictData = new List<SysDictData>();
var newSysDictData = new List<SysDictData>();
foreach (var e in updatedEnumType)
{
if (!sysDictTypeDict.TryGetValue(e.TypeName, out var value))
continue;
var updatedDictType = value;
updatedDictType.Name = e.TypeDescribe;
updatedDictType.Remark = e.TypeRemark;
updatedSysDictTypes.Add(updatedDictType);
var updatedDictData = updatedDictType.Children.Where(u => u.DictTypeId == updatedDictType.Id).ToList();
// 遍历需要更新的字典数据
foreach (var dictData in updatedDictData)
{
var enumData = e.EnumEntities.FirstOrDefault(u => dictData.Code == u.Name);
if (enumData != null)
{
dictData.Value = enumData.Value.ToString();
dictData.OrderNo = enumData.Value + OrderOffset;
dictData.Name = enumData.Describe;
dictData.TagType = enumData.Theme != "" ? enumData.Theme : dictData.TagType != "" ? dictData.TagType : DefaultTagType;
updatedSysDictData.Add(dictData);
}
}
// 新增的枚举值名称列表
var newEnumDataNameList = e.EnumEntities.Select(u => u.Name).Except(updatedDictData.Select(u => u.Code));
foreach (var newEnumDataName in newEnumDataNameList)
{
var enumData = e.EnumEntities.FirstOrDefault(u => newEnumDataName == u.Name);
if (enumData != null)
{
var dictData = new SysDictData
{
Id = YitIdHelper.NextId(),
DictTypeId = updatedDictType.Id,
Name = enumData.Describe,
Value = enumData.Value.ToString(),
Code = enumData.Name,
Remark = updatedDictType.Remark,
OrderNo = enumData.Value + OrderOffset,
TagType = enumData.Theme != "" ? enumData.Theme : DefaultTagType,
};
dictData.TagType = enumData.Theme != "" ? enumData.Theme : dictData.TagType != "" ? dictData.TagType : DefaultTagType;
newSysDictData.Add(dictData);
}
}
// 删除的情况暂不处理
}).ToList();
list.Add(dictType);
}
return (updatedSysDictTypes, updatedSysDictData, newSysDictData);
return list;
}
}

View File

@ -50,6 +50,11 @@ public sealed class DbConnectionConfig : ConnectionConfig
/// 种子配置
/// </summary>
public SeedSettings SeedSettings { get; set; }
/// <summary>
/// 隔离方式
/// </summary>
public TenantTypeEnum TenantType { get; set; } = TenantTypeEnum.Id;
}
/// <summary>

View File

@ -0,0 +1,23 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 系统应用菜单表种子数据
/// </summary>
public class SysAppMenuSeedData : ISqlSugarEntitySeedData<SysAppMenu>
{
/// <summary>
/// 种子数据
/// </summary>
/// <returns></returns>
public IEnumerable<SysAppMenu> HasData()
{
long id = SqlSugarConst.DefaultAppId;
return new SysMenuSeedData().HasData().Select(u => new SysAppMenu { Id = id++, AppId = SqlSugarConst.DefaultAppId, MenuId = u.Id });
}
}

View File

@ -0,0 +1,25 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 系统应用表种子数据
/// </summary>
public class SysAppSeedData : ISqlSugarEntitySeedData<SysApp>
{
/// <summary>
/// 种子数据
/// </summary>
/// <returns></returns>
public IEnumerable<SysApp> HasData()
{
return new[]
{
new SysApp{ Id=SqlSugarConst.DefaultAppId, Name="默认应用", Logo="/upload/logo.png", Title="Admin.NET", ViceTitle="Admin.NET", ViceDesc="站在巨人肩膀上的 .NET 通用权限开发框架", Watermark="Admin.NET", Copyright="Copyright \u00a9 2021-present Admin.NET All rights reserved.", Icp="省ICP备12345678号", Remark="系统默认应用", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
};
}
}

View File

@ -31,13 +31,14 @@ public class SysConfigSeedData : ISqlSugarEntitySeedData<SysConfig>
new SysConfig{ Id=1300000000191, Name="RefreshToken过期时间", Code=ConfigConst.SysRefreshTokenExpire, Value="20160", SysFlag=YesNoEnum.Y, Remark="刷新Token过期时间分钟一般 refresh_token 的有效时间 > 2 * access_token 的有效时间)", OrderNo=100, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000201, Name="发送异常日志邮件", Code=ConfigConst.SysErrorMail, Value="False", SysFlag=YesNoEnum.Y, Remark="是否发送异常日志邮件", OrderNo=110, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000211, Name="域登录验证", Code=ConfigConst.SysDomainLogin, Value="False", SysFlag=YesNoEnum.Y, Remark="是否开启域登录验证", OrderNo=120, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000221, Name="数据校验日志", Code=ConfigConst.SysValidationLog, Value="True", SysFlag=YesNoEnum.Y, Remark="是否数据校验日志", OrderNo=130, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000231, Name="行政区划同步层级", Code=ConfigConst.SysRegionSyncLevel, Value="3", SysFlag=YesNoEnum.Y, Remark="行政区划同步层级 1-省级,2-市级,3-区县级,4-街道级,5-村级", OrderNo=140, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000241, Name="开启强制修改密码", Code=ConfigConst.SysForceChangePassword, Value="False", SysFlag=YesNoEnum.Y, Remark="开启强制修改密码", OrderNo=150, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000221, Name="租户隔离登录验证", Code=ConfigConst.SysTenantHostLogin, Value="False", SysFlag=YesNoEnum.Y, Remark="租户隔离登录验证", OrderNo=370, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000231, Name="数据校验日志", Code=ConfigConst.SysValidationLog, Value="True", SysFlag=YesNoEnum.Y, Remark="是否数据校验日志", OrderNo=130, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000241, Name="行政区划同步层级", Code=ConfigConst.SysRegionSyncLevel, Value="3", SysFlag=YesNoEnum.Y, Remark="行政区划同步层级 1-省级,2-市级,3-区县级,4-街道级,5-村级", OrderNo=140, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000251, Name="开启强制修改密码", Code=ConfigConst.SysForceChangePassword, Value="False", SysFlag=YesNoEnum.Y, Remark="开启强制修改密码", OrderNo=150, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
// 新业务系统记得更改密匙,通过接口(http://localhost:5005/api/sysCommon/smKeyPair)获取
new SysConfig{ Id=1300000000251, Name="国密SM2密匙", Code=ConfigConst.SysSM2Key, Value="04851D329AA3E38C2E7670AFE70E6E70E92F8769CA27C8766B12209A0FFBA4493B603EF7A0B9B1E16F0E8930C0406EA0B179B68DF28E25334BDEC4AE76D907E9E9;3A61D1D30C6302DABFF36201D936D0143EEF0C850AF28C5CA6D5C045AF8C5C8A", SysFlag=YesNoEnum.Y, Remark="国密SM2密匙", OrderNo=160, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2024-11-21 00:00:00") },
new SysConfig{ Id=1300000000261, Name="开启密码强度验证", Code=ConfigConst.SysPasswordStrength, Value="False", SysFlag=YesNoEnum.Y, Remark="开启强制修改密码", OrderNo=150, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000271, Name="密码强度验证正则表达式", Code=ConfigConst.SysPasswordStrengthExpression, Value="(?=^.{6,16}$)(?=.*\\d)(?=.*\\W+)(?=.*[A-Z])(?=.*[a-z])(?!.*\\n).*$", SysFlag=YesNoEnum.Y, Remark="必须须包含大小写字母、数字和特殊字符的组合长度在6-16之间", OrderNo=150, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2024-11-21 00:00:00") },
new SysConfig{ Id=1300000000261, Name="国密SM2密匙", Code=ConfigConst.SysSM2Key, Value="04851D329AA3E38C2E7670AFE70E6E70E92F8769CA27C8766B12209A0FFBA4493B603EF7A0B9B1E16F0E8930C0406EA0B179B68DF28E25334BDEC4AE76D907E9E9;3A61D1D30C6302DABFF36201D936D0143EEF0C850AF28C5CA6D5C045AF8C5C8A", SysFlag=YesNoEnum.Y, Remark="国密SM2密匙", OrderNo=160, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2024-11-21 00:00:00") },
new SysConfig{ Id=1300000000271, Name="开启密码强度验证", Code=ConfigConst.SysPasswordStrength, Value="False", SysFlag=YesNoEnum.Y, Remark="开启强制修改密码", OrderNo=150, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000281, Name="密码强度验证正则表达式", Code=ConfigConst.SysPasswordStrengthExpression, Value="(?=^.{6,16}$)(?=.*\\d)(?=.*\\W+)(?=.*[A-Z])(?=.*[a-z])(?!.*\\n).*$", SysFlag=YesNoEnum.Y, Remark="必须须包含大小写字母、数字和特殊字符的组合长度在6-16之间", OrderNo=150, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2024-11-21 00:00:00") },
new SysConfig{ Id=1310000000301, Name="系统主标题", Code=ConfigConst.SysWebTitle, Value="Admin.NET.Pro", SysFlag=YesNoEnum.Y, Remark="系统主标题", OrderNo=300, GroupCode=ConfigConst.SysWebConfigGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1310000000311, Name="系统副标题", Code=ConfigConst.SysWebViceTitle, Value="Admin.NET.Pro", SysFlag=YesNoEnum.Y, Remark="系统副标题", OrderNo=310, GroupCode=ConfigConst.SysWebConfigGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },

View File

@ -20,16 +20,16 @@ public class SysDictDataSeedData : ISqlSugarEntitySeedData<SysDictData>
return new[]
{
new SysDictData{ Id=1300000000101, DictTypeId=1300000000101, Value="输入框", Code="Input", OrderNo=100, Remark="输入框", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000102, DictTypeId=1300000000101, Value="外键", Code="fk", OrderNo=100, Remark="外键", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000103, DictTypeId=1300000000101, Value="时间选择", Code="DatePicker", OrderNo=100, Remark="时间选择", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000104, DictTypeId=1300000000101, Value="选择器", Code="Select", OrderNo=100, Remark="选择器", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000105, DictTypeId=1300000000101, Value="数字输入框", Code="InputNumber", OrderNo=100, Remark="数字输入框", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000106, DictTypeId=1300000000101, Value="文本域", Code="InputTextArea", OrderNo=100, Remark="文本域", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000107, DictTypeId=1300000000101, Value="上传", Code="Upload", OrderNo=100, Remark="上传", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000108, DictTypeId=1300000000101, Value="树选择", Code="ApiTreeSelect", OrderNo=100, Remark="树选择", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000109, DictTypeId=1300000000101, Value="开关", Code="Switch", OrderNo=100, Remark="开关", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000110, DictTypeId=1300000000101, Value="常量选择器", Code="ConstSelector", OrderNo=100, Remark="常量选择器", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000111, DictTypeId=1300000000101, Value="枚举选择器", Code="EnumSelector", OrderNo=100, Remark="枚举选择器", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000102, DictTypeId=1300000000101, Value="字典选择器", Code="DictSelector", OrderNo=100, Remark="字典选择器", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000103, DictTypeId=1300000000101, Value="常量选择器", Code="ConstSelector", OrderNo=100, Remark="常量选择器", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000104, DictTypeId=1300000000101, Value="枚举选择器", Code="EnumSelector", OrderNo=100, Remark="枚举选择器", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000105, DictTypeId=1300000000101, Value="树选择器", Code="ApiTreeSelector", OrderNo=100, Remark="树选择器", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000106, DictTypeId=1300000000101, Value="外键", Code="ForeignKey", OrderNo=100, Remark="外键", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000107, DictTypeId=1300000000101, Value="数字输入框", Code="InputNumber", OrderNo=100, Remark="数字输入框", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000108, DictTypeId=1300000000101, Value="时间选择", Code="DatePicker", OrderNo=100, Remark="时间选择", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000109, DictTypeId=1300000000101, Value="文本域", Code="InputTextArea", OrderNo=100, Remark="文本域", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000110, DictTypeId=1300000000101, Value="上传", Code="Upload", OrderNo=100, Remark="上传", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000111, DictTypeId=1300000000101, Value="开关", Code="Switch", OrderNo=100, Remark="开关", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000201, DictTypeId=1300000000102, Value="等于", Code="==", OrderNo=1, Remark="等于", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysDictData{ Id=1300000000202, DictTypeId=1300000000102, Value="模糊", Code="like", OrderNo=1, Remark="模糊", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },

View File

@ -21,7 +21,7 @@ public class SysTenantSeedData : ISqlSugarEntitySeedData<SysTenant>
return new[]
{
new SysTenant{ Id=SqlSugarConst.DefaultTenantId, OrgId=SqlSugarConst.DefaultTenantId, UserId=1300000000111, Host="https://gitee.com", TenantType=TenantTypeEnum.Id, DbType=defaultDbConfig.DbType, Connection=defaultDbConfig.ConnectionString, ConfigId=SqlSugarConst.MainConfigId, Remark="系统默认", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysTenant{ Id=SqlSugarConst.DefaultTenantId, AppId=SqlSugarConst.DefaultTenantId, OrgId=SqlSugarConst.DefaultTenantId, UserId=1300000000111, Host=SqlSugarConst.DefaultTenantHost, TenantType=TenantTypeEnum.Id, DbType=defaultDbConfig.DbType, Connection=defaultDbConfig.ConnectionString, ConfigId=SqlSugarConst.MainConfigId, Remark="系统默认", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
};
}
}

View File

@ -0,0 +1,173 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 应用基础输入参数
/// </summary>
public class SysAppInput
{
/// <summary>
/// 主键Id
/// </summary>
public virtual long? Id { get; set; }
/// <summary>
/// 名称
/// </summary>
[Required(ErrorMessage = "名称不能为空")]
public virtual string Name { get; set; }
/// <summary>
/// 标题
/// </summary>
[Required(ErrorMessage = "标题不能为空")]
public virtual string Title { get; set; }
/// <summary>
/// 副标题
/// </summary>
[Required(ErrorMessage = "副标题不能为空")]
public virtual string ViceTitle { get; set; }
/// <summary>
/// 副描述
/// </summary>
public virtual string? ViceDesc { get; set; }
/// <summary>
/// 水印
/// </summary>
public virtual string? Watermark { get; set; }
/// <summary>
/// 版权信息
/// </summary>
public virtual string? Copyright { get; set; }
/// <summary>
/// ICP备案号
/// </summary>
public virtual string? Icp { get; set; }
/// <summary>
/// 排序
/// </summary>
[Required(ErrorMessage = "排序不能为空")]
public virtual int? OrderNo { get; set; }
/// <summary>
/// 备注
/// </summary>
public virtual string? Remark { get; set; }
/// <summary>
/// 图标
/// </summary>
public virtual string? Logo { get; set; }
}
/// <summary>
/// 增加应用输入参数
/// </summary>
public class AddSysAppInput
{
/// <summary>
/// 图标
/// </summary>
[Required(ErrorMessage = "Logo不能为空")]
public string Logo { get; set; }
/// <summary>
/// 名称
/// </summary>
[Required(ErrorMessage = "名称不能为空")]
public string Name { get; set; }
/// <summary>
/// 标题
/// </summary>
[Required(ErrorMessage = "标题不能为空")]
public string Title { get; set; }
/// <summary>
/// 副标题
/// </summary>
[Required(ErrorMessage = "副标题不能为空")]
public string ViceTitle { get; set; }
/// <summary>
/// 副描述
/// </summary>
[Required(ErrorMessage = "副描述不能为空")]
public string ViceDesc { get; set; }
/// <summary>
/// 水印
/// </summary>
[Required(ErrorMessage = "水印不能为空")]
public string Watermark { get; set; }
/// <summary>
/// 版权信息
/// </summary>
[Required(ErrorMessage = "版权信息不能为空")]
public string Copyright { get; set; }
/// <summary>
/// ICP备案号
/// </summary>
[Required(ErrorMessage = "备案号不能为空")]
public string Icp { get; set; }
/// <summary>
/// 排序
/// </summary>
[Required(ErrorMessage = "排序不能为空")]
public int OrderNo { get; set; } = 100;
/// <summary>
/// 备注
/// </summary>
[MaxLength(256, ErrorMessage = "备注字符长度不能超过256")]
public string Remark { get; set; }
}
/// <summary>
/// 更新应用输入参数
/// </summary>
public class UpdateSysAppInput : AddSysAppInput
{
/// <summary>
/// 主键Id
/// </summary>
[Required(ErrorMessage = "主键Id不能为空")]
public long Id { get; set; }
}
/// <summary>
/// 授权应用菜单
/// </summary>
public class UpdateAppMenuInput : BaseIdInput
{
/// <summary>
/// 菜单Id集合
/// </summary>
public List<long> MenuIdList { get; set; }
}
/// <summary>
/// 租户iId
/// </summary>
public class ChangeAppInput : BaseIdInput
{
/// <summary>
/// 租户Id
/// </summary>
[Required(ErrorMessage = "租户不能为空")]
public long TenantId { get; set; }
}

View File

@ -0,0 +1,103 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 应用输出参数
/// </summary>
public class SysAppOutput
{
/// <summary>
/// 主键Id
/// </summary>
public long Id { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 图标
/// </summary>
public string? Logo { get; set; }
/// <summary>
/// 标题
/// </summary>
public string Title { get; set; }
/// <summary>
/// 副标题
/// </summary>
public string ViceTitle { get; set; }
/// <summary>
/// 副描述
/// </summary>
public string? ViceDesc { get; set; }
/// <summary>
/// 水印
/// </summary>
public string? Watermark { get; set; }
/// <summary>
/// 版权信息
/// </summary>
public string? Copyright { get; set; }
/// <summary>
/// ICP备案号
/// </summary>
public string? Icp { get; set; }
/// <summary>
/// 排序
/// </summary>
public int OrderNo { get; set; }
/// <summary>
/// 备注
/// </summary>
public string? Remark { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime? CreateTime { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime? UpdateTime { get; set; }
/// <summary>
/// 创建者Id
/// </summary>
public long? CreateUserId { get; set; }
/// <summary>
/// 创建者姓名
/// </summary>
public string? CreateUserName { get; set; }
/// <summary>
/// 修改者Id
/// </summary>
public long? UpdateUserId { get; set; }
/// <summary>
/// 修改者姓名
/// </summary>
public string? UpdateUserName { get; set; }
/// <summary>
/// 软删除
/// </summary>
public bool IsDelete { get; set; }
}

View File

@ -0,0 +1,183 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 系统应用服务 🧩
/// </summary>
[ApiDescriptionSettings(Name = "SysApp", Order = 495, Description = "系统应用")]
public class SysAppService : IDynamicApiController, ITransient
{
private readonly SqlSugarRepository<SysAppMenu> _sysAppMenuRep;
private readonly SqlSugarRepository<SysApp> _sysAppRep;
private readonly SysAuthService _sysAuthService;
private readonly UserManager _userManager;
public SysAppService(SqlSugarRepository<SysApp> sysAppRep,
SqlSugarRepository<SysAppMenu> sysAppMenuRep,
SysAuthService sysAuthService,
UserManager userManager)
{
_sysAppRep = sysAppRep;
_userManager = userManager;
_sysAppMenuRep = sysAppMenuRep;
_sysAuthService = sysAuthService;
}
/// <summary>
/// 分页查询应用 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("分页查询应用")]
[ApiDescriptionSettings(Name = "Page"), HttpPost]
public async Task<SqlSugarPagedList<SysAppOutput>> Page(BasePageInput input)
{
input.Keyword = input.Keyword?.Trim();
var query = _sysAppRep.AsQueryable()
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.Name.Contains(input.Keyword) ||
u.Title.Contains(input.Keyword) || u.ViceTitle.Contains(input.Keyword) ||
u.ViceDesc.Contains(input.Keyword) || u.Remark.Contains(input.Keyword))
.OrderBy(u => new { u.OrderNo, u.Id })
.Select<SysAppOutput>();
return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
}
/// <summary>
/// 增加应用 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("增加应用")]
[ApiDescriptionSettings(Name = "Add"), HttpPost]
public async Task<long> Add(AddSysAppInput input)
{
var entity = input.Adapt<SysApp>();
return await _sysAppRep.InsertAsync(entity) ? entity.Id : 0;
}
/// <summary>
/// 更新应用 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("更新应用")]
[ApiDescriptionSettings(Name = "Update"), HttpPost]
public async Task Update(UpdateSysAppInput input)
{
_ = await _sysAppRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
var entity = input.Adapt<SysApp>();
await _sysAppRep.AsUpdateable(entity).ExecuteCommandAsync();
}
/// <summary>
/// 删除应用 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("删除应用")]
[ApiDescriptionSettings(Name = "Delete"), HttpPost]
public async Task Delete(BaseIdInput input)
{
var entity = await _sysAppRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
// 禁止删除存在关联租户的应用
if (await _sysAppRep.Context.Queryable<SysTenant>().AnyAsync(u => u.AppId == input.Id)) throw Oops.Oh(ErrorCodeEnum.A1001);
// 禁止删除存在关联菜单的应用
if (await _sysAppMenuRep.AsQueryable().AnyAsync(u => u.AppId == input.Id)) throw Oops.Oh(ErrorCodeEnum.A1002);
await _sysAppRep.DeleteAsync(entity);
}
/// <summary>
/// 获取应用菜单 🔖
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[DisplayName("获取应用菜单")]
public async Task<List<long>> GetMenuList([FromQuery] long id)
{
return await _sysAppMenuRep.AsQueryable().Where(u => u.AppId == id).Select(u => u.MenuId).ToListAsync();
}
/// <summary>
/// 授权应用菜单 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[UnitOfWork]
[DisplayName("授权应用菜单")]
public async Task GrantMenu(UpdateAppMenuInput input)
{
input.MenuIdList ??= new();
await _sysAppMenuRep.DeleteAsync(u => u.AppId == input.Id);
var list = input.MenuIdList.Select(id => new SysAppMenu { AppId = input.Id, MenuId = id }).ToList();
await _sysAppMenuRep.InsertRangeAsync(list);
// 清除应用下其他模块越权的授权数据,包括角色菜单,用户收藏菜单
var tenantIds = await _sysAppRep.Context.Queryable<SysTenant>().Where(u => u.AppId == input.Id).Select(u => u.Id).ToListAsync();
var roleIds = await _sysAppRep.Context.Queryable<SysRole>().Where(u => tenantIds.Contains((long)u.TenantId)).Select(u => u.Id).ToListAsync();
var userIds = await _sysAppRep.Context.Queryable<SysUser>().Where(u => tenantIds.Contains((long)u.TenantId)).Select(u => u.Id).ToListAsync();
await _sysAppRep.Context.Deleteable<SysRoleMenu>().Where(u => roleIds.Contains(u.RoleId) && !input.MenuIdList.Contains(u.MenuId)).ExecuteCommandAsync();
await _sysAppRep.Context.Deleteable<SysUserMenu>().Where(u => userIds.Contains(u.UserId) && !input.MenuIdList.Contains(u.MenuId)).ExecuteCommandAsync();
}
/// <summary>
/// 获取应用数据 🔖
/// </summary>
/// <returns></returns>
[DisplayName("获取应用数据")]
public async Task<dynamic> GetAppData()
{
var list = await _sysAppRep.AsQueryable().Includes(u => u.TenantList).ToListAsync();
return list.Where(u => u.TenantList.Count > 0).Select(u => new
{
u.Id,
Value = u.Id,
Label = u.Name,
Children = u.TenantList.Select(t => new
{
t.Id,
Value = t.Id,
Label = t.Host ?? (t.Id + ""),
})
});
}
/// <summary>
/// 切换应用 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("切换应用")]
public async Task<LoginOutput> ChangeApp(ChangeAppInput input)
{
_ = await _sysAppRep.Context.Queryable<SysTenant>().FirstAsync(u => u.Id == input.TenantId) ?? throw Oops.Oh(ErrorCodeEnum.Z1003);
_ = await _sysAppRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
var user = await _sysAppRep.Context.Queryable<SysUser>().FirstAsync(u => u.Id == _userManager.UserId);
user.TenantId = input.TenantId;
return await _sysAuthService.CreateToken(user, input.Id, LoginModeEnum.PC);
}
/// <summary>
/// 获取当前应用信息
/// </summary>
/// <returns></returns>
[NonAction]
public async Task<SysApp> GetCurrentAppInfo()
{
var appId = _userManager.AppId > 0 ? _userManager.AppId : SqlSugarConst.DefaultAppId;
return await _sysAppRep.GetFirstAsync(u => u.Id == appId) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
}
}

View File

@ -25,6 +25,12 @@ public class LoginInput
[Required(ErrorMessage = "密码不能为空"), MinLength(3, ErrorMessage = "密码不能少于3个字符")]
public string Password { get; set; }
/// <summary>
/// 租户域名
/// </summary>
[Required(ErrorMessage = "租户域名不能为空")]
public string Host { get; set; }
/// <summary>
/// 验证码Id
/// </summary>
@ -58,6 +64,12 @@ public class LoginPhoneInput
[Required(ErrorMessage = "验证码不能为空"), MinLength(4, ErrorMessage = "验证码不能少于4个字符")]
public string Code { get; set; }
/// <summary>
/// 租户域名
/// </summary>
[Required(ErrorMessage = "租户域名不能为空")]
public string? Host { get; set; }
/// <summary>
/// 登录模式
/// </summary>

View File

@ -9,15 +9,10 @@ namespace Admin.NET.Core.Service;
/// <summary>
/// 系统域登录信息配置输入参数
/// </summary>
public class PageSysLdapInput : BasePageInput
public class SysLdapInput : BasePageInput
{
/// <summary>
/// 关键字查询
/// </summary>
public string? SearchKey { get; set; }
/// <summary>
/// 主机
/// 域名
/// </summary>
public string? Host { get; set; }
}

View File

@ -55,31 +55,17 @@ public class SysAuthService : IDynamicApiController, ITransient
var passwordErrorTimes = _sysCacheService.Get<int>(keyPasswordErrorTimes);
var passwordMaxErrorTimes = await _sysConfigService.GetConfigValueByCode<int>(ConfigConst.SysPasswordMaxErrorTimes);
// 若未配置或误配置为0、负数, 则默认密码错误次数最大为5次
if (passwordMaxErrorTimes < 1)
passwordMaxErrorTimes = 5;
if (passwordErrorTimes > passwordMaxErrorTimes)
throw Oops.Oh(ErrorCodeEnum.D1027);
if (passwordMaxErrorTimes < 1) passwordMaxErrorTimes = 5;
if (passwordErrorTimes > passwordMaxErrorTimes) throw Oops.Oh(ErrorCodeEnum.D1027);
// 是否开启验证码
if (await _sysConfigService.GetConfigValueByCode<bool>(ConfigConst.SysCaptcha))
{
// 判断验证码
if (!_captcha.Validate(input.CodeId.ToString(), input.Code))
throw Oops.Oh(ErrorCodeEnum.D0008);
}
// 判断是否开启验证码并校验
if (await _sysConfigService.GetConfigValueByCode<bool>(ConfigConst.SysCaptcha) && !_captcha.Validate(input.CodeId.ToString(), input.Code)) throw Oops.Oh(ErrorCodeEnum.D0008);
// 账号是否存在
var user = await _sysUserRep.AsQueryable().Includes(t => t.SysOrg).ClearFilter().FirstAsync(u => u.Account.Equals(input.Account));
_ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009);
// 获取登录租户和用户
var (tenant, user) = await GetLoginUserAndTenant(input.Host, account: input.Account);
// 账号是否被冻结
if (user.Status == StatusEnum.Disable)
throw Oops.Oh(ErrorCodeEnum.D1017);
// 租户是否被禁用
var tenant = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysTenant>>().GetByIdAsync(user.TenantId);
if (tenant != null && tenant.Status == StatusEnum.Disable)
throw Oops.Oh(ErrorCodeEnum.Z1003);
if (user.Status == StatusEnum.Disable) throw Oops.Oh(ErrorCodeEnum.D1017);
// 是否开启域登录验证
if (await _sysConfigService.GetConfigValueByCode<bool>(ConfigConst.SysDomainLogin))
@ -87,51 +73,100 @@ public class SysAuthService : IDynamicApiController, ITransient
var userLdap = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysUserLdap>>().GetFirstAsync(u => u.UserId == user.Id && u.TenantId == tenant.Id);
if (userLdap == null)
{
VerifyPassword(input, keyPasswordErrorTimes, passwordErrorTimes, user);
VerifyPassword(input.Password, keyPasswordErrorTimes, passwordErrorTimes, user);
}
else if (!await App.GetRequiredService<SysLdapService>().AuthAccount(tenant.Id, userLdap.Account, input.Password))
else if (!await App.GetRequiredService<SysLdapService>().AuthAccount(tenant.Id, userLdap.Account, CryptogramUtil.Decrypt(input.Password)))
{
_sysCacheService.Set(keyPasswordErrorTimes, ++passwordErrorTimes, TimeSpan.FromMinutes(30));
throw Oops.Oh(ErrorCodeEnum.D1000);
}
}
else
VerifyPassword(input, keyPasswordErrorTimes, passwordErrorTimes, user);
VerifyPassword(input.Password, keyPasswordErrorTimes, passwordErrorTimes, user);
// 登录成功则清空密码错误次数
_sysCacheService.Remove(keyPasswordErrorTimes);
return await CreateToken(user, input.LoginMode);
return await CreateToken(user, tenant.AppId, input.LoginMode);
}
/// <summary>
/// 获取登录租户和用户
/// </summary>
/// <param name="host"></param>
/// <param name="account"></param>
/// <param name="phone"></param>
/// <returns></returns>
[NonAction]
public async Task<(SysTenant tenant, SysUser user)> GetLoginUserAndTenant(string host, string account = null, string phone = null)
{
// 是否租户隔离登录验证
var isTenantHostLogin = await _sysConfigService.GetConfigValueByCode<bool>(ConfigConst.SysTenantHostLogin);
SysUser user;
SysTenant tenant;
// 租户隔离登录
if (isTenantHostLogin)
{
// 若租户域名为空或为本地域名,则取默认租户域名
if (string.IsNullOrWhiteSpace(host) || host.StartsWith("localhost")) host = SqlSugarConst.DefaultTenantHost;
// 租户是否存在或已禁用
tenant = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysTenant>>().GetFirstAsync(u => u.Host == host.ToLower());
if (tenant?.Status != StatusEnum.Enable) throw Oops.Oh(ErrorCodeEnum.Z1003);
// 根据入参类型、租户查询登录用户
user = await _sysUserRep.AsQueryable().Includes(t => t.SysOrg).ClearFilter()
.Where(u => u.TenantId == tenant.Id || u.AccountType == AccountTypeEnum.SuperAdmin)
.WhereIF(!string.IsNullOrWhiteSpace(account), u => u.Account.Equals(account))
.WhereIF(!string.IsNullOrWhiteSpace(phone), u => u.Phone.Equals(phone))
.FirstAsync();
_ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009);
// 若登录的是超级管理员,则引用当前绑定的租户,这样登陆后操作的租户数据会与该租户关联
if (user.AccountType == AccountTypeEnum.SuperAdmin) user.TenantId = tenant.Id;
}
else
{
// 根据入参类型查询登录用户
user = await _sysUserRep.AsQueryable().Includes(t => t.SysOrg).ClearFilter()
.WhereIF(!string.IsNullOrWhiteSpace(account), u => u.Account.Equals(account))
.WhereIF(!string.IsNullOrWhiteSpace(phone), u => u.Phone.Equals(phone))
.FirstAsync();
_ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009);
// 租户是否存在或已禁用
tenant = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysTenant>>().GetFirstAsync(u => u.Id == user.TenantId);
if (tenant?.Status != StatusEnum.Enable) throw Oops.Oh(ErrorCodeEnum.Z1003);
}
return (tenant, user);
}
/// <summary>
/// 验证用户密码
/// </summary>
/// <param name="input"></param>
/// <param name="password"></param>
/// <param name="keyPasswordErrorTimes"></param>
/// <param name="passwordErrorTimes"></param>
/// <param name="user"></param>
private void VerifyPassword(LoginInput input, string keyPasswordErrorTimes, int passwordErrorTimes, SysUser user)
private void VerifyPassword(string password, string keyPasswordErrorTimes, int passwordErrorTimes, SysUser user)
{
if (CryptogramUtil.CryptoType == CryptogramEnum.MD5.ToString())
{
if (!user.Password.Equals(MD5Encryption.Encrypt(input.Password)))
{
_sysCacheService.Set(keyPasswordErrorTimes, ++passwordErrorTimes, TimeSpan.FromMinutes(30));
throw Oops.Oh(ErrorCodeEnum.D1000);
}
}
else
{
// 国密SM2解密前端密码传输SM2加密后的
input.Password = CryptogramUtil.SM2Decrypt(input.Password);
if (user.Password.Equals(MD5Encryption.Encrypt(password))) return;
if (!CryptogramUtil.Decrypt(user.Password).Equals(input.Password))
{
_sysCacheService.Set(keyPasswordErrorTimes, ++passwordErrorTimes, TimeSpan.FromMinutes(30));
throw Oops.Oh(ErrorCodeEnum.D1000);
}
_sysCacheService.Set(keyPasswordErrorTimes, ++passwordErrorTimes, TimeSpan.FromMinutes(30));
throw Oops.Oh(ErrorCodeEnum.D1000);
}
// 国密SM2解密前端密码传输SM2加密后的
password = CryptogramUtil.SM2Decrypt(password);
if (CryptogramUtil.Decrypt(user.Password).Equals(password)) return;
_sysCacheService.Set(keyPasswordErrorTimes, ++passwordErrorTimes, TimeSpan.FromMinutes(30));
throw Oops.Oh(ErrorCodeEnum.D1000);
}
/// <summary>
@ -143,23 +178,28 @@ public class SysAuthService : IDynamicApiController, ITransient
public virtual async Task<bool> UnLockScreen([Required, FromQuery] string password)
{
// 账号是否存在
var user = await _sysUserRep.GetByIdAsync(_userManager.UserId);
var user = await _sysUserRep.GetFirstAsync(u => u.Id == _userManager.UserId);
_ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009);
// 国密SM2解密前端密码传输SM2加密后的
password = CryptogramUtil.SM2Decrypt(password);
var keyPasswordErrorTimes = $"{CacheConst.KeyPasswordErrorTimes}{user.Account}";
var passwordErrorTimes = _sysCacheService.Get<int>(keyPasswordErrorTimes);
// 密码是否正确
if (CryptogramUtil.CryptoType == CryptogramEnum.MD5.ToString())
// 是否开启域登录验证
if (await _sysConfigService.GetConfigValueByCode<bool>(ConfigConst.SysDomainLogin))
{
if (!user.Password.Equals(MD5Encryption.Encrypt(password)))
var userLdap = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysUserLdap>>().GetFirstAsync(u => u.UserId == user.Id && u.TenantId == user.TenantId);
if (userLdap == null)
{
VerifyPassword(password, keyPasswordErrorTimes, passwordErrorTimes, user);
}
else if (!await App.GetRequiredService<SysLdapService>().AuthAccount(user.TenantId.Value, userLdap.Account, CryptogramUtil.Decrypt(password)))
{
_sysCacheService.Set(keyPasswordErrorTimes, ++passwordErrorTimes, TimeSpan.FromMinutes(30));
throw Oops.Oh(ErrorCodeEnum.D1000);
}
}
else
{
if (!CryptogramUtil.Decrypt(user.Password).Equals(password))
throw Oops.Oh(ErrorCodeEnum.D1000);
}
VerifyPassword(password, keyPasswordErrorTimes, passwordErrorTimes, user);
return true;
}
@ -176,21 +216,21 @@ public class SysAuthService : IDynamicApiController, ITransient
// 校验短信验证码
App.GetRequiredService<SysSmsService>().VerifyCode(new SmsVerifyCodeInput { Phone = input.Phone, Code = input.Code });
// 账号是否存在
var user = await _sysUserRep.AsQueryable().Includes(u => u.SysOrg).ClearFilter().FirstAsync(u => u.Phone.Equals(input.Phone));
_ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009);
// 获取登录租户和用户
var (tenant, user) = await GetLoginUserAndTenant(input.Host, phone: input.Phone);
return await CreateToken(user, input.LoginMode);
return await CreateToken(user, tenant.AppId, input.LoginMode);
}
/// <summary>
/// 生成Token令牌 🔖
/// </summary>
/// <param name="user"></param>
/// <param name="appId"></param>
/// <param name="loginMode"></param>
/// <returns></returns>
[NonAction]
internal async Task<LoginOutput> CreateToken(SysUser user, LoginModeEnum loginMode)
internal async Task<LoginOutput> CreateToken(SysUser user, long? appId, LoginModeEnum loginMode)
{
// 默认PC端登录模式
if (loginMode == 0)
@ -203,6 +243,7 @@ public class SysAuthService : IDynamicApiController, ITransient
var tokenExpire = await _sysConfigService.GetTokenExpire();
var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
{
{ ClaimConst.AppId, appId },
{ ClaimConst.UserId, user.Id },
{ ClaimConst.TenantId, user.TenantId },
{ ClaimConst.Account, user.Account },
@ -357,6 +398,7 @@ public class SysAuthService : IDynamicApiController, ITransient
{
Account = auth.UserName,
Password = CryptogramUtil.SM2Encrypt(auth.Password),
Host = SqlSugarConst.DefaultTenantHost
});
_sysCacheService.Remove($"{CacheConst.KeyConfig}{ConfigConst.SysCaptcha}");

View File

@ -27,10 +27,10 @@ public class SysLdapService : IDynamicApiController, ITransient
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("获取系统域登录配置分页列表")]
public async Task<SqlSugarPagedList<SysLdap>> Page(PageSysLdapInput input)
public async Task<SqlSugarPagedList<SysLdap>> Page(SysLdapInput input)
{
return await _sysLdapRep.AsQueryable()
.WhereIF(!string.IsNullOrWhiteSpace(input.SearchKey), u => u.Host.Contains(input.SearchKey.Trim()))
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.Host.Contains(input.Keyword.Trim()))
.WhereIF(!string.IsNullOrWhiteSpace(input.Host), u => u.Host.Contains(input.Host.Trim()))
.OrderBy(u => u.CreateTime, OrderByType.Desc)
.ToPagedListAsync(input.Page, input.PageSize);
@ -78,7 +78,7 @@ public class SysLdapService : IDynamicApiController, ITransient
[DisplayName("删除系统域登录配置")]
public async Task Delete(DeleteSysLdapInput input)
{
var entity = await _sysLdapRep.GetByIdAsync(input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
var entity = await _sysLdapRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
await _sysLdapRep.FakeDeleteAsync(entity); // 假删除
//await _rep.DeleteAsync(entity); // 真删除
}
@ -91,7 +91,7 @@ public class SysLdapService : IDynamicApiController, ITransient
[DisplayName("获取系统域登录配置详情")]
public async Task<SysLdap> GetDetail([FromQuery] DetailSysLdapInput input)
{
return await _sysLdapRep.GetByIdAsync(input.Id);
return await _sysLdapRep.GetFirstAsync(u => u.Id == input.Id);
}
/// <summary>
@ -119,18 +119,17 @@ public class SysLdapService : IDynamicApiController, ITransient
try
{
ldapConn.Connect(sysLdap.Host, sysLdap.Port);
ldapConn.Bind(sysLdap.Version, sysLdap.BindDn, sysLdap.BindPass);
var ldapSearchResults = ldapConn.Search(sysLdap.BaseDn, LdapConnection.ScopeSub, sysLdap.AuthFilter.Replace("$s", account), null, false);
string bindPass = CryptogramUtil.Decrypt(sysLdap.BindPass);
ldapConn.Bind(sysLdap.Version, sysLdap.BindDn, bindPass);
var ldapSearchResults = ldapConn.Search(sysLdap.BaseDn, LdapConnection.ScopeSub, sysLdap.AuthFilter.Replace("%s", account), null, false);
string dn = string.Empty;
while (ldapSearchResults.HasMore())
{
var ldapEntry = ldapSearchResults.Next();
var sAMAccountName = ldapEntry.GetAttribute(sysLdap.AuthFilter)?.StringValue;
if (!string.IsNullOrEmpty(sAMAccountName))
{
dn = ldapEntry.Dn;
break;
}
var sAmAccountName = ldapEntry.GetAttribute(sysLdap.BindAttrAccount)?.StringValue;
if (string.IsNullOrEmpty(sAmAccountName)) continue;
dn = ldapEntry.Dn;
break;
}
if (string.IsNullOrEmpty(dn)) throw Oops.Oh(ErrorCodeEnum.D1002);
@ -154,15 +153,39 @@ public class SysLdapService : IDynamicApiController, ITransient
return true;
}
/// <summary>
/// 同步域用户 🔖
/// </summary>
/// <param name="tenantId"></param>
/// <returns></returns>
[DisplayName("同步域用户")]
[NonAction]
public async Task<List<SysUserLdap>> SyncUserTenant(long tenantId)
{
var sysLdap = await _sysLdapRep.GetFirstAsync(c => c.TenantId == tenantId && c.IsDelete == false && c.Status == StatusEnum.Enable) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
return await SyncUser(sysLdap);
}
/// <summary>
/// 同步域用户 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("同步域用户")]
public async Task SyncUser(SyncSysLdapInput input)
public async Task<List<SysUserLdap>> SyncUser(SyncSysLdapInput input)
{
var sysLdap = await _sysLdapRep.GetByIdAsync(input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
return await SyncUser(sysLdap);
}
/// <summary>
/// 同步域用户 🔖
/// </summary>
/// <param name="sysLdap"></param>
/// <returns></returns>
private async Task<List<SysUserLdap>> SyncUser(SysLdap sysLdap)
{
if (sysLdap == null) throw Oops.Oh(ErrorCodeEnum.D1002);
var ldapConn = new LdapConnection();
try
{
@ -192,15 +215,15 @@ public class SysLdapService : IDynamicApiController, ITransient
else
{
var sysUserLdap = CreateSysUserLdap(attrs, sysLdap.BindAttrAccount, sysLdap.BindAttrEmployeeId, deptCode);
if (string.IsNullOrEmpty(sysUserLdap.EmployeeId)) continue;
sysUserLdap.Dn = ldapEntry.Dn;
userLdapList.Add(sysUserLdap);
}
}
if (userLdapList.Count == 0)
return;
if (userLdapList.Count == 0) return null;
await App.GetRequiredService<SysUserLdapService>().InsertUserLdaps(sysLdap.TenantId!.Value, userLdapList);
await App.GetRequiredService<SysUserLdapService>().InsertUserLdapList(sysLdap.TenantId!.Value, userLdapList);
return userLdapList;
}
catch (LdapException e)
{
@ -225,7 +248,7 @@ public class SysLdapService : IDynamicApiController, ITransient
private static string GetDepartmentCode(LdapAttributeSet attrs, string bindAttrCode)
{
return bindAttrCode == "objectGUID"
? new Guid(attrs.GetAttribute(bindAttrCode)?.ByteValue).ToString()
? new Guid(attrs.GetAttribute(bindAttrCode)?.ByteValue!).ToString()
: attrs.GetAttribute(bindAttrCode)?.StringValue ?? "0";
}
@ -239,12 +262,22 @@ public class SysLdapService : IDynamicApiController, ITransient
/// <returns></returns>
private static SysUserLdap CreateSysUserLdap(LdapAttributeSet attrs, string bindAttrAccount, string bindAttrEmployeeId, string deptCode)
{
return new SysUserLdap
var userLdap = new SysUserLdap
{
Account = !attrs.ContainsKey(bindAttrAccount) ? null : attrs.GetAttribute(bindAttrAccount)?.StringValue,
EmployeeId = !attrs.ContainsKey(bindAttrEmployeeId) ? null : attrs.GetAttribute(bindAttrEmployeeId)?.StringValue,
DeptCode = deptCode
DeptCode = deptCode,
UserName = !attrs.ContainsKey("name") ? null : attrs.GetAttribute("name")?.StringValue,
Mail = !attrs.ContainsKey("mail") ? null : attrs.GetAttribute("mail")?.StringValue
};
var pwdLastSet = !attrs.ContainsKey("pwdLastSet") ? null : attrs.GetAttribute("pwdLastSet")?.StringValue;
if (!pwdLastSet!.Equals("0")) userLdap.PwdLastSetTime = DateTime.FromFileTime(Convert.ToInt64(pwdLastSet));
var userAccountControl = !attrs.ContainsKey("userAccountControl") ? null : attrs.GetAttribute("userAccountControl")?.StringValue;
if ((Convert.ToInt32(userAccountControl) & 0x2) == 0x2) // 检查账户是否已过期通过检查userAccountControl属性的特定位
userLdap.AccountExpiresFlag = true;
if ((Convert.ToInt32(userAccountControl) & 0x10000) == 0x10000) // 检查账户密码设置是否永不过期
userLdap.DontExpiresFlag = true;
return userLdap;
}
/// <summary>
@ -279,7 +312,7 @@ public class SysLdapService : IDynamicApiController, ITransient
else
{
var sysUserLdap = CreateSysUserLdap(attrs, sysLdap.BindAttrAccount, sysLdap.BindAttrEmployeeId, deptCode);
sysUserLdap.Dn = ldapEntry.Dn;
if (string.IsNullOrEmpty(sysUserLdap.EmployeeId)) continue;
userLdapList.Add(sysUserLdap);
}
@ -294,12 +327,13 @@ public class SysLdapService : IDynamicApiController, ITransient
[DisplayName("同步域组织")]
public async Task SyncDept(SyncSysLdapInput input)
{
var sysLdap = await _sysLdapRep.GetByIdAsync(input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
var sysLdap = await _sysLdapRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
var ldapConn = new LdapConnection();
try
{
ldapConn.Connect(sysLdap.Host, sysLdap.Port);
ldapConn.Bind(sysLdap.Version, sysLdap.BindDn, sysLdap.BindPass);
string bindPass = CryptogramUtil.Decrypt(sysLdap.BindPass);
ldapConn.Bind(sysLdap.Version, sysLdap.BindDn, bindPass);
var ldapSearchResults = ldapConn.Search(sysLdap.BaseDn, LdapConnection.ScopeOne, "(objectClass=*)", null, false);
var orgList = new List<SysOrg>();
while (ldapSearchResults.HasMore())
@ -316,13 +350,12 @@ public class SysLdapService : IDynamicApiController, ITransient
}
var attrs = ldapEntry.GetAttributeSet();
if (attrs.Count == 0 || attrs.ContainsKey("OU"))
{
var sysOrg = CreateSysOrg(attrs, sysLdap, orgList, new SysOrg { Id = 0, Level = 0 });
orgList.Add(sysOrg);
if (attrs.Count != 0 && !attrs.ContainsKey("OU")) continue;
SearchDnLdapDept(ldapConn, sysLdap, orgList, ldapEntry.Dn, sysOrg);
}
var sysOrg = CreateSysOrg(attrs, sysLdap, orgList, new SysOrg { Id = 0, Level = 0 });
orgList.Add(sysOrg);
SearchDnLdapDept(ldapConn, sysLdap, orgList, ldapEntry.Dn, sysOrg);
}
if (orgList.Count == 0)
@ -369,13 +402,12 @@ public class SysLdapService : IDynamicApiController, ITransient
}
var attrs = ldapEntry.GetAttributeSet();
if (attrs.Count == 0 || attrs.ContainsKey("OU"))
{
var sysOrg = CreateSysOrg(attrs, sysLdap, listOrgs, org);
listOrgs.Add(sysOrg);
if (attrs.Count != 0 && !attrs.ContainsKey("OU")) continue;
SearchDnLdapDept(ldapConn, sysLdap, listOrgs, ldapEntry.Dn, sysOrg);
}
var sysOrg = CreateSysOrg(attrs, sysLdap, listOrgs, org);
listOrgs.Add(sysOrg);
SearchDnLdapDept(ldapConn, sysLdap, listOrgs, ldapEntry.Dn, sysOrg);
}
}

View File

@ -4,7 +4,6 @@
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using NewLife.Reflection;
using Newtonsoft.Json;
namespace Admin.NET.Core.Service;
@ -66,8 +65,7 @@ public class SysCacheService : IDynamicApiController, ISingleton
[NonAction]
public bool Set(string key, object value)
{
if (string.IsNullOrWhiteSpace(key)) return false;
return _cacheProvider.Cache.Set($"{_cacheOptions.Prefix}{key}", value);
return !string.IsNullOrWhiteSpace(key) && _cacheProvider.Cache.Set($"{_cacheOptions.Prefix}{key}", value);
}
/// <summary>
@ -80,8 +78,7 @@ public class SysCacheService : IDynamicApiController, ISingleton
[NonAction]
public bool Set(string key, object value, TimeSpan expire)
{
if (string.IsNullOrWhiteSpace(key)) return false;
return _cacheProvider.Cache.Set($"{_cacheOptions.Prefix}{key}", value, expire);
return !string.IsNullOrWhiteSpace(key) && _cacheProvider.Cache.Set($"{_cacheOptions.Prefix}{key}", value, expire);
}
public async Task<TR> AdGetAsync<TR>(String cacheName, Func<Task<TR>> del, TimeSpan? expiry = default(TimeSpan?)) where TR : class
@ -140,28 +137,15 @@ public class SysCacheService : IDynamicApiController, ISingleton
private static string Key(string cacheName, object[] obs)
{
foreach (var obj in obs)
{
if (obj is TimeSpan)
{
throw new Exception("缓存参数类型不能能是:TimeSpan类型");
}
}
StringBuilder sb = new StringBuilder(cacheName + ":");
foreach (var a in obs)
{
sb.Append($@"<{KeySingle(a)}>");
}
if (obs.OfType<TimeSpan>().Any()) throw new Exception("缓存参数类型不能能是:TimeSpan类型");
StringBuilder sb = new(cacheName + ":");
foreach (var a in obs) sb.Append($"<{KeySingle(a)}>");
return sb.ToString();
}
public static string KeySingle(object t)
private static string KeySingle(object t)
{
if (t.GetType().IsClass && !t.GetType().IsPrimitive)
{
return JsonConvert.SerializeObject(t);
}
return t?.ToString();
return t.GetType().IsClass && !t.GetType().IsPrimitive ? JsonConvert.SerializeObject(t) : t.ToString();
}
/// <summary>
@ -327,6 +311,23 @@ public class SysCacheService : IDynamicApiController, ISingleton
hash.Add(hashKey, value);
}
/// <summary>
/// 添加或更新一条HASH
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="hashKey"></param>
/// <param name="value"></param>
[NonAction]
public void HashAddOrUpdate<T>(string key, string hashKey, T value)
{
var hash = GetHashMap<T>(key);
if (hash.ContainsKey(hashKey))
hash[hashKey] = value;
else
hash.Add(hashKey, value);
}
/// <summary>
/// 获取多条HASH
/// </summary>
@ -352,12 +353,7 @@ public class SysCacheService : IDynamicApiController, ISingleton
public T HashGetOne<T>(string key, string field)
{
var hash = GetHashMap<T>(key);
var value = hash.GetValue(field);
if (value == null)
{
return default(T);
}
return (T)hash.GetValue(field);
return hash.ContainsKey(field) ? hash[field] : default(T);
}
/// <summary>

View File

@ -6,6 +6,9 @@
namespace Admin.NET.Core.Service;
/// <summary>
/// 自定义模板引擎
/// </summary>
public class CustomViewEngine : ViewEngineModel
{
private readonly ISqlSugarClient _db;

View File

@ -161,7 +161,7 @@ public class CodeGenConfig
get
{
string str = "";
if (EffectType == "fk")
if (EffectType == "ForeignKey")
{
str = LowerFkEntityName + "_FK_" + LowerFkColumnName;
}

View File

@ -9,7 +9,7 @@ namespace Admin.NET.Core.Service;
/// <summary>
/// 代码生成参数类
/// </summary>
public class PageCodeGenInput : BasePageInput
public class CodeGenInput : BasePageInput
{
/// <summary>
/// 作者姓名
@ -112,7 +112,7 @@ public class PageCodeGenInput : BasePageInput
public virtual bool IsApiService { get; set; }
}
public class AddCodeGenInput : PageCodeGenInput
public class AddCodeGenInput : CodeGenInput
{
/// <summary>
/// 数据库表名

View File

@ -0,0 +1,31 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 表唯一配置项
/// </summary>
public class TableUniqueConfigItem
{
/// <summary>
/// 字段列表
/// </summary>
public List<string> Columns { get; set; }
/// <summary>
/// 描述信息
/// </summary>
public string Message { get; set; }
/// <summary>
/// 格式化查询条件
/// </summary>
/// <param name="separator">分隔符</param>
/// <param name="format">模板字符串</param>
/// <returns></returns>
public string Format(string separator, string format) => string.Join(separator, Columns.Select(name => string.Format(format, name)));
}

View File

@ -39,7 +39,7 @@ public class SysCodeGenService : IDynamicApiController, ITransient
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("获取代码生成分页列表")]
public async Task<SqlSugarPagedList<SysCodeGen>> Page(PageCodeGenInput input)
public async Task<SqlSugarPagedList<SysCodeGen>> Page(CodeGenInput input)
{
return await _db.Queryable<SysCodeGen>()
.Includes(u => u.CodeGenTemplateRelations)
@ -405,7 +405,7 @@ public class SysCodeGenService : IDynamicApiController, ITransient
#endregion
var queryWhetherList = tableFieldList.Where(u => u.QueryWhether == YesNoEnum.Y.ToString()).ToList(); // 前端查询集合
var joinTableList = tableFieldList.Where(u => u.EffectType == "Upload" || u.EffectType == "fk" || u.EffectType == "ApiTreeSelect").ToList(); // 需要连表查询的字段
var joinTableList = tableFieldList.Where(u => u.EffectType == "Upload" || u.EffectType == "ForeignKey" || u.EffectType == "ApiTreeSelector").ToList(); // 需要连表查询的字段
(string joinTableNames, string lowerJoinTableNames) = GetJoinTableStr(joinTableList); // 获取连表的实体名和别名
var data = new CustomViewEngine(_db)
@ -547,7 +547,7 @@ public class SysCodeGenService : IDynamicApiController, ITransient
#endregion
var queryWhetherList = tableFieldList.Where(u => u.QueryWhether == YesNoEnum.Y.ToString()).ToList(); // 前端查询集合
var joinTableList = tableFieldList.Where(u => u.EffectType == "Upload" || u.EffectType == "fk" || u.EffectType == "ApiTreeSelect").ToList(); // 需要连表查询的字段
var joinTableList = tableFieldList.Where(u => u.EffectType == "Upload" || u.EffectType == "ForeignKey" || u.EffectType == "ApiTreeSelector").ToList(); // 需要连表查询的字段
(string joinTableNames, string lowerJoinTableNames) = GetJoinTableStr(joinTableList); // 获取连表的实体名和别名
var data = new CustomViewEngine(_db)
@ -628,7 +628,7 @@ public class SysCodeGenService : IDynamicApiController, ITransient
private static (string, string) GetJoinTableStr(List<CodeGenConfig> configs)
{
var uploads = configs.Where(u => u.EffectType == "Upload").ToList();
var fks = configs.Where(u => u.EffectType == "fk").ToList();
var fks = configs.Where(u => u.EffectType == "ForeignKey").ToList();
string str = ""; // <Order, OrderItem, Custom>
string lowerStr = ""; // (o, i, c)
foreach (var item in uploads)
@ -855,9 +855,9 @@ public class SysCodeGenService : IDynamicApiController, ITransient
menuOrder += 10;
var menuList = new List<SysMenu>() { menuTypePage, menuTypeDetail, menuTypeAdd, menuTypeDelete, menuTypeUpdate, menuTypePrint, menuTypeImport, menuTypeExport };
// 加入fk、Upload、ApiTreeSelect 等接口的权限
// 加入ForeignKey、Upload、ApiTreeSelector 等接口的权限
// 在生成表格时有些字段只是查询时显示不需要填写WhetherAddUpdate所以这些字段没必要生成相应接口
var fkTableList = tableFieldList.Where(u => u.EffectType == "fk" && (u.WhetherAddUpdate == "Y" || u.QueryWhether == "Y")).ToList();
var fkTableList = tableFieldList.Where(u => u.EffectType == "ForeignKey" && (u.WhetherAddUpdate == "Y" || u.QueryWhether == "Y")).ToList();
foreach (var @column in fkTableList)
{
var menuType1 = new SysMenu
@ -873,7 +873,7 @@ public class SysCodeGenService : IDynamicApiController, ITransient
menuOrder += 10;
menuList.Add(menuType1);
}
var treeSelectTableList = tableFieldList.Where(u => u.EffectType == "ApiTreeSelect").ToList();
var treeSelectTableList = tableFieldList.Where(u => u.EffectType == "ApiTreeSelector").ToList();
foreach (var @column in treeSelectTableList)
{
var menuType1 = new SysMenu

View File

@ -192,8 +192,7 @@ public class SysCommonService : IDynamicApiController, ITransient
var userId = App.User?.FindFirst(ClaimConst.UserId)?.Value;
var resultStream = App.GetRequiredService<SysCacheService>().Get<MemoryStream>(CacheConst.KeyExcelTemp + userId);
if (resultStream == null)
throw Oops.Oh("错误标记文件已过期。");
if (resultStream == null) throw Oops.Oh("错误标记文件已过期。");
return await Task.FromResult(new FileStreamResult(resultStream, "application/octet-stream")
{

View File

@ -61,8 +61,7 @@ public class SysConfigService : IDynamicApiController, ITransient
public async Task AddConfig(AddConfigInput input)
{
var isExist = await _sysConfigRep.IsAnyAsync(u => u.Name == input.Name || u.Code == input.Code);
if (isExist)
throw Oops.Oh(ErrorCodeEnum.D9000);
if (isExist) throw Oops.Oh(ErrorCodeEnum.D9000);
await _sysConfigRep.InsertAsync(input.Adapt<SysConfig>());
}
@ -78,8 +77,7 @@ public class SysConfigService : IDynamicApiController, ITransient
public async Task UpdateConfig(UpdateConfigInput input)
{
var isExist = await _sysConfigRep.IsAnyAsync(u => (u.Name == input.Name || u.Code == input.Code) && u.Id != input.Id);
if (isExist)
throw Oops.Oh(ErrorCodeEnum.D9000);
if (isExist) throw Oops.Oh(ErrorCodeEnum.D9000);
//// 若修改国密SM2密匙则密码重新加密
//if (input.Code == ConfigConst.SysSM2Key && CryptogramUtil.CryptoType == CryptogramEnum.SM2.ToString())
@ -96,7 +94,7 @@ public class SysConfigService : IDynamicApiController, ITransient
var config = input.Adapt<SysConfig>();
await _sysConfigRep.AsUpdateable(config).IgnoreColumns(true).ExecuteCommandAsync();
_sysCacheService.Remove($"{CacheConst.KeyConfig}{config.Code}");
RemoveConfigCache(config);
}
/// <summary>
@ -109,12 +107,12 @@ public class SysConfigService : IDynamicApiController, ITransient
public async Task DeleteConfig(DeleteConfigInput input)
{
var config = await _sysConfigRep.GetByIdAsync(input.Id);
if (config.SysFlag == YesNoEnum.Y) // 禁止删除系统参数
throw Oops.Oh(ErrorCodeEnum.D9001);
// 禁止删除系统参数
if (config.SysFlag == YesNoEnum.Y) throw Oops.Oh(ErrorCodeEnum.D9001);
await _sysConfigRep.DeleteAsync(config);
_sysCacheService.Remove($"{CacheConst.KeyConfig}{config.Code}");
RemoveConfigCache(config);
}
/// <summary>
@ -129,12 +127,12 @@ public class SysConfigService : IDynamicApiController, ITransient
foreach (var id in ids)
{
var config = await _sysConfigRep.GetByIdAsync(id);
if (config.SysFlag == YesNoEnum.Y) // 禁止删除系统参数
continue;
// 禁止删除系统参数
if (config.SysFlag == YesNoEnum.Y) continue;
await _sysConfigRep.DeleteAsync(config);
_sysCacheService.Remove($"{CacheConst.KeyConfig}{config.Code}");
RemoveConfigCache(config);
}
}
@ -207,7 +205,7 @@ public class SysConfigService : IDynamicApiController, ITransient
config.Value = value;
await _sysConfigRep.AsUpdateable(config).ExecuteCommandAsync();
_sysCacheService.Remove($"{CacheConst.KeyConfig}{config.Code}");
RemoveConfigCache(config);
}
/// <summary>
@ -256,10 +254,13 @@ public class SysConfigService : IDynamicApiController, ITransient
[DisplayName("批量更新参数配置值")]
public async Task BatchUpdateConfig(List<BatchConfigInput> input)
{
foreach (var Config in input)
foreach (var config in input)
{
await _sysConfigRep.AsUpdateable().SetColumns(u => u.Value == Config.Value).Where(u => u.Code == Config.Code).ExecuteCommandAsync();
_sysCacheService.Remove($"{CacheConst.KeyConfig}{Config.Code}");
var configInfo = await _sysConfigRep.GetFirstAsync(u => u.Code == config.Code);
if (configInfo == null) continue;
await _sysConfigRep.AsUpdateable().SetColumns(u => u.Value == config.Value).Where(u => u.Code == config.Code).ExecuteCommandAsync();
RemoveConfigCache(configInfo);
}
}
@ -354,4 +355,16 @@ public class SysConfigService : IDynamicApiController, ITransient
await UpdateConfigValue(ConfigConst.SysSecondVer, (input.SysSecondVer ?? false).ToString());
await UpdateConfigValue(ConfigConst.SysCaptcha, (input.SysCaptcha ?? true).ToString());
}
/// <summary>
/// 清除配置缓存
/// </summary>
/// <param name="config"></param>
private void RemoveConfigCache(SysConfig config)
{
_sysCacheService.Remove($"{CacheConst.KeyConfig}Value:{config.Code}");
_sysCacheService.Remove($"{CacheConst.KeyConfig}Remark:{config.Code}");
_sysCacheService.Remove($"{CacheConst.KeyConfig}{config.GroupCode}:GroupWithCache");
_sysCacheService.Remove($"{CacheConst.KeyConfig}{config.Code}");
}
}

View File

@ -27,19 +27,19 @@ public class SysConstService : IDynamicApiController, ITransient
public async Task<List<ConstOutput>> GetList()
{
var key = $"{CacheConst.KeyConst}list";
var constlist = _sysCacheService.Get<List<ConstOutput>>(key);
if (constlist == null)
var constList = _sysCacheService.Get<List<ConstOutput>>(key);
if (constList == null)
{
var typeList = GetConstAttributeList();
constlist = typeList.Select(u => new ConstOutput
constList = typeList.Select(u => new ConstOutput
{
Name = u.CustomAttributes.ToList().FirstOrDefault()?.ConstructorArguments.ToList().FirstOrDefault().Value?.ToString() ?? u.Name,
Code = u.Name,
Data = GetData(Convert.ToString(u.Name))
}).ToList();
_sysCacheService.Set(key, constlist);
_sysCacheService.Set(key, constList);
}
return await Task.FromResult(constlist);
return await Task.FromResult(constList);
}
/// <summary>
@ -51,25 +51,25 @@ public class SysConstService : IDynamicApiController, ITransient
public async Task<List<ConstOutput>> GetData([Required] string typeName)
{
var key = $"{CacheConst.KeyConst}{typeName.ToUpper()}";
var constlist = _sysCacheService.Get<List<ConstOutput>>(key);
if (constlist == null)
var constList = _sysCacheService.Get<List<ConstOutput>>(key);
if (constList == null)
{
var typeList = GetConstAttributeList();
var type = typeList.FirstOrDefault(u => u.Name == typeName);
if (type != null)
{
var isEnum = type.BaseType.Name == "Enum";
constlist = type.GetFields()?
var isEnum = type.BaseType!.Name == "Enum";
constList = type.GetFields()?
.Where(isEnum, u => u.FieldType.Name == typeName)
.Select(u => new ConstOutput
{
Name = u.Name,
Code = isEnum ? (int)u.GetValue(BindingFlags.Instance) : u.GetValue(BindingFlags.Instance)
Code = isEnum ? (int)u.GetValue(BindingFlags.Instance)! : u.GetValue(BindingFlags.Instance)
}).ToList();
_sysCacheService.Set(key, constlist);
_sysCacheService.Set(key, constList);
}
}
return await Task.FromResult(constlist);
return await Task.FromResult(constList);
}
/// <summary>

View File

@ -4,8 +4,6 @@
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Npgsql;
namespace Admin.NET.Core.Service;
@ -213,7 +211,7 @@ public class SysDatabaseService : IDynamicApiController, ITransient
var typeBuilder = db.DynamicBuilder().CreateClass(input.TableName, new SugarTable() { TableName = input.TableName, TableDescription = input.Description });
input.DbColumnInfoList.ForEach(u =>
{
var dbColumnName = config.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(u.DbColumnName.Trim()) : u.DbColumnName.Trim();
var dbColumnName = config!.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(u.DbColumnName.Trim()) : u.DbColumnName.Trim();
// 虚拟类都默认string类型具体以列数据类型为准
typeBuilder.CreateProperty(dbColumnName, typeof(string), new SugarColumn()
{
@ -339,67 +337,60 @@ public class SysDatabaseService : IDynamicApiController, ITransient
input.EntityName = entityType.Name;
input.SeedDataName = entityType.Name + "SeedData";
if (!string.IsNullOrWhiteSpace(input.Suffix))
input.SeedDataName += input.Suffix;
var targetPath = GetSeedDataTargetPath(input);
if (!string.IsNullOrWhiteSpace(input.Suffix)) input.SeedDataName += input.Suffix;
// 查询所有数据
var query = db.QueryableByObject(entityType);
DbColumnInfo orderField = null; // 排序字段
// 优先用创建时间排序
orderField = dbColumnInfos.Where(u => u.DbColumnName.ToLower() == "create_time" || u.DbColumnName.ToLower() == "createtime").FirstOrDefault();
if (orderField != null)
query.OrderBy(orderField.DbColumnName);
// 其次用Id排序
orderField = dbColumnInfos.Where(u => u.DbColumnName.ToLower() == "id").FirstOrDefault();
if (orderField != null)
query.OrderBy(orderField.DbColumnName);
IEnumerable recordsTmp = (IEnumerable)query.ToList();
List<dynamic> records = recordsTmp.ToDynamicList();
DbColumnInfo orderField = dbColumnInfos.FirstOrDefault(u => u.DbColumnName.ToLower() == "create_time" || u.DbColumnName.ToLower() == "createtime");
if (orderField != null) query = query.OrderBy(orderField.DbColumnName);
// 再使用第一个主键排序
query = query.OrderBy(dbColumnInfos.First(u => u.IsPrimarykey).DbColumnName);
var records = ((IEnumerable)await query.ToListAsync()).ToDynamicList();
// 过滤已存在的数据
if (input.FilterExistingData && records.Count() > 0)
if (input.FilterExistingData && records.Any())
{
// 获取实体类型
// 获取实体类型-所有种数据数据类型
var entityTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false) && u.FullName.EndsWith("." + input.EntityName))
.Where(u => !u.GetCustomAttributes<IgnoreTableAttribute>().Any()).ToList();
// 只有一个实体匹配才能过滤
if (entityTypes.Count == 1)
.Where(u => !u.GetCustomAttributes<IgnoreTableAttribute>().Any())
.ToList();
if (entityTypes.Count == 1) // 只有一个实体匹配才能过滤
{
// 获取实体的主键对应的属性名称
var pkInfo = entityTypes[0].GetProperties().Where(u => u.GetCustomAttribute<SugarColumn>() != null && u.GetCustomAttribute<SugarColumn>().IsPrimaryKey).First();
var pkInfo = entityTypes[0].GetProperties().FirstOrDefault(u => u.GetCustomAttribute<SugarColumn>()?.IsPrimaryKey == true);
if (pkInfo != null)
{
var seedDataTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass
&& u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>)) && i.GenericTypeArguments[0] == entityTypes[0])).ToList();
var seedDataTypes = App.EffectiveTypes
.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces().Any(
i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>)) && i.GenericTypeArguments[0] == entityTypes[0]
)
)
.ToList();
// 可能会重名的种子数据不作为过滤项
string doNotFilterfullName1 = $"{input.Position}.SeedData.{input.SeedDataName}";
string doNotFilterfullName2 = $"{input.Position}.{input.SeedDataName}"; // Core中的命名空间没有SeedData
string doNotFilterFullName1 = $"{input.Position}.SeedData.{input.SeedDataName}";
string doNotFilterFullName2 = $"{input.Position}.{input.SeedDataName}"; // Core中的命名空间没有SeedData
PropertyInfo idPropertySeedData = records[0].GetType().GetProperty("Id");
for (int i = seedDataTypes.Count - 1; i >= 0; i--)
{
string fullName = seedDataTypes[i].FullName;
if ((fullName == doNotFilterfullName1) || (fullName == doNotFilterfullName2))
continue;
// 开始删除重复数据
if ((fullName == doNotFilterFullName1) || (fullName == doNotFilterFullName2)) continue;
// 删除重复数据
var instance = Activator.CreateInstance(seedDataTypes[i]);
var hasDataMethod = seedDataTypes[i].GetMethod("HasData");
var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast<object>();
if (seedData == null) continue;
var recordsToRemove = new List<object>();
List<object> recordsToRemove = new();
foreach (var record in records)
{
object recordId = pkInfo.GetValue(record);
foreach (var d1 in seedData)
if (seedData.Select(d1 => idPropertySeedData.GetValue(d1)).Any(dataId => recordId != null && dataId != null && recordId.Equals(dataId)))
{
object dataId = idPropertySeedData.GetValue(d1);
if (recordId != null && dataId != null && recordId.Equals(dataId))
{
recordsToRemove.Add(record);
break;
}
recordsToRemove.Add(record);
}
}
foreach (var itemToRemove in recordsToRemove)
@ -410,39 +401,71 @@ public class SysDatabaseService : IDynamicApiController, ITransient
}
}
}
var timeConverter = new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" };
var recordsJSON = JsonConvert.SerializeObject(records, Formatting.Indented, timeConverter);
// 检查有没有 System.Text.Json.Serialization.JsonIgnore 的属性
var jsonIgnoreProperties = entityType.GetProperties().Where(p => (p.GetAttribute<System.Text.Json.Serialization.JsonIgnoreAttribute>() != null ||
p.GetAttribute<JsonIgnoreAttribute>() != null) && p.GetAttribute<SugarColumn>() != null).ToList();
var jsonIgnoreInfo = new List<List<JsonIgnoredPropertyData>>();
if (jsonIgnoreProperties.Count > 0)
{
int recordIndex = 0;
foreach (var r in (IEnumerable)records)
{
List<JsonIgnoredPropertyData> record = new();
foreach (var item in jsonIgnoreProperties)
{
object v = item.GetValue(r);
string strValue = "null";
if (v != null)
{
strValue = v.ToString();
if (v.GetType() == typeof(string))
strValue = "\"" + strValue + "\"";
else if (v.GetType() == typeof(DateTime))
strValue = "DateTime.Parse(\"" + ((DateTime)v).ToString("yyyy-MM-dd HH:mm:ss") + "\")";
}
record.Add(new JsonIgnoredPropertyData { RecordIndex = recordIndex, Name = item.Name, Value = strValue });
}
recordIndex++;
jsonIgnoreInfo.Add(record);
}
}
// var jsonIgnoreProperties = entityType.GetProperties().Where(p => (p.GetAttribute<System.Text.Json.Serialization.JsonIgnoreAttribute>() != null ||
// p.GetAttribute<JsonIgnoreAttribute>() != null) && p.GetAttribute<SugarColumn>() != null).ToList();
// var jsonIgnoreInfo = new List<List<JsonIgnoredPropertyData>>();
// if (jsonIgnoreProperties.Count > 0)
// {
// int recordIndex = 0;
// foreach (var r in (IEnumerable)records)
// {
// List<JsonIgnoredPropertyData> record = new();
// foreach (var item in jsonIgnoreProperties)
// {
// object v = item.GetValue(r);
// string strValue = "null";
// if (v != null)
// {
// strValue = v.ToString();
// if (v.GetType() == typeof(string))
// strValue = "\"" + strValue + "\"";
// else if (v.GetType() == typeof(DateTime))
// strValue = "DateTime.Parse(\"" + ((DateTime)v).ToString("yyyy-MM-dd HH:mm:ss") + "\")";
// }
// record.Add(new JsonIgnoredPropertyData { RecordIndex = recordIndex, Name = item.Name, Value = strValue });
// }
// recordIndex++;
// jsonIgnoreInfo.Add(record);
// }
// }
var tContent = File.ReadAllText(templatePath);
// 获取所有字段信息
var propertyList = entityType.GetProperties().Where(x => false == (x.GetCustomAttribute<SugarColumn>()?.IsIgnore ?? false)).ToList();
for (var i = 0; i < propertyList.Count; i++)
{
if (propertyList[i].Name != nameof(EntityBaseId.Id) || !(propertyList[i].GetCustomAttribute<SugarColumn>()?.IsPrimaryKey ?? true)) continue;
var temp = propertyList[i];
for (var j = i; j > 0; j--) propertyList[j] = propertyList[j - 1];
propertyList[0] = temp;
}
// 拼接数据
var recordList = records.Select(obj => string.Join(", ", propertyList.Select(prop =>
{
var propType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
object value = prop.GetValue(obj);
if (value == null) value = "null";
else if (propType == typeof(string))
{
value = $"\"{value}\"";
}
else if (propType.IsEnum)
{
value = $"{propType.Name}.{value}";
}
else if (propType == typeof(bool))
{
value = (bool)value ? "true" : "false";
}
else if (propType == typeof(DateTime))
{
value = $"DateTime.Parse(\"{((DateTime)value):yyyy-MM-dd HH:mm:ss.fff}\")";
}
return $"{prop.Name}={value}";
}))).ToList();
var tContent = await File.ReadAllTextAsync(templatePath);
var data = new
{
NameSpace = $"{input.Position}.SeedData",
@ -452,17 +475,19 @@ public class SysDatabaseService : IDynamicApiController, ITransient
input.SeedDataName,
input.ConfigId,
tableInfo.Description,
JsonIgnoreInfo = jsonIgnoreInfo,
RecordsJSON = recordsJSON
// JsonIgnoreInfo = jsonIgnoreInfo,
RecordList = recordList
};
var tResult = _viewEngine.RunCompile(tContent, data, builderAction: builder =>
var tResult = await _viewEngine.RunCompileAsync(tContent, data, builderAction: builder =>
{
builder.AddAssemblyReferenceByName("System.Linq");
builder.AddAssemblyReferenceByName("System.Collections");
builder.AddUsing("System.Collections.Generic");
builder.AddUsing("System.Linq");
});
File.WriteAllText(targetPath, tResult, Encoding.UTF8);
var targetPath = GetSeedDataTargetPath(input);
await File.WriteAllTextAsync(targetPath, tResult, Encoding.UTF8);
}
/// <summary>
@ -477,27 +502,13 @@ public class SysDatabaseService : IDynamicApiController, ITransient
var types = new List<Type>();
if (_codeGenOptions.EntityAssemblyNames != null)
{
foreach (var assemblyName in _codeGenOptions.EntityAssemblyNames)
foreach (var asm in _codeGenOptions.EntityAssemblyNames.Select(Assembly.Load))
{
Assembly asm = Assembly.Load(assemblyName);
types.AddRange(asm.GetExportedTypes().ToList());
}
}
bool IsMyAttribute(Attribute[] o)
{
foreach (Attribute a in o)
{
if (a.GetType() == type)
return true;
}
return false;
}
Type[] cosType = types.Where(o =>
{
return IsMyAttribute(Attribute.GetCustomAttributes(o, true));
}
).ToArray();
Type[] cosType = types.Where(u => IsMyAttribute(Attribute.GetCustomAttributes(u, true))).ToArray();
foreach (var c in cosType)
{
var sugarAttribute = c.GetCustomAttributes(type, true)?.FirstOrDefault();
@ -517,6 +528,11 @@ public class SysDatabaseService : IDynamicApiController, ITransient
});
}
return await Task.FromResult(entityInfos);
bool IsMyAttribute(Attribute[] o)
{
return o.Any(a => a.GetType() == type);
}
}
/// <summary>

View File

@ -71,6 +71,8 @@ public class SysDictTypeService : IDynamicApiController, ITransient
[DisplayName("添加字典类型")]
public async Task AddDictType(AddDictTypeInput input)
{
if (input.Code.ToLower().EndsWith("enum")) throw Oops.Oh(ErrorCodeEnum.D3006);
var isExist = await _sysDictTypeRep.IsAnyAsync(u => u.Code == input.Code);
if (isExist) throw Oops.Oh(ErrorCodeEnum.D3001);
@ -87,10 +89,12 @@ public class SysDictTypeService : IDynamicApiController, ITransient
[DisplayName("更新字典类型")]
public async Task UpdateDictType(UpdateDictTypeInput input)
{
var isExist = await _sysDictTypeRep.IsAnyAsync(u => u.Id == input.Id);
if (!isExist) throw Oops.Oh(ErrorCodeEnum.D3000);
var dict = await _sysDictTypeRep.GetFirstAsync(x => x.Id == input.Id);
if (dict == null) throw Oops.Oh(ErrorCodeEnum.D3000);
isExist = await _sysDictTypeRep.IsAnyAsync(u => u.Code == input.Code && u.Id != input.Id);
if (dict.Code.ToLower().EndsWith("enum") && input.Code != dict.Code) throw Oops.Oh(ErrorCodeEnum.D3007);
var isExist = await _sysDictTypeRep.IsAnyAsync(u => u.Code == input.Code && u.Id != input.Id);
if (isExist) throw Oops.Oh(ErrorCodeEnum.D3001);
_sysCacheService.Remove($"{CacheConst.KeyDict}{input.Code}");

View File

@ -32,12 +32,7 @@ public class SysEnumService : IDynamicApiController, ITransient
.OrderBy(u => u.Name).ThenBy(u => u.FullName)
.ToList();
var result = new List<EnumTypeOutput>();
foreach (var item in enumTypeList)
{
result.Add(GetEnumDescription(item));
}
return result;
return enumTypeList.Select(GetEnumDescription).ToList();
}
/// <summary>

View File

@ -85,8 +85,13 @@ public class UploadFileInput
/// <summary>
/// 上传文件Base64
/// </summary>
public class UploadFileFromBase64Input : SysFile
public class UploadFileFromBase64Input
{
/// <summary>
/// 文件名
/// </summary>
public string FileName { get; set; }
/// <summary>
/// 文件内容
/// </summary>

View File

@ -94,14 +94,11 @@ public class SysFileService : IDynamicApiController, ITransient
/// <param name="files"></param>
/// <returns></returns>
[DisplayName("上传多文件")]
public async Task<List<SysFile>> UploadFiles([Required] List<IFormFile> files)
public List<SysFile> UploadFiles([Required] List<IFormFile> files)
{
var filelist = new List<SysFile>();
foreach (var file in files)
{
filelist.Add(await UploadFile(new UploadFileInput { File = file }));
}
return filelist;
var fileList = new List<SysFile>();
files.ForEach(file => fileList.Add(UploadFile(new UploadFileInput { File = file }).Result));
return fileList;
}
/// <summary>
@ -114,26 +111,7 @@ public class SysFileService : IDynamicApiController, ITransient
{
var file = input.Id > 0 ? await GetFile(input.Id) : await _sysFileRep.CopyNew().GetFirstAsync(u => u.Url == input.Url);
var fileName = HttpUtility.UrlEncode(file.FileName, Encoding.GetEncoding("UTF-8"));
var filePath = Path.Combine(file.FilePath, file.Id.ToString() + file.Suffix);
if (_OSSProviderOptions.Enabled)
{
var stream = await (await _OSSService.PresignedGetObjectAsync(file.BucketName.ToString(), filePath, 5)).GetAsStreamAsync();
return new FileStreamResult(stream.Stream, "application/octet-stream") { FileDownloadName = fileName + file.Suffix };
}
else if (App.Configuration["SSHProvider:Enabled"].ToBoolean())
{
using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"],
App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"]))
{
return new FileStreamResult(helper.OpenRead(filePath), "application/octet-stream") { FileDownloadName = fileName + file.Suffix };
}
}
else
{
var path = Path.Combine(App.WebHostEnvironment.WebRootPath, filePath);
return new FileStreamResult(new FileStream(path, FileMode.Open), "application/octet-stream") { FileDownloadName = fileName + file.Suffix };
}
return await GetFileStreamResult(file, fileName);
}
/// <summary>
@ -146,26 +124,33 @@ public class SysFileService : IDynamicApiController, ITransient
{
var file = await GetFile(id);
//var fileName = HttpUtility.UrlEncode(file.FileName, Encoding.GetEncoding("UTF-8"));
var filePath = Path.Combine(file.FilePath, file.Id.ToString() + file.Suffix);
return await GetFileStreamResult(file, file.Id + "");
}
/// <summary>
/// 获取文件流
/// </summary>
/// <param name="file"></param>
/// <param name="fileName"></param>
/// <returns></returns>
private async Task<IActionResult> GetFileStreamResult(SysFile file, string fileName)
{
var filePath = Path.Combine(file.FilePath ?? "", file.Id + file.Suffix);
if (_OSSProviderOptions.Enabled)
{
var stream = await (await _OSSService.PresignedGetObjectAsync(file.BucketName.ToString(), filePath, 5)).GetAsStreamAsync();
return new FileStreamResult(stream.Stream, "application/octet-stream");
var stream = await (await _OSSService.PresignedGetObjectAsync(file.BucketName, filePath, 5)).GetAsStreamAsync();
return new FileStreamResult(stream.Stream, "application/octet-stream") { FileDownloadName = fileName + file.Suffix };
}
else if (App.Configuration["SSHProvider:Enabled"].ToBoolean())
if (App.Configuration["SSHProvider:Enabled"].ToBoolean())
{
using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"],
App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"]))
{
return new FileStreamResult(helper.OpenRead(filePath), "application/octet-stream");
}
}
else
{
var path = Path.Combine(App.WebHostEnvironment.WebRootPath, filePath);
return new FileStreamResult(new FileStream(path, FileMode.Open), "application/octet-stream");
using SSHHelper helper = new(App.Configuration["SSHProvider:Host"],
App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"]);
return new FileStreamResult(helper.OpenRead(filePath), "application/octet-stream") { FileDownloadName = fileName + file.Suffix };
}
var path = Path.Combine(App.WebHostEnvironment.WebRootPath, filePath);
return new FileStreamResult(new FileStream(path, FileMode.Open), "application/octet-stream") { FileDownloadName = fileName + file.Suffix };
}
/// <summary>
@ -186,19 +171,15 @@ public class SysFileService : IDynamicApiController, ITransient
byte[] fileBytes = await response.Content.ReadAsByteArrayAsync();
return Convert.ToBase64String(fileBytes);
}
else
{
throw new HttpRequestException($"Request failed with status code: {response.StatusCode}");
}
throw new HttpRequestException($"Request failed with status code: {response.StatusCode}");
}
else if (App.Configuration["SSHProvider:Enabled"].ToBoolean())
if (App.Configuration["SSHProvider:Enabled"].ToBoolean())
{
var sysFile = await _sysFileRep.CopyNew().GetFirstAsync(u => u.Url == url) ?? throw Oops.Oh($"文件不存在");
using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"],
App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"]))
{
return Convert.ToBase64String(helper.ReadAllBytes(sysFile.FilePath));
}
using SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"],
App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"]);
return Convert.ToBase64String(helper.ReadAllBytes(sysFile.FilePath));
}
else
{
@ -213,7 +194,7 @@ public class SysFileService : IDynamicApiController, ITransient
Log.Error($"DownloadFileBase64:文件[{realFile}]不存在");
throw Oops.Oh($"文件[{sysFile.FilePath}]不存在");
}
byte[] fileBytes = File.ReadAllBytes(realFile);
byte[] fileBytes = await File.ReadAllBytesAsync(realFile);
return Convert.ToBase64String(fileBytes);
}
}
@ -234,22 +215,19 @@ public class SysFileService : IDynamicApiController, ITransient
if (_OSSProviderOptions.Enabled)
{
await _OSSService.RemoveObjectAsync(file.BucketName.ToString(), string.Concat(file.FilePath, "/", $"{input.Id}{file.Suffix}"));
await _OSSService.RemoveObjectAsync(file.BucketName, string.Concat(file.FilePath, "/", $"{input.Id}{file.Suffix}"));
}
else if (App.Configuration["SSHProvider:Enabled"].ToBoolean())
{
var fullPath = string.Concat(file.FilePath, "/", file.Id + file.Suffix);
using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"],
App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"]))
{
helper.DeleteFile(fullPath);
}
using SSHHelper helper = new(App.Configuration["SSHProvider:Host"],
App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"]);
helper.DeleteFile(fullPath);
}
else
{
var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, file.FilePath, input.Id.ToString() + file.Suffix);
if (File.Exists(filePath))
File.Delete(filePath);
var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, file.FilePath ?? "", input.Id + file.Suffix);
if (File.Exists(filePath)) File.Delete(filePath);
}
}
}
@ -311,7 +289,7 @@ public class SysFileService : IDynamicApiController, ITransient
var fileMd5 = string.Empty;
if (_uploadOptions.EnableMd5)
{
using (var fileStream = input.File.OpenReadStream())
await using (var fileStream = input.File.OpenReadStream())
{
fileMd5 = OssUtils.ComputeContentMd5(fileStream, fileStream.Length);
}
@ -322,12 +300,10 @@ public class SysFileService : IDynamicApiController, ITransient
}
// 验证文件类型
if (!_uploadOptions.ContentType.Contains(input.File.ContentType))
throw Oops.Oh($"{ErrorCodeEnum.D8001}:{input.File.ContentType}");
if (!_uploadOptions.ContentType.Contains(input.File.ContentType)) throw Oops.Oh($"{ErrorCodeEnum.D8001}:{input.File.ContentType}");
// 验证文件大小
if (sizeKb > _uploadOptions.MaxSize)
throw Oops.Oh($"{ErrorCodeEnum.D8002},允许最大:{_uploadOptions.MaxSize}KB");
if (sizeKb > _uploadOptions.MaxSize) throw Oops.Oh($"{ErrorCodeEnum.D8002},允许最大:{_uploadOptions.MaxSize}KB");
// 获取文件后缀
var suffix = Path.GetExtension(input.File.FileName).ToLower(); // 后缀
@ -341,14 +317,11 @@ public class SysFileService : IDynamicApiController, ITransient
if (suffix == ".jpeg" || suffix == ".jpe")
suffix = ".jpg";
}
if (string.IsNullOrWhiteSpace(suffix))
throw Oops.Oh(ErrorCodeEnum.D8003);
if (string.IsNullOrWhiteSpace(suffix)) throw Oops.Oh(ErrorCodeEnum.D8003);
// 防止客户端伪造文件类型
if (!string.IsNullOrWhiteSpace(input.AllowSuffix) && !input.AllowSuffix.Contains(suffix))
throw Oops.Oh(ErrorCodeEnum.D8003);
//if (!VerifyFileExtensionName.IsSameType(file.OpenReadStream(), suffix))
// throw Oops.Oh(ErrorCodeEnum.D8001);
if (!string.IsNullOrWhiteSpace(input.AllowSuffix) && !input.AllowSuffix.Contains(suffix)) throw Oops.Oh(ErrorCodeEnum.D8003);
//if (!VerifyFileExtensionName.IsSameType(file.OpenReadStream(), suffix)) throw Oops.Oh(ErrorCodeEnum.D8001);
// 文件存储位置
var path = string.IsNullOrWhiteSpace(input.SavePath) ? _uploadOptions.Path : input.SavePath;
@ -395,11 +368,9 @@ public class SysFileService : IDynamicApiController, ITransient
else if (App.Configuration["SSHProvider:Enabled"].ToBoolean())
{
var fullPath = string.Concat(path.StartsWith('/') ? path : "/" + path, "/", finalName);
using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"],
App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"]))
{
helper.UploadFile(input.File.OpenReadStream(), fullPath);
}
using SSHHelper helper = new(App.Configuration["SSHProvider:Host"],
App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"]);
helper.UploadFile(input.File.OpenReadStream(), fullPath);
}
else
{
@ -409,7 +380,7 @@ public class SysFileService : IDynamicApiController, ITransient
Directory.CreateDirectory(filePath);
var realFile = Path.Combine(filePath, finalName);
using (var stream = File.Create(realFile))
await using (var stream = File.Create(realFile))
{
await input.File.CopyToAsync(stream);
}
@ -495,8 +466,8 @@ public class SysFileService : IDynamicApiController, ITransient
public async Task<List<SysFile>> GetRelationFiles([FromQuery] RelationQueryInput input)
{
return await _sysFileRep.AsQueryable()
.WhereIF(input.RelationId.HasValue && input.RelationId > 0, u => u.RelationId == input.RelationId)
.WhereIF(input.BelongId.HasValue && input.BelongId > 0, u => u.BelongId == input.BelongId.Value)
.WhereIF(input.RelationId is > 0, u => u.RelationId == input.RelationId)
.WhereIF(input.BelongId is > 0, u => u.BelongId == input.BelongId.Value)
.WhereIF(!string.IsNullOrWhiteSpace(input.RelationName), u => u.RelationName == input.RelationName)
.WhereIF(!string.IsNullOrWhiteSpace(input.FileTypes), u => input.GetFileTypeBS().Contains(u.FileType))
.Select(u => new SysFile

View File

@ -33,7 +33,7 @@ public class DbJobPersistence : IJobPersistence
// 获取所有定义的作业
var allJobs = App.EffectiveTypes.ScanToBuilders().ToList();
// 若数据库不存在任何作业,则直接返回
if (!db.Queryable<SysJobDetail>().Any(u => true)) return allJobs;
if (!await db.Queryable<SysJobDetail>().AnyAsync(u => true, stoppingToken)) return allJobs;
// 遍历所有定义的作业
foreach (var schedulerBuilder in allJobs)

View File

@ -11,11 +11,8 @@ namespace Admin.NET.Core.Service;
/// </summary>
public class JobClusterServer : IJobClusterServer
{
private readonly Random rd = new(DateTime.Now.Millisecond);
public JobClusterServer()
{
}
private static readonly SqlSugarRepository<SysJobCluster> _sysJobClusterRep = App.GetRequiredService<SqlSugarRepository<SysJobCluster>>();
private readonly Random _random = new(DateTime.Now.Millisecond);
/// <summary>
/// 当前作业调度器启动通知
@ -23,7 +20,6 @@ public class JobClusterServer : IJobClusterServer
/// <param name="context">作业集群服务上下文</param>
public async void Start(JobClusterContext context)
{
var _sysJobClusterRep = App.GetRequiredService<SqlSugarRepository<SysJobCluster>>();
// 在作业集群表中,如果 clusterId 不存在,则新增一条(否则更新一条),并设置 status 为 ClusterStatus.Waiting
if (await _sysJobClusterRep.IsAnyAsync(u => u.ClusterId == context.ClusterId))
{
@ -47,21 +43,19 @@ public class JobClusterServer : IJobClusterServer
while (true)
{
// 控制集群心跳频率(放在头部为了防止 IsAnyAsync continue 没sleep占用大量IO和CPU
await Task.Delay(3000 + rd.Next(500, 1000)); // 错开集群同时启动
await Task.Delay(3000 + _random.Next(500, 1000)); // 错开集群同时启动
try
{
ICache _cache = App.GetRequiredService<ICacheProvider>().Cache;
ICache cache = App.GetRequiredService<ICacheProvider>().Cache;
// 使用分布式锁
using (_cache.AcquireLock("lock:JobClusterServer:WaitingForAsync", 1000))
using (cache.AcquireLock("lock:JobClusterServer:WaitingForAsync", 1000))
{
var _sysJobClusterRep = App.GetRequiredService<SqlSugarRepository<SysJobCluster>>();
// 在这里查询数据库,根据以下两种情况处理
// 1) 如果作业集群表已有 status 为 ClusterStatus.Working 则继续循环
// 2) 如果作业集群表中还没有其他服务或只有自己,则插入一条集群服务或调用 await WorkNowAsync(clusterId); 之后 return;
// 3) 如果作业集群表中没有 status 为 ClusterStatus.Working 的,调用 await WorkNowAsync(clusterId); 之后 return;
if (await _sysJobClusterRep.IsAnyAsync(u => u.Status == ClusterStatus.Working))
continue;
if (await _sysJobClusterRep.IsAnyAsync(u => u.Status == ClusterStatus.Working)) continue;
await WorkNowAsync(clusterId);
return;
@ -77,7 +71,6 @@ public class JobClusterServer : IJobClusterServer
/// <param name="context">作业集群服务上下文</param>
public async void Stop(JobClusterContext context)
{
var _sysJobClusterRep = App.GetRequiredService<SqlSugarRepository<SysJobCluster>>();
// 在作业集群表中,更新 clusterId 的 status 为 ClusterStatus.Crashed
await _sysJobClusterRep.UpdateAsync(u => new SysJobCluster { Status = ClusterStatus.Crashed }, u => u.ClusterId == context.ClusterId);
}
@ -88,7 +81,6 @@ public class JobClusterServer : IJobClusterServer
/// <param name="context">作业集群服务上下文</param>
public async void Crash(JobClusterContext context)
{
var _sysJobClusterRep = App.GetRequiredService<SqlSugarRepository<SysJobCluster>>();
// 在作业集群表中,更新 clusterId 的 status 为 ClusterStatus.Crashed
await _sysJobClusterRep.UpdateAsync(u => new SysJobCluster { Status = ClusterStatus.Crashed }, u => u.ClusterId == context.ClusterId);
}
@ -100,7 +92,6 @@ public class JobClusterServer : IJobClusterServer
/// <returns></returns>
private static async Task WorkNowAsync(string clusterId)
{
var _sysJobClusterRep = App.GetRequiredService<SqlSugarRepository<SysJobCluster>>();
// 在作业集群表中,更新 clusterId 的 status 为 ClusterStatus.Working
await _sysJobClusterRep.UpdateAsync(u => new SysJobCluster { Status = ClusterStatus.Working }, u => u.ClusterId == clusterId);
}

View File

@ -12,14 +12,13 @@ namespace Admin.NET.Core.Service;
public class JobMonitor : IJobMonitor
{
private readonly IEventPublisher _eventPublisher;
private readonly IServiceScope _serviceScope;
private readonly SysConfigService _sysConfigService;
public JobMonitor(IServiceScopeFactory scopeFactory)
{
_serviceScope = scopeFactory.CreateScope();
_sysConfigService = _serviceScope.ServiceProvider.GetRequiredService<SysConfigService>();
_eventPublisher = _serviceScope.ServiceProvider.GetRequiredService<IEventPublisher>(); ;
var serviceScope = scopeFactory.CreateScope();
_sysConfigService = serviceScope.ServiceProvider.GetRequiredService<SysConfigService>();
_eventPublisher = serviceScope.ServiceProvider.GetRequiredService<IEventPublisher>(); ;
}
public Task OnExecutingAsync(JobExecutingContext context, CancellationToken stoppingToken)

View File

@ -83,8 +83,7 @@ public class SysJobService : IDynamicApiController, ITransient
public async Task AddJobDetail(AddJobDetailInput input)
{
var isExist = await _sysJobDetailRep.IsAnyAsync(u => u.JobId == input.JobId && u.Id != input.Id);
if (isExist)
throw Oops.Oh(ErrorCodeEnum.D1006);
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1006);
// 动态创建作业
Type jobType;
@ -110,9 +109,7 @@ public class SysJobService : IDynamicApiController, ITransient
throw new NotSupportedException();
}
_schedulerFactory.AddJob(
JobBuilder.Create(jobType)
.LoadFrom(input.Adapt<SysJobDetail>()).SetJobType(jobType));
_schedulerFactory.AddJob(JobBuilder.Create(jobType).LoadFrom(input.Adapt<SysJobDetail>()).SetJobType(jobType));
// 延迟一下等待持久化写入,再执行其他字段的更新
await Task.Delay(500);
@ -130,12 +127,10 @@ public class SysJobService : IDynamicApiController, ITransient
public async Task UpdateJobDetail(UpdateJobDetailInput input)
{
var isExist = await _sysJobDetailRep.IsAnyAsync(u => u.JobId == input.JobId && u.Id != input.Id);
if (isExist)
throw Oops.Oh(ErrorCodeEnum.D1006);
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1006);
var sysJobDetail = await _sysJobDetailRep.GetByIdAsync(input.Id);
if (sysJobDetail.JobId != input.JobId)
throw Oops.Oh(ErrorCodeEnum.D1704);
var sysJobDetail = await _sysJobDetailRep.GetFirstAsync(u => u.Id == input.Id);
if (sysJobDetail.JobId != input.JobId) throw Oops.Oh(ErrorCodeEnum.D1704);
var scheduler = _schedulerFactory.GetJob(sysJobDetail.JobId);
var oldScriptCode = sysJobDetail.ScriptCode; // 旧脚本代码
@ -143,8 +138,7 @@ public class SysJobService : IDynamicApiController, ITransient
if (input.CreateType == JobCreateTypeEnum.Script)
{
if (string.IsNullOrEmpty(input.ScriptCode))
throw Oops.Oh(ErrorCodeEnum.D1701);
if (string.IsNullOrEmpty(input.ScriptCode)) throw Oops.Oh(ErrorCodeEnum.D1701);
if (input.ScriptCode != oldScriptCode)
{
@ -153,8 +147,7 @@ public class SysJobService : IDynamicApiController, ITransient
if (jobType.GetCustomAttributes(typeof(JobDetailAttribute)).FirstOrDefault() is not JobDetailAttribute jobDetailAttribute)
throw Oops.Oh(ErrorCodeEnum.D1702);
if (jobDetailAttribute.JobId != input.JobId)
throw Oops.Oh(ErrorCodeEnum.D1703);
if (jobDetailAttribute.JobId != input.JobId) throw Oops.Oh(ErrorCodeEnum.D1703);
scheduler?.UpdateDetail(JobBuilder.Create(jobType).LoadFrom(sysJobDetail).SetJobType(jobType));
}
@ -205,8 +198,7 @@ public class SysJobService : IDynamicApiController, ITransient
public async Task AddJobTrigger(AddJobTriggerInput input)
{
var isExist = await _sysJobTriggerRep.IsAnyAsync(u => u.TriggerId == input.TriggerId && u.Id != input.Id);
if (isExist)
throw Oops.Oh(ErrorCodeEnum.D1006);
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1006);
var jobTrigger = input.Adapt<SysJobTrigger>();
jobTrigger.Args = "[" + jobTrigger.Args + "]";
@ -224,8 +216,7 @@ public class SysJobService : IDynamicApiController, ITransient
public async Task UpdateJobTrigger(UpdateJobTriggerInput input)
{
var isExist = await _sysJobTriggerRep.IsAnyAsync(u => u.TriggerId == input.TriggerId && u.Id != input.Id);
if (isExist)
throw Oops.Oh(ErrorCodeEnum.D1006);
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1006);
var jobTrigger = input.Adapt<SysJobTrigger>();
jobTrigger.Args = "[" + jobTrigger.Args + "]";
@ -303,8 +294,7 @@ public class SysJobService : IDynamicApiController, ITransient
[DisplayName("执行作业")]
public void RunJob(JobDetailInput input)
{
if (_schedulerFactory.TryRunJob(input.JobId, out _) != ScheduleResult.Succeed)
throw Oops.Oh(ErrorCodeEnum.D1705);
if (_schedulerFactory.TryRunJob(input.JobId, out _) != ScheduleResult.Succeed) throw Oops.Oh(ErrorCodeEnum.D1705);
}
/// <summary>

View File

@ -17,6 +17,16 @@ public class MenuInput
/// 菜单类型1目录 2菜单 3按钮
/// </summary>
public MenuTypeEnum? Type { get; set; }
/// <summary>
/// 获取所有菜单
/// </summary>
public bool All { get; set; }
/// <summary>
/// 应用Id
/// </summary>
public long AppId { get; set; }
}
public class AddMenuInput : SysMenu

View File

@ -37,11 +37,13 @@ public class SysEmailService : IDynamicApiController, ITransient
var webTitle = await _sysConfigService.GetConfigValueByCode<string>(ConfigConst.SysWebTitle);
title = string.IsNullOrWhiteSpace(title) ? $"{webTitle} 系统邮件" : title;
var message = new MimeMessage();
message.From.Add(new MailboxAddress(_emailOptions.DefaultFromEmail, _emailOptions.DefaultFromEmail));
if (string.IsNullOrWhiteSpace(toEmail))
message.To.Add(new MailboxAddress(_emailOptions.DefaultToEmail, _emailOptions.DefaultToEmail));
else
message.To.Add(new MailboxAddress(toEmail, toEmail));
message.To.Add(string.IsNullOrWhiteSpace(toEmail)
? new MailboxAddress(_emailOptions.DefaultToEmail, _emailOptions.DefaultToEmail)
: new MailboxAddress(toEmail, toEmail));
message.Subject = title;
message.Body = new TextPart("html")
{

View File

@ -16,15 +16,12 @@ public class SysMessageService : IDynamicApiController, ITransient
{
private readonly SysCacheService _sysCacheService;
private readonly IHubContext<OnlineUserHub, IOnlineUserHub> _chatHubContext;
private readonly SysConfigService _sysConfigService;
public SysMessageService(SysCacheService sysCacheService,
IHubContext<OnlineUserHub, IOnlineUserHub> chatHubContext,
SysConfigService sysConfigService)
IHubContext<OnlineUserHub, IOnlineUserHub> chatHubContext)
{
_sysCacheService = sysCacheService;
_chatHubContext = chatHubContext;
_sysConfigService = sysConfigService;
}
/// <summary>
@ -47,8 +44,8 @@ public class SysMessageService : IDynamicApiController, ITransient
public async Task SendOtherUser(MessageInput input)
{
var hashKey = _sysCacheService.HashGetAll<SysOnlineUser>(CacheConst.KeyUserOnline);
var exceptRecevieUsers = hashKey.Where(u => u.Value.UserId == input.ReceiveUserId).Select(u => u.Value).ToList();
await _chatHubContext.Clients.AllExcept(exceptRecevieUsers.Select(t => t.ConnectionId)).ReceiveMessage(input);
var exceptReceiveUsers = hashKey.Where(u => u.Value.UserId == input.ReceiveUserId).Select(u => u.Value).ToList();
await _chatHubContext.Clients.AllExcept(exceptReceiveUsers.Select(t => t.ConnectionId)).ReceiveMessage(input);
}
/// <summary>
@ -60,8 +57,8 @@ public class SysMessageService : IDynamicApiController, ITransient
public async Task SendUser(MessageInput input)
{
var hashKey = _sysCacheService.HashGetAll<SysOnlineUser>(CacheConst.KeyUserOnline);
var recevieUsers = hashKey.Where(u => u.Value.UserId == input.ReceiveUserId).Select(u => u.Value).ToList();
await recevieUsers.ForEachAsync(u => _chatHubContext.Clients.Client(u.ConnectionId).ReceiveMessage(input));
var receiveUsers = hashKey.Where(u => u.Value.UserId == input.ReceiveUserId).Select(u => u.Value).ToList();
await receiveUsers.ForEachAsync(u => _chatHubContext.Clients.Client(u.ConnectionId ?? "").ReceiveMessage(input));
}
/// <summary>
@ -73,7 +70,7 @@ public class SysMessageService : IDynamicApiController, ITransient
public async Task SendUsers(MessageInput input)
{
var hashKey = _sysCacheService.HashGetAll<SysOnlineUser>(CacheConst.KeyUserOnline);
var recevieUsers = hashKey.Where(u => input.UserIds.Any(a => a == u.Value.UserId)).Select(u => u.Value).ToList();
await recevieUsers.ForEachAsync(u => _chatHubContext.Clients.Client(u.ConnectionId).ReceiveMessage(input));
var receiveUsers = hashKey.Where(u => input.UserIds.Any(a => a == u.Value.UserId)).Select(u => u.Value).ToList();
await receiveUsers.ForEachAsync(u => _chatHubContext.Clients.Client(u.ConnectionId ?? "").ReceiveMessage(input));
}
}

View File

@ -53,10 +53,8 @@ public class SysSmsService : IDynamicApiController, ITransient
public bool VerifyCode(SmsVerifyCodeInput input)
{
var verifyCode = _sysCacheService.Get<string>($"{CacheConst.KeyPhoneVerCode}{input.Phone}");
if (string.IsNullOrWhiteSpace(verifyCode))
throw Oops.Oh("验证码不存在或已失效,请重新获取!");
if (verifyCode != input.Code)
throw Oops.Oh("验证码错误!");
if (string.IsNullOrWhiteSpace(verifyCode)) throw Oops.Oh("验证码不存在或已失效,请重新获取!");
if (verifyCode != input.Code) throw Oops.Oh("验证码错误!");
return true;
}
@ -70,8 +68,7 @@ public class SysSmsService : IDynamicApiController, ITransient
[DisplayName("阿里云发送短信")]
public async Task AliyunSendSms([Required] string phoneNumber)
{
if (!phoneNumber.TryValidate(ValidationTypes.PhoneNumber).IsValid)
throw Oops.Oh("请正确填写手机号码");
if (!phoneNumber.TryValidate(ValidationTypes.PhoneNumber).IsValid) throw Oops.Oh("请正确填写手机号码");
// 生成随机验证码
var random = new Random();
@ -115,6 +112,10 @@ public class SysSmsService : IDynamicApiController, ITransient
[DisplayName("发送短信模板")]
public async Task AliyunSendSmsTemplate(AliyunSendSmsTemplateInput input)
{
if (!input.PhoneNumber.TryValidate(ValidationTypes.PhoneNumber).IsValid) throw Oops.Oh("请正确填写手机号码");
if (string.IsNullOrWhiteSpace(input.TemplateParam.ToString())) throw Oops.Oh("短信内容不能为空");
var client = CreateAliyunClient();
var template = _smsOptions.Aliyun.GetTemplate(input.TemplateId);
var sendSmsRequest = new SendSmsRequest
@ -146,8 +147,7 @@ public class SysSmsService : IDynamicApiController, ITransient
[DisplayName("腾讯云发送短信")]
public async Task TencentSendSms([Required] string phoneNumber)
{
if (!phoneNumber.TryValidate(ValidationTypes.PhoneNumber).IsValid)
throw Oops.Oh("请正确填写手机号码");
if (!phoneNumber.TryValidate(ValidationTypes.PhoneNumber).IsValid) throw Oops.Oh("请正确填写手机号码");
// 生成随机验证码
var random = new Random();

View File

@ -72,8 +72,7 @@ public class SysNoticeService : IDynamicApiController, ITransient
[DisplayName("更新通知公告")]
public async Task UpdateNotice(UpdateNoticeInput input)
{
if (input.CreateUserId != _userManager.UserId)
throw Oops.Oh(ErrorCodeEnum.D7003);
if (input.CreateUserId != _userManager.UserId) throw Oops.Oh(ErrorCodeEnum.D7003);
var notice = input.Adapt<SysNotice>();
InitNoticeInfo(notice);
@ -91,10 +90,8 @@ public class SysNoticeService : IDynamicApiController, ITransient
public async Task DeleteNotice(DeleteNoticeInput input)
{
var sysNotice = await _sysNoticeRep.GetByIdAsync(input.Id);
if (sysNotice.CreateUserId != _userManager.UserId)
throw Oops.Oh(ErrorCodeEnum.D7003);
if (sysNotice.Status == NoticeStatusEnum.PUBLIC)
throw Oops.Oh(ErrorCodeEnum.D7001);
if (sysNotice.CreateUserId != _userManager.UserId) throw Oops.Oh(ErrorCodeEnum.D7003);
if (sysNotice.Status == NoticeStatusEnum.PUBLIC) throw Oops.Oh(ErrorCodeEnum.D7001);
await _sysNoticeRep.DeleteAsync(u => u.Id == input.Id);
@ -109,8 +106,7 @@ public class SysNoticeService : IDynamicApiController, ITransient
[DisplayName("发布通知公告")]
public async Task Public(NoticeInput input)
{
if (!(await _sysNoticeRep.IsAnyAsync(u => u.Id == input.Id && u.CreateUserId == _userManager.UserId)))
throw Oops.Oh(ErrorCodeEnum.D7003);
if (!(await _sysNoticeRep.IsAnyAsync(u => u.Id == input.Id && u.CreateUserId == _userManager.UserId))) throw Oops.Oh(ErrorCodeEnum.D7003);
// 更新发布状态和时间
await _sysNoticeRep.UpdateAsync(u => new SysNotice() { Status = NoticeStatusEnum.PUBLIC, PublicTime = DateTime.Now }, u => u.Id == input.Id);

View File

@ -31,13 +31,13 @@ public static class OAuthSetup
})
.AddWeixin(options =>
{
options.ClientId = authOpt.Weixin?.ClientId;
options.ClientSecret = authOpt.Weixin?.ClientSecret;
options.ClientId = authOpt.Weixin?.ClientId!;
options.ClientSecret = authOpt.Weixin?.ClientSecret!;
})
.AddGitee(options =>
{
options.ClientId = authOpt.Gitee?.ClientId;
options.ClientSecret = authOpt.Gitee?.ClientSecret;
options.ClientId = authOpt.Gitee?.ClientId!;
options.ClientSecret = authOpt.Gitee?.ClientSecret!;
options.ClaimActions.MapJsonKey(OAuthClaim.GiteeAvatarUrl, "avatar_url");
});

View File

@ -39,10 +39,13 @@ public class SysOAuthService : IDynamicApiController, ITransient
if (string.IsNullOrWhiteSpace(provider) || !await _httpContextAccessor.HttpContext.IsProviderSupportedAsync(provider))
throw Oops.Oh("不支持的OAuth类型");
var request = _httpContextAccessor.HttpContext.Request;
var request = _httpContextAccessor.HttpContext!.Request;
var url = $"{request.Scheme}://{request.Host}{request.PathBase}{request.Path}Callback?provider={provider}&redirectUrl={redirectUrl}";
var properties = new AuthenticationProperties { RedirectUri = url };
properties.Items["LoginProvider"] = provider;
var properties = new AuthenticationProperties
{
RedirectUri = url,
Items = { ["LoginProvider"] = provider }
};
return await Task.FromResult(new ChallengeResult(provider, properties));
}
@ -59,7 +62,7 @@ public class SysOAuthService : IDynamicApiController, ITransient
if (string.IsNullOrWhiteSpace(provider) || !await _httpContextAccessor.HttpContext.IsProviderSupportedAsync(provider))
throw Oops.Oh("不支持的OAuth类型");
var authenticateResult = await _httpContextAccessor.HttpContext.AuthenticateAsync(provider);
var authenticateResult = await _httpContextAccessor.HttpContext!.AuthenticateAsync(provider);
if (!authenticateResult.Succeeded)
throw Oops.Oh("授权失败");
@ -110,7 +113,7 @@ public class SysOAuthService : IDynamicApiController, ITransient
}
// 构建Token令牌默认回调登录为PC模式
var token = await App.GetRequiredService<SysAuthService>().CreateToken(wechatUser.SysUser, LoginModeEnum.PC);
var token = await App.GetRequiredService<SysAuthService>().CreateToken(wechatUser.SysUser, SqlSugarConst.DefaultAppId, LoginModeEnum.PC);
return new RedirectResult($"{redirectUrl}/#/login?token={token.AccessToken}");
}

View File

@ -49,7 +49,7 @@ public class SysOnlineUserService : IDynamicApiController, ITransient
[DisplayName("强制下线")]
public async Task ForceOffline(SysOnlineUser user)
{
await _onlineUserHubContext.Clients.Client(user.ConnectionId).ForceOffline("强制下线");
await _onlineUserHubContext.Clients.Client(user.ConnectionId ?? "").ForceOffline("强制下线");
await _sysOnlineUerRep.DeleteAsync(user);
}
@ -67,7 +67,7 @@ public class SysOnlineUserService : IDynamicApiController, ITransient
foreach (var item in userList)
{
await _onlineUserHubContext.Clients.Client(item.ConnectionId).PublicNotice(notice);
await _onlineUserHubContext.Clients.Client(item.ConnectionId ?? "").PublicNotice(notice);
}
}

View File

@ -56,7 +56,7 @@ public class SysOrgService : IDynamicApiController, ITransient
.ToListAsync();
}
var orgTree = new List<SysOrg>();
List<SysOrg> orgTree;
if (_userManager.SuperAdmin)
{
orgTree = await iSugarQueryable.ToTreeAsync(u => u.Children, u => u.Pid, input.Id);
@ -68,12 +68,11 @@ public class SysOrgService : IDynamicApiController, ITransient
HandlerOrgTree(orgTree, userOrgIdList);
}
var sysOrg = await _sysOrgRep.GetByIdAsync(input.Id);
if (sysOrg != null)
{
sysOrg.Children = orgTree;
orgTree = new List<SysOrg> { sysOrg };
}
var sysOrg = await _sysOrgRep.GetSingleAsync(u => u.Id == input.Id);
if (sysOrg == null) return orgTree;
sysOrg.Children = orgTree;
orgTree = new List<SysOrg> { sysOrg };
return orgTree;
}
@ -261,25 +260,20 @@ public class SysOrgService : IDynamicApiController, ITransient
private void DeleteAllUserOrgCache(long orgId, long orgPid)
{
var userOrgKeyList = _sysCacheService.GetKeysByPrefixKey(CacheConst.KeyUserOrg);
if (userOrgKeyList != null && userOrgKeyList.Count > 0)
if (userOrgKeyList is not { Count: > 0 }) return;
foreach (var userOrgKey in userOrgKeyList)
{
foreach (var userOrgKey in userOrgKeyList)
{
var userOrgs = _sysCacheService.Get<List<long>>(userOrgKey);
var userId = long.Parse(userOrgKey.Substring(CacheConst.KeyUserOrg));
if (userOrgs != null && (userOrgs.Contains(orgId) || userOrgs.Contains(orgPid)))
{
SqlSugarFilter.DeleteUserOrgCache(userId, _sysOrgRep.Context.CurrentConnectionConfig.ConfigId.ToString());
}
if (orgPid == 0)
{
var dataScope = _sysCacheService.Get<int>($"{CacheConst.KeyRoleMaxDataScope}{userId}");
if (dataScope == (int)DataScopeEnum.All)
{
SqlSugarFilter.DeleteUserOrgCache(userId, _sysOrgRep.Context.CurrentConnectionConfig.ConfigId.ToString());
}
}
}
var userOrgList = _sysCacheService.Get<List<long>>(userOrgKey);
var userId = long.Parse(userOrgKey.Substring(CacheConst.KeyUserOrg));
if (userOrgList != null && (userOrgList.Contains(orgId) || userOrgList.Contains(orgPid)))
SqlSugarFilter.DeleteUserOrgCache(userId, _sysOrgRep.Context.CurrentConnectionConfig.ConfigId.ToString());
if (orgPid != 0) continue;
var dataScope = _sysCacheService.Get<int>($"{CacheConst.KeyRoleMaxDataScope}{userId}");
if (dataScope == (int)DataScopeEnum.All)
SqlSugarFilter.DeleteUserOrgCache(userId, _sysOrgRep.Context.CurrentConnectionConfig.ConfigId.ToString());
}
}
@ -290,8 +284,7 @@ public class SysOrgService : IDynamicApiController, ITransient
[NonAction]
public async Task<List<long>> GetUserOrgIdList()
{
if (_userManager.SuperAdmin)
return new List<long>();
if (_userManager.SuperAdmin) return new();
return await GetUserOrgIdList(_userManager.UserId, _userManager.OrgId);
}
@ -303,21 +296,20 @@ public class SysOrgService : IDynamicApiController, ITransient
public async Task<List<long>> GetUserOrgIdList(long userId, long userOrgId)
{
var orgIdList = _sysCacheService.Get<List<long>>($"{CacheConst.KeyUserOrg}{userId}"); // 取缓存
if (orgIdList == null || orgIdList.Count < 1)
{
// 本人创建机构集合
var orgList0 = await _sysOrgRep.AsQueryable().Where(u => u.CreateUserId == userId).Select(u => u.Id).ToListAsync();
// 扩展机构集合
var orgList1 = await _sysUserExtOrgService.GetUserExtOrgList(userId);
// 角色机构集合
var orgList2 = await GetUserRoleOrgIdList(userId, userOrgId);
// 机构并集
orgIdList = orgList1.Select(u => u.OrgId).Union(orgList2).Union(orgList0).ToList();
// 当前所属机构
if (!orgIdList.Contains(userOrgId))
orgIdList.Add(userOrgId);
_sysCacheService.Set($"{CacheConst.KeyUserOrg}{userId}", orgIdList, TimeSpan.FromDays(7)); // 存缓存
}
if (orgIdList is { Count: >= 1 }) return orgIdList;
// 本人创建机构集合
var orgList0 = await _sysOrgRep.AsQueryable().Where(u => u.CreateUserId == userId).Select(u => u.Id).ToListAsync();
// 扩展机构集合
var orgList1 = await _sysUserExtOrgService.GetUserExtOrgList(userId);
// 角色机构集合
var orgList2 = await GetUserRoleOrgIdList(userId, userOrgId);
// 机构并集
orgIdList = orgList1.Select(u => u.OrgId).Union(orgList2).Union(orgList0).ToList();
// 当前所属机构
if (!orgIdList.Contains(userOrgId))
orgIdList.Add(userOrgId);
_sysCacheService.Set($"{CacheConst.KeyUserOrg}{userId}", orgIdList, TimeSpan.FromDays(7)); // 存缓存
return orgIdList;
}
@ -330,8 +322,7 @@ public class SysOrgService : IDynamicApiController, ITransient
private async Task<List<long>> GetUserRoleOrgIdList(long userId, long userOrgId)
{
var roleList = await _sysUserRoleService.GetUserRoleList(userId);
if (roleList.Count < 1)
return new List<long>(); // 空机构Id集合
if (roleList.Count < 1) return new(); // 空机构Id集合
return await GetUserOrgIdList(roleList, userId, userOrgId);
}
@ -354,7 +345,7 @@ public class SysOrgService : IDynamicApiController, ITransient
// 数据范围的机构集合
var dataScopeOrgIdList = new List<long>();
if (roleList != null && roleList.Count > 0)
if (roleList is { Count: > 0 })
{
roleList.ForEach(u =>
{
@ -393,20 +384,20 @@ public class SysOrgService : IDynamicApiController, ITransient
{
var orgId = userOrgId;//var orgId = _userManager.OrgId;
var orgIdList = new List<long>();
// 若数据范围是全部则获取所有机构Id集合
if (dataScope == (int)DataScopeEnum.All)
switch (dataScope)
{
orgIdList = await _sysOrgRep.AsQueryable().Select(u => u.Id).ToListAsync();
}
// 若数据范围是本部门及以下,则获取本节点和子节点集合
else if (dataScope == (int)DataScopeEnum.DeptChild)
{
orgIdList = await GetChildIdListWithSelfById(orgId);
}
// 若数据范围是本部门不含子节点,则直接返回本部门
else if (dataScope == (int)DataScopeEnum.Dept)
{
orgIdList.Add(orgId);
// 若数据范围是全部则获取所有机构Id集合
case (int)DataScopeEnum.All:
orgIdList = await _sysOrgRep.AsQueryable().Select(u => u.Id).ToListAsync();
break;
// 若数据范围是本部门及以下,则获取本节点和子节点集合
case (int)DataScopeEnum.DeptChild:
orgIdList = await GetChildIdListWithSelfById(orgId);
break;
// 若数据范围是本部门不含子节点,则直接返回本部门
case (int)DataScopeEnum.Dept:
orgIdList.Add(orgId);
break;
}
return orgIdList;
}

View File

@ -46,8 +46,7 @@ public class SysPluginService : IDynamicApiController, ITransient
public async Task AddPlugin(AddPluginInput input)
{
var isExist = await _sysPluginRep.IsAnyAsync(u => u.Name == input.Name || u.AssemblyName == input.AssemblyName);
if (isExist)
throw Oops.Oh(ErrorCodeEnum.D1900);
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1900);
// 添加动态程序集/接口
input.AssemblyName = CompileAssembly(input.CsharpCode, input.AssemblyName);
@ -65,8 +64,7 @@ public class SysPluginService : IDynamicApiController, ITransient
public async Task UpdatePlugin(UpdatePluginInput input)
{
var isExist = await _sysPluginRep.IsAnyAsync(u => (u.Name == input.Name || u.AssemblyName == input.AssemblyName) && u.Id != input.Id);
if (isExist)
throw Oops.Oh(ErrorCodeEnum.D1900);
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1900);
// 先移除再添加动态程序集/接口
RemoveAssembly(input.AssemblyName);

View File

@ -75,8 +75,7 @@ public class SysPosService : IDynamicApiController, ITransient
[DisplayName("增加职位")]
public async Task AddPos(AddPosInput input)
{
if (await _sysPosRep.IsAnyAsync(u => u.Name == input.Name && u.Code == input.Code))
throw Oops.Oh(ErrorCodeEnum.D6000);
if (await _sysPosRep.IsAnyAsync(u => u.Name == input.Name && u.Code == input.Code)) throw Oops.Oh(ErrorCodeEnum.D6000);
await _sysPosRep.InsertAsync(input.Adapt<SysPos>());
}
@ -90,12 +89,10 @@ public class SysPosService : IDynamicApiController, ITransient
[DisplayName("更新职位")]
public async Task UpdatePos(UpdatePosInput input)
{
if (await _sysPosRep.IsAnyAsync(u => u.Name == input.Name && u.Code == input.Code && u.Id != input.Id))
throw Oops.Oh(ErrorCodeEnum.D6000);
if (await _sysPosRep.IsAnyAsync(u => u.Name == input.Name && u.Code == input.Code && u.Id != input.Id)) throw Oops.Oh(ErrorCodeEnum.D6000);
var sysPos = await _sysPosRep.GetByIdAsync(input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D6003);
if (!_userManager.SuperAdmin && sysPos.CreateUserId != _userManager.UserId)
throw Oops.Oh(ErrorCodeEnum.D6002);
if (!_userManager.SuperAdmin && sysPos.CreateUserId != _userManager.UserId) throw Oops.Oh(ErrorCodeEnum.D6002);
await _sysPosRep.AsUpdateable(input.Adapt<SysPos>()).IgnoreColumns(true).ExecuteCommandAsync();
}
@ -110,19 +107,16 @@ public class SysPosService : IDynamicApiController, ITransient
public async Task DeletePos(DeletePosInput input)
{
var sysPos = await _sysPosRep.GetByIdAsync(input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D6003);
if (!_userManager.SuperAdmin && sysPos.CreateUserId != _userManager.UserId)
throw Oops.Oh(ErrorCodeEnum.D6002);
if (!_userManager.SuperAdmin && sysPos.CreateUserId != _userManager.UserId) throw Oops.Oh(ErrorCodeEnum.D6002);
// 若职位有用户则禁止删除
var hasPosEmp = await _sysPosRep.ChangeRepository<SqlSugarRepository<SysUser>>()
.IsAnyAsync(u => u.PosId == input.Id);
if (hasPosEmp)
throw Oops.Oh(ErrorCodeEnum.D6001);
if (hasPosEmp) throw Oops.Oh(ErrorCodeEnum.D6001);
// 若附属职位有用户则禁止删除
var hasExtPosEmp = await _sysUserExtOrgService.HasUserPos(input.Id);
if (hasExtPosEmp)
throw Oops.Oh(ErrorCodeEnum.D6001);
if (hasExtPosEmp) throw Oops.Oh(ErrorCodeEnum.D6001);
await _sysPosRep.DeleteByIdAsync(input.Id);
}

View File

@ -54,8 +54,7 @@ public class SysPrintService : IDynamicApiController, ITransient
public async Task AddPrint(AddPrintInput input)
{
var isExist = await _sysPrintRep.IsAnyAsync(u => u.Name == input.Name);
if (isExist)
throw Oops.Oh(ErrorCodeEnum.D1800);
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1800);
await _sysPrintRep.InsertAsync(input.Adapt<SysPrint>());
}
@ -70,8 +69,7 @@ public class SysPrintService : IDynamicApiController, ITransient
public async Task UpdatePrint(UpdatePrintInput input)
{
var isExist = await _sysPrintRep.IsAnyAsync(u => u.Name == input.Name && u.Id != input.Id);
if (isExist)
throw Oops.Oh(ErrorCodeEnum.D1800);
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1800);
await _sysPrintRep.AsUpdateable(input.Adapt<SysPrint>()).IgnoreColumns(true).ExecuteCommandAsync();
}

View File

@ -76,22 +76,19 @@ public class SysRegionService : IDynamicApiController, ITransient
[DisplayName("增加行政区划")]
public async Task<long> AddRegion(AddRegionInput input)
{
input.Code = input.Code.Trim();
if (input.Code.Length != 12 && input.Code.Length != 9 && input.Code.Length != 6)
throw Oops.Oh(ErrorCodeEnum.R2003);
input.Code = input.Code?.Trim() ?? "";
if (input.Code.Length != 12 && input.Code.Length != 9 && input.Code.Length != 6) throw Oops.Oh(ErrorCodeEnum.R2003);
if (input.Pid != 0)
{
var pRegion = await _sysRegionRep.GetByIdAsync(input.Pid);
pRegion ??= await _sysRegionRep.GetFirstAsync(u => u.Code == input.Pid.ToString());
if (pRegion == null)
throw Oops.Oh(ErrorCodeEnum.D2000);
if (pRegion == null) throw Oops.Oh(ErrorCodeEnum.D2000);
input.Pid = pRegion.Id;
}
var isExist = await _sysRegionRep.IsAnyAsync(u => u.Name == input.Name && u.Code == input.Code);
if (isExist)
throw Oops.Oh(ErrorCodeEnum.R2002);
if (isExist) throw Oops.Oh(ErrorCodeEnum.R2002);
var sysRegion = input.Adapt<SysRegion>();
var newRegion = await _sysRegionRep.AsInsertable(sysRegion).ExecuteReturnEntityAsync();
@ -107,34 +104,28 @@ public class SysRegionService : IDynamicApiController, ITransient
[DisplayName("更新行政区划")]
public async Task UpdateRegion(UpdateRegionInput input)
{
input.Code = input.Code.Trim();
if (input.Code.Length != 12 && input.Code.Length != 9 && input.Code.Length != 6)
throw Oops.Oh(ErrorCodeEnum.R2003);
input.Code = input.Code?.Trim() ?? "";
if (input.Code.Length != 12 && input.Code.Length != 9 && input.Code.Length != 6) throw Oops.Oh(ErrorCodeEnum.R2003);
var sysRegion = await _sysRegionRep.GetByIdAsync(input.Id);
if (sysRegion == null)
throw Oops.Oh(ErrorCodeEnum.D1002);
if (sysRegion == null) throw Oops.Oh(ErrorCodeEnum.D1002);
if (sysRegion.Pid != input.Pid && input.Pid != 0)
{
var pRegion = await _sysRegionRep.GetByIdAsync(input.Pid);
pRegion ??= await _sysRegionRep.GetFirstAsync(u => u.Code == input.Pid.ToString());
if (pRegion == null)
throw Oops.Oh(ErrorCodeEnum.D2000);
if (pRegion == null) throw Oops.Oh(ErrorCodeEnum.D2000);
input.Pid = pRegion.Id;
var regionTreeList = await _sysRegionRep.AsQueryable().ToChildListAsync(u => u.Pid, input.Id, true);
var childIdList = regionTreeList.Select(u => u.Id).ToList();
if (childIdList.Contains(input.Pid))
throw Oops.Oh(ErrorCodeEnum.R2004);
if (childIdList.Contains(input.Pid)) throw Oops.Oh(ErrorCodeEnum.R2004);
}
if (input.Id == input.Pid)
throw Oops.Oh(ErrorCodeEnum.R2001);
if (input.Id == input.Pid) throw Oops.Oh(ErrorCodeEnum.R2001);
var isExist = await _sysRegionRep.IsAnyAsync(u => (u.Name == input.Name && u.Code == input.Code) && u.Id != sysRegion.Id);
if (isExist)
throw Oops.Oh(ErrorCodeEnum.R2002);
if (isExist) throw Oops.Oh(ErrorCodeEnum.R2002);
//// 父Id不能为自己的子节点
//var regionTreeList = await _sysRegionRep.AsQueryable().ToChildListAsync(u => u.Pid, input.Id, true);
@ -171,8 +162,7 @@ public class SysRegionService : IDynamicApiController, ITransient
throw Oops.Oh("请正确输入高德地图开发者 Key 值");
var syncLevel = await _sysConfigService.GetConfigValueByCode<int>(ConfigConst.SysRegionSyncLevel);
if (syncLevel < 1 || syncLevel > 5)
syncLevel = 3; // 默认区县级
if (syncLevel is < 1 or > 5) syncLevel = 3; // 默认区县级
var res = await $"https://restapi.amap.com/v3/config/district?subdistrict={syncLevel}&key={key}".GetAsync();
if (!res.IsSuccessStatusCode) return;
@ -187,7 +177,7 @@ public class SysRegionService : IDynamicApiController, ITransient
}
await _sysRegionRep.AsDeleteable().ExecuteCommandAsync();
_sysRegionRep.Context.Fastest<SysRegion>().BulkCopy(regionList);
await _sysRegionRep.Context.Fastest<SysRegion>().BulkCopyAsync(regionList);
}
private void GetChildren(List<SysRegion> regionList, List<GDRegionResponse> responses, int level, long pid)

View File

@ -28,12 +28,12 @@ public class SysRoleOrgService : ITransient
await _sysRoleOrgRep.DeleteAsync(u => u.RoleId == input.Id);
if (input.DataScope == (int)DataScopeEnum.Define)
{
var roleOrgs = input.OrgIdList.Select(u => new SysRoleOrg
var roleOrgList = input.OrgIdList.Select(u => new SysRoleOrg
{
RoleId = input.Id,
OrgId = u
}).ToList();
await _sysRoleOrgRep.InsertRangeAsync(roleOrgs);
await _sysRoleOrgRep.InsertRangeAsync(roleOrgList);
}
}

View File

@ -135,13 +135,11 @@ public class SysRoleService : IDynamicApiController, ITransient
{
// 禁止删除系统管理员角色
var sysRole = await _sysRoleRep.GetByIdAsync(input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
if (sysRole.Code == CommonConst.SysAdminRole)
throw Oops.Oh(ErrorCodeEnum.D1019);
if (sysRole.Code == CommonConst.SysAdminRole) throw Oops.Oh(ErrorCodeEnum.D1019);
// 若角色有用户则禁止删除
var userIds = await _sysUserRoleService.GetUserIdList(input.Id);
if (userIds != null && userIds.Count > 0)
throw Oops.Oh(ErrorCodeEnum.D1025);
if (userIds != null && userIds.Count > 0) throw Oops.Oh(ErrorCodeEnum.D1025);
await _sysRoleRep.DeleteAsync(sysRole);
@ -166,8 +164,7 @@ public class SysRoleService : IDynamicApiController, ITransient
[DisplayName("授权角色菜单")]
public async Task GrantMenu(RoleMenuInput input)
{
if (input.MenuIdList == null || input.MenuIdList.Count < 1)
return;
if (input.MenuIdList == null || input.MenuIdList.Count < 1) return;
//// 将父节点为0的菜单排除防止前端全选异常
//var pMenuIds = await _sysRoleRep.ChangeRepository<SqlSugarRepository<SysMenu>>().AsQueryable().Where(u => input.MenuIdList.Contains(u.Id) && u.Pid == 0).ToListAsync(u => u.Id);
@ -211,26 +208,26 @@ public class SysRoleService : IDynamicApiController, ITransient
SqlSugarFilter.DeleteUserOrgCache(userId, _sysRoleRep.Context.CurrentConnectionConfig.ConfigId.ToString());
}
var role = await _sysRoleRep.GetByIdAsync(input.Id);
var role = await _sysRoleRep.GetFirstAsync(u => u.Id == input.Id);
var dataScope = input.DataScope;
if (!_userManager.SuperAdmin)
{
// 非超级管理员没有全部数据范围权限
if (dataScope == (int)DataScopeEnum.All)
throw Oops.Oh(ErrorCodeEnum.D1016);
// 若数据范围自定义,则判断授权数据范围是否有权限
if (dataScope == (int)DataScopeEnum.Define)
switch (dataScope)
{
var grantOrgIdList = input.OrgIdList;
if (grantOrgIdList.Count > 0)
{
var orgIdList = await _sysOrgService.GetUserOrgIdList();
if (orgIdList.Count < 1)
throw Oops.Oh(ErrorCodeEnum.D1016);
else if (!grantOrgIdList.All(u => orgIdList.Any(c => c == u)))
throw Oops.Oh(ErrorCodeEnum.D1016);
}
// 非超级管理员没有全部数据范围权限
case (int)DataScopeEnum.All: throw Oops.Oh(ErrorCodeEnum.D1016);
// 若数据范围自定义,则判断授权数据范围是否有权限
case (int)DataScopeEnum.Define:
{
var grantOrgIdList = input.OrgIdList;
if (grantOrgIdList.Count > 0)
{
var orgIdList = await _sysOrgService.GetUserOrgIdList();
if (orgIdList.Count < 1) throw Oops.Oh(ErrorCodeEnum.D1016);
if (!grantOrgIdList.All(u => orgIdList.Any(c => c == u))) throw Oops.Oh(ErrorCodeEnum.D1016);
}
break;
}
}
}
role.DataScope = (DataScopeEnum)dataScope;
@ -259,8 +256,7 @@ public class SysRoleService : IDynamicApiController, ITransient
[DisplayName("设置角色状态")]
public async Task<int> SetStatus(RoleInput input)
{
if (!Enum.IsDefined(typeof(StatusEnum), input.Status))
throw Oops.Oh(ErrorCodeEnum.D3005);
if (!Enum.IsDefined(typeof(StatusEnum), input.Status)) throw Oops.Oh(ErrorCodeEnum.D3005);
return await _sysRoleRep.AsUpdateable()
.SetColumns(u => u.Status == input.Status)
@ -329,8 +325,7 @@ public class SysRoleService : IDynamicApiController, ITransient
[DisplayName("获取角色接口黑名单集合")]
public async Task<List<string>> GetRoleApiList([FromQuery] RoleInput input)
{
var roleApis = await _sysRoleApiService.GetRoleApiList(new List<long> { input.Id });
return roleApis;
return await _sysRoleApiService.GetRoleApiList(new List<long> { input.Id });
//var roleButtons = await GetRoleButtonList(new List<long> { input.Id });
//return roleApis.Union(roleButtons).ToList();
@ -345,44 +340,42 @@ public class SysRoleService : IDynamicApiController, ITransient
{
var userId = _userManager.UserId;
var apiList = _sysCacheService.Get<List<List<string>>>(CacheConst.KeyUserApi + userId);
if (apiList == null)
if (apiList != null) return apiList;
apiList = new List<List<string>>() { new(), new() };
// 超管账号获取所有接口
if (_userManager.SuperAdmin)
{
apiList = new List<List<string>>() { new(), new() };
// 超管账号获取所有接口
if (_userManager.SuperAdmin)
var allApiList = _sysCommonService.GetApiList();
foreach (var apiOutput in allApiList)
{
var allApiList = _sysCommonService.GetApiList();
foreach (var apiOutput in allApiList)
{
foreach (var controller in apiOutput.Children)
apiList[0].AddRange(controller.Children.Select(u => u.Route));
}
// 所有按钮权限集合
var allButtonList = await GetButtonList();
// 没有接口对应的按钮权限集合
var diffButtonList = allButtonList.Except(apiList[0]).ToList(); // 差集
apiList[0].AddRange(diffButtonList);
foreach (var controller in apiOutput.Children)
apiList[0].AddRange(controller.Children.Select(u => u.Route));
}
else
{
// 当前账号所有角色集合
var roleIdList = await _sysUserRoleService.GetUserRoleIdList(_userManager.UserId);
// 已勾选按钮权限集合
apiList[0] = await GetRoleButtonList(roleIdList);
// 未勾选按钮权限集合(放到接口黑名单里面)
var allButtonList = await GetButtonList();
apiList[1] = allButtonList.Except(apiList[0]).ToList(); // 差集
// 接口黑名单集合
var roleApiList = await _sysRoleApiService.GetRoleApiList(roleIdList);
apiList[1].AddRange(roleApiList);
}
_sysCacheService.Set(CacheConst.KeyUserApi + userId, apiList, TimeSpan.FromDays(7)); // 缓存7天
// 所有按钮权限集合
var allButtonList = await GetButtonList();
// 没有接口对应的按钮权限集合
var diffButtonList = allButtonList.Except(apiList[0]).ToList(); // 差集
apiList[0].AddRange(diffButtonList);
}
else
{
// 当前账号所有角色集合
var roleIdList = await _sysUserRoleService.GetUserRoleIdList(_userManager.UserId);
// 已勾选按钮权限集合
apiList[0] = await GetRoleButtonList(roleIdList);
// 未勾选按钮权限集合(放到接口黑名单里面)
var allButtonList = await GetButtonList();
apiList[1] = allButtonList.Except(apiList[0]).ToList(); // 差集
// 接口黑名单集合
var roleApiList = await _sysRoleApiService.GetRoleApiList(roleIdList);
apiList[1].AddRange(roleApiList);
}
_sysCacheService.Set(CacheConst.KeyUserApi + userId, apiList, TimeSpan.FromDays(7)); // 缓存7天
return apiList;
}

View File

@ -17,10 +17,10 @@ public class SysScheduleService : IDynamicApiController, ITransient
private readonly SqlSugarRepository<SysSchedule> _sysSchedule;
public SysScheduleService(UserManager userManager,
SqlSugarRepository<SysSchedule> sysSchedle)
SqlSugarRepository<SysSchedule> sysSchedule)
{
_userManager = userManager;
_sysSchedule = sysSchedle;
_sysSchedule = sysSchedule;
}
/// <summary>
@ -94,8 +94,7 @@ public class SysScheduleService : IDynamicApiController, ITransient
[DisplayName("设置日程状态")]
public async Task<int> SetStatus(ScheduleInput input)
{
if (!Enum.IsDefined(typeof(FinishStatusEnum), input.Status))
throw Oops.Oh(ErrorCodeEnum.D3005);
if (!Enum.IsDefined(typeof(FinishStatusEnum), input.Status)) throw Oops.Oh(ErrorCodeEnum.D3005);
return await _sysSchedule.AsUpdateable()
.SetColumns(u => u.Status == input.Status)

View File

@ -31,7 +31,7 @@ public class SysServerService : IDynamicApiController, ITransient
ProcessorCount = Environment.ProcessorCount + " 核", // CPU核心数
SysRunTime = ComputerUtil.GetRunTime(), // 系统运行时间
RemoteIp = ComputerUtil.GetIpFromOnline(), // 外网地址
LocalIp = App.HttpContext?.Connection?.LocalIpAddress.MapToIPv4().ToString(), // 本地地址
LocalIp = App.HttpContext?.Connection?.LocalIpAddress!.MapToIPv4().ToString(), // 本地地址
RuntimeInformation.FrameworkDescription, // NET框架
Environment = App.HostEnvironment.IsDevelopment() ? "Development" : "Production",
Wwwroot = App.WebHostEnvironment.WebRootPath, // 网站根目录

View File

@ -29,6 +29,12 @@ public class PageTenantInput : BasePageInput
public class AddTenantInput : TenantOutput
{
///// <summary>
///// 应用Id
///// </summary>
//[Required(ErrorMessage = "应用不能为空")]
//public new long? AppId { get; set; }
/// <summary>
/// 租户名称
/// </summary>
@ -40,6 +46,12 @@ public class AddTenantInput : TenantOutput
/// </summary>
[Required(ErrorMessage = "租管账号不能为空"), MinLength(3, ErrorMessage = "租管账号不能少于3个字符")]
public override string AdminAccount { get; set; }
///// <summary>
///// 租户域名
///// </summary>
//[Required(ErrorMessage = "域名不能为空"), MinLength(5, ErrorMessage = "域名不能少于5个字符")]
//public new string Host { get; set; }
}
public class UpdateTenantInput : AddTenantInput

View File

@ -13,6 +13,11 @@ public class TenantOutput : SysTenant
/// </summary>
public virtual string Name { get; set; }
///// <summary>
///// 关联应用名称
///// </summary>
//public virtual string AppName { get; set; }
/// <summary>
/// 管理员账号
/// </summary>

View File

@ -22,17 +22,18 @@ public class SysUserLdapService : ITransient
/// 批量插入域账号
/// </summary>
/// <param name="tenantId"></param>
/// <param name="sysUserLdaps"></param>
/// <param name="sysUserLdapList"></param>
/// <returns></returns>
public async Task InsertUserLdaps(long tenantId, List<SysUserLdap> sysUserLdaps)
public async Task InsertUserLdapList(long tenantId, List<SysUserLdap> sysUserLdapList)
{
await _sysUserLdapRep.DeleteAsync(u => u.TenantId == tenantId);
await _sysUserLdapRep.InsertRangeAsync(sysUserLdaps);
await _sysUserLdapRep.InsertRangeAsync(sysUserLdapList);
await _sysUserLdapRep.AsUpdateable()
.InnerJoin<SysUser>((l, u) => l.EmployeeId == u.Account && u.Status == StatusEnum.Enable && u.IsDelete == false && l.IsDelete == false)
.InnerJoin<SysUser>((l, u) => l.EmployeeId == u.Account)
.SetColumns((l, u) => new SysUserLdap { UserId = u.Id })
.Where((l, u) => l.TenantId == tenantId && u.Status == StatusEnum.Enable && u.IsDelete == false && l.IsDelete == false)
.ExecuteCommandAsync();
}
@ -47,8 +48,7 @@ public class SysUserLdapService : ITransient
public async Task AddUserLdap(long tenantId, long userId, string account, string domainAccount)
{
var userLdap = await _sysUserLdapRep.GetFirstAsync(u => u.TenantId == tenantId && u.IsDelete == false && (u.Account == account || u.UserId == userId || u.EmployeeId == domainAccount));
if (userLdap != null)
await _sysUserLdapRep.DeleteByIdAsync(userLdap.Id);
if (userLdap != null) await _sysUserLdapRep.DeleteByIdAsync(userLdap.Id);
if (!string.IsNullOrWhiteSpace(domainAccount))
await _sysUserLdapRep.InsertAsync(new SysUserLdap { EmployeeId = account, TenantId = tenantId, UserId = userId, Account = domainAccount });

View File

@ -14,10 +14,12 @@ namespace Admin.NET.Core.Service;
public class SysUserMenuService : IDynamicApiController, ITransient
{
private readonly SqlSugarRepository<SysUserMenu> _sysUserMenuRep;
private readonly UserManager _userManager;
public SysUserMenuService(SqlSugarRepository<SysUserMenu> sysUserMenuRep)
public SysUserMenuService(SqlSugarRepository<SysUserMenu> sysUserMenuRep, UserManager userManager)
{
_sysUserMenuRep = sysUserMenuRep;
_userManager = userManager;
}
/// <summary>
@ -26,16 +28,16 @@ public class SysUserMenuService : IDynamicApiController, ITransient
/// <param name="input"></param>
/// <returns></returns>
[UnitOfWork]
[ApiDescriptionSettings(Name = "Add"), HttpPost]
[DisplayName("收藏菜单")]
[ApiDescriptionSettings(Name = "Add"), HttpPost]
public async Task AddUserMenu(UserMenuInput input)
{
await _sysUserMenuRep.DeleteAsync(u => u.UserId == input.UserId);
await _sysUserMenuRep.DeleteAsync(u => u.UserId == _userManager.UserId);
if (input.MenuIdList == null || input.MenuIdList.Count < 1) return;
if (input.MenuIdList == null || input.MenuIdList.Count == 0) return;
var menus = input.MenuIdList.Select(u => new SysUserMenu
{
UserId = input.UserId,
UserId = _userManager.UserId,
MenuId = u
}).ToList();
await _sysUserMenuRep.InsertRangeAsync(menus);
@ -46,48 +48,55 @@ public class SysUserMenuService : IDynamicApiController, ITransient
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Delete"), HttpPost]
[ApiDescriptionSettings(Name = "DeleteUserMenu"), HttpPost]
[DisplayName("取消收藏菜单")]
public async Task DeleteUserMenu(UserMenuInput input)
{
await _sysUserMenuRep.DeleteAsync(u => u.UserId == input.UserId && input.MenuIdList.Contains(u.MenuId));
await _sysUserMenuRep.DeleteAsync(u => u.UserId == _userManager.UserId && input.MenuIdList.Contains(u.MenuId));
}
/// <summary>
/// 根据用户Id删除收藏菜单 🔖
/// 获取当前用户收藏的菜单集合 🔖
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "DeleteByUserId"), HttpPost]
[DisplayName("根据用户Id删除收藏菜单")]
public async Task DeleteByUserId(long userId)
[DisplayName("获取当前用户收藏的菜单集合")]
public async Task<List<MenuOutput>> GetUserMenuList()
{
var sysUserMenuList = await _sysUserMenuRep.AsQueryable()
.Includes(u => u.SysMenu)
.Where(u => u.UserId == _userManager.UserId).ToListAsync();
return sysUserMenuList.Where(u => u.SysMenu != null).Select(u => u.SysMenu).ToList().Adapt<List<MenuOutput>>();
}
/// <summary>
/// 获取当前用户收藏的菜单Id集合 🔖
/// </summary>
/// <returns></returns>
[DisplayName("获取当前用户收藏的菜单Id集合")]
public async Task<List<long>> GetUserMenuIdList()
{
return await _sysUserMenuRep.AsQueryable()
.Where(u => u.UserId == _userManager.UserId).Select(u => u.MenuId).ToListAsync();
}
/// <summary>
/// 删除指定用户的收藏菜单
/// </summary>
/// <returns></returns>
[NonAction]
public async Task DeleteUserMenuList(long userId)
{
await _sysUserMenuRep.DeleteAsync(u => u.UserId == userId);
}
/// <summary>
/// 根据用户Id获取收藏菜单集合 🔖
/// 批量删除收藏菜单
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
[DisplayName("根据用户Id获取收藏菜单集合")]
public async Task<List<MenuOutput>> GetUserMenuList(long userId)
/// <param name="ids"></param>
[NonAction]
public async Task DeleteMenuList(List<long> ids)
{
var sysUserMenuList = await _sysUserMenuRep.AsQueryable()
.Includes(u => u.SysMenu)
.Where(u => u.UserId == userId).ToListAsync();
return sysUserMenuList.Where(u => u.SysMenu != null).Select(u => u.SysMenu).ToList().Adapt<List<MenuOutput>>();
}
/// <summary>
/// 根据用户Id获取收藏菜单Id集合 🔖
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
[DisplayName("根据用户Id获取收藏菜单Id集合")]
public async Task<List<long>> GetUserMenuIdList(long userId)
{
return await _sysUserMenuRep.AsQueryable()
.Where(u => u.UserId == userId).Select(u => u.MenuId).ToListAsync();
if (ids == null || ids.Count == 0) return;
await _sysUserMenuRep.DeleteAsync(u => ids.Contains(u.MenuId));
}
}

View File

@ -11,13 +11,10 @@ namespace Admin.NET.Core.Service;
/// </summary>
public class SysUserRoleService : ITransient
{
private readonly SysCacheService _sysCacheService;
private readonly SqlSugarRepository<SysUserRole> _sysUserRoleRep;
public SysUserRoleService(SysCacheService sysCacheService,
SqlSugarRepository<SysUserRole> sysUserRoleRep)
public SysUserRoleService(SqlSugarRepository<SysUserRole> sysUserRoleRep)
{
_sysCacheService = sysCacheService;
_sysUserRoleRep = sysUserRoleRep;
}

View File

@ -19,6 +19,7 @@ public class SysUserService : IDynamicApiController, ITransient
private readonly SysUserRoleService _sysUserRoleService;
private readonly SysConfigService _sysConfigService;
private readonly SysOnlineUserService _sysOnlineUserService;
private readonly SysUserMenuService _sysUserMenuService;
private readonly SysCacheService _sysCacheService;
private readonly SysUserLdapService _sysUserLdapService;
private readonly SqlSugarRepository<SysUser> _sysUserRep;
@ -31,6 +32,7 @@ public class SysUserService : IDynamicApiController, ITransient
SysUserRoleService sysUserRoleService,
SysConfigService sysConfigService,
SysOnlineUserService sysOnlineUserService,
SysUserMenuService sysUserMenuService,
SysCacheService sysCacheService,
SysUserLdapService sysUserLdapService,
SqlSugarRepository<SysUser> sysUserRep,
@ -43,6 +45,7 @@ public class SysUserService : IDynamicApiController, ITransient
_sysUserRoleService = sysUserRoleService;
_sysConfigService = sysConfigService;
_sysOnlineUserService = sysOnlineUserService;
_sysUserMenuService = sysUserMenuService;
_sysCacheService = sysCacheService;
_sysUserLdapService = sysUserLdapService;
_sysUserRep = sysUserRep;
@ -100,8 +103,11 @@ public class SysUserService : IDynamicApiController, ITransient
[DisplayName("增加用户")]
public virtual async Task<long> AddUser(AddUserInput input)
{
var isExist = await _sysUserRep.AsQueryable().ClearFilter().AnyAsync(u => u.Account == input.Account);
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1003);
// 是否租户隔离登录验证
var isTenantHostLogin = await _sysConfigService.GetConfigValueByCode<bool>(ConfigConst.SysTenantHostLogin);
var query = _sysUserRep.AsQueryable().ClearFilter().WhereIF(isTenantHostLogin, u => u.TenantId == _userManager.TenantId || u.AccountType == AccountTypeEnum.SuperAdmin);
if (await query.AnyAsync(u => u.Account == input.Account)) throw Oops.Oh(ErrorCodeEnum.D1003);
if (!string.IsNullOrWhiteSpace(input.Phone) && await query.AnyAsync(u => u.Phone == input.Phone)) throw Oops.Oh(ErrorCodeEnum.D1032);
var password = await _sysConfigService.GetConfigValueByCode<string>(ConfigConst.SysPassword);
@ -114,7 +120,7 @@ public class SysUserService : IDynamicApiController, ITransient
// 增加域账号
if (!string.IsNullOrWhiteSpace(input.DomainAccount))
await _sysUserLdapService.AddUserLdap(newUser.TenantId.Value, newUser.Id, newUser.Account, input.DomainAccount);
await _sysUserLdapService.AddUserLdap(newUser.TenantId!.Value, newUser.Id, newUser.Account, input.DomainAccount);
// 执行订阅事件
_sysUserEventHandler.OnEvent(this, SysUserEventTypeEnum.Add, input);
@ -132,11 +138,15 @@ public class SysUserService : IDynamicApiController, ITransient
[DisplayName("更新用户")]
public virtual async Task UpdateUser(UpdateUserInput input)
{
if (await _sysUserRep.AsQueryable().ClearFilter().AnyAsync(u => u.Account == input.Account && u.Id != input.Id))
throw Oops.Oh(ErrorCodeEnum.D1003);
// 是否租户隔离登录验证
var isTenantHostLogin = await _sysConfigService.GetConfigValueByCode<bool>(ConfigConst.SysTenantHostLogin);
var query = _sysUserRep.AsQueryable().ClearFilter().Where(u => u.Id != input.Id)
.WhereIF(isTenantHostLogin, u => u.TenantId == _userManager.TenantId || u.AccountType == AccountTypeEnum.SuperAdmin);
if (await query.AnyAsync(u => u.Account == input.Account)) throw Oops.Oh(ErrorCodeEnum.D1003);
if (!string.IsNullOrWhiteSpace(input.Phone) && await query.AnyAsync(u => u.Phone == input.Phone)) throw Oops.Oh(ErrorCodeEnum.D1032);
await _sysUserRep.AsUpdateable(input.Adapt<SysUser>()).IgnoreColumns(true)
.IgnoreColumns(u => new { u.Password, u.Status }).ExecuteCommandAsync();
.IgnoreColumns(u => new { u.Password, u.Status, u.TenantId }).ExecuteCommandAsync();
await UpdateRoleAndExtOrg(input);
@ -149,7 +159,7 @@ public class SysUserService : IDynamicApiController, ITransient
if (input.OrgId != user.OrgId || !input.RoleIdList.OrderBy(u => u).SequenceEqual(roleIds.OrderBy(u => u)))
await _sysOnlineUserService.ForceOffline(input.Id);
// 更新域账号
await _sysUserLdapService.AddUserLdap(user.TenantId.Value, user.Id, user.Account, input.DomainAccount);
await _sysUserLdapService.AddUserLdap(user.TenantId!.Value, user.Id, user.Account, input.DomainAccount);
// 执行订阅事件
_sysUserEventHandler.OnEvent(this, SysUserEventTypeEnum.Update, input);
@ -183,13 +193,11 @@ public class SysUserService : IDynamicApiController, ITransient
// 若账号为租户默认账号则禁止删除
var isTenantUser = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysTenant>>().IsAnyAsync(u => u.UserId == input.Id);
if (isTenantUser)
throw Oops.Oh(ErrorCodeEnum.D1029);
if (isTenantUser) throw Oops.Oh(ErrorCodeEnum.D1029);
// 若账号为开放接口绑定账号则禁止删除
var isOpenAccessUser = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysOpenAccess>>().IsAnyAsync(u => u.BindUserId == input.Id);
if (isOpenAccessUser)
throw Oops.Oh(ErrorCodeEnum.D1030);
if (isOpenAccessUser) throw Oops.Oh(ErrorCodeEnum.D1030);
// 设置账号Token黑名单
await SetUserBalckList(user, StatusEnum.Disable);
@ -205,6 +213,9 @@ public class SysUserService : IDynamicApiController, ITransient
// 删除域账号
await _sysUserLdapService.DeleteUserLdapByUserId(input.Id);
// 删除用户收藏菜单
await _sysUserMenuService.DeleteUserMenuList(input.Id);
// 执行订阅事件
_sysUserEventHandler.OnEvent(this, SysUserEventTypeEnum.Delete, input);
}

View File

@ -13,6 +13,11 @@ public class UserManager : IScoped
{
private readonly IHttpContextAccessor _httpContextAccessor;
/// <summary>
/// 应用ID
/// </summary>
public long AppId => (_httpContextAccessor.HttpContext?.User.FindFirst(ClaimConst.AppId)?.Value).ToLong();
/// <summary>
/// 用户ID
/// </summary>

View File

@ -169,19 +169,18 @@ public static class SqlSugarSetup
//}
// 执行时间超过5秒时
if (db.Ado.SqlExecutionTime.TotalSeconds > 5)
{
var fileName = db.Ado.SqlStackTrace.FirstFileName; // 文件名
var fileLine = db.Ado.SqlStackTrace.FirstLine; // 行号
var firstMethodName = db.Ado.SqlStackTrace.FirstMethodName; // 方法名
var log = $"【{DateTime.Now}——超时SQL】\r\n【所在文件名】{fileName}\r\n【代码行数】{fileLine}\r\n【方法名】{firstMethodName}\r\n" + $"【SQL语句】{UtilMethods.GetNativeSql(sql, pars)}";
Log.Warning(log);
App.PrintToMiniProfiler("SqlSugar", "Slow", log);
}
if (db.Ado.SqlExecutionTime.TotalSeconds <= 5) return;
var fileName = db.Ado.SqlStackTrace.FirstFileName; // 文件名
var fileLine = db.Ado.SqlStackTrace.FirstLine; // 行号
var firstMethodName = db.Ado.SqlStackTrace.FirstMethodName; // 方法名
var log = $"【{DateTime.Now}——超时SQL】\r\n【所在文件名】{fileName}\r\n【代码行数】{fileLine}\r\n【方法名】{firstMethodName}\r\n" + $"【SQL语句】{UtilMethods.GetNativeSql(sql, pars)}";
Log.Warning(log);
App.PrintToMiniProfiler("SqlSugar", "Slow", log);
};
}
// 数据审计
db.Aop.DataExecuting = (oldValue, entityInfo) =>
db.Aop.DataExecuting = (_, entityInfo) =>
{
// 若正在处理种子数据则直接返回
if (_isHandlingSeedData) return;
@ -204,34 +203,33 @@ public static class SqlSugarSetup
entityInfo.SetValue(DateTime.Now);
}
// 若当前用户非空web线程时
if (App.User != null)
if (App.User == null) return;
dynamic entityValue = entityInfo.EntityValue;
if (entityInfo.PropertyName == nameof(EntityTenantId.TenantId))
{
dynamic entityValue = entityInfo.EntityValue;
if (entityInfo.PropertyName == nameof(EntityTenantId.TenantId))
{
if (entityValue.TenantId == null || entityValue.TenantId == 0)
entityInfo.SetValue(App.User.FindFirst(ClaimConst.TenantId)?.Value);
}
else if (entityInfo.PropertyName == nameof(EntityBase.CreateUserId))
{
if (entityValue.CreateUserId == null || entityValue.CreateUserId == 0)
entityInfo.SetValue(App.User.FindFirst(ClaimConst.UserId)?.Value);
}
else if (entityInfo.PropertyName == nameof(EntityBase.CreateUserName))
{
if (string.IsNullOrWhiteSpace(entityValue.CreateUserName))
entityInfo.SetValue(App.User.FindFirst(ClaimConst.RealName)?.Value);
}
else if (entityInfo.PropertyName == nameof(EntityBaseData.CreateOrgId))
{
if (entityValue.CreateOrgId == null || entityValue.CreateOrgId == 0)
entityInfo.SetValue(App.User.FindFirst(ClaimConst.OrgId)?.Value);
}
else if (entityInfo.PropertyName == nameof(EntityBaseData.CreateOrgName))
{
if (string.IsNullOrWhiteSpace(entityValue.CreateOrgName))
entityInfo.SetValue(App.User.FindFirst(ClaimConst.OrgName)?.Value);
}
if (entityValue.TenantId == null || entityValue.TenantId == 0)
entityInfo.SetValue(App.User.FindFirst(ClaimConst.TenantId)?.Value);
}
else if (entityInfo.PropertyName == nameof(EntityBase.CreateUserId))
{
if (entityValue.CreateUserId == null || entityValue.CreateUserId == 0)
entityInfo.SetValue(App.User.FindFirst(ClaimConst.UserId)?.Value);
}
else if (entityInfo.PropertyName == nameof(EntityBase.CreateUserName))
{
if (string.IsNullOrWhiteSpace(entityValue.CreateUserName))
entityInfo.SetValue(App.User.FindFirst(ClaimConst.RealName)?.Value);
}
else if (entityInfo.PropertyName == nameof(EntityBaseData.CreateOrgId))
{
if (entityValue.CreateOrgId == null || entityValue.CreateOrgId == 0)
entityInfo.SetValue(App.User.FindFirst(ClaimConst.OrgId)?.Value);
}
else if (entityInfo.PropertyName == nameof(EntityBaseData.CreateOrgName))
{
if (string.IsNullOrWhiteSpace(entityValue.CreateOrgName))
entityInfo.SetValue(App.User.FindFirst(ClaimConst.OrgName)?.Value);
}
}
// 编辑/更新
@ -283,12 +281,11 @@ public static class SqlSugarSetup
var beforeColumns = u.BeforeData[i].Columns;
for (int j = 0; j < afterColumns.Count; j++)
{
if (afterColumns[j].Value.Equals(beforeColumns[j].Value))
{
beforeColumns.Remove(beforeColumns[j]);
afterColumns.Remove(afterColumns[j]);
j--;
}
if (!afterColumns[j].Value.Equals(beforeColumns[j].Value)) continue;
beforeColumns.Remove(beforeColumns[j]);
afterColumns.Remove(afterColumns[j]);
j--;
}
}
@ -306,7 +303,7 @@ public static class SqlSugarSetup
Parameters = JSON.Serialize(u.Parameters),
Elapsed = u.Time == null ? 0 : (long)u.Time.Value.TotalMilliseconds
};
var logDb = ITenant.IsAnyConnection(SqlSugarConst.LogConfigId) ? ITenant.GetConnectionScope(SqlSugarConst.LogConfigId) : db;
var logDb = ITenant.IsAnyConnection(SqlSugarConst.LogConfigId) ? ITenant.GetConnectionScope(SqlSugarConst.LogConfigId) : ITenant.GetConnectionScope(SqlSugarConst.MainConfigId);
await logDb.CopyNew().Insertable(logDiff).ExecuteCommandAsync();
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(DateTime.Now + $"\r\n*****开始差异日志*****\r\n{Environment.NewLine}{JSON.Serialize(logDiff)}{Environment.NewLine}*****结束差异日志*****\r\n");
@ -326,8 +323,7 @@ public static class SqlSugarSetup
if (config.DbSettings.EnableInitDb)
{
Log.Information($"初始化数据库 {config.DbType} - {config.ConfigId} - {config.ConnectionString}");
if (config.DbType != SqlSugar.DbType.Oracle)
dbProvider.DbMaintenance.CreateDatabase();
if (config.DbType != SqlSugar.DbType.Oracle) dbProvider.DbMaintenance.CreateDatabase();
}
// 初始化表结构
@ -357,91 +353,99 @@ public static class SqlSugarSetup
}
// 初始化种子数据
if (config.SeedSettings.EnableInitSeed)
if (config.SeedSettings.EnableInitSeed) InitSeedData(db, config);
}
/// <summary>
/// 初始化种子数据
/// </summary>
/// <param name="db"></param>
/// <param name="config"></param>
private static void InitSeedData(SqlSugarScope db, DbConnectionConfig config)
{
SqlSugarScopeProvider dbProvider = db.GetConnectionScope(config.ConfigId);
_isHandlingSeedData = true;
Log.Information($"初始化种子数据 {config.DbType} - {config.ConfigId}");
var seedDataTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>))))
.Where(u => !u.IsDefined(typeof(TenantSeedAttribute), false))
.WhereIF(config.SeedSettings.EnableIncreSeed, u => u.IsDefined(typeof(IncreSeedAttribute), false))
.OrderBy(u => u.GetCustomAttributes(typeof(SeedDataAttribute), false).Length > 0 ? ((SeedDataAttribute)u.GetCustomAttributes(typeof(SeedDataAttribute), false)[0]).Order : 0).ToList();
int count = 0, sum = seedDataTypes.Count;
foreach (var seedType in seedDataTypes)
{
_isHandlingSeedData = true;
Log.Information($"初始化种子数据 {config.DbType} - {config.ConfigId}");
var seedDataTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>))))
.Where(u => !u.IsDefined(typeof(TenantSeedAttribute), false))
.WhereIF(config.SeedSettings.EnableIncreSeed, u => u.IsDefined(typeof(IncreSeedAttribute), false))
.OrderBy(u => u.GetCustomAttributes(typeof(SeedDataAttribute), false).Length > 0 ? ((SeedDataAttribute)u.GetCustomAttributes(typeof(SeedDataAttribute), false)[0]).Order : 0).ToList();
int count = 0, sum = seedDataTypes.Count;
foreach (var seedType in seedDataTypes)
var entityType = seedType.GetInterfaces().First().GetGenericArguments().First();
if (config.ConfigId.ToString() == SqlSugarConst.MainConfigId) // 默认库(有系统表特性、没有日志表和租户表特性)
{
var entityType = seedType.GetInterfaces().First().GetGenericArguments().First();
if (config.ConfigId.ToString() == SqlSugarConst.MainConfigId) // 默认库(有系统表特性、没有日志表和租户表特性)
{
if (entityType.GetCustomAttribute<SysTableAttribute>() == null && (entityType.GetCustomAttribute<LogTableAttribute>() != null || entityType.GetCustomAttribute<TenantAttribute>() != null))
continue;
}
else if (config.ConfigId.ToString() == SqlSugarConst.LogConfigId) // 日志库
{
if (entityType.GetCustomAttribute<LogTableAttribute>() == null)
continue;
}
else
{
var att = entityType.GetCustomAttribute<TenantAttribute>(); // 自定义的库
if (att == null || att.configId.ToString() != config.ConfigId.ToString()) continue;
}
if (entityType.GetCustomAttribute<SysTableAttribute>() == null && (entityType.GetCustomAttribute<LogTableAttribute>() != null || entityType.GetCustomAttribute<TenantAttribute>() != null))
continue;
}
else if (config.ConfigId.ToString() == SqlSugarConst.LogConfigId) // 日志库
{
if (entityType.GetCustomAttribute<LogTableAttribute>() == null)
continue;
}
else
{
var att = entityType.GetCustomAttribute<TenantAttribute>(); // 自定义的库
if (att == null || att.configId.ToString() != config.ConfigId.ToString()) continue;
}
var instance = Activator.CreateInstance(seedType);
var hasDataMethod = seedType.GetMethod("HasData");
var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast<object>();
if (seedData == null) continue;
var instance = Activator.CreateInstance(seedType);
var hasDataMethod = seedType.GetMethod("HasData");
var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast<object>();
if (seedData == null) continue;
var entityInfo = dbProvider.EntityMaintenance.GetEntityInfo(entityType);
Console.WriteLine($"添加数据 {entityInfo.DbTableName} ({config.ConfigId} - {++count}/{sum},数据量:{seedData.Count()})");
var entityInfo = dbProvider.EntityMaintenance.GetEntityInfo(entityType);
Console.WriteLine($"添加数据 {entityInfo.DbTableName} ({config.ConfigId} - {++count}/{sum},数据量:{seedData.Count()})");
// 若实体包含Id字段则设置为当前租户Id递增1
if (entityInfo.Columns.Any(u => u.PropertyName == nameof(EntityBaseId.Id)))
// 若实体包含Id字段则设置为当前租户Id递增1
if (entityInfo.Columns.Any(u => u.PropertyName == nameof(EntityBaseId.Id)))
{
var seedId = config.ConfigId.ToLong();
foreach (var sd in seedData)
{
var seedId = config.ConfigId.ToLong();
foreach (var sd in seedData)
{
var id = sd.GetType().GetProperty(nameof(EntityBaseId.Id))!.GetValue(sd, null);
if (id != null && (id.ToString() == "0" || string.IsNullOrWhiteSpace(id.ToString())))
sd.GetType().GetProperty(nameof(EntityBaseId.Id))!.SetValue(sd, ++seedId);
}
}
if (entityType.GetCustomAttribute<SplitTableAttribute>(true) != null)
{
// 拆分表的操作需要实体类型,而通过反射很难实现
// 所以这里将Init方法写在“种子数据类”内部再传入 db 反射调用
var hasInitMethod = seedType.GetMethod("Init");
var parameters = new object[] { db };
hasInitMethod?.Invoke(instance, parameters);
}
else
{
if (entityInfo.Columns.Any(u => u.IsPrimarykey))
{
// 按主键进行批量增加和更新
var storage = dbProvider.StorageableByObject(seedData.ToList()).ToStorage();
// 先修改再插入,否则会更新修改时间字段
if (seedType.GetCustomAttribute<IgnoreUpdateSeedAttribute>() == null) // 有忽略更新种子特性时则不更新
{
int updateCount = storage.AsUpdateable.IgnoreColumns(entityInfo.Columns.Where(u => u.PropertyInfo.GetCustomAttribute<IgnoreUpdateSeedColumnAttribute>() != null).Select(u => u.PropertyName).ToArray()).ExecuteCommand();
Console.WriteLine($" 修改 {updateCount}/{seedData.Count()} 条记录");
}
int insertCount = storage.AsInsertable.ExecuteCommand();
Console.WriteLine($" 插入 {insertCount}/{seedData.Count()} 条记录");
}
else
{
// 无主键则只进行插入
if (!dbProvider.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any())
dbProvider.InsertableByObject(seedData.ToList()).ExecuteCommand();
}
var id = sd.GetType().GetProperty(nameof(EntityBaseId.Id))!.GetValue(sd, null);
if (id != null && (id.ToString() == "0" || string.IsNullOrWhiteSpace(id.ToString())))
sd.GetType().GetProperty(nameof(EntityBaseId.Id))!.SetValue(sd, ++seedId);
}
}
_isHandlingSeedData = false;
if (entityType.GetCustomAttribute<SplitTableAttribute>(true) != null)
{
// 拆分表的操作需要实体类型,而通过反射很难实现
// 所以这里将Init方法写在“种子数据类”内部再传入 db 反射调用
var hasInitMethod = seedType.GetMethod("Init");
var parameters = new object[] { db };
hasInitMethod?.Invoke(instance, parameters);
}
else
{
if (entityInfo.Columns.Any(u => u.IsPrimarykey))
{
// 按主键进行批量增加和更新
var storage = dbProvider.StorageableByObject(seedData.ToList()).ToStorage();
// 先修改再插入,否则会更新修改时间字段
if (seedType.GetCustomAttribute<IgnoreUpdateSeedAttribute>() == null) // 有忽略更新种子特性时则不更新
{
int updateCount = storage.AsUpdateable.IgnoreColumns(entityInfo.Columns.Where(u => u.PropertyInfo.GetCustomAttribute<IgnoreUpdateSeedColumnAttribute>() != null).Select(u => u.PropertyName).ToArray()).ExecuteCommand();
Console.WriteLine($" 修改 {updateCount}/{seedData.Count()} 条记录");
}
int insertCount = storage.AsInsertable.ExecuteCommand();
Console.WriteLine($" 插入 {insertCount}/{seedData.Count()} 条记录");
}
else
{
// 无主键则只进行插入
if (!dbProvider.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any())
dbProvider.InsertableByObject(seedData.ToList()).ExecuteCommand();
}
}
}
_isHandlingSeedData = false;
}
/// <summary>

View File

@ -14,16 +14,16 @@ namespace Admin.NET.Core;
/// </summary>
public class ChinaDateTimeConverter : DateTimeConverterBase
{
private static readonly IsoDateTimeConverter dtConverter = new() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" };
private static readonly IsoDateTimeConverter DtConverter = new() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" };
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return dtConverter.ReadJson(reader, objectType, existingValue, serializer);
return DtConverter.ReadJson(reader, objectType, existingValue, serializer);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
dtConverter.WriteJson(writer, value, serializer);
DtConverter.WriteJson(writer, value, serializer);
}
}

View File

@ -62,56 +62,35 @@ public static class CodeGenUtil
return dataType + (dbColumnInfo.IsNullable ? "?" : "");
}
// OracleSQL数据类型对应的字段类型
public static string ConvertDataType_OracleSQL(string dataType, int? length, int? scale)
{
switch (dataType.ToLower())
{
case "interval year to month":
return "int";
case "interval year to month": return "int";
case "interval day to second":
return "TimeSpan";
case "interval day to second": return "TimeSpan";
case "smallint":
return "Int16";
case "smallint": return "Int16";
case "int":
case "integer":
return "int";
case "integer": return "int";
case "long":
return "long";
case "long": return "long";
case "float":
return "float";
case "float": return "float";
case "decimal":
return "decimal";
case "decimal": return "decimal";
case "number":
if (length != null)
if (length == null) return "decimal";
return scale switch
{
if (scale != null && scale > 0)
{
return "decimal";
}
else if ((scale != null && scale == 0) || scale == null)
{
if (length > 1 && length < 12)
{
return "int";
}
else if (length > 11)
{
return "long";
}
}
if (length == 1)
{
return "bool";
}
}
return "decimal";
> 0 => "decimal",
0 or null when length is > 1 and < 12 => "int",
0 or null when length > 11 => "long",
_ => length == 1 ? "bool" : "decimal"
};
case "char":
case "clob":
@ -145,7 +124,7 @@ public static class CodeGenUtil
}
}
//PostgreSQL数据类型对应的字段类型
// PostgreSQL数据类型对应的字段类型
public static string ConvertDataType_PostgreSQL(string dataType)
{
switch (dataType)
@ -229,6 +208,7 @@ public static class CodeGenUtil
}
}
//
public static string ConvertDataType_Default(string dataType)
{
return dataType.ToLower() switch
@ -262,10 +242,10 @@ public static class CodeGenUtil
{
"string" => "Input",
"int" => "InputNumber",
"long" => "Input",
"float" => "Input",
"double" => "Input",
"decimal" => "Input",
"long" => "InputNumber",
"float" => "InputNumber",
"double" => "InputNumber",
"decimal" => "InputNumber",
"bool" => "Switch",
"Guid" => "Input",
"DateTime" => "DatePicker",

View File

@ -17,18 +17,41 @@ namespace Admin.NET.Core;
/// </summary>
public static class CommonUtil
{
/// <summary>
/// 根据字符串获取固定整型哈希值
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static int GetFixedHashCode(string str)
{
if (string.IsNullOrWhiteSpace(str)) return 0;
unchecked
{
int hash1 = (5381 << 16) + 5381;
int hash2 = hash1;
for (int i = 0; i < str.Length; i += 2)
{
hash1 = ((hash1 << 5) + hash1) ^ str[i];
if (i == str.Length - 1)
break;
hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
}
return Math.Abs(hash1 + (hash2 * 1566083941));
}
}
/// <summary>
/// 生成百分数
/// </summary>
/// <param name="PassCount"></param>
/// <param name="passCount"></param>
/// <param name="allCount"></param>
/// <returns></returns>
public static string ExecPercent(decimal PassCount, decimal allCount)
public static string ExecPercent(decimal passCount, decimal allCount)
{
string res = "";
if (allCount > 0)
{
var value = (double)Math.Round(PassCount / allCount * 100, 1);
var value = (double)Math.Round(passCount / allCount * 100, 1);
if (value < 0)
res = Math.Round(value + 5 / Math.Pow(10, 0 + 1), 0, MidpointRounding.AwayFromZero).ToString();
else
@ -132,17 +155,17 @@ public static class CommonUtil
public static async Task<IActionResult> ExportExcelData<TSource, TTarget>(ISugarQueryable<TSource> query, Func<TSource, TTarget, TTarget> action = null)
where TSource : class, new() where TTarget : class, new()
{
var PropMappings = GetExportPropertMap<TSource, TTarget>();
var propMappings = GetExportPropertMap<TSource, TTarget>();
var data = query.ToList();
//相同属性复制值,字典值转换
// 相同属性复制值,字典值转换
var result = new List<TTarget>();
foreach (var item in data)
{
var newData = new TTarget();
foreach (var dict in PropMappings)
foreach (var dict in propMappings)
{
var targeProp = dict.Value.Item3;
if (targeProp != null)
var targetProp = dict.Value.Item3;
if (targetProp != null)
{
var propertyInfo = dict.Value.Item2;
var sourceVal = propertyInfo.GetValue(item, null);
@ -152,21 +175,20 @@ public static class CommonUtil
}
var map = dict.Value.Item1;
if (map != null && map.TryGetValue(sourceVal, out string value))
if (map != null && map.TryGetValue(sourceVal, out string newVal1))
{
var newVal = value;
targeProp.SetValue(newData, newVal);
targetProp.SetValue(newData, newVal1);
}
else
{
if (targeProp.PropertyType.FullName == propertyInfo.PropertyType.FullName)
if (targetProp.PropertyType.FullName == propertyInfo.PropertyType.FullName)
{
targeProp.SetValue(newData, sourceVal);
targetProp.SetValue(newData, sourceVal);
}
else
{
var newVal = sourceVal.ToString().ParseTo(targeProp.PropertyType);
targeProp.SetValue(newData, newVal);
var newVal = sourceVal.ToString().ParseTo(targetProp.PropertyType);
targetProp.SetValue(newData, newVal);
}
}
}
@ -190,23 +212,22 @@ public static class CommonUtil
/// <returns></returns>
public static async Task<ICollection<T>> ImportExcelData<T>([Required] IFormFile file) where T : class, new()
{
var importer = new ExcelImporter();
IImporter importer = new ExcelImporter();
var res = await importer.Import<T>(file.OpenReadStream());
var message = string.Empty;
if (res.HasError)
if (!res.HasError) return res.Data;
if (res.Exception != null)
message += $"\r\n{res.Exception.Message}";
foreach (DataRowErrorInfo drErrorInfo in res.RowErrors)
{
if (res.Exception != null)
message += $"\r\n{res.Exception.Message}";
foreach (DataRowErrorInfo drErrorInfo in res.RowErrors)
{
int rowNum = drErrorInfo.RowIndex;
foreach (var item in drErrorInfo.FieldErrors)
message += $"\r\n{item.Key}{item.Value}(文件第{drErrorInfo.RowIndex}行)";
}
message += "\r\n字段缺失" + string.Join("", res.TemplateErrors.Select(m => m.RequireColumnName).ToList());
throw Oops.Oh("导入异常:" + message);
int rowNum = drErrorInfo.RowIndex;
foreach (var item in drErrorInfo.FieldErrors)
message += $"\r\n{item.Key}{item.Value}(文件第{drErrorInfo.RowIndex}行)";
}
return res.Data;
message += "\r\n字段缺失" + string.Join("", res.TemplateErrors.Select(m => m.RequireColumnName).ToList());
throw Oops.Oh("导入异常:" + message);
}
/// <summary>
@ -218,7 +239,7 @@ public static class CommonUtil
/// <returns></returns>
public static async Task<ICollection<T>> ImportExcelData<T>([Required] IFormFile file, Func<ImportResult<T>, ImportResult<T>> importResultCallback = null) where T : class, new()
{
var importer = new ExcelImporter();
IImporter importer = new ExcelImporter();
var resultStream = new MemoryStream();
var res = await importer.Import<T>(file.OpenReadStream(), resultStream, importResultCallback);
resultStream.Seek(0, SeekOrigin.Begin);
@ -228,24 +249,20 @@ public static class CommonUtil
App.GetRequiredService<SysCacheService>().Set(CacheConst.KeyExcelTemp + userId, resultStream, TimeSpan.FromMinutes(5));
var message = string.Empty;
if (res.HasError)
{
if (res.Exception != null)
message += $"\r\n{res.Exception.Message}";
foreach (DataRowErrorInfo drErrorInfo in res.RowErrors)
{
int rowNum = drErrorInfo.RowIndex;
foreach (var item in drErrorInfo.FieldErrors)
message += $"\r\n{item.Key}{item.Value}(文件第{drErrorInfo.RowIndex}行)";
}
if (res.TemplateErrors.Count > 0)
message += "\r\n字段缺失" + string.Join("", res.TemplateErrors.Select(m => m.RequireColumnName).ToList());
if (!res.HasError) return res.Data;
if (message.Length > 200)
message = string.Concat(message.AsSpan(0, 200), "...\r\n异常过多建议下载错误标记文件查看详细错误信息并重新导入。");
throw Oops.Oh("导入异常:" + message);
if (res.Exception != null)
message += $"\r\n{res.Exception.Message}";
foreach (DataRowErrorInfo drErrorInfo in res.RowErrors)
{
message = drErrorInfo.FieldErrors.Aggregate(message, (current, item) => current + $"\r\n{item.Key}{item.Value}(文件第{drErrorInfo.RowIndex}行)");
}
return res.Data;
if (res.TemplateErrors.Count > 0)
message += "\r\n字段缺失" + string.Join("", res.TemplateErrors.Select(m => m.RequireColumnName).ToList());
if (message.Length > 200)
message = message.Substring(0, 200) + "...\r\n异常过多建议下载错误标记文件查看详细错误信息并重新导入。";
throw Oops.Oh("导入异常:" + message);
}
/// <summary>
@ -409,34 +426,34 @@ public static class CommonUtil
else
{
propMappings.Add(propertyInfo.Name, new Tuple<Dictionary<object, string>, PropertyInfo, PropertyInfo>(
null, sourceProps.TryGetValue(propertyInfo.Name, out PropertyInfo value) ? value : null, propertyInfo));
null, sourceProps.TryGetValue(propertyInfo.Name, out PropertyInfo prop) ? prop : null, propertyInfo));
}
}
return propMappings;
}
///// <summary>
///// 获取属性映射
///// </summary>
///// <typeparam name="TTarget"></typeparam>
///// <returns>整理导入对象的 属性名称, 字典数据,原属性信息,目标属性信息 </returns>
//private static Dictionary<string, Tuple<string, string>> GetExportDicttMap<TTarget>() where TTarget : new()
//{
// // 整理导入对象的属性名称目标属性名字典Code
// var propMappings = new Dictionary<string, Tuple<string, string>>();
// var tTargetProps = typeof(TTarget).GetProperties();
// foreach (var propertyInfo in tTargetProps)
// {
// var attrs = propertyInfo.GetCustomAttribute<ImportDictAttribute>();
// if (attrs != null && !string.IsNullOrWhiteSpace(attrs.TypeCode))
// {
// propMappings.Add(propertyInfo.Name, new Tuple<string, string>(attrs.TargetPropName, attrs.TypeCode));
// }
// }
/// <summary>
/// 获取属性映射
/// </summary>
/// <typeparam name="TTarget"></typeparam>
/// <returns>整理导入对象的 属性名称, 字典数据,原属性信息,目标属性信息 </returns>
private static Dictionary<string, Tuple<string, string>> GetExportDictMap<TTarget>() where TTarget : new()
{
// 整理导入对象的属性名称目标属性名字典Code
var propMappings = new Dictionary<string, Tuple<string, string>>();
var tTargetProps = typeof(TTarget).GetProperties();
foreach (var propertyInfo in tTargetProps)
{
var attrs = propertyInfo.GetCustomAttribute<ImportDictAttribute>();
if (attrs != null && !string.IsNullOrWhiteSpace(attrs.TypeCode))
{
propMappings.Add(propertyInfo.Name, new Tuple<string, string>(attrs.TargetPropName, attrs.TypeCode));
}
}
// return propMappings;
//}
return propMappings;
}
/// <summary>
/// 解析IP地址

View File

@ -30,7 +30,7 @@ public static class ComputerUtil
memoryMetrics.FreeRam = Math.Round(memoryMetrics.Free / 1024, 2) + "GB";
memoryMetrics.UsedRam = Math.Round(memoryMetrics.Used / 1024, 2) + "GB";
memoryMetrics.TotalRam = Math.Round(memoryMetrics.Total / 1024, 2) + "GB";
memoryMetrics.RamRate = Math.Ceiling(100 * memoryMetrics.Used / memoryMetrics.Total).ToString() + "%";
memoryMetrics.RamRate = Math.Ceiling(100 * memoryMetrics.Used / memoryMetrics.Total) + "%";
var cpuRates = GetCPURates();
if (cpuRates != null)
{
@ -45,25 +45,25 @@ public static class ComputerUtil
/// <returns></returns>
public static String GetOSInfo()
{
string opeartion = string.Empty;
string operation = string.Empty;
if (IsMacOS())
{
var output = ShellHelper.Bash("sw_vers | awk 'NR<=2{printf \"%s \", $NF}'");
var output = ShellUtil.Bash("sw_vers | awk 'NR<=2{printf \"%s \", $NF}'");
if (output != null)
{
opeartion = output.Replace("%", string.Empty);
operation = output.Replace("%", string.Empty);
}
}
else if (IsUnix())
{
var output = ShellHelper.Bash("awk -F= '/^VERSION_ID/ {print $2}' /etc/os-release | tr -d '\"'");
opeartion = output ?? string.Empty;
var output = ShellUtil.Bash("awk -F= '/^VERSION_ID/ {print $2}' /etc/os-release | tr -d '\"'");
operation = output ?? string.Empty;
}
else
{
opeartion = RuntimeInformation.OSDescription;
operation = RuntimeInformation.OSDescription;
}
return opeartion;
return operation;
}
/// <summary>
@ -75,19 +75,18 @@ public static class ComputerUtil
var diskInfos = new List<DiskInfo>();
if (IsMacOS())
{
var output = ShellHelper.Bash(@"df -m | awk '/^\/dev\/disk/ {print $1,$2,$3,$4,$5}'");
var output = ShellUtil.Bash(@"df -m | awk '/^\/dev\/disk/ {print $1,$2,$3,$4,$5}'");
var disks = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
if (disks.Length < 1) return diskInfos;
foreach (var item in disks)
{
var disk = item.Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
if (disk == null || disk.Length < 5)
continue;
if (disk.Length < 5) continue;
var diskInfo = new DiskInfo()
{
DiskName = disk[0],
TypeName = ShellHelper.Bash("diskutil info " + disk[0] + " | awk '/File System Personality/ {print $4}'").Replace("\n", string.Empty),
TypeName = ShellUtil.Bash("diskutil info " + disk[0] + " | awk '/File System Personality/ {print $4}'").Replace("\n", string.Empty),
TotalSize = Math.Round(long.Parse(disk[1]) / 1024.0m, 2, MidpointRounding.AwayFromZero),
Used = Math.Round(long.Parse(disk[2]) / 1024.0m, 2, MidpointRounding.AwayFromZero),
AvailableFreeSpace = Math.Round(long.Parse(disk[3]) / 1024.0m, 2, MidpointRounding.AwayFromZero),
@ -98,7 +97,7 @@ public static class ComputerUtil
}
else if (IsUnix())
{
var output = ShellHelper.Bash(@"df -mT | awk '/^\/dev\/(sd|vd|xvd|nvme|sda|vda)/ {print $1,$2,$3,$4,$5,$6}'");
var output = ShellUtil.Bash(@"df -mT | awk '/^\/dev\/(sd|vd|xvd|nvme|sda|vda|mapper)/ {print $1,$2,$3,$4,$5,$6}'");
var disks = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
if (disks.Length < 1) return diskInfos;
@ -109,8 +108,7 @@ public static class ComputerUtil
foreach (var item in disks)
{
var disk = item.Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
if (disk == null || disk.Length < 6)
continue;
if (disk.Length < 6) continue;
var diskInfo = new DiskInfo()
{
@ -126,8 +124,8 @@ public static class ComputerUtil
}
else
{
var driv = DriveInfo.GetDrives().Where(u => u.IsReady);
foreach (var item in driv)
var driveList = DriveInfo.GetDrives().Where(u => u.IsReady);
foreach (var item in driveList)
{
if (item.DriveType == DriveType.CDRom) continue;
var obj = new DiskInfo()
@ -184,7 +182,7 @@ public static class ComputerUtil
}
else if (IsUnix())
{
string output = ShellUtil.Bash("top -b -n1 | grep \"Cpu(s)\" | awk '{print $2 + $4}'");
string output = ShellUtil.Bash("awk '{u=$2+$4; t=$2+$4+$5; if (NR==1){u1=u; t1=t;} else print ($2+$4-u1) * 100 / (t-t1); }' <(grep 'cpu ' /proc/stat) <(sleep 1;grep 'cpu ' /proc/stat)");
cpuRates.Add(output.Trim());
}
else
@ -217,7 +215,7 @@ public static class ComputerUtil
}
else if (IsUnix())
{
string output = ShellUtil.Bash("uptime -s").Trim();
string output = ShellUtil.Bash("date -d \"$(awk -F. '{print $1}' /proc/uptime) second ago\" +\"%Y-%m-%d %H:%M:%S\"").Trim();
runTime = DateTimeUtil.FormatTime((DateTime.Now - output.ParseToDateTime()).TotalMilliseconds.ToString().Split('.')[0].ParseToLong());
}
else
@ -362,21 +360,14 @@ public class MemoryMetricsClient
/// <returns></returns>
public static MemoryMetrics GetUnixMetrics()
{
string output = ShellUtil.Bash("free -m | awk '{print $2,$3,$4,$5,$6}'");
string output = ShellUtil.Bash("awk '/MemTotal/ {total=$2} /MemAvailable/ {available=$2} END {print total,available}' /proc/meminfo");
var metrics = new MemoryMetrics();
var lines = output.Split('\n', (char)StringSplitOptions.RemoveEmptyEntries);
if (lines.Length <= 0) return metrics;
var memory = output.Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
if (memory.Length != 2) return metrics;
if (lines != null && lines.Length > 0)
{
var memory = lines[1].Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
if (memory.Length >= 3)
{
metrics.Total = double.Parse(memory[0]);
metrics.Used = double.Parse(memory[1]);
metrics.Free = double.Parse(memory[2]);//m
}
}
metrics.Total = double.Parse(memory[0]) / 1024;
metrics.Free = double.Parse(memory[1]) / 1024;
metrics.Used = metrics.Total - metrics.Free;
return metrics;
}

View File

@ -36,10 +36,7 @@ public class DateTimeUtil
/// <returns></returns>
public static DateTime GetBeginTime(DateTime? dateTime, int days = 0)
{
if (dateTime == DateTime.MinValue || dateTime == null)
return DateTime.Now.AddDays(days);
return dateTime ?? DateTime.Now;
return dateTime == DateTime.MinValue || dateTime == null ? DateTime.Now.AddDays(days) : (DateTime)dateTime;
}
/// <summary>
@ -90,16 +87,16 @@ public class DateTimeUtil
long hour = (ms - day * dd) / hh;
long minute = (ms - day * dd - hour * hh) / mi;
long second = (ms - day * dd - hour * hh - minute * mi) / ss;
long milliSecond = ms - day * dd - hour * hh - minute * mi - second * ss;
//long milliSecond = ms - day * dd - hour * hh - minute * mi - second * ss;
string sDay = day < 10 ? "0" + day : "" + day; //天
string sHour = hour < 10 ? "0" + hour : "" + hour;//小时
string sMinute = minute < 10 ? "0" + minute : "" + minute;//分钟
string sSecond = second < 10 ? "0" + second : "" + second;//秒
string sMilliSecond = milliSecond < 10 ? "0" + milliSecond : "" + milliSecond;//毫秒
sMilliSecond = milliSecond < 100 ? "0" + sMilliSecond : "" + sMilliSecond;
//string sMilliSecond = milliSecond < 10 ? "0" + milliSecond : "" + milliSecond;//毫秒
//sMilliSecond = milliSecond < 100 ? "0" + sMilliSecond : "" + sMilliSecond;
return string.Format("{0} 天 {1} 小时 {2} 分 {3} 秒", sDay, sHour, sMinute, sSecond);
return $"{sDay} 天 {sHour} 小时 {sMinute} 分 {sSecond} 秒";
}
/// <summary>
@ -140,12 +137,7 @@ public class DateTimeUtil
/// <returns></returns>
public static string FormatDateTime(DateTime? dt)
{
if (dt == null) return string.Empty;
if (dt.Value.Year == DateTime.Now.Year)
return dt.Value.ToString("MM-dd HH:mm");
else
return dt.Value.ToString("yyyy-MM-dd HH:mm");
return dt == null ? string.Empty : dt.Value.ToString(dt.Value.Year == DateTime.Now.Year ? "MM-dd HH:mm" : "yyyy-MM-dd HH:mm");
}
/// <summary>
@ -156,8 +148,8 @@ public class DateTimeUtil
{
return new List<DateTime>
{
Convert.ToDateTime(time.ToString("D").ToString()),
Convert.ToDateTime(time.AddDays(1).ToString("D").ToString()).AddSeconds(-1)
Convert.ToDateTime(time.ToString("D")),
Convert.ToDateTime(time.AddDays(1).ToString("D")).AddSeconds(-1)
};
}

View File

@ -78,7 +78,7 @@ public class ExcelHelper
/// <param name="filename"></param>
/// <param name="addListValidationFun"></param>
/// <returns></returns>
public static IActionResult ExportTemplate<T>(List<T> list, string filename = "导入模板", Func<ExcelWorksheet, PropertyInfo, IEnumerable<string>> addListValidationFun = null)
public static IActionResult ExportTemplate<T>(IEnumerable<T> list, string filename = "导入模板", Func<ExcelWorksheet, PropertyInfo, IEnumerable<string>> addListValidationFun = null)
{
using var package = new ExcelPackage((ExportData(list, filename) as XlsxFileResult)!.Stream);
var worksheet = package.Workbook.Worksheets[0];

View File

@ -10,8 +10,8 @@ namespace Admin.NET.Core;
public static class MiniExcelUtil
{
private const string _sheetName = "ImportTemplate";
private const string _directory = "export";
private const string SheetName = "ImportTemplate";
private const string DirectoryName = "export";
/// <summary>
/// 导出模板Excel
@ -23,7 +23,7 @@ public static class MiniExcelUtil
// 在内存中当开辟空间
var memoryStream = new MemoryStream();
// 将数据写到内存当中
await memoryStream.SaveAsAsync(values, sheetName: _sheetName);
await memoryStream.SaveAsAsync(values, sheetName: SheetName);
// 从0的位置开始写入
memoryStream.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
@ -41,7 +41,7 @@ public static class MiniExcelUtil
{
using MemoryStream stream = new MemoryStream();
await file.CopyToAsync(stream);
var res = await stream.QueryAsync<T>(sheetName: _sheetName);
var res = await stream.QueryAsync<T>(sheetName: SheetName);
return res.ToArray();
}
@ -54,7 +54,7 @@ public static class MiniExcelUtil
var fileName = string.Format("{0}.xlsx", YitIdHelper.NextId());
try
{
var path = Path.Combine(App.WebHostEnvironment.WebRootPath, _directory);
var path = Path.Combine(App.WebHostEnvironment.WebRootPath, DirectoryName);
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
var filePath = Path.Combine(path, fileName);
@ -65,6 +65,6 @@ public static class MiniExcelUtil
throw Oops.Oh("出现错误:" + error);
}
var host = CommonUtil.GetLocalhost();
return $"{host}/{_directory}/{fileName}";
return $"{host}/{DirectoryName}/{fileName}";
}
}

View File

@ -22,13 +22,13 @@ public static class TripleDES
[Obsolete]
public static void EncryptFile(string inputFile, string outputFile, string password)
{
using var tdes = new TripleDESCryptoServiceProvider();
tdes.Mode = CipherMode.ECB;
tdes.Padding = PaddingMode.PKCS7;
tdes.Key = Encoding.UTF8.GetBytes(password);
using var ties = new TripleDESCryptoServiceProvider();
ties.Mode = CipherMode.ECB;
ties.Padding = PaddingMode.PKCS7;
ties.Key = Encoding.UTF8.GetBytes(password);
using var inputFileStream = new FileStream(inputFile, FileMode.Open);
using var encryptedFileStream = new FileStream(outputFile, FileMode.Create);
using var cryptoStream = new CryptoStream(encryptedFileStream, tdes.CreateEncryptor(), CryptoStreamMode.Write);
using var cryptoStream = new CryptoStream(encryptedFileStream, ties.CreateEncryptor(), CryptoStreamMode.Write);
inputFileStream.CopyTo(cryptoStream);
}
@ -41,13 +41,13 @@ public static class TripleDES
[Obsolete]
public static void DecryptFile(string inputFile, string outputFile, string password)
{
using var tdes = new TripleDESCryptoServiceProvider();
tdes.Mode = CipherMode.ECB;
tdes.Padding = PaddingMode.PKCS7;
tdes.Key = Encoding.UTF8.GetBytes(password);
using var ties = new TripleDESCryptoServiceProvider();
ties.Mode = CipherMode.ECB;
ties.Padding = PaddingMode.PKCS7;
ties.Key = Encoding.UTF8.GetBytes(password);
using var encryptedFileStream = new FileStream(inputFile, FileMode.Open);
using var decryptedFileStream = new FileStream(outputFile, FileMode.Create);
using var cryptoStream = new CryptoStream(encryptedFileStream, tdes.CreateDecryptor(), CryptoStreamMode.Read);
using var cryptoStream = new CryptoStream(encryptedFileStream, ties.CreateDecryptor(), CryptoStreamMode.Read);
cryptoStream.CopyTo(decryptedFileStream);
}
}

View File

@ -11,81 +11,81 @@ namespace Admin.NET.Core;
/// </summary>
public static class VerifyFileExtensionName
{
private static readonly IDictionary<string, string> dics_ext = new Dictionary<string, string>();
private static readonly IDictionary<string, HashSet<int>> ext_dics = new Dictionary<string, HashSet<int>>();
private static readonly IDictionary<string, string> DicsExt = new Dictionary<string, string>();
private static readonly IDictionary<string, HashSet<int>> ExtDics = new Dictionary<string, HashSet<int>>();
static VerifyFileExtensionName()
{
dics_ext.Add("FFD8FFE0", ".jpg");
dics_ext.Add("FFD8FFE1", ".jpg");
dics_ext.Add("89504E47", ".png");
dics_ext.Add("47494638", ".gif");
dics_ext.Add("49492A00", ".tif");
dics_ext.Add("424D", ".bmp");
DicsExt.Add("FFD8FFE0", ".jpg");
DicsExt.Add("FFD8FFE1", ".jpg");
DicsExt.Add("89504E47", ".png");
DicsExt.Add("47494638", ".gif");
DicsExt.Add("49492A00", ".tif");
DicsExt.Add("424D", ".bmp");
// PS和CAD
dics_ext.Add("38425053", ".psd");
dics_ext.Add("41433130", ".dwg"); // CAD
dics_ext.Add("252150532D41646F6265", ".ps");
DicsExt.Add("38425053", ".psd");
DicsExt.Add("41433130", ".dwg"); // CAD
DicsExt.Add("252150532D41646F6265", ".ps");
// 办公文档类
dics_ext.Add("D0CF11E0", ".ppt,.doc,.xls"); // ppt、doc、xls
dics_ext.Add("504B0304", ".pptx,.docx,.xlsx"); // pptx、docx、xlsx
DicsExt.Add("D0CF11E0", ".ppt,.doc,.xls"); // ppt、doc、xls
DicsExt.Add("504B0304", ".pptx,.docx,.xlsx"); // pptx、docx、xlsx
/* 注意由于文本文档录入内容过多,则读取文件头时较为多变-START */
dics_ext.Add("0D0A0D0A", ".txt"); // txt
dics_ext.Add("0D0A2D2D", ".txt"); // txt
dics_ext.Add("0D0AB4B4", ".txt"); // txt
dics_ext.Add("B4B4BDA8", ".txt"); // 文件头部为汉字
dics_ext.Add("73646673", ".txt"); // txt,文件头部为英文字母
dics_ext.Add("32323232", ".txt"); // txt,文件头部内容为数字
dics_ext.Add("0D0A09B4", ".txt"); // txt,文件头部内容为数字
dics_ext.Add("3132330D", ".txt"); // txt,文件头部内容为数字
DicsExt.Add("0D0A0D0A", ".txt"); // txt
DicsExt.Add("0D0A2D2D", ".txt"); // txt
DicsExt.Add("0D0AB4B4", ".txt"); // txt
DicsExt.Add("B4B4BDA8", ".txt"); // 文件头部为汉字
DicsExt.Add("73646673", ".txt"); // txt,文件头部为英文字母
DicsExt.Add("32323232", ".txt"); // txt,文件头部内容为数字
DicsExt.Add("0D0A09B4", ".txt"); // txt,文件头部内容为数字
DicsExt.Add("3132330D", ".txt"); // txt,文件头部内容为数字
/* 注意由于文本文档录入内容过多,则读取文件头时较为多变-END */
dics_ext.Add("7B5C727466", ".rtf"); // 日记本
DicsExt.Add("7B5C727466", ".rtf"); // 日记本
dics_ext.Add("255044462D312E", ".pdf");
DicsExt.Add("255044462D312E", ".pdf");
// 视频或音频类
dics_ext.Add("3026B275", ".wma");
dics_ext.Add("57415645", ".wav");
dics_ext.Add("41564920", ".avi");
dics_ext.Add("4D546864", ".mid");
dics_ext.Add("2E524D46", ".rm");
dics_ext.Add("000001BA", ".mpg");
dics_ext.Add("000001B3", ".mpg");
dics_ext.Add("6D6F6F76", ".mov");
dics_ext.Add("3026B2758E66CF11", ".asf");
DicsExt.Add("3026B275", ".wma");
DicsExt.Add("57415645", ".wav");
DicsExt.Add("41564920", ".avi");
DicsExt.Add("4D546864", ".mid");
DicsExt.Add("2E524D46", ".rm");
DicsExt.Add("000001BA", ".mpg");
DicsExt.Add("000001B3", ".mpg");
DicsExt.Add("6D6F6F76", ".mov");
DicsExt.Add("3026B2758E66CF11", ".asf");
// 压缩包
dics_ext.Add("52617221", ".rar");
dics_ext.Add("504B03040A000000", ".zip");
dics_ext.Add("504B030414000000", ".zip");
dics_ext.Add("1F8B08", ".gz");
DicsExt.Add("52617221", ".rar");
DicsExt.Add("504B03040A000000", ".zip");
DicsExt.Add("504B030414000000", ".zip");
DicsExt.Add("1F8B08", ".gz");
// 程序文件
dics_ext.Add("3C3F786D6C", ".xml");
dics_ext.Add("68746D6C3E", ".html");
dics_ext.Add("04034b50", ".apk");
DicsExt.Add("3C3F786D6C", ".xml");
DicsExt.Add("68746D6C3E", ".html");
DicsExt.Add("04034b50", ".apk");
//dics_ext.Add("7061636B", ".java");
//dics_ext.Add("3C254020", ".jsp");
//dics_ext.Add("4D5A9000", ".exe");
dics_ext.Add("44656C69766572792D646174653A", ".eml"); // 邮件
dics_ext.Add("5374616E64617264204A", ".mdb"); // Access数据库文件
DicsExt.Add("44656C69766572792D646174653A", ".eml"); // 邮件
DicsExt.Add("5374616E64617264204A", ".mdb"); // Access数据库文件
dics_ext.Add("46726F6D", ".mht");
dics_ext.Add("4D494D45", ".mhtml");
DicsExt.Add("46726F6D", ".mht");
DicsExt.Add("4D494D45", ".mhtml");
foreach (var dics in dics_ext)
foreach (var dics in DicsExt)
{
foreach (var ext in dics.Value.Split(","))
{
if (!ext_dics.ContainsKey(ext))
ext_dics.Add(ext, new HashSet<int> { dics.Key.Length / 2 });
if (!ExtDics.ContainsKey(ext))
ExtDics.Add(ext, new HashSet<int> { dics.Key.Length / 2 });
else
ext_dics[ext].Add(dics.Key.Length / 2);
ExtDics[ext].Add(dics.Key.Length / 2);
}
}
}
@ -102,18 +102,17 @@ public static class VerifyFileExtensionName
return false;
suffix = suffix.ToLower();
if (!ext_dics.ContainsKey(suffix))
return false;
if (!ExtDics.TryGetValue(suffix, out HashSet<int> dic)) return false;
try
{
foreach (var Len in ext_dics[suffix])
foreach (var len in dic)
{
byte[] b = new byte[Len];
byte[] b = new byte[len];
_ = stream.Read(b, 0, b.Length);
// string fileType = System.Text.Encoding.UTF8.GetString(b);
string fileKey = GetFileHeader(b);
if (dics_ext.ContainsKey(fileKey))
if (DicsExt.ContainsKey(fileKey))
return true;
}
}
@ -152,14 +151,12 @@ public static class VerifyFileExtensionName
if (src == null || src.Length <= 0)
return null;
string hv;
for (int i = 0; i < src.Length; i++)
{
// 以十六进制(基数 16无符号整数形式返回一个整数参数的字符串表示形式并转换为大写
hv = Convert.ToString(src[i] & 0xFF, 16).ToUpper();
if (hv.Length < 2)
builder.Append(0);
builder.Append(hv);
string hVal = Convert.ToString(src[i] & 0xFF, 16).ToUpper();
if (hVal.Length < 2) builder.Append(0);
builder.Append(hVal);
}
return builder.ToString();
}

View File

@ -89,15 +89,9 @@ public class XlsxFileResultBase : ActionResult
var response = context.HttpContext.Response;
response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
if (downloadFileName == null)
{
downloadFileName = Guid.NewGuid().ToString("N") + ".xlsx";
}
downloadFileName ??= Guid.NewGuid().ToString("N") + ".xlsx";
if (string.IsNullOrEmpty(Path.GetExtension(downloadFileName)))
{
downloadFileName += ".xlsx";
}
if (string.IsNullOrEmpty(Path.GetExtension(downloadFileName))) downloadFileName += ".xlsx";
context.HttpContext.Response.Headers.Append("Content-Disposition", new[] { "attachment; filename=" + HttpUtility.UrlEncode(downloadFileName) });
await stream.CopyToAsync(context.HttpContext.Response.Body);

View File

@ -12,7 +12,7 @@ namespace @Model.NameSpace;
public class @(@Model.ClassName)Dto
{
@foreach (var column in Model.TableField){
if(@column.EffectType == "fk" && @column.FkEntityName != "" && @column.FkColumnName != ""){
if(@column.EffectType == "ForeignKey" && @column.FkEntityName != "" && @column.FkColumnName != ""){
@:/// <summary>
@:/// @column.ColumnComment
@:/// </summary>

View File

@ -3,11 +3,11 @@ import { BasicColumn, FormSchema } from '/@@/components/Table';
@foreach (var column in Model.TableField){
if(@column.EffectType == "Upload"){
@:import { uploadFile } from '/@@/api/sys/admin';
}else if(@column.EffectType == "fk"){
}else if(@column.EffectType == "ForeignKey"){
@:import { get@(@column.FkEntityName)Dropdown } from '/@@/api/main/@(@Model.ClassName)';
}else if(@column.EffectType == "Select"){
}else if(@column.EffectType == "DictSelector"){
@:import { getDataList } from '/@@/api/sys/admin';
}else if(@column.EffectType == "ApiTreeSelect"){
}else if(@column.EffectType == "ApiTreeSelector"){
@:import { get@(@column.FkEntityName)Tree } from '/@@/api/main/@(@Model.ClassName)';
}else if(@column.EffectType == "ConstSelector"){
@:import { codeToName, getSelector } from '/@@/utils/helper/constSelectorHelper';
@ -24,7 +24,7 @@ export const columns: BasicColumn[] = [
@:sorter: true,
if(@column.EffectType == "Upload"){
@:slots: { customRender: '@(@column.LowerPropertyName)' },
}else if(@column.EffectType == "fk"){
}else if(@column.EffectType == "ForeignKey"){
@:customRender: ({ record }) => {
@:return record.fk@(@column.PropertyName).@(@column.LowerFkColumnName);
@:},
@ -49,14 +49,14 @@ export const searchFormSchema: FormSchema[] = [
@:field: '@column.LowerPropertyName',
@:label: '@column.ColumnComment',
@:colProps: { span: 8 },
if(@column.EffectType == "fk"){
if(@column.EffectType == "ForeignKey"){
@:component: 'ApiSelect',
@:componentProps: {
@:api: get@(@column.FkEntityName)Dropdown,
@:labelField: 'label',
@:valueField: 'value',
@:},
}else if(@column.EffectType == "Select"){
}else if(@column.EffectType == "DictSelector"){
@:component: 'ApiSelect',
@:componentProps: {
@:api: getDataList,
@ -75,7 +75,7 @@ if(@column.EffectType == "fk"){
@:value: 'code',
@:},
@:},
}else if(@column.EffectType == "ApiTreeSelect"){
}else if(@column.EffectType == "ApiTreeSelector"){
@:component: '@(@column.EffectType)',
@:componentProps: {
@:api: get@(@column.FkEntityName)Tree,
@ -100,14 +100,14 @@ export const formSchema: FormSchema[] = [
@:{
@:label: '@column.ColumnComment',
@:field: '@column.LowerPropertyName',
if(@column.EffectType == "fk"){
if(@column.EffectType == "ForeignKey"){
@:component: 'ApiSelect',
@:componentProps: {
@:api: get@(@column.FkEntityName)Dropdown,
@:labelField: 'label',
@:valueField: 'value',
@:},
}else if(@column.EffectType == "Select"){
}else if(@column.EffectType == "DictSelector"){
@:component: 'ApiSelect',
@:componentProps: {
@:api: getDataList,
@ -126,7 +126,7 @@ if(@column.EffectType == "fk"){
@:value: 'code',
@:},
@:},
}else if(@column.EffectType == "ApiTreeSelect"){
}else if(@column.EffectType == "ApiTreeSelector"){
@:component: '@(@column.EffectType)',
@:componentProps: {
@:api: get@(@column.FkEntityName)Tree,

View File

@ -15,7 +15,7 @@ public class @(@Model.ClassName)Output
@:/// <summary>
@:/// @column.ColumnComment
@:/// </summary>
if(column.EffectType == "fk")
if(column.EffectType == "ForeignKey")
{
@:public @column.NetType @column.PropertyName { get; set; }
@:
@ -27,7 +27,7 @@ if(column.EffectType == "fk")
}else if(column.EffectType == "Upload"){
@:public @column.NetType @column.PropertyName { get; set; }
@:public SysFile @(@column.PropertyName)Attachment { get; set; }
}else if(column.EffectType == "ApiTreeSelect"){
}else if(column.EffectType == "ApiTreeSelector"){
@:public @column.NetType @column.PropertyName { get; set; }
@:
@:/// <summary>
@ -43,7 +43,7 @@ if(column.EffectType == "fk")
@foreach (var column in Model.TableField){
if (@column.EffectType == "ApiTreeSelect"){
if (@column.EffectType == "ApiTreeSelector"){
@:// 使用实际实体@(@column.FkTableName),所以这里就删了
@:/*
@:[SugarTable("@(@column.FkTableName)")]

View File

@ -85,10 +85,10 @@ if (@column.QueryWhether == "Y"){
@if(Model.IsJoinTable){
@://处理外键和TreeSelector相关字段的连接
@foreach (var column in Model.TableField){
if(@column.EffectType == "fk"){
if(@column.EffectType == "ForeignKey"){
joinTableName += ", " + column.PropertyName.ToLower();
@:.LeftJoin<@(@column.FkEntityName)>((@(@joinTableName)) => u.@(@column.PropertyName) == @(@column.PropertyName.ToLower()).@(@column.FkLinkColumnName) )
} else if(@column.EffectType == "ApiTreeSelect"){
} else if(@column.EffectType == "ApiTreeSelector"){
joinTableName += ", " + column.PropertyName.ToLower();
@:.LeftJoin<@(@column.FkEntityName)>((@(@joinTableName)) => u.@(@column.PropertyName) == @(@column.PropertyName.ToLower()).@(@column.ValueColumn) )
}
@ -96,10 +96,10 @@ if (@column.QueryWhether == "Y"){
@:.Select((@(@joinTableName)) => new @(@Model.ClassName)Output
@:{
@foreach (var column in Model.TableField){
if(@column.EffectType == "fk"){
if(@column.EffectType == "ForeignKey"){
@:@(@column.PropertyName) = u.@(@column.PropertyName),
@:@(@column.PropertyName)@(@column.FkColumnName) = @(@column.PropertyName.ToLower()).@(@column.FkColumnName),
} else if(@column.EffectType == "ApiTreeSelect"){
} else if(@column.EffectType == "ApiTreeSelector"){
@:@(@column.PropertyName) = u.@(@column.PropertyName),
@:@(@column.PropertyName)@(@column.DisplayColumn) = @(@column.PropertyName.ToLower()).@(@column.DisplayColumn),
} else if(@column.NetType?.TrimEnd('?').EndsWith("Enum") == true){
@ -110,7 +110,7 @@ if (@column.QueryWhether == "Y"){
}
@:});
@foreach (var column in Model.TableField){
if(@column.EffectType == "fk"){
if(@column.EffectType == "ForeignKey"){
}else if(@column.EffectType == "Upload"){
@://.Mapper(c => c.@(@column.PropertyName)Attachment, c => c.@(@column.PropertyName))
@ -119,7 +119,7 @@ if (@column.QueryWhether == "Y"){
} else {
@:.Select<@(@Model.ClassName)Output>();
}
@if(@Model.TableField.Any(x=>x.EffectType == "fk")){
@if(@Model.TableField.Any(x=>x.EffectType == "ForeignKey")){
@:return await query.OrderBuilder(input,"[u].","[@(PKName)]").ToPagedListAsync(input.Page, input.PageSize);
} else {
@:return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
@ -216,7 +216,7 @@ if (@column.ColumnKey == "True"){
}
@foreach (var column in Model.TableField){
if(@column.EffectType == "fk" && (@column.WhetherAddUpdate == "Y" || column.QueryWhether == "Y")){
if(@column.EffectType == "ForeignKey" && (@column.WhetherAddUpdate == "Y" || column.QueryWhether == "Y")){
@:/// <summary>
@:/// 获取@(@column.ColumnComment)列表
@:/// </summary>
@ -254,7 +254,7 @@ if(@column.EffectType == "Upload"){
}
@foreach (var column in Model.TableField){
if(@column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("@(@column.FkEntityName)Tree")){
if(@column.EffectType == "ApiTreeSelector" && !definedObjects.ContainsKey("@(@column.FkEntityName)Tree")){
@{definedObjects.Add("@(@column.FkEntityName)Tree", 1);}
@:[ApiDescriptionSettings(Name = "@(LowerFirstLetter(@column.FkEntityName))Tree", Description = "获取@(@column.ColumnComment)列表", Order = 920), HttpGet]
@:[DisplayName("获取@(@column.FkEntityName)Tree")]

View File

@ -22,9 +22,9 @@ enum Api {
@:Exists@(RemoteField) = '/api/@(@Model.LowerClassName)/exists@(RemoteField)',
}
@foreach (var column in Model.TableField){
if(@column.EffectType == "fk" && (@column.WhetherAddUpdate == "Y" || column.QueryWhether == "Y")){
if(@column.EffectType == "ForeignKey" && (@column.WhetherAddUpdate == "Y" || column.QueryWhether == "Y")){
@:Get@(@column.FkEntityName)@(@column.PropertyName)Dropdown = '/api/@(@Model.LowerClassName)/@(LowerFirstLetter(@column.FkEntityName))@(@column.PropertyName)Dropdown',
}else if(@column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("Get@(@column.FkEntityName)Tree")){
}else if(@column.EffectType == "ApiTreeSelector" && !definedObjects.ContainsKey("Get@(@column.FkEntityName)Tree")){
@{definedObjects.Add("Get@(@column.FkEntityName)Tree", 1);}
@:Get@(@column.FkEntityName)Tree = '/api/@(@Model.LowerClassName)/@(LowerFirstLetter(@column.FkEntityName))Tree',
}else if(@column.EffectType == "Upload"){
@ -74,13 +74,13 @@ export const detail@(@Model.ClassName) = (id: any) =>
});
@foreach (var column in Model.TableField){
if(@column.EffectType == "fk" && (@column.WhetherAddUpdate == "Y" || column.QueryWhether == "Y")){
if(@column.EffectType == "ForeignKey" && (@column.WhetherAddUpdate == "Y" || column.QueryWhether == "Y")){
@:export const get@(@column.FkEntityName)@(@column.PropertyName)Dropdown = () =>
@:request({
@:url: Api.Get@(@column.FkEntityName)@(@column.PropertyName)Dropdown,
@:method: 'get'
@:});
}else if(@column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("get@(@column.FkEntityName)Tree")){
}else if(@column.EffectType == "ApiTreeSelector" && !definedObjects.ContainsKey("get@(@column.FkEntityName)Tree")){
@{definedObjects.Add("get@(@column.FkEntityName)Tree", 1);}
@:export const get@(@column.FkEntityName)Tree = () =>
@:request({

View File

@ -31,7 +31,7 @@
</el-form-item>
}else{
if (@column.WhetherAddUpdate == "Y"){
if(@column.EffectType == "fk"){
if(@column.EffectType == "ForeignKey"){
@:<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
@:<el-form-item label="@column.ColumnComment" prop="@(@column.LowerPropertyName)">
@:<el-select clearable filterable v-model="state.ruleForm.@(@column.LowerPropertyName)" placeholder="请选择@(@column.ColumnComment)">
@ -47,7 +47,7 @@
</el-select>
</el-form-item>
</el-col>
}else if(@column.EffectType == "ApiTreeSelect"){
}else if(@column.EffectType == "ApiTreeSelector"){
@:<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
@:<el-form-item label="@column.ColumnComment" prop="@(@column.LowerPropertyName)">
<el-cascader
@ -87,7 +87,7 @@
@:show-word-limit clearable />
</el-form-item>
</el-col>
}else if(@column.EffectType == "Select"){
}else if(@column.EffectType == "DictSelector"){
@:<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
@:<el-form-item label="@column.ColumnComment" prop="@(@column.LowerPropertyName)">
@:<el-select clearable v-model="state.ruleForm.@(@column.LowerPropertyName)" placeholder="请选择@(@column.ColumnComment)">
@ -191,7 +191,7 @@ import type { FormRules } from "element-plus";
@if(Model.RemoteVerify){
@:exists@(RemoteField),
}
@foreach (var column in Model.TableField.Where(x=>x.EffectType == "fk").ToList()){
@foreach (var column in Model.TableField.Where(x=>x.EffectType == "ForeignKey").ToList()){
@:get@(@column.FkEntityName)@(@column.PropertyName)Dropdown,
}
@:add@(@Model.ClassName),
@ -207,7 +207,7 @@ if(@Model.TableField.Any(x=>x.EffectType == "Upload")){
@if(@Model.TableField.Any(x=>x.EffectType == "ConstSelector")){
@:import { getConstType } from "/@@/utils/constHelper";
}
@if(@Model.TableField.Any(x=>x.EffectType == "Select") || @Model.TableField.Any(x=>x.EffectType == "EnumSelector")){
@if(@Model.TableField.Any(x=>x.EffectType == "DictSelector") || @Model.TableField.Any(x=>x.EffectType == "EnumSelector")){
@:import { getDictDataItem as di, getDictDataList as dl } from '/@@/utils/dict-utils';
}
@if(@Model.TableField.Any(x=>x.EffectType == "EnumSelector")){
@ -217,7 +217,7 @@ if(@Model.TableField.Any(x=>x.EffectType == "Upload")){
@:import { formatDate } from '/@@/utils/formatTime';
}
@foreach (var column in Model.TableField){
if(@column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("import__@(@column.FkEntityName)Tree")){
if(@column.EffectType == "ApiTreeSelector" && !definedObjects.ContainsKey("import__@(@column.FkEntityName)Tree")){
@{definedObjects.Add("import__@(@column.FkEntityName)Tree", 1);}
}
}
@ -268,7 +268,7 @@ const rules = ref<FormRules>({
@:@column.LowerPropertyName: [
@foreach(var rule in @column.RuleItems){
string trigger="blur";
if(@column.EffectType == "DatePicker" || @column.EffectType == "Select" ||@column.EffectType == "ApiTreeSelect"){
if(@column.EffectType == "DatePicker" || @column.EffectType == "DictSelector" ||@column.EffectType == "ApiTreeSelector"){
trigger="change";
}
if(rule.Type=="required"){
@ -389,7 +389,7 @@ const submit = async () => {
};
@foreach (var column in Model.TableField){
if(@column.EffectType == "fk" && @column.WhetherAddUpdate == "Y"){
if(@column.EffectType == "ForeignKey" && @column.WhetherAddUpdate == "Y"){
@://下拉列表@(@column.ColumnComment)
@:const @LowerFirstLetter(@column.FkEntityName)@(@column.PropertyName)DropdownList = ref<any>([]);
@:const get@(@column.FkEntityName)@(@column.PropertyName)DropdownList = async () => {
@ -406,7 +406,7 @@ if(@column.EffectType == "fk" && @column.WhetherAddUpdate == "Y"){
}
@foreach (var column in Model.TableField){
if(@column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("define_get@(@column.FkEntityName)TreeData")){
if(@column.EffectType == "ApiTreeSelector" && !definedObjects.ContainsKey("define_get@(@column.FkEntityName)TreeData")){
@{definedObjects.Add("define_get@(@column.FkEntityName)TreeData", 1);}
@:const @LowerFirstLetter(@column.FkEntityName)TreeData = ref<any>([]);
@:const get@(@column.FkEntityName)TreeData = async () => {

View File

@ -44,7 +44,7 @@
@:<el-input-number v-model="queryParams.@(@column.LowerPropertyName)" clearable="" placeholder="请输入@(@column.ColumnComment)"/>
@:
</el-form-item>
}else if(@column.EffectType == "fk"){
}else if(@column.EffectType == "ForeignKey"){
@:<el-form-item label="@column.ColumnComment">
@:<el-select clearable="" filterable="" v-model="queryParams.@(@column.LowerPropertyName)" placeholder="请选择@(@column.ColumnComment)">
@:<el-option v-for="(item,index) in @LowerFirstLetter(@column.FkEntityName)@(@column.PropertyName)DropdownList" :key="index" :value="item.value" :label="item.label" />
@ -52,7 +52,7 @@
</el-select>
@:
</el-form-item>
}else if(@column.EffectType == "Select"){
}else if(@column.EffectType == "DictSelector"){
@:<el-form-item label="@column.ColumnComment">
@:<el-select clearable="" v-model="queryParams.@(@column.LowerPropertyName)" placeholder="请选择@(@column.ColumnComment)">
@:<el-option v-for="(item,index) in dl('@(@column.DictTypeCode)')" :key="index" :value="item.code" :label="`[${item.code}] ${item.value}`" />
@ -126,7 +126,7 @@
<el-table-column type="index" label="序号" width="55" align="center"/>
@foreach (var column in Model.TableField){
if(@column.WhetherTable == "Y"){
if(@column.EffectType == "Upload"||@column.EffectType == "fk"||@column.EffectType == "ApiTreeSelect"||@column.EffectType == "Switch"||@column.EffectType == "ConstSelector"){
if(@column.EffectType == "Upload"||@column.EffectType == "ForeignKey"||@column.EffectType == "ApiTreeSelector"||@column.EffectType == "Switch"||@column.EffectType == "ConstSelector"){
@:<el-table-column prop="@column.LowerPropertyName" label="@column.ColumnComment" @(column.WhetherSortable == "Y" ? "sortable='custom'" : "") show-overflow-tooltip="">
@:<template #default="scope">
if(@column.EffectType == "Upload"){
@ -140,9 +140,9 @@
@::initial-index="0"
@:fit="scale-down"
@:preview-teleported=""/>
}else if(@column.EffectType == "fk"){
}else if(@column.EffectType == "ForeignKey"){
@:<span>{{scope.row.@LowerFirstLetter(@column.PropertyName)@(@column.FkColumnName)}}</span>
}else if(@column.EffectType == "ApiTreeSelect"){
}else if(@column.EffectType == "ApiTreeSelector"){
@:<span>{{scope.row.@LowerFirstLetter(@column.PropertyName)@(column.DisplayColumn)}}</span>
}else if(@column.EffectType == "Switch"){
@:<el-tag v-if="scope.row.@(@column.LowerPropertyName)"> 是 </el-tag>
@ -155,7 +155,7 @@
@:
</el-table-column>
}
else if(@column.EffectType == "Select"){
else if(@column.EffectType == "DictSelector"){
@:<el-table-column prop="@column.LowerPropertyName" label="@column.ColumnComment" @(column.WhetherSortable == "Y" ? "sortable='custom'" : "") show-overflow-tooltip="" >
@:<template #default="scope">
@:<el-tag :type="di('@(@column.DictTypeCode)', scope.row.@(@column.LowerPropertyName))?.tagType"> {{di("@(@column.DictTypeCode)", scope.row.@(@column.LowerPropertyName))?.value}} </el-tag>
@ -221,7 +221,7 @@
@if(@Model.TableField.Any(x=>x.EffectType == "ConstSelector")){
@:import { codeToName, getConstType } from "/@@/utils/constHelper";
}
@if(@Model.TableField.Any(x=>x.EffectType == "Select") || @Model.TableField.Any(x=>x.EffectType == "EnumSelector")){
@if(@Model.TableField.Any(x=>x.EffectType == "DictSelector") || @Model.TableField.Any(x=>x.EffectType == "EnumSelector")){
@:import { getDictDataItem as di, getDictDataList as dl } from '/@@/utils/dict-utils';
}
@if(@Model.TableField.Any(x=>x.EffectType == "EnumSelector")){
@ -245,7 +245,7 @@
import editDialog from '/@@/views/@(@Model.PagePath)/@(@Model.LowerClassName)/component/editDialog.vue'
import { page@(@Model.ClassName), delete@(@Model.ClassName) } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)';
@foreach (var column in Model.QueryWhetherList){
if(@column.EffectType == "fk"){
if(@column.EffectType == "ForeignKey"){
@:import { get@(@column.FkEntityName)@(@column.PropertyName)Dropdown } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)';
}
}
@ -372,7 +372,7 @@
};
@foreach (var column in Model.QueryWhetherList){
if(@column.EffectType == "fk"){
if(@column.EffectType == "ForeignKey"){
@:const @LowerFirstLetter(@column.FkEntityName)@(@column.PropertyName)DropdownList = ref<any>([]);
@:const get@(@column.FkEntityName)@(@column.PropertyName)DropdownList = async () => {
@:let list = await get@(@column.FkEntityName)@(@column.PropertyName)Dropdown();

Some files were not shown because too many files have changed in this diff Show More