using System.Linq.Expressions; using Fake.Application; using Microsoft.AspNetCore.Mvc; using Oms.Application.Dtos.Shared; using Oms.Application.Dtos.System; using Oms.Application.Shared; using Oms.Domain.SystemAggregate.Consts; using Oms.Domain.SystemAggregate.Entities; using Oms.Domain.SystemAggregate.Repositories; namespace Oms.Application.Services.System; /// /// 系统其他配置 API 服务。 /// [ApiExplorerSettings(GroupName = "OMS-System")] public class SystemOtherConfigService : ApplicationService { private const string SecretMask = "******"; private ISystemOtherConfigRepository SystemOtherConfigRepository => LazyServiceProvider.GetRequiredService(); /// /// 查询系统其他配置列表。 /// [HttpGet("/system/other-config/list")] public async Task> GetListAsync([FromQuery] SystemOtherConfigListInput input) { var predicate = BuildListPredicate(input); var pageIndex = OmsDocHelper.NormalizePage(input.Page); var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize); var items = await SystemOtherConfigRepository.GetPagedListAsync( predicate, pageIndex, pageSize, new Dictionary { [nameof(SystemOtherConfig.Id)] = false }); var total = await SystemOtherConfigRepository.CountAsync(predicate); return new OmsPageResult { Items = items.Select(x => MapOutput(x, maskSecret: true)).ToList(), Total = total }; } /// /// 查询系统其他配置详情。 /// [HttpGet("/system/other-config/detail")] public async Task GetDetailAsync([FromQuery] int id) { var entity = await GetConfigOrThrowAsync(id); return MapOutput(entity, maskSecret: false); } /// /// 新增系统其他配置。 /// [HttpPost("/system/other-config/create")] public async Task CreateAsync(SystemOtherConfigCreateInput input) { await ValidateInputAsync(input, null); await SystemOtherConfigRepository.InsertAsync(new SystemOtherConfig { ConfigCode = input.ConfigCode.Trim(), ConfigName = input.ConfigName.Trim(), Enabled = input.Enabled, BaseUrl = NormalizeOptional(input.BaseUrl), AppKey = NormalizeOptional(input.AppKey), AppSecret = NormalizeOptional(input.AppSecret), TimeoutSeconds = NormalizeTimeoutSeconds(input.TimeoutSeconds), Remark = NormalizeOptional(input.Remark) }); } /// /// 更新系统其他配置。 /// [HttpPost("/system/other-config/update")] public async Task UpdateAsync(SystemOtherConfigUpdateInput input) { var entity = await GetConfigOrThrowAsync(input.Id); await ValidateInputAsync(input, entity.Id); var clearWeChatAccessToken = ShouldClearWeChatAccessToken(entity, input); entity.ConfigCode = input.ConfigCode.Trim(); entity.ConfigName = input.ConfigName.Trim(); entity.Enabled = input.Enabled; entity.BaseUrl = NormalizeOptional(input.BaseUrl); entity.AppKey = NormalizeOptional(input.AppKey); if (!ShouldKeepExistingSecret(input.AppSecret)) { entity.AppSecret = NormalizeOptional(input.AppSecret); } entity.TimeoutSeconds = NormalizeTimeoutSeconds(input.TimeoutSeconds); entity.Remark = NormalizeOptional(input.Remark); if (clearWeChatAccessToken) { entity.AccessToken = null; entity.AccessTokenExpiresIn = 0; entity.AccessTokenUpdatedAt = null; } await SystemOtherConfigRepository.UpdateAsync(entity); } /// /// 删除系统其他配置。 /// [HttpPost("/system/other-config/delete")] public async Task DeleteAsync(IdInput input) { var entity = await GetConfigOrThrowAsync(input.Id); await SystemOtherConfigRepository.DeleteAsync(entity); } private static Expression> BuildListPredicate(SystemOtherConfigListInput input) { var keyword = input.Keyword?.Trim(); return x => string.IsNullOrWhiteSpace(keyword) || x.ConfigCode.Contains(keyword) || x.ConfigName.Contains(keyword); } private async Task GetConfigOrThrowAsync(int id) { var entity = await SystemOtherConfigRepository.FirstOrDefaultAsync(x => x.Id == id); if (entity == null) { throw new BusinessException("配置不存在"); } return entity; } private async Task ValidateInputAsync(SystemOtherConfigCreateInput input, int? excludeId) { if (string.IsNullOrWhiteSpace(input.ConfigCode)) { throw new BusinessException("配置编码不能为空"); } if (string.IsNullOrWhiteSpace(input.ConfigName)) { throw new BusinessException("配置名称不能为空"); } var configCode = input.ConfigCode.Trim(); var existed = await SystemOtherConfigRepository.GetByConfigCodeAsync(configCode); if (existed != null && existed.Id != excludeId) { throw new BusinessException("配置编码已存在"); } if (string.Equals(configCode, SystemOtherConfigCodes.TrackService, StringComparison.OrdinalIgnoreCase)) { if (string.IsNullOrWhiteSpace(input.BaseUrl)) { throw new BusinessException("轨迹服务地址不能为空"); } if (string.IsNullOrWhiteSpace(input.AppKey)) { throw new BusinessException("轨迹服务 AppKey 不能为空"); } if (excludeId == null && ShouldKeepExistingSecret(input.AppSecret)) { throw new BusinessException("轨迹服务 AppSecret 不能为空"); } } else if (string.Equals(configCode, SystemOtherConfigCodes.ApiCenter, StringComparison.OrdinalIgnoreCase)) { if (string.IsNullOrWhiteSpace(input.BaseUrl)) { throw new BusinessException("API 中心服务地址不能为空"); } if (string.IsNullOrWhiteSpace(input.AppKey)) { throw new BusinessException("API 中心应用 AppKey 不能为空"); } if (excludeId == null && ShouldKeepExistingSecret(input.AppSecret)) { throw new BusinessException("API 中心应用 AppSecret 不能为空"); } } else if (string.Equals(configCode, SystemOtherConfigCodes.PickupOpenApi, StringComparison.OrdinalIgnoreCase)) { // 鉴权已改由 itfx 上游完成;本配置不再强制 AppKey } else if (string.Equals(configCode, SystemOtherConfigCodes.WeChatMp, StringComparison.OrdinalIgnoreCase)) { if (string.IsNullOrWhiteSpace(input.AppKey)) { throw new BusinessException("微信公众号 AppId 不能为空"); } if (string.IsNullOrWhiteSpace(input.Remark)) { throw new BusinessException("微信公众号 Token 不能为空"); } if (excludeId == null && ShouldKeepExistingSecret(input.AppSecret)) { throw new BusinessException("微信公众号 AppSecret 不能为空"); } } else if (string.Equals(configCode, SystemOtherConfigCodes.PlatformShop, StringComparison.OrdinalIgnoreCase)) { if (string.IsNullOrWhiteSpace(input.BaseUrl)) { throw new BusinessException("店铺授权前端回调根地址不能为空"); } } else if (IsPlatformShopAppConfig(configCode)) { if (string.IsNullOrWhiteSpace(input.AppKey)) { throw new BusinessException("平台 AppKey / PartnerId 不能为空"); } if (excludeId == null && ShouldKeepExistingSecret(input.AppSecret)) { throw new BusinessException("平台 AppSecret / PartnerKey 不能为空"); } if (string.Equals(configCode, SystemOtherConfigCodes.PlatformShopShopee, StringComparison.OrdinalIgnoreCase) && !int.TryParse(input.AppKey.Trim(), out _)) { throw new BusinessException("Shopee PartnerId 必须为数字"); } if (string.IsNullOrWhiteSpace(input.BaseUrl)) { throw new BusinessException("平台授权/API 地址不能为空"); } } } private static bool IsPlatformShopAppConfig(string configCode) => string.Equals(configCode, SystemOtherConfigCodes.PlatformShopLazada, StringComparison.OrdinalIgnoreCase) || string.Equals(configCode, SystemOtherConfigCodes.PlatformShopShopee, StringComparison.OrdinalIgnoreCase) || string.Equals(configCode, SystemOtherConfigCodes.PlatformShopTiktok, StringComparison.OrdinalIgnoreCase) || string.Equals(configCode, SystemOtherConfigCodes.PlatformShopTemu, StringComparison.OrdinalIgnoreCase); private static SystemOtherConfigOutput MapOutput(SystemOtherConfig entity, bool maskSecret) { return new SystemOtherConfigOutput { Id = entity.Id, ConfigCode = entity.ConfigCode, ConfigName = entity.ConfigName, Enabled = entity.Enabled, BaseUrl = entity.BaseUrl, AppKey = entity.AppKey, AppSecret = maskSecret ? MaskSecret(entity.AppSecret) : entity.AppSecret, TimeoutSeconds = entity.TimeoutSeconds, Remark = entity.Remark, CreateTime = entity.CreateTime, UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime) }; } private static string? MaskSecret(string? secret) => string.IsNullOrWhiteSpace(secret) ? secret : SecretMask; private static bool ShouldKeepExistingSecret(string? secret) => string.IsNullOrWhiteSpace(secret) || string.Equals(secret.Trim(), SecretMask, StringComparison.Ordinal); private static bool ShouldClearWeChatAccessToken(SystemOtherConfig entity, SystemOtherConfigUpdateInput input) { if (!string.Equals(entity.ConfigCode, SystemOtherConfigCodes.WeChatMp, StringComparison.OrdinalIgnoreCase) && !string.Equals(input.ConfigCode.Trim(), SystemOtherConfigCodes.WeChatMp, StringComparison.OrdinalIgnoreCase)) { return false; } var nextAppKey = NormalizeOptional(input.AppKey); if (!string.Equals(entity.AppKey, nextAppKey, StringComparison.Ordinal)) { return true; } if (!ShouldKeepExistingSecret(input.AppSecret) && !string.Equals(entity.AppSecret, NormalizeOptional(input.AppSecret), StringComparison.Ordinal)) { return true; } return false; } private static string? NormalizeOptional(string? value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim(); private static int NormalizeTimeoutSeconds(int timeoutSeconds) => timeoutSeconds <= 0 ? 30 : timeoutSeconds; }