暂存提交

This commit is contained in:
2026-07-31 16:53:07 +08:00
parent 45358ef40f
commit 704cb15ccf
98 changed files with 1944 additions and 134 deletions
@@ -22,6 +22,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@@ -40,6 +41,71 @@ public class PlatformFirstLegOrderController {
@Resource
private OrderApplicationService service;
@BusinessAudit(event = "ORDER_CREATE", resourceType = "FIRST_LEG_ORDER", description = "平台代客创建草稿")
@Operation(summary = "平台代客创建草稿", description = "在明确指定的目标租户内创建头程订单草稿,必须携带幂等键。")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "创建成功或幂等重放成功"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "请求参数或幂等键无效"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "无平台创建权限"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "409", description = "幂等键冲突或请求处理中"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "503", description = "主数据下游服务不可用")
})
@PostMapping
@PreAuthorize("hasAuthority('platform:order:create')")
public ApiResponse<Long> create(
@AuthenticationPrincipal AuthenticatedUser user,
@Parameter(description = "目标租户 ID", required = true) @RequestParam long tenantId,
@Parameter(description = "创建草稿幂等键,同键同请求返回首次结果", required = true)
@RequestHeader("Idempotency-Key") String idempotencyKey,
@Valid @RequestBody OrderRequests.SaveRequest request) {
long scopedTenant = OrderScopeGuard.platform(user, tenantId);
return ApiResponse.success(service.createDraft(scopedTenant, user.userId(), request, idempotencyKey));
}
@BusinessAudit(event = "ORDER_UPDATE", resourceType = "FIRST_LEG_ORDER", resourceId = "#id", description = "平台编辑草稿")
@Operation(summary = "平台编辑草稿", description = "在明确指定的目标租户内编辑草稿或驳回订单。")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "编辑成功"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "无平台编辑权限"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "订单不存在"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "409", description = "订单当前不可编辑"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "503", description = "主数据下游服务不可用")
})
@PutMapping("/{id}")
@PreAuthorize("hasAuthority('platform:order:edit')")
public ApiResponse<Void> updateDraft(
@AuthenticationPrincipal AuthenticatedUser user,
@Parameter(description = "目标租户 ID", required = true) @RequestParam long tenantId,
@Parameter(description = "订单 ID", required = true) @PathVariable long id,
@Valid @RequestBody OrderRequests.SaveRequest request) {
long scopedTenant = OrderScopeGuard.platform(user, tenantId);
service.updateDraft(scopedTenant, user.userId(), id, request);
return ApiResponse.success(null);
}
@BusinessAudit(event = "ORDER_SUBMIT", resourceType = "FIRST_LEG_ORDER", resourceId = "#id", description = "平台代客提交订单")
@Operation(summary = "平台代客提交订单", description = "在明确指定的目标租户内提交草稿订单,必须携带幂等键。")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "提交成功或幂等重放成功"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "请求参数或幂等键无效"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "无平台提交权限"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "订单不存在"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "409", description = "幂等键冲突或订单状态不可提交"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "503", description = "主数据下游服务不可用")
})
@PostMapping("/{id}/submit")
@PreAuthorize("hasAuthority('platform:order:submit')")
public ApiResponse<Void> submit(
@AuthenticationPrincipal AuthenticatedUser user,
@Parameter(description = "目标租户 ID", required = true) @RequestParam long tenantId,
@Parameter(description = "订单 ID", required = true) @PathVariable long id,
@Parameter(description = "提交订单幂等键,同键同请求返回首次结果", required = true)
@RequestHeader("Idempotency-Key") String idempotencyKey) {
long scopedTenant = OrderScopeGuard.platform(user, tenantId);
service.submit(scopedTenant, user.userId(), user.username(), id, idempotencyKey);
return ApiResponse.success(null);
}
@Operation(summary = "Tenant order query / 租户订单查询", description = "Queries a page of first-leg orders in the explicit target tenant. / 分页查询明确目标租户下的头程订单。")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Query succeeded / 查询成功"),
@@ -25,6 +25,11 @@ public class OrderEligibilityGuard {
}
} catch (FeignException exception) {
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "IAM eligibility service is unavailable");
} catch (BizException exception) {
throw exception;
} catch (RuntimeException exception) {
// Feign 解码或负载契约漂移不能泄露为通用 500,统一转换为可追踪的下游不可用。
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "IAM 下单资格响应无效");
}
}
}
+4
View File
@@ -22,6 +22,8 @@ CREATE TABLE IF NOT EXISTS ord_order (
pickup_method VARCHAR(24) NULL COMMENT '取货方式',
receiving_warehouse_id BIGINT UNSIGNED NULL COMMENT '国内收货仓ID',
receiving_warehouse_name VARCHAR(128) NULL COMMENT '国内收货仓快照',
receiving_warehouse_address VARCHAR(512) NULL COMMENT '国内收货仓完整地址快照',
receiving_time VARCHAR(128) NULL COMMENT '国内收货仓收货时间快照',
channel_code VARCHAR(64) NULL COMMENT '渠道编码',
channel_name VARCHAR(128) NULL COMMENT '渠道名称快照',
tax_mode VARCHAR(24) NULL COMMENT '买单或退税',
@@ -31,6 +33,8 @@ CREATE TABLE IF NOT EXISTS ord_order (
amazon_warehouse_code VARCHAR(32) NULL COMMENT '亚马逊仓库编码',
shipment_id VARCHAR(64) NULL COMMENT 'Amazon Shipment ID',
reference_id VARCHAR(64) NULL COMMENT 'Amazon Reference ID',
express_company VARCHAR(64) NULL COMMENT '履约快递公司名称快照',
express_tracking_no VARCHAR(128) NULL COMMENT '履约快递单号',
submitted_at DATETIME(3) NULL COMMENT '提交时间',
completed_at DATETIME(3) NULL COMMENT '完成时间',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
@@ -28,6 +28,8 @@ DELIMITER ;
CALL phase1_add_column('ord_order', 'pickup_method', "VARCHAR(24) NULL COMMENT 'Pickup method'");
CALL phase1_add_column('ord_order', 'receiving_warehouse_id', "BIGINT UNSIGNED NULL COMMENT 'Domestic receiving warehouse ID'");
CALL phase1_add_column('ord_order', 'receiving_warehouse_name', "VARCHAR(128) NULL COMMENT 'Domestic receiving warehouse snapshot'");
CALL phase1_add_column('ord_order', 'receiving_warehouse_address', "VARCHAR(512) NULL COMMENT '国内收货仓完整地址快照'");
CALL phase1_add_column('ord_order', 'receiving_time', "VARCHAR(128) NULL COMMENT '国内收货仓收货时间快照'");
CALL phase1_add_column('ord_order', 'channel_code', "VARCHAR(64) NULL COMMENT 'Shipping channel code'");
CALL phase1_add_column('ord_order', 'channel_name', "VARCHAR(128) NULL COMMENT 'Shipping channel snapshot'");
CALL phase1_add_column('ord_order', 'tax_mode', "VARCHAR(24) NULL COMMENT 'BUY_DECLARATION or TAX_REFUND'");
@@ -37,6 +39,8 @@ CALL phase1_add_column('ord_order', 'destination_type', "VARCHAR(24) NULL COMMEN
CALL phase1_add_column('ord_order', 'amazon_warehouse_code', "VARCHAR(32) NULL COMMENT 'Amazon warehouse code'");
CALL phase1_add_column('ord_order', 'shipment_id', "VARCHAR(64) NULL COMMENT 'Amazon Shipment ID'");
CALL phase1_add_column('ord_order', 'reference_id', "VARCHAR(64) NULL COMMENT 'Amazon Reference ID'");
CALL phase1_add_column('ord_order', 'express_company', "VARCHAR(64) NULL COMMENT '履约快递公司名称快照'");
CALL phase1_add_column('ord_order', 'express_tracking_no', "VARCHAR(128) NULL COMMENT '履约快递单号'");
CALL phase1_add_column('ord_order_cargo', 'sku_no', "VARCHAR(64) NULL COMMENT 'SKU number'");
CALL phase1_add_column('ord_order_cargo', 'upc_no', "VARCHAR(64) NULL COMMENT 'UPC number'");
@@ -52,3 +56,22 @@ CALL phase1_add_column('ord_order_cargo', 'model', "VARCHAR(64) NULL COMMENT 'Mo
CALL phase1_add_column('ord_order_cargo', 'brand', "VARCHAR(64) NULL COMMENT 'Brand'");
DROP PROCEDURE phase1_add_column;
-- 已有订单库的幂等记录迁移,和订单写入处于同一个本地事务。
CREATE TABLE IF NOT EXISTS ord_idempotency_record (
id BIGINT UNSIGNED NOT NULL COMMENT '幂等记录 ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '租户 ID',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人 ID',
operation VARCHAR(32) NOT NULL COMMENT '操作类型',
resource_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '资源 ID',
idempotency_key VARCHAR(128) NOT NULL COMMENT '客户端幂等键',
request_hash CHAR(64) NOT NULL COMMENT '请求内容摘要',
status VARCHAR(16) NOT NULL COMMENT '处理状态',
result_order_id BIGINT UNSIGNED NULL COMMENT '重放结果订单 ID',
completed_at DATETIME(3) NULL COMMENT '完成时间',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_order_idempotency_scope (tenant_id, operator_id, operation, idempotency_key),
KEY idx_order_idempotency_created (created_at)
) ENGINE=InnoDB COMMENT='订单写接口持久化幂等记录';
@@ -42,6 +42,62 @@ paths:
description: 幂等键冲突、请求处理中或订单状态冲突
'503':
description: 仓库或往来单位下游服务异常
/api/platform/v1/orders/first-leg:
post:
summary: 平台代客创建订单草稿
parameters:
- $ref: '#/components/parameters/TenantId'
- $ref: '#/components/parameters/IdempotencyKey'
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
'200':
description: 创建成功或同请求重放成功
'403':
description: 需要 platform:order:create 权限
'409':
description: 幂等键冲突或请求仍在处理
/api/platform/v1/orders/first-leg/{id}/submit:
post:
summary: 平台代客提交指定订单
parameters:
- $ref: '#/components/parameters/OrderId'
- $ref: '#/components/parameters/TenantId'
- $ref: '#/components/parameters/IdempotencyKey'
responses:
'200':
description: 提交成功或同请求重放成功
'403':
description: 需要 platform:order:submit 权限
'404':
description: 订单不存在
'409':
description: 幂等键冲突或当前订单不可提交
/api/platform/v1/orders/first-leg/{id}:
put:
summary: 平台编辑指定订单草稿
parameters:
- $ref: '#/components/parameters/OrderId'
- $ref: '#/components/parameters/TenantId'
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
'200':
description: 编辑成功
'403':
description: 需要 platform:order:edit 权限
'404':
description: 订单不存在
'409':
description: 当前订单不可编辑
components:
securitySchemes:
bearerAuth:
@@ -49,6 +105,13 @@ components:
scheme: bearer
bearerFormat: JWT
parameters:
TenantId:
name: tenantId
in: query
required: true
schema:
type: integer
format: int64
OrderId:
name: id
in: path