feat: add mini ordering catalog and order APIs
All checks were successful
Build and Deploy MiniApi / build-and-deploy (push) Successful in 23s
All checks were successful
Build and Deploy MiniApi / build-and-deploy (push) Successful in 23s
This commit is contained in:
113
src/Api/TakeoutSaaS.MiniApi/Controllers/CatalogController.cs
Normal file
113
src/Api/TakeoutSaaS.MiniApi/Controllers/CatalogController.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TakeoutSaaS.Application.App.Mini;
|
||||
using TakeoutSaaS.Application.App.Mini.Contracts;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
using TakeoutSaaS.Shared.Web.Api;
|
||||
|
||||
namespace TakeoutSaaS.MiniApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 小程序商品与门店查询接口。
|
||||
/// </summary>
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/mini/v{version:apiVersion}")]
|
||||
public sealed class CatalogController(IMiniAppService miniAppService, ITenantProvider tenantProvider) : BaseApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前租户下可用门店列表。
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">请求取消令牌。</param>
|
||||
/// <returns>门店摘要列表。</returns>
|
||||
[HttpGet("stores")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<MiniStoreSummaryDto>>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<IReadOnlyList<MiniStoreSummaryDto>>> GetStoresAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var data = await miniAppService.GetStoresAsync(tenantProvider.GetCurrentTenantId(), cancellationToken);
|
||||
return ApiResponse<IReadOnlyList<MiniStoreSummaryDto>>.Ok(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定门店在当前履约场景下的商品分类。
|
||||
/// </summary>
|
||||
/// <param name="storeId">门店编号。</param>
|
||||
/// <param name="scene">履约场景。</param>
|
||||
/// <param name="channel">访问渠道。</param>
|
||||
/// <param name="cancellationToken">请求取消令牌。</param>
|
||||
/// <returns>分类列表。</returns>
|
||||
[HttpGet("categories")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<MiniCategoryDto>>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<IReadOnlyList<MiniCategoryDto>>> GetCategoriesAsync([FromQuery] string storeId, [FromQuery] string scene, [FromQuery] string channel, CancellationToken cancellationToken)
|
||||
{
|
||||
var data = await miniAppService.GetCategoriesAsync(tenantProvider.GetCurrentTenantId(), storeId, scene, channel, cancellationToken);
|
||||
return ApiResponse<IReadOnlyList<MiniCategoryDto>>.Ok(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定门店的菜单分组与商品列表。
|
||||
/// </summary>
|
||||
/// <param name="storeId">门店编号。</param>
|
||||
/// <param name="scene">履约场景。</param>
|
||||
/// <param name="channel">访问渠道。</param>
|
||||
/// <param name="cancellationToken">请求取消令牌。</param>
|
||||
/// <returns>菜单分组列表。</returns>
|
||||
[HttpGet("menus/{storeId}")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<MiniMenuSectionDto>>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<IReadOnlyList<MiniMenuSectionDto>>> GetMenuAsync([FromRoute] string storeId, [FromQuery] string scene, [FromQuery] string channel, CancellationToken cancellationToken)
|
||||
{
|
||||
var data = await miniAppService.GetMenuAsync(tenantProvider.GetCurrentTenantId(), storeId, scene, channel, cancellationToken);
|
||||
return ApiResponse<IReadOnlyList<MiniMenuSectionDto>>.Ok(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取商品详情与可选规格信息。
|
||||
/// </summary>
|
||||
/// <param name="productId">商品编号。</param>
|
||||
/// <param name="scene">履约场景。</param>
|
||||
/// <param name="channel">访问渠道。</param>
|
||||
/// <param name="cancellationToken">请求取消令牌。</param>
|
||||
/// <returns>商品详情。</returns>
|
||||
[HttpGet("products/{productId}")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(ApiResponse<MiniProductDetailDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<MiniProductDetailDto>> GetProductDetailAsync([FromRoute] string productId, [FromQuery] string scene, [FromQuery] string channel, CancellationToken cancellationToken)
|
||||
{
|
||||
var data = await miniAppService.GetProductDetailAsync(tenantProvider.GetCurrentTenantId(), productId, scene, channel, cancellationToken);
|
||||
return data == null ? ApiResponse<MiniProductDetailDto>.Error(ErrorCodes.NotFound, "商品不存在") : ApiResponse<MiniProductDetailDto>.Ok(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 试算购物车金额。
|
||||
/// </summary>
|
||||
/// <param name="request">试算请求。</param>
|
||||
/// <param name="cancellationToken">请求取消令牌。</param>
|
||||
/// <returns>金额试算结果。</returns>
|
||||
[HttpPost("products/price-estimate")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(ApiResponse<MiniPriceEstimateResponse>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<MiniPriceEstimateResponse>> EstimatePriceAsync([FromBody] MiniPriceEstimateRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var data = await miniAppService.EstimatePriceAsync(tenantProvider.GetCurrentTenantId(), request, cancellationToken);
|
||||
return ApiResponse<MiniPriceEstimateResponse>.Ok(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下单前校验商品与价格是否有效。
|
||||
/// </summary>
|
||||
/// <param name="request">结算校验请求。</param>
|
||||
/// <param name="cancellationToken">请求取消令牌。</param>
|
||||
/// <returns>结算校验结果。</returns>
|
||||
[HttpPost("products/checkout-validate")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(ApiResponse<MiniCheckoutValidationResponse>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<MiniCheckoutValidationResponse>> CheckoutValidateAsync([FromBody] MiniCheckoutValidationRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var data = await miniAppService.ValidateCheckoutAsync(tenantProvider.GetCurrentTenantId(), request, cancellationToken);
|
||||
return ApiResponse<MiniCheckoutValidationResponse>.Ok(data);
|
||||
}
|
||||
}
|
||||
87
src/Api/TakeoutSaaS.MiniApi/Controllers/OrdersController.cs
Normal file
87
src/Api/TakeoutSaaS.MiniApi/Controllers/OrdersController.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TakeoutSaaS.Application.App.Mini;
|
||||
using TakeoutSaaS.Application.App.Mini.Contracts;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
using TakeoutSaaS.Shared.Web.Api;
|
||||
|
||||
namespace TakeoutSaaS.MiniApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 小程序订单接口。
|
||||
/// </summary>
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/mini/v{version:apiVersion}/orders")]
|
||||
public sealed class OrdersController(IMiniAppService miniAppService, ITenantProvider tenantProvider) : BaseApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前顾客的订单列表。
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">请求取消令牌。</param>
|
||||
/// <returns>订单摘要列表。</returns>
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<MiniOrderSummaryDto>>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<IReadOnlyList<MiniOrderSummaryDto>>> GetOrdersAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var data = await miniAppService.GetOrdersAsync(tenantProvider.GetCurrentTenantId(), ResolveCustomerPhone(), cancellationToken);
|
||||
return ApiResponse<IReadOnlyList<MiniOrderSummaryDto>>.Ok(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定订单详情。
|
||||
/// </summary>
|
||||
/// <param name="orderId">订单编号。</param>
|
||||
/// <param name="cancellationToken">请求取消令牌。</param>
|
||||
/// <returns>订单详情。</returns>
|
||||
[HttpGet("{orderId}")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(ApiResponse<MiniOrderDetailDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<MiniOrderDetailDto>> GetOrderDetailAsync([FromRoute] string orderId, CancellationToken cancellationToken)
|
||||
{
|
||||
var data = await miniAppService.GetOrderDetailAsync(tenantProvider.GetCurrentTenantId(), orderId, ResolveCustomerPhone(), cancellationToken);
|
||||
return data == null ? ApiResponse<MiniOrderDetailDto>.Error(ErrorCodes.NotFound, "订单不存在") : ApiResponse<MiniOrderDetailDto>.Ok(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建订单。
|
||||
/// </summary>
|
||||
/// <param name="request">下单请求。</param>
|
||||
/// <param name="cancellationToken">请求取消令牌。</param>
|
||||
/// <returns>创建结果。</returns>
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(ApiResponse<MiniCreateOrderResponse>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<MiniCreateOrderResponse>> CreateOrderAsync([FromBody] MiniCreateOrderRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var data = await miniAppService.CreateOrderAsync(tenantProvider.GetCurrentTenantId(), ResolveCustomerName(), ResolveCustomerPhone(), request, cancellationToken);
|
||||
return ApiResponse<MiniCreateOrderResponse>.Ok(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模拟支付指定订单。
|
||||
/// </summary>
|
||||
/// <param name="orderId">订单编号。</param>
|
||||
/// <param name="cancellationToken">请求取消令牌。</param>
|
||||
/// <returns>模拟支付结果。</returns>
|
||||
[HttpPost("{orderId}/mock-pay")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(ApiResponse<MiniMockPayResponse>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<MiniMockPayResponse>> MockPayAsync([FromRoute] string orderId, CancellationToken cancellationToken)
|
||||
{
|
||||
var data = await miniAppService.MockPayAsync(tenantProvider.GetCurrentTenantId(), orderId, ResolveCustomerPhone(), cancellationToken);
|
||||
return ApiResponse<MiniMockPayResponse>.Ok(data);
|
||||
}
|
||||
|
||||
private string ResolveCustomerPhone()
|
||||
{
|
||||
return Request.Headers.TryGetValue("X-Mini-Customer-Phone", out var phone) ? phone.FirstOrDefault()?.Trim() ?? string.Empty : string.Empty;
|
||||
}
|
||||
|
||||
private string ResolveCustomerName()
|
||||
{
|
||||
return Request.Headers.TryGetValue("X-Mini-Customer-Name", out var name) ? name.FirstOrDefault()?.Trim() ?? string.Empty : string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,17 @@
|
||||
{
|
||||
"Cors": {
|
||||
"Mini": []
|
||||
"Database": {
|
||||
"DataSources": {
|
||||
"AppDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
"MaxRetryDelaySeconds": 5
|
||||
}
|
||||
}
|
||||
},
|
||||
"Otel": {
|
||||
"Endpoint": "",
|
||||
"UseConsoleExporter": true
|
||||
}
|
||||
"Cors": { "Mini": [] },
|
||||
"Otel": { "Endpoint": "", "UseConsoleExporter": true }
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
{
|
||||
"Cors": {
|
||||
"Mini": []
|
||||
"Database": {
|
||||
"DataSources": {
|
||||
"AppDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
"MaxRetryDelaySeconds": 5
|
||||
}
|
||||
}
|
||||
},
|
||||
"Otel": {
|
||||
"Endpoint": "",
|
||||
"UseConsoleExporter": false
|
||||
}
|
||||
"Cors": { "Mini": [] },
|
||||
"Otel": { "Endpoint": "", "UseConsoleExporter": false }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user