Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
6438995d47
@ -40,12 +40,12 @@
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Web" Version="3.1.2" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.8" />
|
||||
<PackageReference Include="SKIT.FlurlHttpClient.Wechat.Api" Version="3.5.0" />
|
||||
<PackageReference Include="SKIT.FlurlHttpClient.Wechat.TenpayV3" Version="3.8.0" />
|
||||
<PackageReference Include="SKIT.FlurlHttpClient.Wechat.Api" Version="3.6.0" />
|
||||
<PackageReference Include="SKIT.FlurlHttpClient.Wechat.TenpayV3" Version="3.9.0" />
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.170" />
|
||||
<PackageReference Include="SSH.NET" Version="2024.1.0" />
|
||||
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.4.8" />
|
||||
<PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1116" />
|
||||
<PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1118" />
|
||||
<PackageReference Include="UAParser" Version="3.1.47" />
|
||||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -110,7 +110,7 @@ public enum ErrorCodeEnum
|
||||
D1012,
|
||||
|
||||
/// <summary>
|
||||
/// 所属机构不在自己的数据范围内
|
||||
/// 没有权限操作该数据
|
||||
/// </summary>
|
||||
[ErrorCodeItemMetadata("没有权限操作该数据")]
|
||||
D1013,
|
||||
|
||||
62
Admin.NET/Admin.NET.Core/Enum/SysUserEventTypeEnum.cs
Normal file
62
Admin.NET/Admin.NET.Core/Enum/SysUserEventTypeEnum.cs
Normal file
@ -0,0 +1,62 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 事件类型-系统用户操作枚举
|
||||
/// </summary>
|
||||
[Description("事件类型-系统用户操作枚举")]
|
||||
public enum SysUserEventTypeEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 增加用户
|
||||
/// </summary>
|
||||
[Description("增加用户")]
|
||||
Add = 111,
|
||||
|
||||
/// <summary>
|
||||
/// 更新用户
|
||||
/// </summary>
|
||||
[Description("更新用户")]
|
||||
Update = 222,
|
||||
|
||||
/// <summary>
|
||||
/// 授权用户角色
|
||||
/// </summary>
|
||||
[Description("授权用户角色")]
|
||||
UpdateRole = 333,
|
||||
|
||||
/// <summary>
|
||||
/// 删除用户
|
||||
/// </summary>
|
||||
[Description("删除用户")]
|
||||
Delete = 444,
|
||||
|
||||
/// <summary>
|
||||
/// 设置用户状态
|
||||
/// </summary>
|
||||
[Description("设置用户状态")]
|
||||
SetStatus = 555,
|
||||
|
||||
/// <summary>
|
||||
/// 修改密码
|
||||
/// </summary>
|
||||
[Description("修改密码")]
|
||||
ChangePwd = 666,
|
||||
|
||||
/// <summary>
|
||||
/// 重置密码
|
||||
/// </summary>
|
||||
[Description("重置密码")]
|
||||
ResetPwd = 777,
|
||||
|
||||
/// <summary>
|
||||
/// 解除登录锁定
|
||||
/// </summary>
|
||||
[Description("解除登录锁定")]
|
||||
UnlockLogin = 888
|
||||
}
|
||||
@ -49,6 +49,7 @@ public class SysDatabaseService : IDynamicApiController, ITransient
|
||||
var visualTableList = new List<VisualTable>();
|
||||
var visualColumnList = new List<VisualColumn>();
|
||||
var columnRelationList = new List<ColumnRelation>();
|
||||
var dbOptions = App.GetOptions<DbConnectionOptions>().ConnectionConfigs.First(u => u.ConfigId.ToString() == SqlSugarConst.MainConfigId);
|
||||
|
||||
// 遍历所有实体获取所有库表结构
|
||||
var random = new Random();
|
||||
@ -71,7 +72,7 @@ public class SysDatabaseService : IDynamicApiController, ITransient
|
||||
var visualColumn = new VisualColumn
|
||||
{
|
||||
TableName = columnInfo.DbTableName,
|
||||
ColumnName = columnInfo.DbColumnName,
|
||||
ColumnName = dbOptions.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(columnInfo.DbColumnName) : columnInfo.DbColumnName,
|
||||
DataType = columnInfo.PropertyInfo.PropertyType.Name,
|
||||
DataLength = columnInfo.Length.ToString(),
|
||||
ColumnDescription = columnInfo.ColumnDescription,
|
||||
@ -83,13 +84,14 @@ public class SysDatabaseService : IDynamicApiController, ITransient
|
||||
{
|
||||
var name1 = columnInfo.Navigat.GetName();
|
||||
var name2 = columnInfo.Navigat.GetName2();
|
||||
var targetColumnName = string.IsNullOrEmpty(name2) ? "Id" : name2;
|
||||
var relation = new ColumnRelation
|
||||
{
|
||||
SourceTableName = columnInfo.DbTableName,
|
||||
SourceColumnName = name1,
|
||||
SourceColumnName = dbOptions.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(name1) : name1,
|
||||
Type = columnInfo.Navigat.GetNavigateType() == NavigateType.OneToOne ? "ONE_TO_ONE" : "ONE_TO_MANY",
|
||||
TargetTableName = columnInfo.DbColumnName,
|
||||
TargetColumnName = string.IsNullOrEmpty(name2) ? "Id" : name2
|
||||
TargetTableName = dbOptions.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(columnInfo.DbColumnName) : columnInfo.DbColumnName,
|
||||
TargetColumnName = dbOptions.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(targetColumnName) : targetColumnName
|
||||
};
|
||||
columnRelationList.Add(relation);
|
||||
}
|
||||
|
||||
@ -30,6 +30,8 @@ public class SysDbBackupService : IDynamicApiController, ITransient
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(backupDir))
|
||||
Directory.CreateDirectory(backupDir);
|
||||
var fileList = Directory.GetFiles(backupDir);
|
||||
|
||||
var dbBackupList = new List<DbBackupOutput>();
|
||||
|
||||
@ -6,13 +6,8 @@
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class DictDataInput : BaseIdInput
|
||||
public class DictDataInput : BaseStatusInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
[Dict("StatusEnum")]
|
||||
public StatusEnum Status { get; set; }
|
||||
}
|
||||
|
||||
public class PageDictDataInput : BasePageInput
|
||||
|
||||
@ -6,13 +6,8 @@
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class DictTypeInput : BaseIdInput
|
||||
public class DictTypeInput : BaseStatusInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
[Dict("StatusEnum")]
|
||||
public StatusEnum Status { get; set; }
|
||||
}
|
||||
|
||||
public class PageDictTypeInput : BasePageInput
|
||||
|
||||
@ -72,6 +72,9 @@ public class SysNoticeService : IDynamicApiController, ITransient
|
||||
[DisplayName("更新通知公告")]
|
||||
public async Task UpdateNotice(UpdateNoticeInput input)
|
||||
{
|
||||
if (input.CreateUserId != _userManager.UserId)
|
||||
throw Oops.Oh(ErrorCodeEnum.D7003);
|
||||
|
||||
var notice = input.Adapt<SysNotice>();
|
||||
InitNoticeInfo(notice);
|
||||
await _sysNoticeRep.UpdateAsync(notice);
|
||||
@ -87,6 +90,12 @@ public class SysNoticeService : IDynamicApiController, ITransient
|
||||
[DisplayName("删除通知公告")]
|
||||
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);
|
||||
|
||||
await _sysNoticeRep.DeleteAsync(u => u.Id == input.Id);
|
||||
|
||||
await _sysNoticeUserRep.DeleteAsync(u => u.NoticeId == input.Id);
|
||||
@ -100,6 +109,9 @@ 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);
|
||||
|
||||
// 更新发布状态和时间
|
||||
await _sysNoticeRep.UpdateAsync(u => new SysNotice() { Status = NoticeStatusEnum.PUBLIC, PublicTime = DateTime.Now }, u => u.Id == input.Id);
|
||||
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 系统用户操作事件参数
|
||||
/// </summary>
|
||||
public class SysUserEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 事件类型
|
||||
/// </summary>
|
||||
[Required]
|
||||
public SysUserEventTypeEnum EventType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 接口输入参数
|
||||
/// </summary>
|
||||
public object Input { get; set; }
|
||||
|
||||
public SysUserEventArgs(SysUserEventTypeEnum eventType, object input)
|
||||
{
|
||||
this.EventType = eventType;
|
||||
this.Input = input;
|
||||
}
|
||||
}
|
||||
@ -9,13 +9,8 @@ namespace Admin.NET.Core.Service;
|
||||
/// <summary>
|
||||
/// 设置用户状态输入参数
|
||||
/// </summary>
|
||||
public class UserInput : BaseIdInput
|
||||
public class UserInput : BaseStatusInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
[Dict("StatusEnum")]
|
||||
public StatusEnum Status { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
32
Admin.NET/Admin.NET.Core/Service/User/SysUserEventHandler.cs
Normal file
32
Admin.NET/Admin.NET.Core/Service/User/SysUserEventHandler.cs
Normal file
@ -0,0 +1,32 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 系统用户事件处理类
|
||||
/// </summary>
|
||||
public class SysUserEventHandler : ISingleton
|
||||
{
|
||||
private event EventHandler Event;
|
||||
|
||||
/// <summary>
|
||||
/// 订阅
|
||||
/// </summary>
|
||||
/// <param name="eventHandler"></param>
|
||||
public void Subscribe(EventHandler eventHandler) => Event += eventHandler;
|
||||
|
||||
/// <summary>
|
||||
/// 发布事件
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="eventType"></param>
|
||||
/// <param name="input"></param>
|
||||
public void OnEvent(object sender, SysUserEventTypeEnum eventType, object input)
|
||||
{
|
||||
Event?.Invoke(sender, new SysUserEventArgs(eventType, input));
|
||||
}
|
||||
}
|
||||
@ -22,6 +22,7 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
private readonly SysCacheService _sysCacheService;
|
||||
private readonly SysUserLdapService _sysUserLdapService;
|
||||
private readonly SqlSugarRepository<SysUser> _sysUserRep;
|
||||
private readonly SysUserEventHandler _sysUserEventHandler;
|
||||
|
||||
public SysUserService(UserManager userManager,
|
||||
SysOrgService sysOrgService,
|
||||
@ -32,7 +33,8 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
SysOnlineUserService sysOnlineUserService,
|
||||
SysCacheService sysCacheService,
|
||||
SysUserLdapService sysUserLdapService,
|
||||
SqlSugarRepository<SysUser> sysUserRep)
|
||||
SqlSugarRepository<SysUser> sysUserRep,
|
||||
SysUserEventHandler sysUserEventHandler)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_sysOrgService = sysOrgService;
|
||||
@ -44,6 +46,7 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
_sysCacheService = sysCacheService;
|
||||
_sysUserLdapService = sysUserLdapService;
|
||||
_sysUserRep = sysUserRep;
|
||||
_sysUserEventHandler = sysUserEventHandler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -113,6 +116,9 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
if (!string.IsNullOrWhiteSpace(input.DomainAccount))
|
||||
await _sysUserLdapService.AddUserLdap(newUser.TenantId.Value, newUser.Id, newUser.Account, input.DomainAccount);
|
||||
|
||||
// 执行订阅事件
|
||||
_sysUserEventHandler.OnEvent(this, SysUserEventTypeEnum.Add, input);
|
||||
|
||||
return newUser.Id;
|
||||
}
|
||||
|
||||
@ -144,6 +150,9 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
await _sysOnlineUserService.ForceOffline(input.Id);
|
||||
// 更新域账号
|
||||
await _sysUserLdapService.AddUserLdap(user.TenantId.Value, user.Id, user.Account, input.DomainAccount);
|
||||
|
||||
// 执行订阅事件
|
||||
_sysUserEventHandler.OnEvent(this, SysUserEventTypeEnum.Update, input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -195,6 +204,9 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
|
||||
// 删除域账号
|
||||
await _sysUserLdapService.DeleteUserLdapByUserId(input.Id);
|
||||
|
||||
// 执行订阅事件
|
||||
_sysUserEventHandler.OnEvent(this, SysUserEventTypeEnum.Delete, input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -241,7 +253,12 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
await SetUserBalckList(user, input.Status);
|
||||
|
||||
user.Status = input.Status;
|
||||
return await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new { u.Status }).ExecuteCommandAsync();
|
||||
var rows = await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new { u.Status }).ExecuteCommandAsync();
|
||||
|
||||
// 执行订阅事件
|
||||
if (rows > 0) _sysUserEventHandler.OnEvent(this, SysUserEventTypeEnum.SetStatus, input);
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -280,6 +297,9 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
// throw Oops.Oh(ErrorCodeEnum.D1022);
|
||||
|
||||
await _sysUserRoleService.GrantUserRole(input);
|
||||
|
||||
// 执行订阅事件
|
||||
_sysUserEventHandler.OnEvent(this, SysUserEventTypeEnum.UpdateRole, input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -322,7 +342,12 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
}
|
||||
|
||||
user.LastChangePasswordTime = DateTime.Now;
|
||||
return await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new { u.Password, u.LastChangePasswordTime }).ExecuteCommandAsync();
|
||||
var rows = await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new { u.Password, u.LastChangePasswordTime }).ExecuteCommandAsync();
|
||||
|
||||
// 执行订阅事件
|
||||
if (rows > 0) _sysUserEventHandler.OnEvent(this, SysUserEventTypeEnum.ChangePwd, input);
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -343,6 +368,9 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
var keyPasswordErrorTimes = $"{CacheConst.KeyPasswordErrorTimes}{user.Account}";
|
||||
_sysCacheService.Remove(keyPasswordErrorTimes);
|
||||
|
||||
// 执行订阅事件
|
||||
_sysUserEventHandler.OnEvent(this, SysUserEventTypeEnum.ResetPwd, input);
|
||||
|
||||
return password;
|
||||
}
|
||||
|
||||
@ -359,6 +387,9 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
// 清空密码错误次数
|
||||
var keyPasswordErrorTimes = $"{CacheConst.KeyPasswordErrorTimes}{user.Account}";
|
||||
_sysCacheService.Remove(keyPasswordErrorTimes);
|
||||
|
||||
// 执行订阅事件
|
||||
_sysUserEventHandler.OnEvent(this, SysUserEventTypeEnum.UnlockLogin, input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -368,7 +368,7 @@ public class SysWechatPayService : IDynamicApiController, ITransient
|
||||
{
|
||||
TransactionId = input.OutTradeNumber,
|
||||
OutTradeNumber = request.OutTradeNumber,
|
||||
OutRefundNo = request.OutTradeNumber, //每笔付款只退一次,所以这里直接用付款单号
|
||||
OutRefundNo = request.OutTradeNumber, // 每笔付款只退一次,所以这里直接用付款单号
|
||||
Reason = request.Reason,
|
||||
Refund = input.Refund,
|
||||
Total = input.Total,
|
||||
@ -422,7 +422,7 @@ public class SysWechatPayService : IDynamicApiController, ITransient
|
||||
{
|
||||
MerchantId = _wechatPayOptions.MerchantId,
|
||||
TransactionId = transactionId,
|
||||
WechatpayCertificateSerialNumber = _wechatPayOptions.MerchantCertificateSerialNumber
|
||||
WechatpaySerialNumber = _wechatPayOptions.MerchantCertificateSerialNumber
|
||||
};
|
||||
var response = await _wechatTenpayClient.ExecuteGetPayTransactionByIdAsync(request);
|
||||
if (response.TradeState == "SUCCESS" || response.TradeState == "CLOSED")
|
||||
@ -463,7 +463,7 @@ public class SysWechatPayService : IDynamicApiController, ITransient
|
||||
{
|
||||
MerchantId = _wechatPayOptions.MerchantId,
|
||||
OutTradeNumber = outTradeNumber,
|
||||
WechatpayCertificateSerialNumber = _wechatPayOptions.MerchantCertificateSerialNumber
|
||||
WechatpaySerialNumber = _wechatPayOptions.MerchantCertificateSerialNumber,
|
||||
};
|
||||
var response = await _wechatTenpayClient.ExecuteGetPayTransactionByOutTradeNumberAsync(request);
|
||||
if (response.TradeState == "SUCCESS" || response.TradeState == "CLOSED")
|
||||
|
||||
27
Admin.NET/Admin.NET.Core/Utils/BaseImportInput.cs
Normal file
27
Admin.NET/Admin.NET.Core/Utils/BaseImportInput.cs
Normal file
@ -0,0 +1,27 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 数据导入输入参数
|
||||
/// </summary>
|
||||
public class BaseImportInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 记录Id
|
||||
/// </summary>
|
||||
[ImporterHeader(IsIgnore = true)]
|
||||
[ExporterHeader(IsIgnore = true)]
|
||||
public virtual long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
[ImporterHeader(IsIgnore = true)]
|
||||
[ExporterHeader("错误信息", ColumnIndex = 9999, IsBold = true, IsAutoFit = true)]
|
||||
public virtual string Error { get; set; }
|
||||
}
|
||||
19
Admin.NET/Admin.NET.Core/Utils/BaseStatusInput.cs
Normal file
19
Admin.NET/Admin.NET.Core/Utils/BaseStatusInput.cs
Normal file
@ -0,0 +1,19 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 设置状态输入参数
|
||||
/// </summary>
|
||||
public class BaseStatusInput : BaseIdInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
[Dict(nameof(StatusEnum))]
|
||||
public StatusEnum Status { get; set; }
|
||||
}
|
||||
@ -248,6 +248,34 @@ public static class CommonUtil
|
||||
return res.Data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导入数据Excel
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<List<T>> ImportExcelDataAsync<T>([Required] IFormFile file) where T : class, new()
|
||||
{
|
||||
var sysFileService = App.GetRequiredService<SysFileService>();
|
||||
var newFile = await sysFileService.UploadFile(new UploadFileInput { File = file });
|
||||
var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, newFile.FilePath!, newFile.Id + newFile.Suffix);
|
||||
|
||||
IImporter importer = new ExcelImporter();
|
||||
var res = await importer.Import<T>(filePath);
|
||||
|
||||
// 删除文件
|
||||
_ = sysFileService.DeleteFile(new DeleteFileInput { Id = newFile.Id });
|
||||
|
||||
if (res == null)
|
||||
throw Oops.Oh("导入数据为空");
|
||||
if (res.Exception != null)
|
||||
throw Oops.Oh("导入异常:" + res.Exception);
|
||||
if (res.TemplateErrors?.Count > 0)
|
||||
throw Oops.Oh("模板异常:" + res.TemplateErrors.Select(x => $"[{x.RequireColumnName}]{x.Message}").Join("\n"));
|
||||
|
||||
return res.Data.ToList();
|
||||
}
|
||||
|
||||
// 例:List<Dm_ApplyDemo> ls = CommonUtil.ParseList<Dm_ApplyDemoInport, Dm_ApplyDemo>(importResult.Data);
|
||||
/// <summary>
|
||||
/// 对象转换 含字典转换
|
||||
|
||||
120
Admin.NET/Admin.NET.Core/Utils/ExcelHelper.cs
Normal file
120
Admin.NET/Admin.NET.Core/Utils/ExcelHelper.cs
Normal file
@ -0,0 +1,120 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using OfficeOpenXml;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
public class ExcelHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据导入
|
||||
/// </summary>
|
||||
/// <param name="file"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <returns></returns>
|
||||
public static IActionResult ImportData<IN, T>(IFormFile file, Action<List<IN>, Action<StorageableResult<T>, List<IN>, List<T>>> action) where IN : BaseImportInput, new() where T : EntityBaseId, new()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = CommonUtil.ImportExcelDataAsync<IN>(file).Result ?? throw Oops.Oh("有效数据为空");
|
||||
|
||||
var tasks = new List<Task>();
|
||||
action.Invoke(result, (storageable, pageItems, rows) =>
|
||||
{
|
||||
// 标记校验信息
|
||||
tasks.Add(Task.Run(() =>
|
||||
{
|
||||
if (storageable.TotalList.Any())
|
||||
{
|
||||
for (int i = 0; i < rows.Count; i++) pageItems[i].Id = rows[i].Id;
|
||||
|
||||
for (int i = 0; i < storageable.TotalList.Count; i++)
|
||||
pageItems[i].Error = storageable.TotalList[i].StorageMessage;
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
// 等待所有标记验证信息任务完成
|
||||
Task.WhenAll(tasks).GetAwaiter().GetResult();
|
||||
|
||||
return ExportData(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.HttpContext.Response.Headers["Content-Type"] = "application/json; charset=utf-8";
|
||||
throw Oops.Oh(new AdminResult<object>
|
||||
{
|
||||
Code = 500,
|
||||
Message = ex.Message,
|
||||
Result = null,
|
||||
Type = "error",
|
||||
Extras = UnifyContext.Take(),
|
||||
Time = DateTime.Now
|
||||
}.ToJson());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出Xlsx数据
|
||||
/// </summary>
|
||||
/// <param name="list"></param>
|
||||
/// <param name="fileName"></param>
|
||||
/// <returns></returns>
|
||||
public static IActionResult ExportData(dynamic list, string fileName = "导入记录")
|
||||
{
|
||||
var exporter = new ExcelExporter();
|
||||
var fs = new MemoryStream(exporter.ExportAsByteArray(list).GetAwaiter().GetResult());
|
||||
return new XlsxFileResult(stream: fs, fileDownloadName: $"{fileName}-{DateTime.Now:yyyy-MM-dd_HHmmss}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据类型导出Xlsx模板
|
||||
/// </summary>
|
||||
/// <param name="list"></param>
|
||||
/// <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)
|
||||
{
|
||||
using var package = new ExcelPackage((ExportData(list, filename) as XlsxFileResult)!.Stream);
|
||||
var worksheet = package.Workbook.Worksheets[0];
|
||||
|
||||
foreach (var prop in typeof(T).GetProperties())
|
||||
{
|
||||
var propType = prop.PropertyType;
|
||||
|
||||
var headerAttr = prop.GetCustomAttribute<ExporterHeaderAttribute>();
|
||||
var isNullableEnum = propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof(Nullable<>) && Nullable.GetUnderlyingType(propType).IsEnum();
|
||||
if (isNullableEnum) propType = Nullable.GetUnderlyingType(propType);
|
||||
if (headerAttr == null) continue;
|
||||
|
||||
// 获取列序号
|
||||
var columnIndex = 0;
|
||||
foreach (var item in worksheet.Cells[1, 1, 1, worksheet.Dimension.End.Column])
|
||||
if (++columnIndex > 0 && item.Text.Equals(headerAttr.DisplayName)) break;
|
||||
if (columnIndex <= 0) continue;
|
||||
|
||||
// 优先从代理函数中获取下列列表,若为空且字段为枚举型,则填充枚举项为下列列表,否则不设置下列列表
|
||||
var dataList = addListValidationFun?.Invoke(worksheet, prop)?.ToList();
|
||||
if (dataList == null && propType.IsEnum()) dataList = propType.EnumToList()?.Select(it => it.Describe).ToList();
|
||||
if (dataList != null) AddListValidation(columnIndex, dataList);
|
||||
}
|
||||
|
||||
void AddListValidation(int columnIndex, List<string> dataList)
|
||||
{
|
||||
var validation = worksheet.DataValidations.AddListValidation(worksheet.Cells[2, columnIndex, 99999, columnIndex].Address);
|
||||
dataList.ForEach(e => validation!.Formula.Values.Add(e));
|
||||
validation.ShowErrorMessage = true;
|
||||
validation.ErrorTitle = "无效输入";
|
||||
validation.Error = "请从列表中选择一个有效的选项";
|
||||
}
|
||||
|
||||
package.Save();
|
||||
package.Stream.Position = 0;
|
||||
return new XlsxFileResult(stream: package.Stream, fileDownloadName: $"{filename}-{DateTime.Now:yyyy-MM-dd_HHmmss}");
|
||||
}
|
||||
}
|
||||
105
Admin.NET/Admin.NET.Core/Utils/XlsxFileResult.cs
Normal file
105
Admin.NET/Admin.NET.Core/Utils/XlsxFileResult.cs
Normal file
@ -0,0 +1,105 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Excel文件ActionResult
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class XlsxFileResult<T> : XlsxFileResultBase where T : class, new()
|
||||
{
|
||||
public string FileDownloadName { get; }
|
||||
public ICollection<T> Data { get; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="fileDownloadName"></param>
|
||||
public XlsxFileResult(ICollection<T> data, string fileDownloadName = null)
|
||||
{
|
||||
FileDownloadName = fileDownloadName;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public override async Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
var exporter = new ExcelExporter();
|
||||
var bytes = await exporter.ExportAsByteArray(Data);
|
||||
var fs = new MemoryStream(bytes);
|
||||
await DownloadExcelFileAsync(context, fs, FileDownloadName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class XlsxFileResult : XlsxFileResultBase
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="fileDownloadName"></param>
|
||||
public XlsxFileResult(Stream stream, string fileDownloadName = null)
|
||||
{
|
||||
Stream = stream;
|
||||
FileDownloadName = fileDownloadName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <param name="fileDownloadName"></param>
|
||||
|
||||
public XlsxFileResult(byte[] bytes, string fileDownloadName = null)
|
||||
{
|
||||
Stream = new MemoryStream(bytes);
|
||||
FileDownloadName = fileDownloadName;
|
||||
}
|
||||
|
||||
public Stream Stream { get; protected set; }
|
||||
public string FileDownloadName { get; protected set; }
|
||||
|
||||
public override async Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
await DownloadExcelFileAsync(context, Stream, FileDownloadName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基类
|
||||
/// </summary>
|
||||
public class XlsxFileResultBase : ActionResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 下载Excel文件
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="downloadFileName"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual async Task DownloadExcelFileAsync(ActionContext context, Stream stream, string downloadFileName)
|
||||
{
|
||||
var response = context.HttpContext.Response;
|
||||
response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
|
||||
if (downloadFileName == null)
|
||||
{
|
||||
downloadFileName = Guid.NewGuid().ToString("N") + ".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);
|
||||
}
|
||||
}
|
||||
@ -57,7 +57,7 @@ namespace Admin.NET.Web.Core
|
||||
var tenant = sysCacheService.Get<List<SysTenant>>(CacheConst.KeyTenant)?.FirstOrDefault(u => u.Id == long.Parse(tenantId));
|
||||
if (tenant != null && tenant.ExpirationTime != null && DateTime.Now > tenant.ExpirationTime)
|
||||
{
|
||||
context.Fail();
|
||||
context.Fail(new AuthorizationFailureReason(this, "租户已过期,请联系相关管理人员。"));
|
||||
context.GetCurrentHttpContext().SignoutToSwagger();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -240,6 +240,9 @@ public class Startup : AppStartup
|
||||
"image/svg+xml"
|
||||
});
|
||||
});
|
||||
|
||||
// 注册虚拟文件系统服务
|
||||
services.AddVirtualFileServer();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"name": "admin.net.pro",
|
||||
"type": "module",
|
||||
"version": "2.4.33",
|
||||
"lastBuildTime": "2024.11.04",
|
||||
"lastBuildTime": "2024.11.06",
|
||||
"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",
|
||||
"author": "zuohuaijun",
|
||||
"license": "MIT",
|
||||
@ -20,7 +20,7 @@
|
||||
"@microsoft/signalr": "^8.0.7",
|
||||
"@vue-office/docx": "^1.6.2",
|
||||
"@vue-office/excel": "^1.7.11",
|
||||
"@vue-office/pdf": "^2.0.7",
|
||||
"@vue-office/pdf": "^2.0.8",
|
||||
"@vueuse/core": "^11.2.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
@ -35,7 +35,7 @@
|
||||
"echarts-wordcloud": "^2.1.0",
|
||||
"element-plus": "^2.8.7",
|
||||
"exceljs": "^4.4.0",
|
||||
"ezuikit-js": "^8.1.1-alpha.2",
|
||||
"ezuikit-js": "^8.1.1-alpha.3",
|
||||
"gcoord": "^1.0.6",
|
||||
"js-cookie": "^3.0.5",
|
||||
"js-table2excel": "^1.1.2",
|
||||
@ -71,7 +71,7 @@
|
||||
"vue-router": "^4.4.5",
|
||||
"vue-signature-pad": "^3.0.2",
|
||||
"vue3-tree-org": "^4.2.2",
|
||||
"vxe-pc-ui": "^4.2.38",
|
||||
"vxe-pc-ui": "^4.2.41",
|
||||
"vxe-table": "^4.7.59",
|
||||
"vxe-table-plugin-element": "^4.0.4",
|
||||
"vxe-table-plugin-export-xlsx": "^4.0.7",
|
||||
@ -85,15 +85,15 @@
|
||||
"@types/node": "^20.16.5",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "^8.12.2",
|
||||
"@typescript-eslint/parser": "^8.12.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.13.0",
|
||||
"@typescript-eslint/parser": "^8.13.0",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||
"@vue/compiler-sfc": "^3.5.12",
|
||||
"code-inspector-plugin": "^0.17.7",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-plugin-vue": "^9.29.1",
|
||||
"globals": "^15.11.0",
|
||||
"globals": "^15.12.0",
|
||||
"less": "^4.2.0",
|
||||
"prettier": "^3.3.3",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
|
||||
@ -85,14 +85,6 @@ export interface SysOAuthUser {
|
||||
*/
|
||||
isDelete?: boolean;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof SysOAuthUser
|
||||
*/
|
||||
email?: string | null;
|
||||
|
||||
/**
|
||||
* 系统用户Id
|
||||
*
|
||||
@ -155,6 +147,14 @@ export interface SysOAuthUser {
|
||||
*/
|
||||
avatar?: string | null;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof SysOAuthUser
|
||||
*/
|
||||
email?: string | null;
|
||||
|
||||
/**
|
||||
* 手机号码
|
||||
*
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
</template>
|
||||
<el-tag>{{ props.data.updateTime ?? '无' }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<el-descriptions-item v-if="'remark' in props.data">
|
||||
<template #label>
|
||||
<el-text>
|
||||
<el-icon><ele-Tickets /></el-icon>备注
|
||||
|
||||
Loading…
Reference in New Issue
Block a user