From d952942627d265dea2e145fe8933bf51f796c273 Mon Sep 17 00:00:00 2001 From: erio Date: Mon, 2 Mar 2026 20:40:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AE=A2=E5=8D=95=E6=9D=A5=E6=BA=90?= =?UTF-8?q?=E8=BF=BD=E8=B8=AA=EF=BC=8C=E4=BF=9D=E5=AD=98=20src=5Fhost=20/?= =?UTF-8?q?=20src=5Furl=20=E5=88=B0=E8=AE=A2=E5=8D=95=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iframe 嵌入充值页面时 URL 自动附带来源参数,写入数据库用于追踪分析。 Co-Authored-By: Claude Opus 4.6 --- .../20260302000000_add_order_source_tracking/migration.sql | 3 +++ prisma/schema.prisma | 2 ++ src/app/admin/page.tsx | 2 ++ src/app/api/orders/route.ts | 6 +++++- src/app/pay/page.tsx | 4 ++++ src/components/admin/OrderDetail.tsx | 4 ++++ src/lib/order/service.ts | 4 ++++ 7 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 prisma/migrations/20260302000000_add_order_source_tracking/migration.sql diff --git a/prisma/migrations/20260302000000_add_order_source_tracking/migration.sql b/prisma/migrations/20260302000000_add_order_source_tracking/migration.sql new file mode 100644 index 0000000..ba277b6 --- /dev/null +++ b/prisma/migrations/20260302000000_add_order_source_tracking/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "orders" ADD COLUMN "src_host" TEXT, +ADD COLUMN "src_url" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5eb32df..03b2d33 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -34,6 +34,8 @@ model Order { createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") clientIp String? @map("client_ip") + srcHost String? @map("src_host") + srcUrl String? @map("src_url") auditLogs AuditLog[] diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 349ed35..724d2cf 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -31,6 +31,8 @@ interface AdminOrderDetail extends AdminOrder { failedAt: string | null; updatedAt: string; clientIp: string | null; + srcHost: string | null; + srcUrl: string | null; paymentSuccess?: boolean; rechargeSuccess?: boolean; rechargeStatus?: string; diff --git a/src/app/api/orders/route.ts b/src/app/api/orders/route.ts index 7b40b62..4150c9f 100644 --- a/src/app/api/orders/route.ts +++ b/src/app/api/orders/route.ts @@ -7,6 +7,8 @@ const createOrderSchema = z.object({ user_id: z.number().int().positive(), amount: z.number().positive(), payment_type: z.enum(['alipay', 'wxpay', 'stripe']), + src_host: z.string().optional(), + src_url: z.string().optional(), }); export async function POST(request: NextRequest) { @@ -19,7 +21,7 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: '参数错误', details: parsed.error.flatten().fieldErrors }, { status: 400 }); } - const { user_id, amount, payment_type } = parsed.data; + const { user_id, amount, payment_type, src_host, src_url } = parsed.data; // Validate amount range if (amount < env.MIN_RECHARGE_AMOUNT || amount > env.MAX_RECHARGE_AMOUNT) { @@ -42,6 +44,8 @@ export async function POST(request: NextRequest) { amount, paymentType: payment_type, clientIp, + srcHost: src_host, + srcUrl: src_url, }); // 不向客户端暴露 userName / userBalance 等隐私字段 diff --git a/src/app/pay/page.tsx b/src/app/pay/page.tsx index e39bf1f..2c35dbb 100644 --- a/src/app/pay/page.tsx +++ b/src/app/pay/page.tsx @@ -38,6 +38,8 @@ function PayContent() { const theme = searchParams.get('theme') === 'dark' ? 'dark' : 'light'; const uiMode = searchParams.get('ui_mode') || 'standalone'; const tab = searchParams.get('tab'); + const srcHost = searchParams.get('src_host') || undefined; + const srcUrl = searchParams.get('src_url') || undefined; const isDark = theme === 'dark'; const [isIframeContext, setIsIframeContext] = useState(true); @@ -230,6 +232,8 @@ function PayContent() { user_id: effectiveUserId, amount, payment_type: paymentType, + src_host: srcHost, + src_url: srcUrl, }), }); diff --git a/src/components/admin/OrderDetail.tsx b/src/components/admin/OrderDetail.tsx index 18d8d4b..60eed16 100644 --- a/src/components/admin/OrderDetail.tsx +++ b/src/components/admin/OrderDetail.tsx @@ -31,6 +31,8 @@ interface OrderDetailProps { createdAt: string; updatedAt: string; clientIp: string | null; + srcHost: string | null; + srcUrl: string | null; paymentSuccess?: boolean; rechargeSuccess?: boolean; rechargeStatus?: string; @@ -54,6 +56,8 @@ export default function OrderDetail({ order, onClose }: OrderDetailProps) { { label: '充值码', value: order.rechargeCode }, { label: '支付单号', value: order.paymentTradeNo || '-' }, { label: '客户端IP', value: order.clientIp || '-' }, + { label: '来源域名', value: order.srcHost || '-' }, + { label: '来源页面', value: order.srcUrl || '-' }, { label: '创建时间', value: new Date(order.createdAt).toLocaleString('zh-CN') }, { label: '过期时间', value: new Date(order.expiresAt).toLocaleString('zh-CN') }, { label: '支付时间', value: order.paidAt ? new Date(order.paidAt).toLocaleString('zh-CN') : '-' }, diff --git a/src/lib/order/service.ts b/src/lib/order/service.ts index 91a74a9..8613bab 100644 --- a/src/lib/order/service.ts +++ b/src/lib/order/service.ts @@ -15,6 +15,8 @@ export interface CreateOrderInput { amount: number; paymentType: PaymentType; clientIp: string; + srcHost?: string; + srcUrl?: string; } export interface CreateOrderResult { @@ -106,6 +108,8 @@ export async function createOrder(input: CreateOrderInput): Promise