101 lines
3.3 KiB
TypeScript
101 lines
3.3 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
||
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 { normalizeJdItem } from "@/lib/product-planning/taobao";
|
||
import type { ProductPlanningTaobaoItemResponse } from "@/lib/product-planning/types";
|
||
|
||
export const runtime = "nodejs";
|
||
|
||
function jsonError(status: number, error: string) {
|
||
const payload: ProductPlanningTaobaoItemResponse = {
|
||
success: false,
|
||
error,
|
||
};
|
||
return NextResponse.json(payload, { 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();
|
||
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}/jd/item_get_app/?key=${encodeURIComponent(apiKey)}` +
|
||
`&secret=${encodeURIComponent(apiSecret)}&num_iid=${encodeURIComponent(itemId)}&domain_type=jd`;
|
||
const descApiUrl =
|
||
`${config.oneboundBaseUrl}/jd/item_get_desc/?key=${encodeURIComponent(apiKey)}` +
|
||
`&secret=${encodeURIComponent(apiSecret)}&num_iid=${encodeURIComponent(itemId)}&domain_type=jd`;
|
||
|
||
try {
|
||
const [response, descResponse] = await Promise.all([
|
||
fetch(apiUrl, {
|
||
signal: AbortSignal.timeout(15000),
|
||
headers: { Accept: "application/json" },
|
||
cache: "no-store",
|
||
}),
|
||
fetch(descApiUrl, {
|
||
signal: AbortSignal.timeout(15000),
|
||
headers: { Accept: "application/json" },
|
||
cache: "no-store",
|
||
}).catch(() => null),
|
||
]);
|
||
|
||
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 || "京东商品详情接口返回错误");
|
||
}
|
||
|
||
if (!data?.item) {
|
||
return jsonError(404, "未找到商品信息");
|
||
}
|
||
|
||
let descHtml = "";
|
||
if (descResponse?.ok) {
|
||
try {
|
||
const descData = await descResponse.json();
|
||
descHtml = String(descData?.item?.desc || "").trim();
|
||
} catch (error) {
|
||
console.warn("[ProductPlanning][JdItem] parse desc failed:", error);
|
||
}
|
||
}
|
||
|
||
return NextResponse.json({
|
||
success: true,
|
||
item: normalizeJdItem(
|
||
{
|
||
...data.item,
|
||
desc: descHtml || data.item.desc,
|
||
},
|
||
itemId
|
||
),
|
||
} satisfies ProductPlanningTaobaoItemResponse);
|
||
} catch (error) {
|
||
console.error("[ProductPlanning][JdItem] request failed:", error);
|
||
return jsonError(502, "京东商品详情接口调用失败");
|
||
}
|
||
}
|