import { NextRequest, NextResponse } from "next/server"; import { createLogger } from "@/lib/observability/logger"; import type { ProductPlanningRowsResponse, TaobaoReviewRow } from "@/lib/product-planning/types"; import { getResolvedProductPlanningApiConfig } from "@/lib/product-planning/server-config"; import { isProductPlanningEnabled, PRODUCT_PLANNING_DISABLED_MESSAGE, } from "@/lib/product-planning/server-feature"; import { getProductPlanningToken } from "@/lib/product-planning/server-auth"; import { ensureHttpsUrl } from "@/lib/product-planning/utils"; export const runtime = "nodejs"; const logger = createLogger("product-planning-taobao-reviews-api"); function normalizeMediaUrl(value: unknown) { if (typeof value === "string") { return ensureHttpsUrl(value); } if (value && typeof value === "object") { const candidate = (value as Record).url; if (typeof candidate === "string") { return ensureHttpsUrl(candidate); } } return ""; } function jsonError(status: number, error: string) { return NextResponse.json({ success: false, error }, { status }); } export async function GET(request: NextRequest) { const token = await getProductPlanningToken(request); if (!token?.sub) { return jsonError(401, "未登录"); } if (!(await isProductPlanningEnabled())) { return jsonError(403, PRODUCT_PLANNING_DISABLED_MESSAGE); } const itemId = String(request.nextUrl.searchParams.get("id") || "").trim(); const page = Math.max( Number.parseInt(String(request.nextUrl.searchParams.get("page") || "1"), 10) || 1, 1 ); if (!itemId) { return jsonError(400, "缺少商品ID参数"); } const config = await getResolvedProductPlanningApiConfig(); const apiKey = config.taobaoApiKey; const apiSecret = config.taobaoApiSecret; if (!apiKey || !apiSecret) { return jsonError(500, "缺少商品策划工具淘宝 API Key / Secret,请前往系统设置配置"); } const apiUrl = `${config.oneboundBaseUrl}/taobao/item_review/?key=${encodeURIComponent(apiKey)}` + `&secret=${encodeURIComponent(apiSecret)}&num_iid=${encodeURIComponent(itemId)}` + `&page=${page}&version=1&cache=no&lang=zh-CN`; try { const response = await fetch(apiUrl, { signal: AbortSignal.timeout(15000), headers: { Accept: "application/json", }, cache: "no-store", }); if (!response.ok) { return jsonError(502, `淘宝评价接口请求失败:${response.status}`); } const data = await response.json(); if (data?.error_code && data.error_code !== "0000") { return jsonError(502, data.error || data.reason || "淘宝评价接口返回错误"); } const rows: TaobaoReviewRow[] = Array.isArray(data?.items?.item) ? data.items.item.map((item: any) => ({ itemId, date: String(item?.rate_date || "").trim(), content: String(item?.rate_content || "").trim(), appendComment: String(item?.add_feedback || "").trim() || undefined, sku: String(item?.auction_sku || "").trim() || undefined, userId: String(item?.display_user_nick || "").trim() || undefined, images: Array.isArray(item?.pics) ? item.pics.map((image: unknown) => normalizeMediaUrl(image)).filter(Boolean) : [], videoUrls: item?.video ? [normalizeMediaUrl(item.video)].filter(Boolean) : [], })) : []; const payload: ProductPlanningRowsResponse = { success: true, itemId, page, totalPages: Number.parseInt(String(data?.items?.totalpage || "1"), 10) || 1, rows, }; return NextResponse.json(payload); } catch (error) { logger.error("request_failed", error); return jsonError(502, "淘宝评价接口调用失败"); } }