mirror of
https://github.com/nsnail/NetAdmin.git
synced 2025-06-19 18:28:17 +08:00
feat: ✨ 版本更新日志组件 (#96)
This commit is contained in:
@ -2,6 +2,7 @@
|
||||
<Import Project="$(SolutionDir)/build/code.quality.props"/>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="$(SolutionDir)/assets/captcha/**" LinkBase="Assets/Captcha"/>
|
||||
<EmbeddedResource Include="$(SolutionDir)/CHANGELOG.md" LogicalName="CHANGELOG.md"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../NetAdmin.SysComponent.Host/NetAdmin.SysComponent.Host.csproj"/>
|
||||
|
@ -19,8 +19,8 @@ namespace NetAdmin.BizServer.Tests;
|
||||
/// 所有测试
|
||||
/// </summary>
|
||||
[SuppressMessage("Usage", "xUnit1028:Test method must have valid return type")]
|
||||
public class AllTests(WebApplicationFactory<Startup> factory, ITestOutputHelper testOutputHelper)
|
||||
: WebApiTestBase<Startup>(factory, testOutputHelper), IToolsModule, ICacheModule, IApiModule, IConfigModule
|
||||
public class AllTests(WebApplicationFactory<Startup> factory, ITestOutputHelper testOutputHelper) :
|
||||
WebApiTestBase<Startup>(factory, testOutputHelper), IToolsModule, ICacheModule, IApiModule, IConfigModule
|
||||
|
||||
{
|
||||
/// <inheritdoc cref="ICrudModule{TCreateReq,TCreateRsp,TQueryReq,TQueryRsp,TUpdateReq,TUpdateRsp,TDelReq}.BulkDeleteAsync" />
|
||||
@ -92,6 +92,12 @@ public class AllTests(WebApplicationFactory<Startup> factory, ITestOutputHelper
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<string> GetChangeLogAsync()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<QueryConfigRsp> GetLatestConfigAsync()
|
||||
{
|
||||
|
@ -18,7 +18,7 @@ public sealed class UserIdAttribute : ValidationAttribute
|
||||
var req = new QueryReq<QueryUserReq> { Filter = new QueryUserReq { Id = (long)value! } };
|
||||
|
||||
var method = service.GetType().GetMethod("ExistAsync");
|
||||
var exist = ((Task<bool>)method!.Invoke(service, [req]))!.ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
var exist = (Task<bool>)method!.Invoke(service, [req])!.ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
return !exist ? new ValidationResult(Ln.用户编号不存在) : ValidationResult.Success;
|
||||
}
|
||||
}
|
@ -18,7 +18,8 @@ public sealed record SqlCommandAfterEvent : SqlCommandBeforeEvent
|
||||
/// <summary>
|
||||
/// 耗时(单位:微秒)
|
||||
/// </summary>
|
||||
public long ElapsedMicroseconds { get; init; }
|
||||
/// de
|
||||
private long ElapsedMicroseconds { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
|
@ -3,12 +3,15 @@ namespace NetAdmin.Domain.Events;
|
||||
/// <summary>
|
||||
/// Sql命令事件
|
||||
/// </summary>
|
||||
public record SqlCommandEvent : DataAbstraction, IEventSource
|
||||
public abstract record SqlCommandEvent : DataAbstraction, IEventSource
|
||||
{
|
||||
/// <summary>
|
||||
/// 标识符缩写
|
||||
/// Initializes a new instance of the <see cref="SqlCommandEvent" /> class.
|
||||
/// </summary>
|
||||
public string Id => Identifier.ToString()[..8].ToUpperInvariant();
|
||||
protected SqlCommandEvent(bool isConsumOnce = false)
|
||||
{
|
||||
IsConsumOnce = isConsumOnce;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsConsumOnce { get; }
|
||||
@ -22,16 +25,21 @@ public record SqlCommandEvent : DataAbstraction, IEventSource
|
||||
/// <inheritdoc />
|
||||
public string EventId { get; protected init; }
|
||||
|
||||
/// <summary>
|
||||
/// 标识符,可将 CommandBefore 与 CommandAfter 进行匹配
|
||||
/// </summary>
|
||||
public Guid Identifier { get; protected init; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Payload { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 标识符缩写
|
||||
/// </summary>
|
||||
protected string Id => Identifier.ToString()[..8].ToUpperInvariant();
|
||||
|
||||
/// <summary>
|
||||
/// 标识符,可将 CommandBefore 与 CommandAfter 进行匹配
|
||||
/// </summary>
|
||||
protected Guid Identifier { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联的Sql语句
|
||||
/// </summary>
|
||||
public string Sql { get; protected init; }
|
||||
protected string Sql { get; init; }
|
||||
}
|
@ -19,7 +19,7 @@ public record SyncStructureBeforeEvent : SqlCommandEvent
|
||||
/// <summary>
|
||||
/// 实体类型
|
||||
/// </summary>
|
||||
public Type[] EntityTypes { get; }
|
||||
protected Type[] EntityTypes { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
|
@ -10,9 +10,14 @@ public sealed record RequestLogEvent : DataAbstraction, IEventSourceGeneric<Crea
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestLogEvent" /> class.
|
||||
/// </summary>
|
||||
public RequestLogEvent(CreateRequestLogReq data)
|
||||
public RequestLogEvent(CreateRequestLogReq data, bool isConsumOnce = false, object payload = default
|
||||
, DateTime createdTime = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Data = data;
|
||||
Data = data;
|
||||
IsConsumOnce = isConsumOnce;
|
||||
Payload = payload;
|
||||
CreatedTime = createdTime;
|
||||
CancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -10,9 +10,14 @@ public sealed record UserUpdatedEvent : DataAbstraction, IEventSourceGeneric<Use
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserUpdatedEvent" /> class.
|
||||
/// </summary>
|
||||
public UserUpdatedEvent(UserInfoRsp data)
|
||||
public UserUpdatedEvent(UserInfoRsp data, DateTime createdTime = default, bool isConsumOnce = false
|
||||
, object payload = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Data = data;
|
||||
Data = data;
|
||||
CancellationToken = cancellationToken;
|
||||
CreatedTime = createdTime;
|
||||
IsConsumOnce = isConsumOnce;
|
||||
Payload = payload;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -10,9 +10,14 @@ public sealed record VerifyCodeCreatedEvent : DataAbstraction, IEventSourceGener
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VerifyCodeCreatedEvent" /> class.
|
||||
/// </summary>
|
||||
public VerifyCodeCreatedEvent(QueryVerifyCodeRsp data)
|
||||
public VerifyCodeCreatedEvent(QueryVerifyCodeRsp data, DateTime createdTime = default, bool isConsumOnce = false
|
||||
, object payload = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Data = data;
|
||||
Data = data;
|
||||
CancellationToken = cancellationToken;
|
||||
CreatedTime = createdTime;
|
||||
IsConsumOnce = isConsumOnce;
|
||||
Payload = payload;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -54,7 +54,7 @@ public abstract class WorkBase<TLogger>
|
||||
/// 通用工作流
|
||||
/// </summary>
|
||||
/// <exception cref="NetAdminGetLockerException">加锁失败异常</exception>
|
||||
protected virtual async ValueTask WorkflowAsync(bool singleInstance, CancellationToken cancelToken)
|
||||
protected async ValueTask WorkflowAsync(bool singleInstance, CancellationToken cancelToken)
|
||||
{
|
||||
if (singleInstance) {
|
||||
// 加锁
|
||||
|
@ -108,7 +108,7 @@ public static class IMvcBuilderExtensions
|
||||
options.JsonSerializerOptions.TypeInfoResolver = new CollectionJsonTypeInfoResolver();
|
||||
|
||||
// 日期格式 2023-01-18 20:02:12
|
||||
_ = options.JsonSerializerOptions.Converters.AddDateTimeTypeConverters(Chars.TPL_DATE_YYYY_MM_DD_HH_MM_SS);
|
||||
_ = options.JsonSerializerOptions.Converters.AddDateTimeTypeConverters();
|
||||
|
||||
// object->json 枚举显名 而非数字 ,json->object 可以枚举名 也可以数值
|
||||
if (enumToString) {
|
||||
|
@ -159,9 +159,7 @@ public static class ServiceCollectionExtensions
|
||||
var freeSql = new FreeSqlBuilder(App.GetOptions<DatabaseOptions>()).Build(initMethods);
|
||||
_ = me.AddSingleton(freeSql);
|
||||
|
||||
var sqlAuditor = App.GetService<SqlAuditor>();
|
||||
|
||||
freeSql.Aop.AuditValue += sqlAuditor.DataAuditHandler; // Insert/Update自动值处理
|
||||
freeSql.Aop.AuditValue += SqlAuditor.DataAuditHandler; // Insert/Update自动值处理
|
||||
var eventPublisher = App.GetService<IEventPublisher>();
|
||||
|
||||
#pragma warning disable VSTHRD110
|
||||
|
@ -34,7 +34,7 @@ public sealed class SqlAuditor : ISingleton
|
||||
/// 对Insert/Update的数据加工
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException">e</exception>
|
||||
public void DataAuditHandler(object sender, AuditValueEventArgs e)
|
||||
public static void DataAuditHandler(object sender, AuditValueEventArgs e)
|
||||
{
|
||||
// SetServerTime(e);
|
||||
SetSnowflake(e);
|
||||
@ -56,6 +56,48 @@ public sealed class SqlAuditor : ISingleton
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetCreatedClientIp(AuditValueEventArgs e)
|
||||
{
|
||||
if (e.Value is null or 0) {
|
||||
e.Value = App.HttpContext?.GetRemoteIpAddressToIPv4().IpV4ToInt32();
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetCreatedReferer(AuditValueEventArgs e)
|
||||
{
|
||||
if (e.Value is null or "") {
|
||||
e.Value = App.HttpContext?.Request.GetRefererUrlAddress();
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetCreatedTime(AuditValueEventArgs e)
|
||||
{
|
||||
if (e.Value == null || (e.Value is DateTime val && val == default)) {
|
||||
e.Value = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetCreatedUserAgent(AuditValueEventArgs e)
|
||||
{
|
||||
if (e.Value is null or "") {
|
||||
e.Value = App.HttpContext?.Request.Headers[Chars.FLG_HTTP_HEADER_USER_AGENT].ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetCreatedUserId(AuditValueEventArgs e, ContextUserInfo userInfo)
|
||||
{
|
||||
if (userInfo != null && e.Value is null or (long and 0)) {
|
||||
e.Value = userInfo.Id;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetCreatedUserName(AuditValueEventArgs e, ContextUserInfo userInfo)
|
||||
{
|
||||
if (userInfo != null && e.Value is null or "") {
|
||||
e.Value = userInfo.UserName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置创建者
|
||||
/// </summary>
|
||||
@ -63,41 +105,22 @@ public sealed class SqlAuditor : ISingleton
|
||||
{
|
||||
switch (e.Property.Name) {
|
||||
case nameof(IFieldCreatedTime.CreatedTime):
|
||||
if (e.Value == null || (e.Value is DateTime val && val == default)) {
|
||||
e.Value = DateTime.Now;
|
||||
}
|
||||
|
||||
SetCreatedTime(e);
|
||||
break;
|
||||
case nameof(IFieldCreatedUser.CreatedUserId):
|
||||
if (userInfo != null && e.Value is null or (long and 0)) {
|
||||
e.Value = userInfo.Id;
|
||||
}
|
||||
|
||||
SetCreatedUserId(e, userInfo);
|
||||
break;
|
||||
|
||||
case nameof(IFieldCreatedUser.CreatedUserName):
|
||||
if (userInfo != null && e.Value is null or "") {
|
||||
e.Value = userInfo.UserName;
|
||||
}
|
||||
|
||||
SetCreatedUserName(e, userInfo);
|
||||
break;
|
||||
case nameof(IFieldCreatedClient.CreatedClientIp):
|
||||
if (e.Value is null or 0) {
|
||||
e.Value = App.HttpContext?.GetRemoteIpAddressToIPv4().IpV4ToInt32();
|
||||
}
|
||||
|
||||
SetCreatedClientIp(e);
|
||||
break;
|
||||
case nameof(IFieldCreatedClient.CreatedUserAgent):
|
||||
if (e.Value is null or "") {
|
||||
e.Value = App.HttpContext?.Request.Headers[Chars.FLG_HTTP_HEADER_USER_AGENT].ToString();
|
||||
}
|
||||
|
||||
SetCreatedUserAgent(e);
|
||||
break;
|
||||
case nameof(IFieldCreatedClient.CreatedReferer):
|
||||
if (e.Value is null or "") {
|
||||
e.Value = App.HttpContext?.Request.GetRefererUrlAddress();
|
||||
}
|
||||
|
||||
SetCreatedReferer(e);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
|
@ -13,10 +13,7 @@ public static class HttpRequestPartExtensions
|
||||
public static HttpRequestPart SetLog<T>(this HttpRequestPart me, ILogger<T> logger
|
||||
, Func<string, string> bodyHandle = null)
|
||||
{
|
||||
Task RequestHandleAsync(HttpClient _, HttpRequestMessage req)
|
||||
{
|
||||
return req.LogAsync(logger);
|
||||
}
|
||||
return me.OnRequesting(RequestHandleAsync).OnResponsing(ResponseHandleAsync).OnException(ExceptionHandleAsync);
|
||||
|
||||
Task ExceptionHandleAsync(HttpClient _, HttpResponseMessage rsp, string errors)
|
||||
{
|
||||
@ -28,6 +25,9 @@ public static class HttpRequestPartExtensions
|
||||
return rsp.LogAsync(logger, bodyHandle);
|
||||
}
|
||||
|
||||
return me.OnRequesting(RequestHandleAsync).OnResponsing(ResponseHandleAsync).OnException(ExceptionHandleAsync);
|
||||
Task RequestHandleAsync(HttpClient _, HttpRequestMessage req)
|
||||
{
|
||||
return req.LogAsync(logger);
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
<Import Project="$(SolutionDir)/build/copy.pkg.xml.comment.files.targets"/>
|
||||
<Import Project="$(SolutionDir)/build/prebuild.targets"/>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Cronos" Version="0.8.3"/>
|
||||
<PackageReference Include="Cronos" Version="0.8.4"/>
|
||||
<PackageReference Include="FreeSql.DbContext.NS" Version="3.2.813-preview20240208-ns1"/>
|
||||
<PackageReference Include="FreeSql.Provider.Sqlite.NS" Version="3.2.813-preview20240208-ns1"/>
|
||||
<PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.1.31"/>
|
||||
@ -14,7 +14,7 @@
|
||||
<PackageReference Include="Furion.Pure.NS" Version="4.9.1.31-ns2"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.0-preview.1.24081.5"/>
|
||||
<PackageReference Include="Minio" Version="6.0.2"/>
|
||||
<PackageReference Include="NSExt" Version="2.0.11"/>
|
||||
<PackageReference Include="NSExt" Version="2.1.0"/>
|
||||
<PackageReference Include="RedLock.net" Version="2.3.2"/>
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0"/>
|
||||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14"/>
|
||||
|
@ -12,7 +12,7 @@ namespace NetAdmin.Infrastructure.Utils;
|
||||
/// </summary>
|
||||
public static class CaptchaImageHelper
|
||||
{
|
||||
private static readonly int[] _randRange = [70, 100];
|
||||
private static readonly int[] _randRange =
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个缺口滑块验证码图片
|
||||
@ -96,6 +96,26 @@ public static class CaptchaImageHelper
|
||||
, offsetRand);
|
||||
}
|
||||
|
||||
private static int BuildPathList(Span<Rgba32> rowSpan, int temp, List<IPath> pathList, int y)
|
||||
{
|
||||
for (var x = 0; x < rowSpan.Length; x++) {
|
||||
ref var pixel = ref rowSpan[x];
|
||||
if (pixel.A != 0) {
|
||||
temp = temp switch { 0 => x, _ => temp };
|
||||
}
|
||||
else {
|
||||
if (temp == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pathList.Add(new RectangularPolygon(temp, y, x - temp, 1));
|
||||
temp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
private static ComplexPolygon CalcBlockShape(Image<Rgba32> templateDarkImage)
|
||||
{
|
||||
var temp = 0;
|
||||
@ -103,20 +123,7 @@ public static class CaptchaImageHelper
|
||||
templateDarkImage.ProcessPixelRows(accessor => {
|
||||
for (var y = 0; y < templateDarkImage.Height; y++) {
|
||||
var rowSpan = accessor.GetRowSpan(y);
|
||||
for (var x = 0; x < rowSpan.Length; x++) {
|
||||
ref var pixel = ref rowSpan[x];
|
||||
if (pixel.A != 0) {
|
||||
temp = temp switch { 0 => x, _ => temp };
|
||||
}
|
||||
else {
|
||||
if (temp == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pathList.Add(new RectangularPolygon(temp, y, x - temp, 1));
|
||||
temp = 0;
|
||||
}
|
||||
}
|
||||
temp = BuildPathList(rowSpan, temp, pathList, y);
|
||||
}
|
||||
});
|
||||
|
||||
@ -175,4 +182,6 @@ public static class CaptchaImageHelper
|
||||
{
|
||||
return (endNum > startNum ? new[] { 0, endNum - startNum }.Rand() : 0) + startNum;
|
||||
}
|
||||
|
||||
[70, 100];
|
||||
}
|
@ -266,7 +266,7 @@ public sealed class UserAgentParser
|
||||
/// <summary>
|
||||
/// 平台
|
||||
/// </summary>
|
||||
public string Platform { get; set; } = string.Empty;
|
||||
public string Platform { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 机器人
|
||||
@ -301,49 +301,43 @@ public sealed class UserAgentParser
|
||||
|
||||
private bool SetMobile()
|
||||
{
|
||||
#pragma warning disable S3267
|
||||
foreach (var item in _mobiles) {
|
||||
#pragma warning restore S3267
|
||||
if (_agent.Contains(item.Key, StringComparison.OrdinalIgnoreCase)) {
|
||||
IsMobile = true;
|
||||
Mobile = item.Value;
|
||||
return true;
|
||||
}
|
||||
var kv = _mobiles.FirstOrDefault(x => //
|
||||
_agent.Contains(x.Key, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (kv.Key == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IsMobile = true;
|
||||
Mobile = kv.Value;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool SetPlatform()
|
||||
{
|
||||
#pragma warning disable S3267
|
||||
foreach (var item in _platforms) {
|
||||
#pragma warning restore S3267
|
||||
if (!Regex.IsMatch(_agent, $"{Regex.Escape(item.Key)}", RegexOptions.IgnoreCase)) {
|
||||
continue;
|
||||
}
|
||||
var kv = _platforms.First(x => //
|
||||
Regex.IsMatch(_agent, $"{Regex.Escape(x.Key)}", RegexOptions.IgnoreCase));
|
||||
|
||||
Platform = item.Value;
|
||||
return true;
|
||||
if (kv.Key == null) {
|
||||
Platform = "Unknown Platform";
|
||||
return false;
|
||||
}
|
||||
|
||||
Platform = "Unknown Platform";
|
||||
return false;
|
||||
Platform = kv.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool SetRobot()
|
||||
{
|
||||
#pragma warning disable S3267
|
||||
foreach (var item in _robots) {
|
||||
#pragma warning restore S3267
|
||||
if (Regex.IsMatch(_agent, $"{Regex.Escape(item.Key)}", RegexOptions.IgnoreCase)) {
|
||||
IsRobot = true;
|
||||
Robot = item.Value;
|
||||
_ = SetMobile();
|
||||
return true;
|
||||
}
|
||||
var kv = _robots.FirstOrDefault(x => //
|
||||
Regex.IsMatch(_agent, $"{Regex.Escape(x.Key)}", RegexOptions.IgnoreCase));
|
||||
if (kv.Key == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
IsRobot = true;
|
||||
Robot = kv.Value;
|
||||
_ = SetMobile();
|
||||
return true;
|
||||
}
|
||||
}
|
@ -7,6 +7,11 @@ namespace NetAdmin.SysComponent.Application.Modules.Sys;
|
||||
/// </summary>
|
||||
public interface IToolsModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取更新日志
|
||||
/// </summary>
|
||||
Task<string> GetChangeLogAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 获取模块信息
|
||||
/// </summary>
|
||||
|
@ -16,12 +16,14 @@ public sealed class ConfigService(DefaultRepository<Sys_Config> rpo) //
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -15,12 +15,14 @@ public sealed class DeptService(DefaultRepository<Sys_Dept> rpo) //
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -15,12 +15,14 @@ public sealed class DicCatalogService(DefaultRepository<Sys_DicCatalog> rpo) //
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -15,12 +15,14 @@ public sealed class DicContentService(DefaultRepository<Sys_DicContent> rpo) //
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -16,12 +16,14 @@ public sealed class JobRecordService(DefaultRepository<Sys_JobRecord> rpo) //
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -19,12 +19,14 @@ public sealed class JobService(DefaultRepository<Sys_Job> rpo, IJobRecordService
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -56,11 +58,12 @@ public sealed class JobService(DefaultRepository<Sys_Job> rpo, IJobRecordService
|
||||
var ret = await Rpo.UpdateDiy.Set(a => a.ExecutionCron == req.ExecutionCron)
|
||||
.Set(a => a.HttpMethod == req.HttpMethod)
|
||||
.Set(a => a.JobName == req.JobName)
|
||||
.Set(a => a.RequestHeader == req.RequestHeader)
|
||||
.Set(a => a.RequestBody == req.RequestBody)
|
||||
.Set(a => a.RequestUrl == req.RequestUrl)
|
||||
.Set(a => a.UserId == req.UserId)
|
||||
.Where(a => a.Id == req.Id)
|
||||
.SetIf(req.RequestHeaders == null, a => a.RequestHeader, null)
|
||||
.SetIf(req.RequestHeaders != null, a => a.RequestHeader, req.RequestHeaders.Json())
|
||||
.Set(a => a.RequestBody == req.RequestBody)
|
||||
.Set(a => a.RequestUrl == req.RequestUrl)
|
||||
.Set(a => a.UserId == req.UserId)
|
||||
.Where(a => a.Id == req.Id)
|
||||
.ExecuteUpdatedAsync()
|
||||
.ConfigureAwait(false);
|
||||
return ret[0].Adapt<QueryJobRsp>();
|
||||
@ -97,24 +100,22 @@ public sealed class JobService(DefaultRepository<Sys_Job> rpo, IJobRecordService
|
||||
public async Task<QueryJobRsp> GetNextJobAsync()
|
||||
{
|
||||
var df = new DynamicFilterInfo {
|
||||
Filters = [
|
||||
new DynamicFilterInfo {
|
||||
Field = nameof(QueryJobReq.NextExecTime)
|
||||
, Value = DateTime.UtcNow
|
||||
, Operator = DynamicFilterOperators.LessThan
|
||||
}
|
||||
, new DynamicFilterInfo {
|
||||
Field = nameof(QueryJobReq.Status)
|
||||
, Value = JobStatues.Idle
|
||||
, Operator = DynamicFilterOperators.Eq
|
||||
}
|
||||
, new DynamicFilterInfo {
|
||||
Field = nameof(QueryJobReq.Enabled)
|
||||
, Value = true
|
||||
, Operator = DynamicFilterOperators.Eq
|
||||
}
|
||||
]
|
||||
};
|
||||
Filters = [ new DynamicFilterInfo { Field = nameof(QueryJobReq.NextExecTime)
|
||||
, Value = DateTime.UtcNow
|
||||
, Operator = DynamicFilterOperators.LessThan
|
||||
}
|
||||
, new DynamicFilterInfo {
|
||||
Field = nameof(QueryJobReq.Status)
|
||||
, Value = JobStatues.Idle
|
||||
, Operator = DynamicFilterOperators.Eq
|
||||
}
|
||||
, new DynamicFilterInfo {
|
||||
Field = nameof(QueryJobReq.Enabled)
|
||||
, Value = true
|
||||
, Operator = DynamicFilterOperators.Eq
|
||||
}
|
||||
]
|
||||
};
|
||||
var job = await QueryInternal(new QueryReq<QueryJobReq> { DynamicFilter = df, Count = 1 }, true)
|
||||
.Where(a => !Rpo.Orm.Select<Sys_JobRecord>()
|
||||
.As("b")
|
||||
|
@ -15,12 +15,14 @@ public sealed class MenuService(DefaultRepository<Sys_Menu> rpo, IUserService us
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -32,10 +34,15 @@ public sealed class MenuService(DefaultRepository<Sys_Menu> rpo, IUserService us
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<int> DeleteAsync(DelReq req)
|
||||
public async Task<int> DeleteAsync(DelReq req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
return Rpo.DeleteAsync(a => a.Id == req.Id);
|
||||
var effect = await Rpo.DeleteAsync(a => a.Id == req.Id).ConfigureAwait(false);
|
||||
effect += await Rpo.Orm.Delete<Sys_RoleMenu>()
|
||||
.Where(a => a.MenuId == req.Id)
|
||||
.ExecuteAffrowsAsync()
|
||||
.ConfigureAwait(false);
|
||||
return effect;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -15,12 +15,14 @@ public sealed class RequestLogService(DefaultRepository<Sys_RequestLog> rpo) //
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -15,12 +15,14 @@ public sealed class RoleService(DefaultRepository<Sys_Role> rpo) //
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -16,12 +16,14 @@ public sealed class SiteMsgDeptService(DefaultRepository<Sys_SiteMsgDept> rpo) /
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -16,12 +16,14 @@ public sealed class SiteMsgFlagService(DefaultRepository<Sys_SiteMsgFlag> rpo) /
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -16,12 +16,14 @@ public sealed class SiteMsgRoleService(DefaultRepository<Sys_SiteMsgRole> rpo) /
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -12,22 +12,22 @@ using NetAdmin.SysComponent.Application.Services.Sys.Dependency;
|
||||
namespace NetAdmin.SysComponent.Application.Services.Sys;
|
||||
|
||||
/// <inheritdoc cref="ISiteMsgService" />
|
||||
public sealed class SiteMsgService(
|
||||
DefaultRepository<Sys_SiteMsg> rpo
|
||||
, ContextUserInfo contextUserInfo
|
||||
, ISiteMsgFlagService siteMsgFlagService) //
|
||||
public sealed class SiteMsgService(DefaultRepository<Sys_SiteMsg> rpo, ContextUserInfo contextUserInfo
|
||||
, ISiteMsgFlagService siteMsgFlagService) //
|
||||
: RepositoryService<Sys_SiteMsg, ISiteMsgService>(rpo), ISiteMsgService
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -300,9 +300,9 @@ public sealed class SiteMsgService(
|
||||
.WhereDynamicFilter(req.DynamicFilter)
|
||||
.Where((a, _, c, d, e, f) =>
|
||||
(SqlExt.EqualIsNull(f.UserSiteMsgStatus) ||
|
||||
f.UserSiteMsgStatus != UserSiteMsgStatues.Deleted) &&
|
||||
(a.MsgType == SiteMsgTypes.Public || c.DeptId == contextUserInfo.DeptId ||
|
||||
roleIds.Contains(d.RoleId) || e.UserId == contextUserInfo.Id))
|
||||
f.UserSiteMsgStatus != UserSiteMsgStatues.Deleted) && (a.MsgType == SiteMsgTypes.Public ||
|
||||
c.DeptId == contextUserInfo.DeptId || roleIds.Contains(d.RoleId) ||
|
||||
e.UserId == contextUserInfo.Id))
|
||||
.GroupBy((a, _, _, _, _, _) => a.Id);
|
||||
}
|
||||
}
|
@ -16,12 +16,14 @@ public sealed class SiteMsgUserService(DefaultRepository<Sys_SiteMsgUser> rpo) /
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -7,6 +7,14 @@ namespace NetAdmin.SysComponent.Application.Services.Sys;
|
||||
/// <inheritdoc cref="IToolsService" />
|
||||
public sealed class ToolsService : ServiceBase<IToolsService>, IToolsService
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<string> GetChangeLogAsync()
|
||||
{
|
||||
await using var stream = Assembly.GetEntryAssembly()!.GetManifestResourceStream("CHANGELOG.md");
|
||||
using var streamReader = new StreamReader(stream!);
|
||||
return await streamReader.ReadToEndAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<GetModulesRsp>> GetModulesAsync()
|
||||
{
|
||||
|
@ -17,12 +17,14 @@ public sealed class UserProfileService(DefaultRepository<Sys_UserProfile> rpo) /
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -13,11 +13,10 @@ using NetAdmin.SysComponent.Application.Services.Sys.Dependency;
|
||||
namespace NetAdmin.SysComponent.Application.Services.Sys;
|
||||
|
||||
/// <inheritdoc cref="IUserService" />
|
||||
public sealed class UserService(
|
||||
DefaultRepository<Sys_User> rpo //
|
||||
, IUserProfileService userProfileService //
|
||||
, IVerifyCodeService verifyCodeService //
|
||||
, IEventPublisher eventPublisher) //
|
||||
public sealed class UserService(DefaultRepository<Sys_User> rpo //
|
||||
, IUserProfileService userProfileService //
|
||||
, IVerifyCodeService verifyCodeService //
|
||||
, IEventPublisher eventPublisher) //
|
||||
: RepositoryService<Sys_User, IUserService>(rpo), IUserService
|
||||
{
|
||||
private readonly Expression<Func<Sys_User, Sys_User>> _selectUserFields = a => new Sys_User {
|
||||
@ -38,12 +37,14 @@ public sealed class UserService(
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -358,21 +359,18 @@ public sealed class UserService(
|
||||
req.ThrowIfInvalid();
|
||||
var version = await Rpo.Where(a => a.Id == UserToken.Id && a.Password == req.OldPassword.Pwd().Guid())
|
||||
.ToOneAsync(a => new long?(a.Version))
|
||||
.ConfigureAwait(false);
|
||||
if (version != null) {
|
||||
var ret = await Rpo.UpdateDiy
|
||||
.SetSource(new Sys_User {
|
||||
Id = UserToken.Id
|
||||
, Password = req.NewPassword.Pwd().Guid()
|
||||
, Version = version.Value
|
||||
})
|
||||
.UpdateColumns(a => a.Password)
|
||||
.ExecuteAffrowsAsync()
|
||||
.ConfigureAwait(false);
|
||||
return ret <= 0 ? throw new NetAdminUnexpectedException() : (uint)ret;
|
||||
}
|
||||
.ConfigureAwait(false) ?? throw new NetAdminInvalidOperationException($"{Ln.旧密码不正确}");
|
||||
|
||||
throw new NetAdminInvalidOperationException($"{Ln.旧密码不正确}");
|
||||
var ret = await Rpo.UpdateDiy
|
||||
.SetSource(new Sys_User {
|
||||
Id = UserToken.Id
|
||||
, Password = req.NewPassword.Pwd().Guid()
|
||||
, Version = version
|
||||
})
|
||||
.UpdateColumns(a => a.Password)
|
||||
.ExecuteAffrowsAsync()
|
||||
.ConfigureAwait(false);
|
||||
return ret <= 0 ? throw new NetAdminUnexpectedException() : (uint)ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -14,18 +14,20 @@ namespace NetAdmin.SysComponent.Application.Services.Sys;
|
||||
public sealed class VerifyCodeService(DefaultRepository<Sys_VerifyCode> rpo, IEventPublisher eventPublisher) //
|
||||
: RepositoryService<Sys_VerifyCode, IVerifyCodeService>(rpo), IVerifyCodeService
|
||||
{
|
||||
private static readonly int[] _randRange = [0, 10000];
|
||||
private static readonly int[] _randRange =
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -161,6 +163,8 @@ public sealed class VerifyCodeService(DefaultRepository<Sys_VerifyCode> rpo, IEv
|
||||
: await GetAsync(new QueryVerifyCodeReq { Id = req.Id }).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[0, 10000];
|
||||
|
||||
private Task<Sys_VerifyCode> GetLastSentAsync(string destDevice)
|
||||
{
|
||||
return QueryInternal(new QueryReq<QueryVerifyCodeReq> {
|
||||
|
@ -16,12 +16,14 @@ public sealed class ExampleService(DefaultRepository<Tpl_Example> rpo) //
|
||||
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var sum = 0;
|
||||
var ret = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in req.Items) {
|
||||
sum += await DeleteAsync(item).ConfigureAwait(false);
|
||||
ret += await DeleteAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return sum;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -9,6 +9,12 @@ namespace NetAdmin.SysComponent.Cache.Sys;
|
||||
public sealed class ToolsCache(IDistributedCache cache, IToolsService service) //
|
||||
: DistributedCache<IToolsService>(cache, service), IScoped, IToolsCache
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task<string> GetChangeLogAsync()
|
||||
{
|
||||
return Service.GetChangeLogAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<GetModulesRsp>> GetModulesAsync()
|
||||
{
|
||||
|
@ -12,6 +12,15 @@ namespace NetAdmin.SysComponent.Host.Controllers.Sys;
|
||||
[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))]
|
||||
public sealed class ToolsController(IToolsCache cache) : ControllerBase<IToolsCache, IToolsService>(cache), IToolsModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取更新日志
|
||||
/// </summary>
|
||||
[AllowAnonymous]
|
||||
public Task<string> GetChangeLogAsync()
|
||||
{
|
||||
return Cache.GetChangeLogAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取模块信息
|
||||
/// </summary>
|
||||
|
Reference in New Issue
Block a user