From 5110915aa8126a35c28e482d7aa67e5ec20ec005 Mon Sep 17 00:00:00 2001 From: hailin Date: Sun, 1 Mar 2026 21:16:28 -0800 Subject: [PATCH] =?UTF-8?q?feat(transfer):=20=E6=B7=BB=E5=8A=A0=20transfer?= =?UTF-8?q?-service=20=E5=88=9D=E5=A7=8B=E6=95=B0=E6=8D=AE=E5=BA=93=20migr?= =?UTF-8?q?ation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 基于 schema.prisma 创建初始 migration SQL,包含: - transfer_orders: 转让订单表(Saga 聚合根) - transfer_status_logs: 状态变更审计日志 - outbox_events: Outbox Pattern 事件表 - processed_events: 幂等性保证表 容器启动时 Dockerfile 会自动执行 prisma migrate deploy 建表。 Co-Authored-By: Claude Opus 4.6 --- .../20260301000000_init/migration.sql | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 backend/services/transfer-service/prisma/migrations/20260301000000_init/migration.sql diff --git a/backend/services/transfer-service/prisma/migrations/20260301000000_init/migration.sql b/backend/services/transfer-service/prisma/migrations/20260301000000_init/migration.sql new file mode 100644 index 00000000..eb9dcea3 --- /dev/null +++ b/backend/services/transfer-service/prisma/migrations/20260301000000_init/migration.sql @@ -0,0 +1,118 @@ +-- CreateTable +CREATE TABLE "transfer_orders" ( + "id" BIGSERIAL NOT NULL, + "transfer_order_no" VARCHAR(50) NOT NULL, + "seller_user_id" BIGINT NOT NULL, + "seller_account_sequence" VARCHAR(20) NOT NULL, + "buyer_user_id" BIGINT NOT NULL, + "buyer_account_sequence" VARCHAR(20) NOT NULL, + "source_order_no" VARCHAR(50) NOT NULL, + "source_adoption_id" BIGINT NOT NULL, + "tree_count" INTEGER NOT NULL, + "contribution_per_tree" DECIMAL(20,10) NOT NULL, + "original_adoption_date" DATE NOT NULL, + "original_expire_date" DATE NOT NULL, + "selected_province" VARCHAR(10) NOT NULL, + "selected_city" VARCHAR(10) NOT NULL, + "transfer_price" DECIMAL(20,8) NOT NULL, + "platform_fee_rate" DECIMAL(5,4) NOT NULL, + "platform_fee_amount" DECIMAL(20,8) NOT NULL, + "seller_receive_amount" DECIMAL(20,8) NOT NULL, + "status" VARCHAR(30) NOT NULL DEFAULT 'PENDING', + "saga_step" VARCHAR(30) NOT NULL DEFAULT 'INIT', + "fail_reason" VARCHAR(500), + "retry_count" INTEGER NOT NULL DEFAULT 0, + "seller_confirmed_at" TIMESTAMP(3), + "payment_frozen_at" TIMESTAMP(3), + "trees_locked_at" TIMESTAMP(3), + "ownership_transferred_at" TIMESTAMP(3), + "contribution_adjusted_at" TIMESTAMP(3), + "stats_updated_at" TIMESTAMP(3), + "payment_settled_at" TIMESTAMP(3), + "completed_at" TIMESTAMP(3), + "cancelled_at" TIMESTAMP(3), + "rolled_back_at" TIMESTAMP(3), + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "transfer_orders_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "transfer_status_logs" ( + "id" BIGSERIAL NOT NULL, + "transfer_order_no" VARCHAR(50) NOT NULL, + "from_status" VARCHAR(30) NOT NULL, + "to_status" VARCHAR(30) NOT NULL, + "from_saga_step" VARCHAR(30) NOT NULL, + "to_saga_step" VARCHAR(30) NOT NULL, + "operator_type" VARCHAR(20) NOT NULL, + "operator_id" VARCHAR(50), + "remark" VARCHAR(500), + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "transfer_status_logs_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "outbox_events" ( + "id" BIGSERIAL NOT NULL, + "event_type" VARCHAR(100) NOT NULL, + "topic" VARCHAR(200) NOT NULL, + "key" VARCHAR(200) NOT NULL, + "payload" JSONB NOT NULL, + "aggregate_id" VARCHAR(100) NOT NULL, + "aggregate_type" VARCHAR(100) NOT NULL, + "status" VARCHAR(20) NOT NULL DEFAULT 'PENDING', + "retry_count" INTEGER NOT NULL DEFAULT 0, + "max_retries" INTEGER NOT NULL DEFAULT 5, + "last_error" VARCHAR(1000), + "published_at" TIMESTAMP(3), + "next_retry_at" TIMESTAMP(3), + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "outbox_events_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "processed_events" ( + "id" BIGSERIAL NOT NULL, + "event_id" VARCHAR(200) NOT NULL, + "event_type" VARCHAR(100) NOT NULL, + "processed_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "processed_events_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "transfer_orders_transfer_order_no_key" ON "transfer_orders"("transfer_order_no"); + +-- CreateIndex +CREATE INDEX "transfer_orders_seller_user_id_idx" ON "transfer_orders"("seller_user_id"); + +-- CreateIndex +CREATE INDEX "transfer_orders_buyer_user_id_idx" ON "transfer_orders"("buyer_user_id"); + +-- CreateIndex +CREATE INDEX "transfer_orders_source_order_no_idx" ON "transfer_orders"("source_order_no"); + +-- CreateIndex +CREATE INDEX "transfer_orders_status_idx" ON "transfer_orders"("status"); + +-- CreateIndex +CREATE INDEX "transfer_orders_created_at_idx" ON "transfer_orders"("created_at"); + +-- CreateIndex +CREATE INDEX "transfer_status_logs_transfer_order_no_idx" ON "transfer_status_logs"("transfer_order_no"); + +-- CreateIndex +CREATE INDEX "outbox_events_status_created_at_idx" ON "outbox_events"("status", "created_at"); + +-- CreateIndex +CREATE INDEX "outbox_events_status_next_retry_at_idx" ON "outbox_events"("status", "next_retry_at"); + +-- CreateIndex +CREATE UNIQUE INDEX "processed_events_event_id_key" ON "processed_events"("event_id"); + +-- AddForeignKey +ALTER TABLE "transfer_status_logs" ADD CONSTRAINT "transfer_status_logs_transfer_order_no_fkey" FOREIGN KEY ("transfer_order_no") REFERENCES "transfer_orders"("transfer_order_no") ON DELETE RESTRICT ON UPDATE CASCADE;