admin/src/Oms.Application/Services/Customers/CustomerOpenApiCredentialService.cs
zzh f063c99d1b feat: OpenAPI 增加轨迹/文件查询,创单按 clientNo 幂等
客户 HMAC 通过后再查本客户订单;禁止下游面单,附件增加 POD。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-05 18:48:21 +08:00

257 lines
9.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Security.Cryptography;
using Fake.Application;
using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.Customers;
using Oms.Application.Shared;
using Oms.Domain.CustomersAggregate.Entities;
using Oms.Domain.CustomersAggregate.Repositories;
namespace Oms.Application.Services.Customers;
/// <summary>
/// 客户 OpenAPI 凭证管理(门户当前用户 + 后台代管)。
/// </summary>
[ApiExplorerSettings(GroupName = "OMS-Customer")]
public class CustomerOpenApiCredentialService : ApplicationService
{
private const string SecretMask = "********";
private ICustomerRepository CustomerRepository =>
LazyServiceProvider.GetRequiredService<ICustomerRepository>();
private ICustomerOpenApiCredentialRepository CredentialRepository =>
LazyServiceProvider.GetRequiredService<ICustomerOpenApiCredentialRepository>();
/// <summary>当前登录主客户查看凭证(Secret 掩码)。</summary>
[HttpGet("/customer/openapi-credential")]
public async Task<CustomerOpenApiCredentialOutput> GetMineAsync()
{
var customer = await GetCurrentMainCustomerOrThrowAsync();
return await MapGetAsync(customer.Id);
}
/// <summary>当前登录主客户:无则生成并返回完整 Secret;已有则掩码返回。</summary>
[HttpPost("/customer/openapi-credential/ensure")]
public async Task<CustomerOpenApiCredentialOutput> EnsureMineAsync()
{
var customer = await GetCurrentMainCustomerOrThrowAsync();
return await EnsureAsync(customer.Id);
}
/// <summary>当前登录主客户重置 Secret。</summary>
[HttpPost("/customer/openapi-credential/reset-secret")]
public async Task<CustomerOpenApiCredentialOutput> ResetMineSecretAsync()
{
var customer = await GetCurrentMainCustomerOrThrowAsync();
return await ResetSecretAsync(customer.Id);
}
/// <summary>当前登录主客户启停。</summary>
[HttpPost("/customer/openapi-credential/set-enabled")]
public async Task<CustomerOpenApiCredentialOutput> SetMineEnabledAsync(
[FromBody] CustomerOpenApiCredentialSetEnabledInput input)
{
var customer = await GetCurrentMainCustomerOrThrowAsync();
return await SetEnabledAsync(customer.Id, input.Enabled);
}
/// <summary>后台按客户查看凭证。</summary>
[HttpGet("/customer/openapi-credential/admin/get")]
public async Task<CustomerOpenApiCredentialOutput> AdminGetAsync([FromQuery] int customerId)
{
EnsureAdminSession();
await EnsureMainCustomerAsync(customerId);
return await MapGetAsync(customerId);
}
/// <summary>后台确保生成凭证。</summary>
[HttpPost("/customer/openapi-credential/admin/ensure")]
public async Task<CustomerOpenApiCredentialOutput> AdminEnsureAsync(
[FromBody] CustomerOpenApiCredentialAdminInput input)
{
EnsureAdminSession();
await EnsureMainCustomerAsync(input.CustomerId);
return await EnsureAsync(input.CustomerId);
}
/// <summary>后台重置 Secret。</summary>
[HttpPost("/customer/openapi-credential/admin/reset-secret")]
public async Task<CustomerOpenApiCredentialOutput> AdminResetSecretAsync(
[FromBody] CustomerOpenApiCredentialAdminInput input)
{
EnsureAdminSession();
await EnsureMainCustomerAsync(input.CustomerId);
return await ResetSecretAsync(input.CustomerId);
}
/// <summary>后台启停。</summary>
[HttpPost("/customer/openapi-credential/admin/set-enabled")]
public async Task<CustomerOpenApiCredentialOutput> AdminSetEnabledAsync(
[FromBody] CustomerOpenApiCredentialAdminSetEnabledInput input)
{
EnsureAdminSession();
await EnsureMainCustomerAsync(input.CustomerId);
return await SetEnabledAsync(input.CustomerId, input.Enabled);
}
private async Task<CustomerOpenApiCredentialOutput> MapGetAsync(int customerId)
{
var entity = await CredentialRepository.GetByCustomerIdAsync(customerId);
if (entity == null)
{
return new CustomerOpenApiCredentialOutput
{
CustomerId = customerId,
HasCredential = false,
Enabled = false,
AppKey = string.Empty,
AppSecret = string.Empty,
SecretRevealed = false
};
}
return MapOutput(entity, revealSecret: false);
}
private async Task<CustomerOpenApiCredentialOutput> EnsureAsync(int customerId)
{
var existing = await CredentialRepository.GetByCustomerIdAsync(customerId);
if (existing != null)
{
return MapOutput(existing, revealSecret: false);
}
var entity = new CustomerOpenApiCredential
{
CustomerId = customerId,
AppKey = await GenerateUniqueAppKeyAsync(),
AppSecret = GenerateAppSecret(),
Enabled = true
};
entity = await CredentialRepository.InsertAsync(entity);
return MapOutput(entity, revealSecret: true);
}
private async Task<CustomerOpenApiCredentialOutput> ResetSecretAsync(int customerId)
{
var entity = await CredentialRepository.GetByCustomerIdAsync(customerId);
if (entity == null)
{
throw new BusinessException("尚未生成开放接口凭证,请先生成");
}
entity.AppSecret = GenerateAppSecret();
await CredentialRepository.UpdateAsync(entity);
return MapOutput(entity, revealSecret: true);
}
private async Task<CustomerOpenApiCredentialOutput> SetEnabledAsync(int customerId, bool enabled)
{
var entity = await CredentialRepository.GetByCustomerIdAsync(customerId);
if (entity == null)
{
throw new BusinessException("尚未生成开放接口凭证,请先生成");
}
entity.Enabled = enabled;
await CredentialRepository.UpdateAsync(entity);
return MapOutput(entity, revealSecret: false);
}
private async Task EnsureMainCustomerAsync(int customerId)
{
if (customerId <= 0)
{
throw new BusinessException("客户ID不能为空");
}
var customer = await CustomerRepository.FirstOrDefaultAsync(x => x.Id == customerId);
if (customer == null)
{
throw new BusinessException("客户不存在");
}
if (customer.ParentCustomerId.HasValue)
{
throw new BusinessException("仅主客户可配置开放接口凭证");
}
}
private async Task<Customer> GetCurrentMainCustomerOrThrowAsync()
{
if (!OmsSessionHelper.IsCustomerLogin(CurrentUser))
{
throw new BusinessException(
"当前是管理后台登录,不能调用客户门户接口。请到「客户」列表打开对应客户的开放接口凭证;客户请从客户门户登录后操作");
}
var mapId = OmsSessionHelper.TryGetCustomerMapId(CurrentUser);
if (!mapId.HasValue || mapId.Value <= 0)
{
throw new BusinessException("当前登录不是客户账号,无法读取开放接口凭证");
}
var customer = await CustomerRepository.FirstOrDefaultAsync(x => x.UserId == mapId.Value);
if (customer == null)
{
throw new BusinessException("客户不存在");
}
if (customer.ParentCustomerId.HasValue)
{
throw new BusinessException("仅主客户可管理开放接口凭证");
}
return customer;
}
private void EnsureAdminSession()
{
if (OmsSessionHelper.IsCustomerLogin(CurrentUser))
{
throw new BusinessException("无权限");
}
}
private async Task<string> GenerateUniqueAppKeyAsync()
{
for (var i = 0; i < 8; i++)
{
var key = $"oms_{Guid.NewGuid():N}"[..20];
var existed = await CredentialRepository.GetByAppKeyAsync(key);
if (existed == null)
{
return key;
}
}
throw new BusinessException("生成 AppKey 失败,请重试");
}
private static string GenerateAppSecret()
{
var bytes = RandomNumberGenerator.GetBytes(24);
return Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', 'x').Replace('/', 'y');
}
private static CustomerOpenApiCredentialOutput MapOutput(
CustomerOpenApiCredential entity,
bool revealSecret)
{
return new CustomerOpenApiCredentialOutput
{
Id = entity.Id,
CustomerId = entity.CustomerId,
AppKey = entity.AppKey,
AppSecret = revealSecret ? entity.AppSecret : SecretMask,
Enabled = entity.Enabled,
HasCredential = true,
SecretRevealed = revealSecret,
LastUsedAt = entity.LastUsedAt,
LastUsedIp = entity.LastUsedIp,
CreateTime = entity.CreateTime,
UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime)
};
}
}