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; /// /// 仓配对外开放 API(Swagger 文档分组:warehouseOpenApi)。 /// 鉴权:Authorization: {token}(账号密码换取,有效期 48 小时;无需 Bearer 前缀)。 /// 与客户 HMAC 分组 openApi 相互独立。 /// [ApiExplorerSettings(GroupName = "warehouseOpenApi")] public class WarehouseOpenApiService : ApplicationService { private AccountManager AccountManager => LazyServiceProvider.GetRequiredService(); private IJwtService JwtService => LazyServiceProvider.GetRequiredService(); private OpenApiTokenIssuer TokenIssuer => LazyServiceProvider.GetRequiredService(); private IOrderRepository OrderRepository => LazyServiceProvider.GetRequiredService(); private OrderFileManager OrderFileManager => LazyServiceProvider.GetRequiredService(); private InboundRecordService InboundRecordService => LazyServiceProvider.GetRequiredService(); private OutboundRecordService OutboundRecordService => LazyServiceProvider.GetRequiredService(); /// /// 获取 Token(有效期 48 小时)。后续请求 Header:Authorization: {token}(无需 Bearer)。 /// [AllowAnonymous] [HttpPost("/openapi/warehouse/token")] [Consumes("application/json")] [Produces("application/json")] public async Task 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(); await profileRepository.EnsureEnabledOrThrowAsync(user.Id, cancellationToken); var claims = await JwtService.GenerateClaimsByUserIdAsync(user.Id, cancellationToken); return TokenIssuer.Issue(claims); } /// /// 入库上传件重尺。与操作台「入库扫描」页「入库」按钮同源(POST /operation/inbound/create)。 /// 写入收货件重尺、入库记录,并触发入库计费/自动触发。重量默认 kg,长度默认 cm。 /// Header:Authorization: {token}(无需 Bearer)。 /// [Authorize] [HttpPost("/openapi/warehouse/inbound")] [Consumes("application/json")] [Produces("application/json")] public Task InboundAsync([FromBody] OpenApiUploadWeightInput input) => UploadWeightCoreAsync(input, forceMode: 0); /// /// 上传件重尺。mode:0=入库(同「入库」按钮),1=出库(同「出库」按钮)。 /// Header:Authorization: {token}(无需 Bearer)。 /// [Authorize] [HttpPost("/openapi/warehouse/weight/upload")] [Consumes("application/json")] [Produces("application/json")] public Task UploadWeightAsync([FromBody] OpenApiUploadWeightInput input) => UploadWeightCoreAsync(input, forceMode: null); private async Task 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); } } /// /// 上传红外图片。Base64 解码后转存 COS,写入订单附件 fileType=infraredImage(Source=upload), /// 与订单详情「附件」Tab →「红外图片」→「点击上传」(POST /order/files/upload)同源展示。 /// 成功无响应体。Header:Authorization: {token}(无需 Bearer)。 /// [Authorize] [HttpPost("/openapi/warehouse/image/upload")] [Consumes("application/json")] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task 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() : 0; // 固定红外图片类型,路径 uploads/order-files/{orderId}/infraredImage/... await OrderFileManager.AddFromBase64Async( order.Id, OrderFileTypeCodes.InfraredImage, input.ImageBase64, operatorUserId, cancellationToken); return new NoContentResult(); } }