All checks were successful
Build and Deploy / deploy (push) Successful in 4m34s
Co-authored-by: Cursor <cursoragent@cursor.com>
202 lines
8.1 KiB
C#
202 lines
8.1 KiB
C#
using Fake.Application;
|
||
using Fake.Rbac.Application.Jwt;
|
||
using Fake.Rbac.Domain.Managers;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using Microsoft.AspNetCore.Http;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using Oms.Application.Dtos.OpenApi;
|
||
using Oms.Application.Dtos.Operations;
|
||
using Oms.Application.Rbac;
|
||
using Oms.Application.Services.Operations;
|
||
using Oms.Application.Services.Orders;
|
||
using Oms.Application.Shared;
|
||
using Oms.Domain.OrdersAggregate.Consts;
|
||
using Oms.Domain.OrdersAggregate.Repositories;
|
||
|
||
namespace Oms.Application.Services.OpenApi;
|
||
|
||
/// <summary>
|
||
/// 仓配对外开放 API(Swagger 文档分组:warehouseOpenApi)。
|
||
/// 鉴权:Authorization: {token}(账号密码换取,有效期 48 小时;无需 Bearer 前缀)。
|
||
/// 与客户 HMAC 分组 openApi 相互独立。
|
||
/// </summary>
|
||
[ApiExplorerSettings(GroupName = "warehouseOpenApi")]
|
||
public class WarehouseOpenApiService : ApplicationService
|
||
{
|
||
private AccountManager AccountManager =>
|
||
LazyServiceProvider.GetRequiredService<AccountManager>();
|
||
|
||
private IJwtService JwtService =>
|
||
LazyServiceProvider.GetRequiredService<IJwtService>();
|
||
|
||
private OpenApiTokenIssuer TokenIssuer =>
|
||
LazyServiceProvider.GetRequiredService<OpenApiTokenIssuer>();
|
||
|
||
private IOrderRepository OrderRepository =>
|
||
LazyServiceProvider.GetRequiredService<IOrderRepository>();
|
||
|
||
private OrderFileManager OrderFileManager =>
|
||
LazyServiceProvider.GetRequiredService<OrderFileManager>();
|
||
|
||
private InboundRecordService InboundRecordService =>
|
||
LazyServiceProvider.GetRequiredService<InboundRecordService>();
|
||
|
||
private OutboundRecordService OutboundRecordService =>
|
||
LazyServiceProvider.GetRequiredService<OutboundRecordService>();
|
||
|
||
/// <summary>
|
||
/// 获取 Token(有效期 48 小时)。后续请求 Header:Authorization: {token}(无需 Bearer)。
|
||
/// </summary>
|
||
[AllowAnonymous]
|
||
[HttpPost("/openapi/warehouse/token")]
|
||
[Consumes("application/json")]
|
||
[Produces("application/json")]
|
||
public async Task<string> GetTokenAsync(
|
||
[FromBody] OpenApiGetTokenInput input,
|
||
CancellationToken cancellationToken = default)
|
||
{
|
||
if (input == null
|
||
|| string.IsNullOrWhiteSpace(input.Username)
|
||
|| string.IsNullOrWhiteSpace(input.Password))
|
||
{
|
||
throw new BusinessException("用户名或密码不能为空");
|
||
}
|
||
|
||
var user = await AccountManager.ValidateCredentialsAsync(
|
||
input.Username.Trim(),
|
||
input.Password,
|
||
cancellationToken);
|
||
var profileRepository = LazyServiceProvider.GetRequiredService<UserProfileRepository>();
|
||
await profileRepository.EnsureEnabledOrThrowAsync(user.Id, cancellationToken);
|
||
var claims = await JwtService.GenerateClaimsByUserIdAsync(user.Id, cancellationToken);
|
||
return TokenIssuer.Issue(claims);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 入库上传件重尺。与操作台「入库扫描」页「入库」按钮同源(POST /operation/inbound/create)。
|
||
/// 写入收货件重尺、入库记录,并触发入库计费/自动触发。重量默认 kg,长度默认 cm。
|
||
/// Header:Authorization: {token}(无需 Bearer)。
|
||
/// </summary>
|
||
[Authorize]
|
||
[HttpPost("/openapi/warehouse/inbound")]
|
||
[Consumes("application/json")]
|
||
[Produces("application/json")]
|
||
public Task<OpenApiSimpleResult> InboundAsync([FromBody] OpenApiUploadWeightInput input) =>
|
||
UploadWeightCoreAsync(input, forceMode: 0);
|
||
|
||
/// <summary>
|
||
/// 上传件重尺。mode:0=入库(同「入库」按钮),1=出库(同「出库」按钮)。
|
||
/// Header:Authorization: {token}(无需 Bearer)。
|
||
/// </summary>
|
||
[Authorize]
|
||
[HttpPost("/openapi/warehouse/weight/upload")]
|
||
[Consumes("application/json")]
|
||
[Produces("application/json")]
|
||
public Task<OpenApiSimpleResult> UploadWeightAsync([FromBody] OpenApiUploadWeightInput input) =>
|
||
UploadWeightCoreAsync(input, forceMode: null);
|
||
|
||
private async Task<OpenApiSimpleResult> UploadWeightCoreAsync(
|
||
OpenApiUploadWeightInput? input,
|
||
int? forceMode)
|
||
{
|
||
try
|
||
{
|
||
if (input == null || string.IsNullOrWhiteSpace(input.OrderNo))
|
||
{
|
||
return OpenApiSimpleResult.Fail("订单号不能为空");
|
||
}
|
||
|
||
var mode = forceMode ?? input.Mode;
|
||
var weightKg = OpenApiMeasureUnitConverter.ToKilograms(input.Weight, input.WeightUnit);
|
||
var lengthCm = OpenApiMeasureUnitConverter.ToCentimeters(input.Length, input.LengthUnit);
|
||
var widthCm = OpenApiMeasureUnitConverter.ToCentimeters(input.Width, input.LengthUnit);
|
||
var heightCm = OpenApiMeasureUnitConverter.ToCentimeters(input.Height, input.LengthUnit);
|
||
var orderNo = input.OrderNo.Trim();
|
||
|
||
if (weightKg <= 0 || lengthCm <= 0 || widthCm <= 0 || heightCm <= 0)
|
||
{
|
||
return OpenApiSimpleResult.Fail("实重与长宽高须大于 0");
|
||
}
|
||
|
||
if (mode == 1)
|
||
{
|
||
await OutboundRecordService.CreateAsync(new CreateOutboundRecordInput
|
||
{
|
||
WaybillNo = orderNo,
|
||
Weight = weightKg,
|
||
Length = lengthCm,
|
||
Width = widthCm,
|
||
Height = heightCm
|
||
});
|
||
return OpenApiSimpleResult.Ok("出库成功");
|
||
}
|
||
|
||
if (mode == 0)
|
||
{
|
||
// 与入库扫描页「入库」按钮同一实现:InboundRecordService.CreateAsync
|
||
var result = await InboundRecordService.CreateAsync(new CreateInboundRecordInput
|
||
{
|
||
WaybillNo = orderNo,
|
||
Weight = weightKg,
|
||
Length = lengthCm,
|
||
Width = widthCm,
|
||
Height = heightCm
|
||
});
|
||
var statusName = string.IsNullOrWhiteSpace(result.OrderStatusName)
|
||
? "入库成功"
|
||
: $"入库成功({result.OrderStatusName})";
|
||
return OpenApiSimpleResult.Ok(statusName);
|
||
}
|
||
|
||
return OpenApiSimpleResult.Fail("mode 仅支持 0(入库)或 1(出库)");
|
||
}
|
||
catch (BusinessException ex)
|
||
{
|
||
return OpenApiSimpleResult.Fail(ex.Message);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return OpenApiSimpleResult.Fail(
|
||
string.IsNullOrWhiteSpace(ex.Message) ? "系统异常" : ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 上传红外图片。Base64 解码后转存 COS,写入订单附件 fileType=infraredImage(Source=upload),
|
||
/// 与订单详情「附件」Tab →「红外图片」→「点击上传」(POST /order/files/upload)同源展示。
|
||
/// 成功无响应体。Header:Authorization: {token}(无需 Bearer)。
|
||
/// </summary>
|
||
[Authorize]
|
||
[HttpPost("/openapi/warehouse/image/upload")]
|
||
[Consumes("application/json")]
|
||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||
public async Task<IActionResult> UploadImageAsync(
|
||
[FromBody] OpenApiUploadImageInput input,
|
||
CancellationToken cancellationToken = default)
|
||
{
|
||
if (input == null || string.IsNullOrWhiteSpace(input.OrderNo))
|
||
{
|
||
throw new BusinessException("订单号不能为空");
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(input.ImageBase64))
|
||
{
|
||
throw new BusinessException("图片内容不能为空");
|
||
}
|
||
|
||
var order = await OrderRepository.GetByWaybillNoAsync(input.OrderNo.Trim(), cancellationToken)
|
||
?? throw new BusinessException("未找到对应订单,请检查单号");
|
||
|
||
var operatorUserId = CurrentUser.IsAuthenticated ? CurrentUser.GetId<int>() : 0;
|
||
// 固定红外图片类型,路径 uploads/order-files/{orderId}/infraredImage/...
|
||
await OrderFileManager.AddFromBase64Async(
|
||
order.Id,
|
||
OrderFileTypeCodes.InfraredImage,
|
||
input.ImageBase64,
|
||
operatorUserId,
|
||
cancellationToken);
|
||
|
||
return new NoContentResult();
|
||
}
|
||
}
|