feat: 版本更新日志组件 (#96)

This commit is contained in:
2024-03-02 23:06:04 +08:00
committed by GitHub
parent 8fee14cd6e
commit a37acc4b55
130 changed files with 978 additions and 3246 deletions

View File

@ -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"/>

View File

@ -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()
{

View File

@ -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;
}
}

View File

@ -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()

View File

@ -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; }
}

View File

@ -19,7 +19,7 @@ public record SyncStructureBeforeEvent : SqlCommandEvent
/// <summary>
/// 实体类型
/// </summary>
public Type[] EntityTypes { get; }
protected Type[] EntityTypes { get; }
/// <inheritdoc />
public override string ToString()

View File

@ -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 />

View File

@ -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 />

View File

@ -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 />

View File

@ -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) {
// 加锁

View File

@ -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) {

View File

@ -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

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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"/>

View File

@ -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];
}

View File

@ -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;
}
}

View File

@ -7,6 +7,11 @@ namespace NetAdmin.SysComponent.Application.Modules.Sys;
/// </summary>
public interface IToolsModule
{
/// <summary>
/// 获取更新日志
/// </summary>
Task<string> GetChangeLogAsync();
/// <summary>
/// 获取模块信息
/// </summary>

View File

@ -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 />

View File

@ -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 />

View File

@ -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 />

View File

@ -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 />

View File

@ -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 />

View File

@ -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")

View File

@ -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 />

View File

@ -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 />

View File

@ -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 />

View File

@ -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 />

View File

@ -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 />

View File

@ -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 />

View File

@ -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);
}
}

View File

@ -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 />

View File

@ -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()
{

View File

@ -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 />

View File

@ -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 />

View File

@ -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> {

View File

@ -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 />

View File

@ -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()
{

View File

@ -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>