All checks were successful
Build and Deploy / deploy (push) Successful in 58s
Swagger openApi 提供 /openapi/pickup-order/push-fee 与 /openapi/parcel/create;删除 method 路由与 whoami。 Co-authored-by: Cursor <cursoragent@cursor.com>
303 lines
11 KiB
C#
303 lines
11 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// 系统其他配置 API 服务。
|
|
/// </summary>
|
|
[ApiExplorerSettings(GroupName = "OMS-System")]
|
|
public class SystemOtherConfigService : ApplicationService
|
|
{
|
|
private const string SecretMask = "******";
|
|
|
|
private ISystemOtherConfigRepository SystemOtherConfigRepository =>
|
|
LazyServiceProvider.GetRequiredService<ISystemOtherConfigRepository>();
|
|
|
|
/// <summary>
|
|
/// 查询系统其他配置列表。
|
|
/// </summary>
|
|
[HttpGet("/system/other-config/list")]
|
|
public async Task<OmsPageResult<SystemOtherConfigOutput>> 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<string, bool> { [nameof(SystemOtherConfig.Id)] = false });
|
|
var total = await SystemOtherConfigRepository.CountAsync(predicate);
|
|
|
|
return new OmsPageResult<SystemOtherConfigOutput>
|
|
{
|
|
Items = items.Select(x => MapOutput(x, maskSecret: true)).ToList(),
|
|
Total = total
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 查询系统其他配置详情。
|
|
/// </summary>
|
|
[HttpGet("/system/other-config/detail")]
|
|
public async Task<SystemOtherConfigOutput> GetDetailAsync([FromQuery] int id)
|
|
{
|
|
var entity = await GetConfigOrThrowAsync(id);
|
|
return MapOutput(entity, maskSecret: false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 新增系统其他配置。
|
|
/// </summary>
|
|
[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)
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// 更新系统其他配置。
|
|
/// </summary>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 删除系统其他配置。
|
|
/// </summary>
|
|
[HttpPost("/system/other-config/delete")]
|
|
public async Task DeleteAsync(IdInput input)
|
|
{
|
|
var entity = await GetConfigOrThrowAsync(input.Id);
|
|
await SystemOtherConfigRepository.DeleteAsync(entity);
|
|
}
|
|
|
|
private static Expression<Func<SystemOtherConfig, bool>> BuildListPredicate(SystemOtherConfigListInput input)
|
|
{
|
|
var keyword = input.Keyword?.Trim();
|
|
return x =>
|
|
string.IsNullOrWhiteSpace(keyword) ||
|
|
x.ConfigCode.Contains(keyword) ||
|
|
x.ConfigName.Contains(keyword);
|
|
}
|
|
|
|
private async Task<SystemOtherConfig> 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;
|
|
}
|