refactor(errors): migrate decisions to {code,msg}

This commit is contained in:
zetaloop
2026-02-28 07:21:51 +08:00
parent 4e2ee5be54
commit cc24a0cbc3
23 changed files with 157 additions and 165 deletions
+18 -24
View File
@@ -1,14 +1,14 @@
import type { Actor } from "@/lib/actor"
import {
ORDER_ACCEPT_TIMEOUT_MS,
ORDER_CLOSE_TIMEOUT_MS,
ORDER_REVIEW_TIMEOUT_MS,
} from "@/lib/config/demo-timers"
import { allow, deny } from "@/lib/decision"
import { evaluateOrderTransition, type OrderAction } from "@/lib/domain/order-machine"
import type { ApiDecision } from "@/lib/errors"
import { generateId } from "@/lib/id"
import { mockOrders } from "@/lib/mock"
import type { Actor } from "@/lib/policy/actor"
import { allow, deny } from "@/lib/policy/assert"
import type { PolicyDecision } from "@/lib/policy/decision"
import type { Order, OrderStatus, PlayerService } from "@/lib/types"
import { useAuthStore } from "@/store/auth"
import { useChatStore } from "@/store/chat"
@@ -35,7 +35,7 @@ interface CreatePaidOrderInput extends CreateOrderInput {
}
interface OrderMutationResult {
decision: PolicyDecision
decision: ApiDecision
order?: Order
}
@@ -73,41 +73,35 @@ function isOrderOwnerActor(order: Order, actor: Actor) {
return actor.role === "owner" && Boolean(order.shopId) && actor.shopId === order.shopId
}
function validateActorForAction(order: Order, action: OrderAction, actor?: Actor): PolicyDecision {
function validateActorForAction(order: Order, action: OrderAction, actor?: Actor): ApiDecision {
if (action.startsWith("AUTO_TIMEOUT_") || (action === "RESOLVE_DISPUTE" && !actor?.userId)) {
return allow()
}
if (!actor?.userId) {
return deny("AUTH_REQUIRED", "请先登录")
return deny(401, "请先登录")
}
if (order.status === "disputed" && action !== "RESOLVE_DISPUTE") {
return deny("DISPUTE_LOCKED", "争议处理中,暂不可执行此操作")
return deny(400, "争议处理中,暂不可执行此操作")
}
if (action === "PAY" || action === "CANCEL_PRE_ACCEPT" || action === "CONFIRM_CLOSE") {
return order.consumerId === actor.userId
? allow()
: deny("NOT_PARTICIPANT", "仅下单客户可执行该操作")
return order.consumerId === actor.userId ? allow() : deny(403, "仅下单客户可执行该操作")
}
if (action === "ACCEPT") {
return order.playerId === actor.userId || isOrderOwnerActor(order, actor)
? allow()
: deny("NOT_PARTICIPANT", "仅该订单打手或所属店主可执行接单")
: deny(403, "仅该订单打手或所属店主可执行接单")
}
if (action === "REQUEST_CLOSE" || action === "OPEN_DISPUTE" || action === "SUBMIT_REVIEW") {
return isParticipant(order, actor.userId)
? allow()
: deny("NOT_PARTICIPANT", "仅订单参与方可执行该操作")
return isParticipant(order, actor.userId) ? allow() : deny(403, "仅订单参与方可执行该操作")
}
if (action === "RESOLVE_DISPUTE") {
return isOrderOwnerActor(order, actor)
? allow()
: deny("ROLE_FORBIDDEN", "仅订单所属店主可执行该操作")
return isOrderOwnerActor(order, actor) ? allow() : deny(403, "仅订单所属店主可执行该操作")
}
return allow()
@@ -261,7 +255,7 @@ export const useOrderStore = create<OrderState>((set, get) => {
actor?: Actor,
): OrderMutationResult => {
const order = get().orders.find((item) => item.id === orderId)
if (!order) return { decision: deny("NOT_FOUND", "订单不存在") }
if (!order) return { decision: deny(404, "订单不存在") }
const actorDecision = validateActorForAction(order, action, actor)
if (!actorDecision.ok) return { decision: actorDecision }
@@ -285,7 +279,7 @@ export const useOrderStore = create<OrderState>((set, get) => {
}))
if (!updatedOrder || !previousOrder) {
return { decision: deny("NOT_FOUND", "订单不存在") }
return { decision: deny(404, "订单不存在") }
}
if (previousOrder.status !== "completed" && updatedOrder.status === "completed") {
@@ -339,19 +333,19 @@ export const useOrderStore = create<OrderState>((set, get) => {
},
createPaidOrder: (input, actor) => {
if (actor.role !== "consumer" || actor.userId !== input.consumerId) {
return { decision: deny("ROLE_FORBIDDEN", "仅客户可下单支付") }
return { decision: deny(403, "仅客户可下单支付") }
}
const dedupeKey = `${input.consumerId}-${input.service.id}`
if (pendingCreations.has(dedupeKey)) {
return { decision: deny("DUPLICATE_REQUEST", "订单正在创建中,请勿重复提交") }
return { decision: deny(400, "订单正在创建中,请勿重复提交") }
}
pendingCreations.add(dedupeKey)
const orderId = generateId("ord")
const paid = useWalletStore.getState().deductBalance(orderId, input.totalPrice)
if (!paid) {
pendingCreations.delete(dedupeKey)
return { decision: deny("PAYMENT_FAILED", "余额不足或订单已支付") }
return { decision: deny(400, "余额不足或订单已支付") }
}
const order: Order = {
@@ -380,9 +374,9 @@ export const useOrderStore = create<OrderState>((set, get) => {
},
payOrder: (orderId, actor) => {
const order = get().orders.find((item) => item.id === orderId)
if (!order) return { decision: deny("NOT_FOUND", "订单不存在") }
if (!order) return { decision: deny(404, "订单不存在") }
const paid = useWalletStore.getState().deductBalance(orderId, order.totalPrice)
if (!paid) return { decision: deny("PAYMENT_FAILED", "余额不足或订单已支付") }
if (!paid) return { decision: deny(400, "余额不足或订单已支付") }
return applyTransition(orderId, "PAY", actor)
},
acceptOrder: (orderId, actor) => applyTransition(orderId, "ACCEPT", actor),