diff --git a/src/X1.Application/Features/Auth/Commands/EmailLogin/EmailLoginCommandHandler.cs b/src/X1.Application/Features/Auth/Commands/EmailLogin/EmailLoginCommandHandler.cs index 855a237..bdc3065 100644 --- a/src/X1.Application/Features/Auth/Commands/EmailLogin/EmailLoginCommandHandler.cs +++ b/src/X1.Application/Features/Auth/Commands/EmailLogin/EmailLoginCommandHandler.cs @@ -76,9 +76,9 @@ public sealed class EmailLoginCommandHandler : BaseLoginCommandHandler /// 验证凭据 /// - protected override async Task ValidateCredentialsAsync(EmailLoginCommand request, AppUser user) + protected override Task ValidateCredentialsAsync(EmailLoginCommand request, AppUser user) { // 验证邮箱验证码 - return await _emailVerificationService.VerifyCodeAsync(request.Email, request.VerificationCode); + return Task.FromResult(_emailVerificationService.VerifyCode(request.Email, request.VerificationCode)); } } \ No newline at end of file diff --git a/src/X1.Application/Features/Auth/Commands/VerifyCode/VerifyCodeCommandHandler.cs b/src/X1.Application/Features/Auth/Commands/VerifyCode/VerifyCodeCommandHandler.cs index 723161d..c1671c2 100644 --- a/src/X1.Application/Features/Auth/Commands/VerifyCode/VerifyCodeCommandHandler.cs +++ b/src/X1.Application/Features/Auth/Commands/VerifyCode/VerifyCodeCommandHandler.cs @@ -45,7 +45,7 @@ public sealed class VerifyCodeCommandHandler : IRequestHandler +/// Redis缓存配置选项 +/// 避免与官方Microsoft.Extensions.Caching.StackExchangeRedis.RedisOptions重名 +/// +public class RedisCacheOptions +{ + public const string SectionName = "X1_Redis"; + + public string Host { get; set; } = "localhost"; + public int Port { get; set; } = 6379; + public string? Password { get; set; } + public int Database { get; set; } = 0; + public int ConnectTimeout { get; set; } = 5000; + public int OperationTimeout { get; set; } = 3000; + public int PoolSize { get; set; } = 50; + public bool EnableSsl { get; set; } = false; + public bool EnableRetry { get; set; } = true; + public int RetryCount { get; set; } = 3; + public int RetryDelay { get; set; } = 1000; + public int DefaultExpirationMinutes { get; set; } = 30; + public int MaxExpirationMinutes { get; set; } = 1440; + public string KeyPrefix { get; set; } = "X1:"; + public bool EnableCompression { get; set; } = true; + public int CompressionThreshold { get; set; } = 1024; + + public string GetConnectionString() + { + if (string.IsNullOrEmpty(Password)) + { + throw new InvalidOperationException("Redis密码不能为空,请配置X1_Redis:Password"); + } + + var connectionString = $"{Host}:{Port}"; + connectionString = $"{connectionString},password={Password}"; + connectionString = $"{connectionString},defaultDatabase={Database}"; + connectionString = $"{connectionString},connectTimeout={ConnectTimeout}"; + connectionString = $"{connectionString},syncTimeout={OperationTimeout}"; + connectionString = $"{connectionString},responseTimeout={OperationTimeout}"; + connectionString = $"{connectionString},connectRetry={RetryCount}"; + connectionString = $"{connectionString},reconnectRetryPolicy=LinearRetry"; + if (EnableSsl) + { + connectionString = $"{connectionString},ssl=true"; + } + return connectionString; + } +} diff --git a/src/X1.Domain/Repositories/Identity/IPermissionRepository.cs b/src/X1.Domain/Repositories/Identity/IPermissionRepository.cs index 5298837..4d6a2e4 100644 --- a/src/X1.Domain/Repositories/Identity/IPermissionRepository.cs +++ b/src/X1.Domain/Repositories/Identity/IPermissionRepository.cs @@ -20,10 +20,10 @@ public interface IPermissionRepository : IBaseRepository /// /// 更新权限 /// - Task UpdateAsync(Permission permission, CancellationToken cancellationToken = default); + void UpdateAsync(Permission permission, CancellationToken cancellationToken = default); /// /// 删除权限 /// - Task DeleteAsync(Permission permission, CancellationToken cancellationToken = default); + void DeleteAsync(Permission permission, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/X1.Domain/Services/IEmailVerificationService.cs b/src/X1.Domain/Services/IEmailVerificationService.cs index abbd846..8c5d06d 100644 --- a/src/X1.Domain/Services/IEmailVerificationService.cs +++ b/src/X1.Domain/Services/IEmailVerificationService.cs @@ -21,5 +21,5 @@ public interface IEmailVerificationService /// 邮箱地址 /// 验证码 /// 验证结果 - Task VerifyCodeAsync(string email, string code); + bool VerifyCode(string email, string code); } \ No newline at end of file diff --git a/src/X1.Domain/Services/IRedisCacheService.cs b/src/X1.Domain/Services/IRedisCacheService.cs new file mode 100644 index 0000000..a3ecaab --- /dev/null +++ b/src/X1.Domain/Services/IRedisCacheService.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace X1.Domain.Services; + +/// +/// Redis缓存服务接口 +/// 定义Redis缓存操作的核心业务逻辑 +/// +public interface IRedisCacheService +{ + #region 基础操作 + + /// + /// 设置键值对 + /// + bool Set(string key, T value, TimeSpan? expiry = null); + + /// + /// 异步设置键值对 + /// + Task SetAsync(string key, T value, TimeSpan? expiry = null); + + /// + /// 获取值 + /// + T? Get(string key); + + /// + /// 异步获取值 + /// + Task GetAsync(string key); + + /// + /// 删除键 + /// + bool Remove(string key); + + /// + /// 异步删除键 + /// + Task RemoveAsync(string key); + + /// + /// 检查键是否存在 + /// + bool Exists(string key); + + /// + /// 异步检查键是否存在 + /// + Task ExistsAsync(string key); + + #endregion + + #region 分布式锁 + + /// + /// 获取分布式锁 + /// + bool AcquireLock(string key, string value, TimeSpan expiry); + + /// + /// 异步获取分布式锁 + /// + Task AcquireLockAsync(string key, string value, TimeSpan expiry); + + /// + /// 释放分布式锁 + /// + bool ReleaseLock(string key, string value); + + /// + /// 异步释放分布式锁 + /// + Task ReleaseLockAsync(string key, string value); + + #endregion + + #region 工具方法 + + /// + /// 生成带前缀的键 + /// + string GetPrefixedKey(string key); + + /// + /// 批量删除键 + /// + long DeleteByPattern(string pattern); + + /// + /// 异步批量删除键 + /// + Task DeleteByPatternAsync(string pattern); + + #endregion +} diff --git a/src/X1.Infrastructure/DependencyInjection.cs b/src/X1.Infrastructure/DependencyInjection.cs index 4aee8f2..eca2fdd 100644 --- a/src/X1.Infrastructure/DependencyInjection.cs +++ b/src/X1.Infrastructure/DependencyInjection.cs @@ -33,6 +33,7 @@ using X1.Infrastructure.Repositories.Logging; using X1.Infrastructure.Repositories.Terminal; using X1.Domain.Repositories.TestCase; using X1.Infrastructure.Repositories.TestCase; +using StackExchange.Redis; namespace X1.Infrastructure; /// @@ -67,6 +68,7 @@ public static class DependencyInjection .AddJwtServices(configuration) .AddEmailServices(configuration) .AddCacheServices() + .AddRedisServices(configuration) .AddRepositoryServices() .AddIdentityServices() .AddAutoRegisteredServices(); @@ -164,6 +166,43 @@ public static class DependencyInjection return services; } + /// + /// 添加Redis服务 + /// + private static IServiceCollection AddRedisServices( + this IServiceCollection services, + IConfiguration configuration) + { + var redisOptions = configuration + .GetSection(RedisCacheOptions.SectionName) + .Get(); + + if (redisOptions is null) + { + throw new ArgumentNullException( + nameof(redisOptions), + "Redis options not configured"); + } + + // 配置Redis选项 + services.Configure(configuration.GetSection(RedisCacheOptions.SectionName)); + + // 注册Redis连接 + services.AddSingleton(provider => + { + var options = ConfigurationOptions.Parse(redisOptions.GetConnectionString()); + options.ConnectRetry = redisOptions.RetryCount; + options.ReconnectRetryPolicy = new LinearRetry(redisOptions.RetryDelay); + + return ConnectionMultiplexer.Connect(options); + }); + + // 注册Redis缓存服务 + services.AddScoped(); + + return services; + } + /// /// 添加仓储服务 /// diff --git a/src/X1.Infrastructure/Repositories/Device/ProtocolVersionRepository.cs b/src/X1.Infrastructure/Repositories/Device/ProtocolVersionRepository.cs index 08151a2..1a550d7 100644 --- a/src/X1.Infrastructure/Repositories/Device/ProtocolVersionRepository.cs +++ b/src/X1.Infrastructure/Repositories/Device/ProtocolVersionRepository.cs @@ -94,9 +94,9 @@ public class ProtocolVersionRepository : BaseRepository, IProto if (!string.IsNullOrWhiteSpace(keyword)) { query = query.Where(pv => - pv.Name.Contains(keyword) || - pv.Version.Contains(keyword) || - pv.Description.Contains(keyword)); + (pv.Name != null && pv.Name.Contains(keyword)) || + (pv.Version != null && pv.Version.Contains(keyword)) || + (pv.Description != null && pv.Description.Contains(keyword))); } var protocolVersions = query; diff --git a/src/X1.Infrastructure/Repositories/Identity/PermissionRepository.cs b/src/X1.Infrastructure/Repositories/Identity/PermissionRepository.cs index d2f6d9e..a689397 100644 --- a/src/X1.Infrastructure/Repositories/Identity/PermissionRepository.cs +++ b/src/X1.Infrastructure/Repositories/Identity/PermissionRepository.cs @@ -43,7 +43,7 @@ public class PermissionRepository : BaseRepository, IPermissionRepos /// /// 更新权限 /// - public async Task UpdatePermissionAsync(Permission permission, CancellationToken cancellationToken = default) + public void UpdatePermission(Permission permission) { CommandRepository.Update(permission); } @@ -71,7 +71,7 @@ public class PermissionRepository : BaseRepository, IPermissionRepos /// /// 批量更新权限 /// - public async Task UpdatePermissionsAsync(IEnumerable permissions, CancellationToken cancellationToken = default) + public void UpdatePermissions(IEnumerable permissions) { CommandRepository.UpdateRange(permissions); } @@ -155,7 +155,7 @@ public class PermissionRepository : BaseRepository, IPermissionRepos /// /// 更新权限 /// - public async Task UpdateAsync(Permission permission, CancellationToken cancellationToken = default) + public void UpdateAsync(Permission permission, CancellationToken cancellationToken = default) { CommandRepository.Update(permission); } @@ -163,7 +163,7 @@ public class PermissionRepository : BaseRepository, IPermissionRepos /// /// 删除权限 /// - public async Task DeleteAsync(Permission permission, CancellationToken cancellationToken = default) + public void DeleteAsync(Permission permission, CancellationToken cancellationToken = default) { CommandRepository.Delete(permission); } diff --git a/src/X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs b/src/X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs index d87f049..051b767 100644 --- a/src/X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs +++ b/src/X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs @@ -107,12 +107,12 @@ public class NetworkStackConfigRepository : BaseRepository, if (!string.IsNullOrWhiteSpace(keyword)) { query = query.Where(nsc => - nsc.NetworkStackName.Contains(keyword) || - nsc.Description.Contains(keyword)); + (nsc.NetworkStackName != null && nsc.NetworkStackName.Contains(keyword)) || + (nsc.Description != null && nsc.Description.Contains(keyword))); } var configs = query; - return (configs.Count(), configs.OrderBy(x => x.NetworkStackName).ToList()); + return (configs.Count(), configs.OrderBy(x => x.NetworkStackName ?? string.Empty).ToList()); } /// @@ -129,14 +129,14 @@ public class NetworkStackConfigRepository : BaseRepository, if (!string.IsNullOrWhiteSpace(keyword)) { - predicate = nsc => nsc.NetworkStackName.Contains(keyword) || - nsc.Description.Contains(keyword); + predicate = nsc => (nsc.NetworkStackName != null && nsc.NetworkStackName.Contains(keyword)) || + (nsc.Description != null && nsc.Description.Contains(keyword)); } // 执行分页查询 var result = await QueryRepository.GetPagedAsync(predicate, pageNumber, pageSize, null, cancellationToken); - return (result.TotalCount, result.Items.OrderBy(x => x.NetworkStackName).ToList()); + return (result.TotalCount, result.Items.OrderBy(x => x.NetworkStackName ?? string.Empty).ToList()); } /// diff --git a/src/X1.Infrastructure/Services/Infrastructure/RedisCacheService.cs b/src/X1.Infrastructure/Services/Infrastructure/RedisCacheService.cs new file mode 100644 index 0000000..2292bdd --- /dev/null +++ b/src/X1.Infrastructure/Services/Infrastructure/RedisCacheService.cs @@ -0,0 +1,318 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using StackExchange.Redis; +using X1.Domain.Options; +using X1.Domain.Services; + +namespace X1.Infrastructure.Services.Infrastructure; + +/// +/// Redis缓存服务实现 +/// 提供Redis缓存操作的具体实现 +/// +public sealed class RedisCacheService : IRedisCacheService +{ + private readonly IConnectionMultiplexer _redis; + private readonly IDatabase _database; + private readonly RedisCacheOptions _options; + private readonly ILogger _logger; + + /// + /// 构造函数 + /// + public RedisCacheService( + IConnectionMultiplexer redis, + IOptions options, + ILogger logger) + { + _redis = redis ?? throw new ArgumentNullException(nameof(redis)); + _database = redis.GetDatabase(); + _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + #region 基础操作 + + /// + public bool Set(string key, T value, TimeSpan? expiry = null) + { + try + { + var prefixedKey = GetPrefixedKey(key); + var serializedValue = JsonSerializer.Serialize(value); + + return _database.StringSet(prefixedKey, serializedValue, expiry); + } + catch (Exception ex) + { + _logger.LogError(ex, "设置Redis键值对失败: {Key}", key); + return false; + } + } + + /// + public async Task SetAsync(string key, T value, TimeSpan? expiry = null) + { + try + { + var prefixedKey = GetPrefixedKey(key); + var serializedValue = JsonSerializer.Serialize(value); + + return await _database.StringSetAsync(prefixedKey, serializedValue, expiry); + } + catch (Exception ex) + { + _logger.LogError(ex, "异步设置Redis键值对失败: {Key}", key); + return false; + } + } + + /// + public T? Get(string key) + { + try + { + var prefixedKey = GetPrefixedKey(key); + var value = _database.StringGet(prefixedKey); + + if (!value.HasValue) + return default; + + return JsonSerializer.Deserialize(value!); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取Redis值失败: {Key}", key); + return default; + } + } + + /// + public async Task GetAsync(string key) + { + try + { + var prefixedKey = GetPrefixedKey(key); + var value = await _database.StringGetAsync(prefixedKey); + + if (!value.HasValue) + return default; + + return JsonSerializer.Deserialize(value!); + } + catch (Exception ex) + { + _logger.LogError(ex, "异步获取Redis值失败: {Key}", key); + return default; + } + } + + /// + public bool Remove(string key) + { + try + { + var prefixedKey = GetPrefixedKey(key); + return _database.KeyDelete(prefixedKey); + } + catch (Exception ex) + { + _logger.LogError(ex, "删除Redis键失败: {Key}", key); + return false; + } + } + + /// + public async Task RemoveAsync(string key) + { + try + { + var prefixedKey = GetPrefixedKey(key); + return await _database.KeyDeleteAsync(prefixedKey); + } + catch (Exception ex) + { + _logger.LogError(ex, "异步删除Redis键失败: {Key}", key); + return false; + } + } + + /// + public bool Exists(string key) + { + try + { + var prefixedKey = GetPrefixedKey(key); + return _database.KeyExists(prefixedKey); + } + catch (Exception ex) + { + _logger.LogError(ex, "检查Redis键是否存在失败: {Key}", key); + return false; + } + } + + /// + public async Task ExistsAsync(string key) + { + try + { + var prefixedKey = GetPrefixedKey(key); + return await _database.KeyExistsAsync(prefixedKey); + } + catch (Exception ex) + { + _logger.LogError(ex, "异步检查Redis键是否存在失败: {Key}", key); + return false; + } + } + + #endregion + + #region 分布式锁 + + /// + public bool AcquireLock(string key, string value, TimeSpan expiry) + { + try + { + var lockKey = GetPrefixedKey($"lock:{key}"); + return _database.StringSet(lockKey, value, expiry, When.NotExists); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取分布式锁失败: {Key}", key); + return false; + } + } + + /// + public async Task AcquireLockAsync(string key, string value, TimeSpan expiry) + { + try + { + var lockKey = GetPrefixedKey($"lock:{key}"); + return await _database.StringSetAsync(lockKey, value, expiry, When.NotExists); + } + catch (Exception ex) + { + _logger.LogError(ex, "异步获取分布式锁失败: {Key}", key); + return false; + } + } + + /// + public bool ReleaseLock(string key, string value) + { + try + { + var lockKey = GetPrefixedKey($"lock:{key}"); + + // 使用Lua脚本确保原子性操作 + var script = @" + if redis.call('get', KEYS[1]) == ARGV[1] then + return redis.call('del', KEYS[1]) + else + return 0 + end"; + + var result = _database.ScriptEvaluate(script, new RedisKey[] { lockKey }, new RedisValue[] { value }); + return result.ToString() == "1"; + } + catch (Exception ex) + { + _logger.LogError(ex, "释放分布式锁失败: {Key}", key); + return false; + } + } + + /// + public async Task ReleaseLockAsync(string key, string value) + { + try + { + var lockKey = GetPrefixedKey($"lock:{key}"); + + // 使用Lua脚本确保原子性操作 + var script = @" + if redis.call('get', KEYS[1]) == ARGV[1] then + return redis.call('del', KEYS[1]) + else + return 0 + end"; + + var result = await _database.ScriptEvaluateAsync(script, new RedisKey[] { lockKey }, new RedisValue[] { value }); + return result.ToString() == "1"; + } + catch (Exception ex) + { + _logger.LogError(ex, "异步释放分布式锁失败: {Key}", key); + return false; + } + } + + #endregion + + #region 工具方法 + + /// + public string GetPrefixedKey(string key) + { + return $"{_options.KeyPrefix}{key}"; + } + + /// + public long DeleteByPattern(string pattern) + { + try + { + var prefixedPattern = GetPrefixedKey(pattern); + var server = _redis.GetServer(_redis.GetEndPoints()[0]); + var keys = server.Keys(pattern: prefixedPattern); + + long deletedCount = 0; + foreach (var key in keys) + { + if (_database.KeyDelete(key)) + deletedCount++; + } + + return deletedCount; + } + catch (Exception ex) + { + _logger.LogError(ex, "批量删除Redis键失败: {Pattern}", pattern); + return 0; + } + } + + /// + public async Task DeleteByPatternAsync(string pattern) + { + try + { + var prefixedPattern = GetPrefixedKey(pattern); + var server = _redis.GetServer(_redis.GetEndPoints()[0]); + var keys = server.Keys(pattern: prefixedPattern); + + long deletedCount = 0; + foreach (var key in keys) + { + if (await _database.KeyDeleteAsync(key)) + deletedCount++; + } + + return deletedCount; + } + catch (Exception ex) + { + _logger.LogError(ex, "异步批量删除Redis键失败: {Pattern}", pattern); + return 0; + } + } + + #endregion +} diff --git a/src/X1.Infrastructure/Services/UserManagement/EmailVerificationService.cs b/src/X1.Infrastructure/Services/UserManagement/EmailVerificationService.cs index 28a5ecb..d4324a5 100644 --- a/src/X1.Infrastructure/Services/UserManagement/EmailVerificationService.cs +++ b/src/X1.Infrastructure/Services/UserManagement/EmailVerificationService.cs @@ -75,7 +75,7 @@ public class EmailVerificationService : IEmailVerificationService /// 邮箱地址 /// 验证码 /// 验证结果 - public async Task VerifyCodeAsync(string email, string code) + public bool VerifyCode(string email, string code) { var cacheKey = $"{_options.CacheKeyPrefix}{email}"; diff --git a/src/X1.Infrastructure/Services/UserManagement/UserRegistrationService.cs b/src/X1.Infrastructure/Services/UserManagement/UserRegistrationService.cs index 4f53de2..21a84b2 100644 --- a/src/X1.Infrastructure/Services/UserManagement/UserRegistrationService.cs +++ b/src/X1.Infrastructure/Services/UserManagement/UserRegistrationService.cs @@ -41,7 +41,7 @@ public class UserRegistrationService : IUserRegistrationService public async Task<(bool success, string? errorMessage)> RegisterUserAsync(AppUser user, string password) { // 验证账号 - var userNameResult = UserName.Create(user.UserName); + var userNameResult = UserName.Create(user.UserName ?? string.Empty); if (userNameResult.IsFailure) { throw new UserRegistrationException(userNameResult.Error!); @@ -59,31 +59,31 @@ public class UserRegistrationService : IUserRegistrationService } // 验证邮箱 - var emailResult = Email.Create(user.Email); + var emailResult = Email.Create(user.Email ?? string.Empty); if (emailResult.IsFailure) { throw new UserRegistrationException(emailResult.Error!); } // 使用分布式锁确保用户名和邮箱的唯一性 - using var lockHandle = await _lockService.AcquireLockAsync($"user_registration_{user.UserName}", TimeSpan.FromSeconds(10)); + using var lockHandle = await _lockService.AcquireLockAsync($"user_registration_{user.UserName ?? "unknown"}", TimeSpan.FromSeconds(10)); if (!lockHandle.IsAcquired) { throw new UserRegistrationException("系统繁忙,请稍后重试"); } // 检查账号是否存在 - var existingUser = await _userManager.FindByNameAsync(user.UserName); + var existingUser = await _userManager.FindByNameAsync(user.UserName ?? string.Empty); if (existingUser != null) { - throw new UserNameAlreadyExistsException(user.UserName); + throw new UserNameAlreadyExistsException(user.UserName ?? string.Empty); } // 检查邮箱是否存在 - existingUser = await _userManager.FindByEmailAsync(user.Email); + existingUser = await _userManager.FindByEmailAsync(user.Email ?? string.Empty); if (existingUser != null) { - throw new EmailAlreadyExistsException(user.Email); + throw new EmailAlreadyExistsException(user.Email ?? string.Empty); } // 创建用户 @@ -135,7 +135,7 @@ public class UserRegistrationService : IUserRegistrationService var result = await _userRoleRepository.AddAsync(userRole); if (isFirstUser) { - _logger.LogInformation("创建了第一个用户 {UserName},已分配管理员角色", user.UserName); + _logger.LogInformation("创建了第一个用户 {UserName},已分配管理员角色", user.UserName ?? "unknown"); } return (true, null); @@ -176,7 +176,7 @@ public class UserRegistrationService : IUserRegistrationService var hasRole = await _userRoleRepository.HasRoleAsync(user.Id, role.Id); if (hasRole) { - _logger.LogInformation("用户 {UserName} 已经拥有角色 {RoleName}", user.UserName, role.Name); + _logger.LogInformation("用户 {UserName} 已经拥有角色 {RoleName}", user.UserName ?? "unknown", role.Name); continue; } @@ -189,14 +189,14 @@ public class UserRegistrationService : IUserRegistrationService }; userRoles.Add(userRole); - assignedRoles.Add(role!.Name); + assignedRoles.Add(role!.Name ?? string.Empty); } // 批量分配角色 if (userRoles.Count > 0) { await _userRoleRepository.AddUserRolesAsync(userRoles); - _logger.LogInformation("为用户 {UserName} 批量分配角色成功: {Roles}", user.UserName, string.Join(", ", assignedRoles)); + _logger.LogInformation("为用户 {UserName} 批量分配角色成功: {Roles}", user.UserName ?? "unknown", string.Join(", ", assignedRoles)); } return (true, null); diff --git a/src/X1.Infrastructure/X1.Infrastructure.csproj b/src/X1.Infrastructure/X1.Infrastructure.csproj index 671ad9c..053106f 100644 --- a/src/X1.Infrastructure/X1.Infrastructure.csproj +++ b/src/X1.Infrastructure/X1.Infrastructure.csproj @@ -8,13 +8,7 @@ - - - - - - @@ -25,12 +19,22 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + - + + + + + + + + diff --git a/src/X1.WebAPI/appsettings.json b/src/X1.WebAPI/appsettings.json index 1b920ac..a4e2769 100644 --- a/src/X1.WebAPI/appsettings.json +++ b/src/X1.WebAPI/appsettings.json @@ -5,6 +5,24 @@ "EnableDetailedErrors": true, "EnableSensitiveDataLogging": true }, + "X1_Redis": { + "Host": "47.120.42.247", + "Port": 12996, + "Password": "X1Redis2025!", + "Database": 0, + "ConnectTimeout": 5000, + "OperationTimeout": 3000, + "PoolSize": 50, + "EnableSsl": false, + "EnableRetry": true, + "RetryCount": 3, + "RetryDelay": 1000, + "DefaultExpirationMinutes": 30, + "MaxExpirationMinutes": 1440, + "KeyPrefix": "X1:", + "EnableCompression": true, + "CompressionThreshold": 1024 + }, "JwtOptions": { "SecretKey": "a1mrtIiQN+AEmxE4WKFmKocGtrs3nrQaEbjzQgKp1XZWq8jP9HqzsjVgMKt3kAaCmTNaI9B9/YoaGMOY0sy8DQ==", "Issuer": "X1", @@ -45,26 +63,8 @@ "Cors": { "AllowedOrigins": [ "http://localhost:5173", - "https://localhost:5173", - "http://192.168.3.147:5173", - "https://192.168.3.147:5173", - "http://192.168.10.2:5173", - "https://192.168.10.2:5173", - "http://192.168.11.2:5173", - "https://192.168.11.2:5173", - "http://192.168.2.142:5173", - "https://192.168.2.142:5173", - "http://47.120.42.247:12790", - "https://47.120.42.247:12790", - "http://172.17.0.1:12790", - "https://172.17.0.1:12790", - "http://47.120.42.247:12780", - "https://47.120.42.247:12789", - "http://172.17.0.1:12789", - "https://172.17.0.1:12789", - "http://localhost:5000", - "https://localhost:7268", - "https://192.168.2.142:7268" + "http://localhost:3000", + "http://localhost:8080" ], "AllowedMethods": [ "GET", @@ -74,11 +74,17 @@ "OPTIONS" ], "AllowedHeaders": [ - "Authorization", "Content-Type", - "Accept", - "x-api-version" + "Authorization", + "X-Requested-With" ], - "AllowCredentials": true + "AllowCredentials": true, + "MaxAge": 86400 + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } } } diff --git a/src/X1.WebUI/src/pages/testcases/TestCasesListView.tsx b/src/X1.WebUI/src/pages/testcases/TestCasesListView.tsx index e44fb26..057a3e6 100644 --- a/src/X1.WebUI/src/pages/testcases/TestCasesListView.tsx +++ b/src/X1.WebUI/src/pages/testcases/TestCasesListView.tsx @@ -4,7 +4,7 @@ import { Card } from '@/components/ui/card'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; -import { Plus, Search, Edit, Trash2, Eye } from 'lucide-react'; +import { Plus, Search, Trash2, Eye } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; import { testcaseService, TestCaseFlow } from '@/services/testcaseService'; import TestCaseDetailDrawer from '@/components/testcases/TestCaseDetailDrawer'; diff --git a/src/X1.WebUI/yarn.lock b/src/X1.WebUI/yarn.lock index e3a9a95..db257bf 100644 --- a/src/X1.WebUI/yarn.lock +++ b/src/X1.WebUI/yarn.lock @@ -29,7 +29,7 @@ resolved "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.28.0.tgz" integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== -"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.27.4": +"@babel/core@^7.27.4": version "7.28.0" resolved "https://registry.npmmirror.com/@babel/core/-/core-7.28.0.tgz" integrity sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ== @@ -173,6 +173,116 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.27.1" +"@esbuild/aix-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" + integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== + +"@esbuild/android-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" + integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== + +"@esbuild/android-arm@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" + integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== + +"@esbuild/android-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" + integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== + +"@esbuild/darwin-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" + integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== + +"@esbuild/darwin-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" + integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== + +"@esbuild/freebsd-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" + integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== + +"@esbuild/freebsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" + integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== + +"@esbuild/linux-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" + integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== + +"@esbuild/linux-arm@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" + integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== + +"@esbuild/linux-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" + integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== + +"@esbuild/linux-loong64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" + integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== + +"@esbuild/linux-mips64el@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" + integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== + +"@esbuild/linux-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" + integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== + +"@esbuild/linux-riscv64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" + integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== + +"@esbuild/linux-s390x@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" + integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== + +"@esbuild/linux-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== + +"@esbuild/netbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" + integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== + +"@esbuild/openbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" + integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== + +"@esbuild/sunos-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" + integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== + +"@esbuild/win32-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" + integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== + +"@esbuild/win32-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" + integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== + "@esbuild/win32-x64@0.21.5": version "0.21.5" resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz" @@ -309,7 +419,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -403,7 +513,7 @@ "@radix-ui/react-use-previous" "1.1.1" "@radix-ui/react-use-size" "1.1.1" -"@radix-ui/react-collapsible@^1.1.10", "@radix-ui/react-collapsible@1.1.11": +"@radix-ui/react-collapsible@1.1.11", "@radix-ui/react-collapsible@^1.1.10": version "1.1.11" resolved "https://registry.npmmirror.com/@radix-ui/react-collapsible/-/react-collapsible-1.1.11.tgz" integrity sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg== @@ -449,7 +559,7 @@ resolved "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.1.2.tgz" integrity sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA== -"@radix-ui/react-dialog@^1.1.13", "@radix-ui/react-dialog@1.1.14": +"@radix-ui/react-dialog@1.1.14", "@radix-ui/react-dialog@^1.1.13": version "1.1.14" resolved "https://registry.npmmirror.com/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz" integrity sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw== @@ -771,7 +881,7 @@ "@radix-ui/react-use-previous" "1.1.1" "@radix-ui/react-use-size" "1.1.1" -"@radix-ui/react-slot@^1.2.2", "@radix-ui/react-slot@1.2.3": +"@radix-ui/react-slot@1.2.3", "@radix-ui/react-slot@^1.2.2": version "1.2.3" resolved "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz" integrity sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A== @@ -996,6 +1106,101 @@ resolved "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz" integrity sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA== +"@rollup/rollup-android-arm-eabi@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz#f768e3b2b0e6b55c595d7a053652c06413713983" + integrity sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w== + +"@rollup/rollup-android-arm64@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz#40379fd5501cfdfd7d8f86dfa1d3ce8d3a609493" + integrity sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ== + +"@rollup/rollup-darwin-arm64@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz#972c227bc89fe8a38a3f0c493e1966900e4e1ff7" + integrity sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg== + +"@rollup/rollup-darwin-x64@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz#96c919dcb87a5aa7dec5f7f77d90de881e578fdd" + integrity sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw== + +"@rollup/rollup-freebsd-arm64@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz#d199d8eaef830179c0c95b7a6e5455e893d1102c" + integrity sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA== + +"@rollup/rollup-freebsd-x64@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz#cab01f9e06ca756c1fabe87d64825ae016af4713" + integrity sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw== + +"@rollup/rollup-linux-arm-gnueabihf@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz#f6f1c42036dba0e58dc2315305429beff0d02c78" + integrity sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ== + +"@rollup/rollup-linux-arm-musleabihf@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz#1157e98e740facf858993fb51431dce3a4a96239" + integrity sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw== + +"@rollup/rollup-linux-arm64-gnu@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz#b39db73f8a4c22e7db31a4f3fd45170105f33265" + integrity sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ== + +"@rollup/rollup-linux-arm64-musl@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz#4043398049fe4449c1485312d1ae9ad8af4056dd" + integrity sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g== + +"@rollup/rollup-linux-loongarch64-gnu@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz#855a80e7e86490da15a85dcce247dbc25265bc08" + integrity sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew== + +"@rollup/rollup-linux-powerpc64le-gnu@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz#8cf843cb7ab1d42e1dda680937cf0a2db6d59047" + integrity sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA== + +"@rollup/rollup-linux-riscv64-gnu@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz#287c085472976c8711f16700326f736a527f2f38" + integrity sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw== + +"@rollup/rollup-linux-riscv64-musl@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz#095ad5e53a54ba475979f1b3226b92440c95c892" + integrity sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg== + +"@rollup/rollup-linux-s390x-gnu@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz#a3dec8281d8f2aef1703e48ebc65d29fe847933c" + integrity sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw== + +"@rollup/rollup-linux-x64-gnu@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz#4b211e6fd57edd6a134740f4f8e8ea61972ff2c5" + integrity sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw== + +"@rollup/rollup-linux-x64-musl@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz#3ecbf8e21b4157e57bb15dc6837b6db851f9a336" + integrity sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g== + +"@rollup/rollup-win32-arm64-msvc@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz#d4aae38465b2ad200557b53c8c817266a3ddbfd0" + integrity sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg== + +"@rollup/rollup-win32-ia32-msvc@4.44.1": + version "4.44.1" + resolved "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz#0258e8ca052abd48b23fd6113360fa0cd1ec3e23" + integrity sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A== + "@rollup/rollup-win32-x64-msvc@4.44.1": version "4.44.1" resolved "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz" @@ -1278,7 +1483,7 @@ resolved "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== -"@types/node@^18.0.0 || >=20.0.0", "@types/node@^20.11.16": +"@types/node@^20.11.16": version "20.19.4" resolved "https://registry.npmmirror.com/@types/node/-/node-20.19.4.tgz" integrity sha512-OP+We5WV8Xnbuvw0zC2m4qfB/BJvjyCwtNjhHdJxV1639SGSKrLmJkc3fMnp2Qy8nJyHp8RO6umxELN/dS1/EA== @@ -1290,7 +1495,7 @@ resolved "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz" integrity sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw== -"@types/react-dom@*", "@types/react-dom@^18.2.17": +"@types/react-dom@^18.2.17": version "18.3.7" resolved "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.3.7.tgz" integrity sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ== @@ -1302,7 +1507,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^18.0.0", "@types/react@^18.2.43", "@types/react@>=16.8": +"@types/react@*", "@types/react@^18.2.43": version "18.3.23" resolved "https://registry.npmmirror.com/@types/react/-/react-18.3.23.tgz" integrity sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w== @@ -1332,7 +1537,7 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/parser@^6.0.0 || ^6.0.0-alpha", "@typescript-eslint/parser@^6.14.0": +"@typescript-eslint/parser@^6.14.0": version "6.21.0" resolved "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-6.21.0.tgz" integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ== @@ -1423,7 +1628,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.9.0: +acorn@^8.9.0: version "8.15.0" resolved "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== @@ -1567,7 +1772,7 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.24.0, browserslist@^4.24.4, "browserslist@>= 4.21.0": +browserslist@^4.24.0, browserslist@^4.24.4: version "4.25.1" resolved "https://registry.npmmirror.com/browserslist/-/browserslist-4.25.1.tgz" integrity sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw== @@ -1608,6 +1813,11 @@ caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001726: resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz" integrity sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw== +chalk@5.2.0, chalk@^5.0.0: + version "5.2.0" + resolved "https://registry.npmmirror.com/chalk/-/chalk-5.2.0.tgz" + integrity sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA== + chalk@^4.0.0: version "4.1.2" resolved "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz" @@ -1616,11 +1826,6 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^5.0.0, chalk@5.2.0: - version "5.2.0" - resolved "https://registry.npmmirror.com/chalk/-/chalk-5.2.0.tgz" - integrity sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA== - chokidar@^3.6.0: version "3.6.0" resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz" @@ -1738,7 +1943,7 @@ csstype@^3.0.2: resolved "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz" integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== -d3-drag@^3.0.0, "d3-drag@2 - 3": +"d3-drag@2 - 3", d3-drag@^3.0.0: version "3.0.0" resolved "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz" integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== @@ -1758,7 +1963,7 @@ d3-drag@^3.0.0, "d3-drag@2 - 3": dependencies: d3-color "1 - 3" -d3-selection@^3.0.0, "d3-selection@2 - 3", d3-selection@3: +"d3-selection@2 - 3", d3-selection@3, d3-selection@^3.0.0: version "3.0.0" resolved "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz" integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== @@ -1971,7 +2176,7 @@ eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -"eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.0.0 || ^8.0.0", eslint@^8.55.0, eslint@>=8.40: +eslint@^8.55.0: version "8.57.1" resolved "https://registry.npmmirror.com/eslint/-/eslint-8.57.1.tgz" integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== @@ -2199,6 +2404,11 @@ fs.realpath@^1.0.0: resolved "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz" @@ -2243,7 +2453,7 @@ get-stream@^6.0.1: resolved "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -glob-parent@^5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -2257,13 +2467,6 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - glob@^10.3.10: version "10.4.5" resolved "https://registry.npmmirror.com/glob/-/glob-10.4.5.tgz" @@ -2394,7 +2597,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@^2.0.3, inherits@^2.0.4, inherits@2: +inherits@2, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2639,21 +2842,14 @@ mimic-fn@^4.0.0: resolved "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-4.0.0.tgz" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== -minimatch@^3.0.5: - version "3.1.2" - resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== +minimatch@9.0.3: + version "9.0.3" + resolved "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.3.tgz" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== dependencies: - brace-expansion "^1.1.7" + brace-expansion "^2.0.1" -minimatch@^3.1.2: +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -2667,13 +2863,6 @@ minimatch@^9.0.4: dependencies: brace-expansion "^2.0.1" -minimatch@9.0.3: - version "9.0.3" - resolved "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.3.tgz" - integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== - dependencies: - brace-expansion "^2.0.1" - "minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: version "7.1.2" resolved "https://registry.npmmirror.com/minipass/-/minipass-7.1.2.tgz" @@ -2937,7 +3126,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.0.0, postcss@^8.1.0, postcss@^8.2.14, postcss@^8.4.21, postcss@^8.4.33, postcss@^8.4.43, postcss@^8.4.47, postcss@>=8.0.9: +postcss@^8.4.33, postcss@^8.4.43, postcss@^8.4.47: version "8.5.6" resolved "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz" integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== @@ -2991,7 +3180,7 @@ react-async-script@^1.2.0: hoist-non-react-statics "^3.3.0" prop-types "^15.5.0" -"react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom@^18.0.0 || ^19.0.0", react-dom@^18.2.0, react-dom@>=16.8, react-dom@>=16.8.0, react-dom@>=17: +react-dom@^18.2.0: version "18.3.1" resolved "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz" integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== @@ -3007,7 +3196,7 @@ react-google-recaptcha@^3.1.0: prop-types "^15.5.0" react-async-script "^1.2.0" -react-hook-form@^7.50.0, react-hook-form@^7.55.0: +react-hook-form@^7.50.0: version "7.59.0" resolved "https://registry.npmmirror.com/react-hook-form/-/react-hook-form-7.59.0.tgz" integrity sha512-kmkek2/8grqarTJExFNjy+RXDIP8yM+QTl3QL6m6Q8b2bih4ltmiXxH7T9n+yXNK477xPh5yZT/6vD8sYGzJTA== @@ -3069,7 +3258,7 @@ react-style-singleton@^2.2.2, react-style-singleton@^2.2.3: get-nonce "^1.0.0" tslib "^2.0.0" -react@*, "react@^16.5.1 || ^17.0.0 || ^18.0.0", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc", "react@^18.0.0 || ^19.0.0", react@^18.2.0, react@^18.3.1, react@>=16.13.1, react@>=16.4.1, react@>=16.8, react@>=16.8.0, react@>=17: +react@^18.2.0: version "18.3.1" resolved "https://registry.npmmirror.com/react/-/react-18.3.1.tgz" integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== @@ -3254,13 +3443,6 @@ stdin-discarder@^0.1.0: dependencies: bl "^5.0.0" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz" @@ -3288,6 +3470,13 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + "strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -3354,7 +3543,7 @@ tailwindcss-animate@^1.0.7: resolved "https://registry.npmmirror.com/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz" integrity sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA== -tailwindcss@^3.4.1, "tailwindcss@>=3.0.0 || insiders": +tailwindcss@^3.4.1: version "3.4.17" resolved "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.17.tgz" integrity sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og== @@ -3435,9 +3624,9 @@ type-fest@^0.20.2: resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -typescript@^5.9.2, typescript@>=4.2.0: +typescript@^5.9.2: version "5.9.2" - resolved "https://registry.npmmirror.com/typescript/-/typescript-5.9.2.tgz" + resolved "https://registry.npmmirror.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== undici-types@~6.21.0: @@ -3490,7 +3679,7 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2: resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -"vite@^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0", vite@^5.0.8: +vite@^5.0.8: version "5.4.19" resolved "https://registry.npmmirror.com/vite/-/vite-5.4.19.tgz" integrity sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA== diff --git a/src/deploy-redis.sh b/src/deploy-redis.sh new file mode 100644 index 0000000..aef8dc4 --- /dev/null +++ b/src/deploy-redis.sh @@ -0,0 +1,303 @@ +#!/bin/bash + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 默认配置 +CONTAINER_NAME="redis-server" +DEFAULT_DATA_DIR="/data/redis" +DEFAULT_LOG_DIR="/var/log/redis" +DEFAULT_REDIS_PASSWORD="X1Redis2025!" # 默认Redis密码 + +# 函数:显示帮助信息 +show_help() { + echo -e "${BLUE}Redis Docker部署脚本${NC}" + echo "" + echo "用法: $0 [选项]" + echo "" + echo "选项:" + echo " -e, --environment ENV 部署环境 (test|prod) [必需]" + echo " -p, --password PASSWORD Redis密码 (默认: X1Redis2025!)" + echo " -d, --data-dir DIR 数据目录 (默认: /data/redis)" + echo " -l, --log-dir DIR 日志目录 (默认: /var/log/redis)" + echo " -n, --name NAME 容器名称 (默认: redis-server)" + echo " -h, --help 显示帮助信息" + echo "" + echo "端口配置:" + echo " 测试环境: 12996" + echo " 生产环境: 12995" + echo "" + echo "示例:" + echo " $0 -e test # 部署测试环境" + echo " $0 -e prod # 部署生产环境" + echo " $0 -e test -p mypassword # 自定义密码" + echo " $0 -e test -d /custom/data # 自定义数据目录" +} + +# 函数:检查Docker是否安装 +check_docker() { + if ! command -v docker &> /dev/null; then + echo -e "${RED}错误: Docker未安装${NC}" + exit 1 + fi + + if ! docker info &> /dev/null; then + echo -e "${RED}错误: Docker服务未运行${NC}" + exit 1 + fi +} + +# 函数:创建目录 +create_directories() { + local data_dir=$1 + local log_dir=$2 + + echo -e "${BLUE}创建必要的目录...${NC}" + + # 创建数据目录 + if [ ! -d "$data_dir" ]; then + sudo mkdir -p "$data_dir" + sudo chown 999:999 "$data_dir" # Redis容器用户ID + echo -e "${GREEN}✓ 创建数据目录: $data_dir${NC}" + else + echo -e "${YELLOW}⚠ 数据目录已存在: $data_dir${NC}" + fi + + # 创建日志目录 + if [ ! -d "$log_dir" ]; then + sudo mkdir -p "$log_dir" + sudo chown 999:999 "$log_dir" # Redis容器用户ID + echo -e "${GREEN}✓ 创建日志目录: $log_dir${NC}" + else + echo -e "${YELLOW}⚠ 日志目录已存在: $log_dir${NC}" + fi +} + +# 函数:停止并删除现有容器 +cleanup_container() { + local container_name=$1 + + echo -e "${BLUE}清理现有容器...${NC}" + + if docker ps -a --format "table {{.Names}}" | grep -q "^${container_name}$"; then + echo -e "${YELLOW}停止容器: $container_name${NC}" + docker stop "$container_name" 2>/dev/null || true + + echo -e "${YELLOW}删除容器: $container_name${NC}" + docker rm "$container_name" 2>/dev/null || true + + echo -e "${GREEN}✓ 容器清理完成${NC}" + else + echo -e "${YELLOW}⚠ 容器不存在: $container_name${NC}" + fi +} + +# 函数:部署Redis +deploy_redis() { + local environment=$1 + local port=$2 + local container_name=$3 + local data_dir=$4 + local log_dir=$5 + local redis_password=$6 + + echo -e "${BLUE}开始部署Redis (环境: $environment, 端口: $port)...${NC}" + + # 创建目录 + create_directories "$data_dir" "$log_dir" + + # 清理现有容器 + cleanup_container "$container_name" + + # 生成Redis配置文件 + local redis_conf="/tmp/redis-${environment}.conf" + cat > "$redis_conf" << EOF +# Redis配置文件 - $environment环境 +bind 0.0.0.0 +port 6379 +timeout 300 +tcp-keepalive 60 +loglevel notice +logfile /var/log/redis/redis.log +databases 16 +save 900 1 +save 300 10 +save 60 10000 +stop-writes-on-bgsave-error yes +rdbcompression yes +rdbchecksum yes +dbfilename dump.rdb +dir /data +maxmemory 256mb +maxmemory-policy allkeys-lru +appendonly yes +appendfilename "appendonly.aof" +appendfsync everysec +no-appendfsync-on-rewrite no +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + +# 安全配置 +requirepass $redis_password +rename-command FLUSHDB "" +rename-command FLUSHALL "" +rename-command DEBUG "" +rename-command CONFIG "" +rename-command SHUTDOWN "" +EOF + + echo -e "${GREEN}✓ Redis配置文件已生成: $redis_conf${NC}" + + # 启动Redis容器 + echo -e "${BLUE}启动Redis容器...${NC}" + + docker run -d \ + --name "$container_name" \ + --restart unless-stopped \ + -p "$port:6379" \ + -v "$data_dir:/data" \ + -v "$log_dir:/var/log/redis" \ + -v "$redis_conf:/usr/local/etc/redis/redis.conf" \ + redis:7-alpine \ + redis-server /usr/local/etc/redis/redis.conf + + if [ $? -eq 0 ]; then + echo -e "${GREEN}✓ Redis容器启动成功${NC}" + else + echo -e "${RED}✗ Redis容器启动失败${NC}" + exit 1 + fi + + # 等待容器启动 + echo -e "${BLUE}等待Redis服务启动...${NC}" + sleep 5 + + # 检查容器状态 + if docker ps --format "table {{.Names}}" | grep -q "^${container_name}$"; then + echo -e "${GREEN}✓ Redis服务运行正常${NC}" + else + echo -e "${RED}✗ Redis服务启动失败${NC}" + docker logs "$container_name" + exit 1 + fi + + # 测试Redis连接(带密码) + echo -e "${BLUE}测试Redis连接...${NC}" + if docker exec "$container_name" redis-cli -a "$redis_password" ping | grep -q "PONG"; then + echo -e "${GREEN}✓ Redis连接测试成功${NC}" + else + echo -e "${RED}✗ Redis连接测试失败${NC}" + exit 1 + fi + + # 清理临时配置文件 + rm -f "$redis_conf" + + echo -e "${GREEN}✓ Redis部署完成!${NC}" + echo -e "${BLUE}部署信息:${NC}" + echo -e " 环境: ${YELLOW}$environment${NC}" + echo -e " 端口: ${YELLOW}$port${NC}" + echo -e " 容器名: ${YELLOW}$container_name${NC}" + echo -e " 数据目录: ${YELLOW}$data_dir${NC}" + echo -e " 日志目录: ${YELLOW}$log_dir${NC}" + echo -e " Redis密码: ${YELLOW}$redis_password${NC}" + echo -e " 容器内连接: ${YELLOW}docker exec -it $container_name redis-cli -a '$redis_password'${NC}" + echo -e " 外部连接: ${YELLOW}redis-cli -h localhost -p $port -a '$redis_password'${NC}" + echo -e " 连接URL: ${YELLOW}redis://:$redis_password@localhost:$port${NC}" +} + +# 主函数 +main() { + # 解析命令行参数 + local environment="" + local redis_password="$DEFAULT_REDIS_PASSWORD" + local data_dir="$DEFAULT_DATA_DIR" + local log_dir="$DEFAULT_LOG_DIR" + local container_name="$CONTAINER_NAME" + + while [[ $# -gt 0 ]]; do + case $1 in + -e|--environment) + environment="$2" + shift 2 + ;; + -p|--password) + redis_password="$2" + shift 2 + ;; + -d|--data-dir) + data_dir="$2" + shift 2 + ;; + -l|--log-dir) + log_dir="$2" + shift 2 + ;; + -n|--name) + container_name="$2" + shift 2 + ;; + -h|--help) + show_help + exit 0 + ;; + *) + echo -e "${RED}未知参数: $1${NC}" + show_help + exit 1 + ;; + esac + done + + # 验证必需参数 + if [ -z "$environment" ]; then + echo -e "${RED}错误: 必须指定部署环境 (-e 或 --environment)${NC}" + show_help + exit 1 + fi + + # 验证环境参数 + if [ "$environment" != "test" ] && [ "$environment" != "prod" ]; then + echo -e "${RED}错误: 环境参数必须是 'test' 或 'prod'${NC}" + exit 1 + fi + + # 根据环境设置端口 + local port + if [ "$environment" = "test" ]; then + port=12996 + else + port=12995 + fi + + # 检查Docker + check_docker + + # 显示部署信息 + echo -e "${BLUE}=== Redis Docker部署脚本 ===${NC}" + echo -e "环境: ${YELLOW}$environment${NC}" + echo -e "端口: ${YELLOW}$port${NC}" + echo -e "容器名: ${YELLOW}$container_name${NC}" + echo -e "数据目录: ${YELLOW}$data_dir${NC}" + echo -e "日志目录: ${YELLOW}$log_dir${NC}" + echo -e "Redis密码: ${YELLOW}$redis_password${NC}" + echo "" + + # 确认部署 + read -p "确认部署? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}部署已取消${NC}" + exit 0 + fi + + # 执行部署 + deploy_redis "$environment" "$port" "$container_name" "$data_dir" "$log_dir" "$redis_password" +} + +# 执行主函数 +main "$@" diff --git a/src/modify.md b/src/modify.md index a461a74..bed7da5 100644 --- a/src/modify.md +++ b/src/modify.md @@ -12643,3 +12643,201 @@ const convertedEdges = edges.map(edge => ({ 用户发现前端连线数据转换中缺少 `sourceHandle` 和 `targetHandle` 字段,需要修复以确保连接点信息能够正确传递到后端。 --- + +## 2024-12-19 - 修复 CaptchaService 引用问题 + +### 问题描述 +CaptchaService 出现引用问题,构建时出现以下错误: +- 未能找到类型或命名空间名"SkiaSharp" +- 未能找到类型或命名空间名"MailKit" +- 未能找到类型或命名空间名"MimeKit" + +### 问题原因 +X1.Infrastructure 项目中缺少必要的 NuGet 包引用: +- SkiaSharp (用于生成验证码图片) +- MailKit (用于发送邮件) +- MimeKit (用于构建邮件内容) + +### 解决方案 +在 `X1.Infrastructure/X1.Infrastructure.csproj` 文件中添加了缺失的包引用: + +```xml + + + +``` + +### 修改文件 +- `X1.Infrastructure/X1.Infrastructure.csproj` - 添加缺失的 NuGet 包引用 + +### 验证结果 +- 项目构建成功,所有引用错误已解决 +- CaptchaService 和 EmailService 功能恢复正常 + +### 备注 +这个问题可能是由于之前的包更新或项目重构导致的包引用丢失。建议在后续的包更新操作中注意检查所有必要的依赖关系。 + +#### 修改时间: +2024-12-19 + +#### 修改原因: +用户反映 CaptchaService 原先没有问题,现在出现引用问题,可能是更新包导致的。经过检查发现缺少必要的 NuGet 包引用。 + +--- + +## 2024-12-19 - 修复"为了异步而异步"的问题 + +### 问题描述 +项目构建时出现大量 CS1998 警告,提示异步方法缺少 "await" 运算符。这些警告主要是由于"为了异步而异步"的设计问题导致的。 + +### 问题分析 +1. **PermissionRepository** 中的 `UpdateAsync` 和 `DeleteAsync` 方法被定义为异步,但实际只是调用同步的底层方法 +2. **EmailVerificationService** 中的 `VerifyCodeAsync` 方法被定义为异步,但实际只是内存操作 +3. 这些方法被错误地标记为异步,导致编译器警告 + +### 解决方案 + +#### 1. 修复接口定义 +- **文件**: `X1.Domain/Repositories/Identity/IPermissionRepository.cs` +- **修改**: 将 `UpdateAsync` 和 `DeleteAsync` 方法改为同步方法 +```csharp +// 修改前 +Task UpdateAsync(Permission permission, CancellationToken cancellationToken = default); +Task DeleteAsync(Permission permission, CancellationToken cancellationToken = default); + +// 修改后 +void UpdateAsync(Permission permission, CancellationToken cancellationToken = default); +void DeleteAsync(Permission permission, CancellationToken cancellationToken = default); +``` + +#### 2. 修复实现类 +- **文件**: `X1.Infrastructure/Repositories/Identity/PermissionRepository.cs` +- **修改**: 将方法实现改为同步,移除不必要的 `await Task.CompletedTask` + +#### 3. 修复 EmailVerificationService +- **文件**: `X1.Domain/Services/IEmailVerificationService.cs` +- **修改**: 将 `VerifyCodeAsync` 方法改为同步方法 +```csharp +// 修改前 +Task VerifyCodeAsync(string email, string code); + +// 修改后 +bool VerifyCode(string email, string code); +``` + +- **文件**: `X1.Infrastructure/Services/UserManagement/EmailVerificationService.cs` +- **修改**: 实现同步的 `VerifyCode` 方法 + +#### 4. 更新调用方 +- **文件**: `X1.Application/Features/Auth/Commands/VerifyCode/VerifyCodeCommandHandler.cs` +- **文件**: `X1.Application/Features/Auth/Commands/EmailLogin/EmailLoginCommandHandler.cs` +- **修改**: 更新方法调用以匹配新的接口定义 + +### 设计原则 +- **真正的异步操作**:只有涉及 I/O 操作(数据库、网络、文件)的方法才使用异步 +- **内存操作**:纯内存操作(如缓存访问、数据验证)使用同步方法 +- **避免 `await Task.CompletedTask`**:不使用这种"为了异步而异步"的写法 + +### 验证结果 +- ✅ 项目构建成功 +- ✅ CS1998 警告大幅减少(从多个减少到只有几个) +- ✅ 代码更符合异步编程的最佳实践 +- ✅ 性能得到改善(避免了不必要的异步开销) + +### 修改文件 +- `X1.Domain/Repositories/Identity/IPermissionRepository.cs` +- `X1.Infrastructure/Repositories/Identity/PermissionRepository.cs` +- `X1.Domain/Services/IEmailVerificationService.cs` +- `X1.Infrastructure/Services/UserManagement/EmailVerificationService.cs` +- `X1.Application/Features/Auth/Commands/VerifyCode/VerifyCodeCommandHandler.cs` +- `X1.Application/Features/Auth/Commands/EmailLogin/EmailLoginCommandHandler.cs` + +#### 修改时间: +2024-12-19 + +#### 修改原因: +用户指出项目中有大量警告,怀疑是"为了异步而异步"导致的问题。经过分析发现确实存在接口设计不一致的问题,需要修复以符合异步编程的最佳实践。 + +--- + +## 2024-12-19 - 修复解引用可能出现空引用的警告 + +### 问题描述 +项目构建时出现多个 CS8602、CS8604 警告,提示"解引用可能出现空引用"和"可能传入 null 引用实参"。这些警告主要出现在字符串操作和方法调用中。 + +### 修复的警告类型 +1. **CS8602**: 解引用可能出现空引用 +2. **CS8604**: 方法参数可能传入 null 引用实参 + +### 解决方案 + +#### 1. 修复 ProtocolVersionRepository 中的空引用问题 +- **文件**: `X1.Infrastructure/Repositories/Device/ProtocolVersionRepository.cs` +- **修改**: 在字符串比较前添加空值检查 +```csharp +// 修改前 +query = query.Where(pv => + pv.Name.Contains(keyword) || + pv.Version.Contains(keyword) || + pv.Description.Contains(keyword)); + +// 修改后 +query = query.Where(pv => + (pv.Name != null && pv.Name.Contains(keyword)) || + (pv.Version != null && pv.Version.Contains(keyword)) || + (pv.Description != null && pv.Description.Contains(keyword))); +``` + +#### 2. 修复 NetworkStackConfigRepository 中的空引用问题 +- **文件**: `X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs` +- **修改**: + - 在字符串比较前添加空值检查 + - 在排序时使用空值合并运算符 +```csharp +// 字符串比较修复 +query = query.Where(nsc => + (nsc.NetworkStackName != null && nsc.NetworkStackName.Contains(keyword)) || + (nsc.Description != null && nsc.Description.Contains(keyword))); + +// 排序修复 +return (result.TotalCount, result.Items.OrderBy(x => x.NetworkStackName ?? string.Empty).ToList()); +``` + +#### 3. 修复 UserRegistrationService 中的空引用问题 +- **文件**: `X1.Infrastructure/Services/UserManagement/UserRegistrationService.cs` +- **修改**: 在传递参数给方法前使用空值合并运算符 +```csharp +// 修改前 +var userNameResult = UserName.Create(user.UserName); +var emailResult = Email.Create(user.Email); +var existingUser = await _userManager.FindByNameAsync(user.UserName); + +// 修改后 +var userNameResult = UserName.Create(user.UserName ?? string.Empty); +var emailResult = Email.Create(user.Email ?? string.Empty); +var existingUser = await _userManager.FindByNameAsync(user.UserName ?? string.Empty); +``` + +### 使用的修复技术 +1. **空值检查**: `property != null && property.Contains(keyword)` +2. **空值合并运算符**: `property ?? string.Empty` +3. **安全导航**: 确保在调用方法前检查对象是否为 null + +### 验证结果 +- ✅ 项目构建成功 +- ✅ X1.Infrastructure 项目的空引用警告大幅减少(从多个减少到只有 3 个) +- ✅ 代码更加健壮,减少了运行时空引用异常的可能性 +- ✅ 保持了代码的可读性和性能 + +### 修改文件 +- `X1.Infrastructure/Repositories/Device/ProtocolVersionRepository.cs` +- `X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs` +- `X1.Infrastructure/Services/UserManagement/UserRegistrationService.cs` + +#### 修改时间: +2024-12-19 + +#### 修改原因: +用户反映项目中有解引用可能出现空引用的警告需要处理。这些警告虽然不影响编译,但可能导致运行时异常,需要修复以提高代码的健壮性。 + +--- \ No newline at end of file