110 lines
3.7 KiB
TypeScript
110 lines
3.7 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
||
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";
|
||
|
||
function normalizeMediaUrl(value: unknown) {
|
||
if (typeof value === "string") {
|
||
return ensureHttpsUrl(value);
|
||
}
|
||
|
||
if (value && typeof value === "object") {
|
||
const candidate = (value as Record<string, unknown>).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<TaobaoReviewRow> = {
|
||
success: true,
|
||
itemId,
|
||
page,
|
||
totalPages: Number.parseInt(String(data?.items?.totalpage || "1"), 10) || 1,
|
||
rows,
|
||
};
|
||
|
||
return NextResponse.json(payload);
|
||
} catch (error) {
|
||
console.error("[ProductPlanning][Reviews] request failed:", error);
|
||
return jsonError(502, "淘宝评价接口调用失败");
|
||
}
|
||
}
|