初步修复ISR增量静态再生功能
This commit is contained in:
parent
4ecdb86cfc
commit
4089d1a448
@ -4,6 +4,7 @@ import { getSiteSettings } from "@/utlis/siteSettings";
|
|||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import DynamicListPage from "@/components/common/DynamicListPage";
|
import DynamicListPage from "@/components/common/DynamicListPage";
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
import { getPageData, getAllSlugs } from "@/utlis/data";
|
||||||
|
|
||||||
const LoadingSpinner = () => (
|
const LoadingSpinner = () => (
|
||||||
<div className="flex justify-center items-center p-8">
|
<div className="flex justify-center items-center p-8">
|
||||||
@ -11,39 +12,24 @@ const LoadingSpinner = () => (
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const revalidate = 3600;
|
export const revalidate = 10;
|
||||||
export const dynamicParams = true;
|
export const dynamicParams = true;
|
||||||
|
|
||||||
export async function generateStaticParams() {
|
export async function generateStaticParams() {
|
||||||
try {
|
const slugs = await getAllSlugs();
|
||||||
const res = await fetch(`${process.env.SITE_URL}/api/get-all-slugs`);
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`Failed to fetch slugs: ${res.status}`);
|
|
||||||
}
|
|
||||||
const slugs = await res.json();
|
|
||||||
if (!Array.isArray(slugs)) {
|
|
||||||
console.error("generateStaticParams: received non-array from /api/get-all-slugs", slugs);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return slugs.map(slug => ({
|
return slugs.map(slug => ({
|
||||||
slug: slug,
|
slug: slug,
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
|
||||||
console.error("Could not generate static params:", error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({ params }) {
|
export async function generateMetadata({ params }) {
|
||||||
const resolvedParams = await params;
|
const resolvedParams = await params;
|
||||||
const slugArr = resolvedParams.slug;
|
const slugArr = resolvedParams.slug;
|
||||||
const res = await fetch(`${process.env.SITE_URL}/api/get-page-data`, {
|
const { data, error, page_info } = await getPageData({
|
||||||
method: 'POST',
|
slug_list: slugArr,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
downloadFiles: false // Do not download files for metadata
|
||||||
body: JSON.stringify({ slug_list: slugArr })
|
|
||||||
});
|
});
|
||||||
const json = await res.json();
|
|
||||||
const { data, error, page_info } = json;
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return {
|
return {
|
||||||
title: error.title || 'Page Error',
|
title: error.title || 'Page Error',
|
||||||
@ -70,13 +56,12 @@ export default async function DynamicPage({ params }) {
|
|||||||
const pageSize = Number(siteSettings.page_size) || 12;
|
const pageSize = Number(siteSettings.page_size) || 12;
|
||||||
|
|
||||||
// 始终获取第一页的数据用于静态生成
|
// 始终获取第一页的数据用于静态生成
|
||||||
const res = await fetch(`${process.env.SITE_URL}/api/get-page-data`, {
|
const { data, error, total } = await getPageData({
|
||||||
method: 'POST',
|
slug_list: slugArr,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
page: 1,
|
||||||
body: JSON.stringify({ slug_list: slugArr, page: 1, page_size: pageSize })
|
page_size: pageSize,
|
||||||
|
downloadFiles: true // Download files for page rendering
|
||||||
});
|
});
|
||||||
const result = await res.json();
|
|
||||||
const { data, error, total } = result;
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
notFound();
|
notFound();
|
||||||
|
|||||||
@ -1,26 +0,0 @@
|
|||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
const JINGROW_SERVER_URL = process.env.JINGROW_SERVER_URL;
|
|
||||||
|
|
||||||
export async function GET() {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(
|
|
||||||
`${JINGROW_SERVER_URL}/api/method/jsite.api.v1.get_all_slugs`
|
|
||||||
);
|
|
||||||
|
|
||||||
const slugs = response.data.message?.data;
|
|
||||||
|
|
||||||
if (!Array.isArray(slugs)) {
|
|
||||||
console.error('API did not return an array of slugs:', response.data);
|
|
||||||
return Response.json({ error: '返回的slugs格式不正确' }, { status: 500 });
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response.json(slugs);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching slugs:', error.message, error?.response?.data);
|
|
||||||
return Response.json(
|
|
||||||
{ error: '获取slugs失败', detail: error?.response?.data || error.message },
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,125 +0,0 @@
|
|||||||
import axios from 'axios';
|
|
||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
const JINGROW_SERVER_URL = process.env.JINGROW_SERVER_URL;
|
|
||||||
const PUBLIC_FILES_DIR = path.join(process.cwd(), 'public/files');
|
|
||||||
|
|
||||||
if (!fs.existsSync(PUBLIC_FILES_DIR)) {
|
|
||||||
fs.mkdirSync(PUBLIC_FILES_DIR, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function downloadToLocal(fileUrl) {
|
|
||||||
try {
|
|
||||||
let fullUrl = fileUrl;
|
|
||||||
if (!/^https?:\/\//.test(fileUrl)) {
|
|
||||||
fullUrl = `${JINGROW_SERVER_URL}${fileUrl}`;
|
|
||||||
}
|
|
||||||
const fileName = path.basename(fullUrl.split('?')[0]);
|
|
||||||
const localPath = path.join(PUBLIC_FILES_DIR, fileName);
|
|
||||||
const localUrl = `/files/${fileName}`;
|
|
||||||
if (!fs.existsSync(localPath)) {
|
|
||||||
const response = await axios.get(fullUrl, { responseType: 'stream' });
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
const writer = fs.createWriteStream(localPath);
|
|
||||||
response.data.pipe(writer);
|
|
||||||
writer.on('finish', resolve);
|
|
||||||
writer.on('error', reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return localUrl;
|
|
||||||
} catch (e) {
|
|
||||||
return fileUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提取富文本中的所有图片链接
|
|
||||||
function extractImageUrlsFromHtml(html) {
|
|
||||||
if (!html) return [];
|
|
||||||
const regex = /<img[^>]+src=["']([^"'>]+)["']/g;
|
|
||||||
const urls = [];
|
|
||||||
let match;
|
|
||||||
while ((match = regex.exec(html)) !== null) {
|
|
||||||
urls.push(match[1]);
|
|
||||||
}
|
|
||||||
return urls;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 替换富文本中的图片链接为本地路径
|
|
||||||
async function replaceImageUrlsInHtml(html) {
|
|
||||||
if (!html) return html;
|
|
||||||
const regex = /<img([^>]+)src=["']([^"'>]+)["']/g;
|
|
||||||
return await html.replace(regex, async (match, pre, url) => {
|
|
||||||
const localUrl = await downloadToLocal(url);
|
|
||||||
return `<img${pre}src="${localUrl}"`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST(request) {
|
|
||||||
try {
|
|
||||||
const body = await request.json();
|
|
||||||
const slug_list = body.slug_list;
|
|
||||||
const page = body.page || 1;
|
|
||||||
const page_size = body.page_size || undefined;
|
|
||||||
if (!Array.isArray(slug_list)) {
|
|
||||||
return Response.json({ error: 'slug_list参数必须为数组' }, { status: 400 });
|
|
||||||
}
|
|
||||||
const params = { slug_list: JSON.stringify(slug_list), page };
|
|
||||||
if (page_size) params.page_size = page_size;
|
|
||||||
const response = await axios.get(
|
|
||||||
`${JINGROW_SERVER_URL}/api/method/jsite.api.v1.get_page_data`,
|
|
||||||
{ params }
|
|
||||||
);
|
|
||||||
const message = response.data.message;
|
|
||||||
let data = message?.data;
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
for (const item of data) {
|
|
||||||
if (item.image) {
|
|
||||||
item.image = await downloadToLocal(item.image);
|
|
||||||
}
|
|
||||||
for (const key of ['content', 'additional_content']) {
|
|
||||||
if (item[key]) {
|
|
||||||
const urls = extractImageUrlsFromHtml(item[key]);
|
|
||||||
let html = item[key];
|
|
||||||
for (const url of urls) {
|
|
||||||
const localUrl = await downloadToLocal(url);
|
|
||||||
html = html.replaceAll(url, localUrl);
|
|
||||||
}
|
|
||||||
item[key] = html;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (data) {
|
|
||||||
if (data.image) {
|
|
||||||
data.image = await downloadToLocal(data.image);
|
|
||||||
}
|
|
||||||
for (const key of ['content', 'additional_content']) {
|
|
||||||
if (data[key]) {
|
|
||||||
const urls = extractImageUrlsFromHtml(data[key]);
|
|
||||||
let html = data[key];
|
|
||||||
for (const url of urls) {
|
|
||||||
const localUrl = await downloadToLocal(url);
|
|
||||||
html = html.replaceAll(url, localUrl);
|
|
||||||
}
|
|
||||||
data[key] = html;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (message?.data) {
|
|
||||||
return Response.json({
|
|
||||||
data: message.data,
|
|
||||||
total: message.total,
|
|
||||||
page_info: message.page_info
|
|
||||||
});
|
|
||||||
} else if (message?.error) {
|
|
||||||
return Response.json({ error: message.error }, { status: 400 });
|
|
||||||
} else {
|
|
||||||
return Response.json({ error: '未知错误' }, { status: 500 });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return Response.json(
|
|
||||||
{ error: error.message, detail: error?.response?.data || null },
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
173
utlis/data.js
Normal file
173
utlis/data.js
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const JINGROW_SERVER_URL = process.env.JINGROW_SERVER_URL;
|
||||||
|
const PUBLIC_FILES_DIR = path.join(process.cwd(), 'public/files');
|
||||||
|
|
||||||
|
if (!fs.existsSync(PUBLIC_FILES_DIR)) {
|
||||||
|
fs.mkdirSync(PUBLIC_FILES_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadToLocal(fileUrl) {
|
||||||
|
if (!fileUrl) return fileUrl;
|
||||||
|
try {
|
||||||
|
let fullUrl = fileUrl;
|
||||||
|
if (!/^https?:\/\//.test(fileUrl)) {
|
||||||
|
fullUrl = `${JINGROW_SERVER_URL}${fileUrl}`;
|
||||||
|
}
|
||||||
|
const fileName = path.basename(fullUrl.split('?')[0]);
|
||||||
|
const localPath = path.join(PUBLIC_FILES_DIR, fileName);
|
||||||
|
const localUrl = `/files/${fileName}`;
|
||||||
|
|
||||||
|
if (!fs.existsSync(localPath)) {
|
||||||
|
const response = await axios.get(fullUrl, { responseType: 'stream' });
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const writer = fs.createWriteStream(localPath);
|
||||||
|
response.data.pipe(writer);
|
||||||
|
writer.on('finish', resolve);
|
||||||
|
writer.on('error', (error) => {
|
||||||
|
console.error(`Error writing file ${localPath}:`, error);
|
||||||
|
// Don't reject, just resolve and return original url later
|
||||||
|
fs.unlink(localPath, () => resolve());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return localUrl;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to download ${fileUrl}:`, e.message);
|
||||||
|
return fileUrl; // Return original url on error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractImageUrlsFromHtml(html) {
|
||||||
|
if (!html) return [];
|
||||||
|
const regex = /<img[^>]+src=["']([^"'>]+)["']/g;
|
||||||
|
const urls = [];
|
||||||
|
let match;
|
||||||
|
while ((match = regex.exec(html)) !== null) {
|
||||||
|
urls.push(match[1]);
|
||||||
|
}
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processDataItem(item, downloadFiles) {
|
||||||
|
if (!downloadFiles) return item;
|
||||||
|
|
||||||
|
if (item.image) {
|
||||||
|
item.image = await downloadToLocal(item.image);
|
||||||
|
}
|
||||||
|
if (item.image_1) {
|
||||||
|
item.image_1 = await downloadToLocal(item.image_1);
|
||||||
|
}
|
||||||
|
if (item.image_2) {
|
||||||
|
item.image_2 = await downloadToLocal(item.image_2);
|
||||||
|
}
|
||||||
|
if (item.video_src) {
|
||||||
|
item.video_src = await downloadToLocal(item.video_src);
|
||||||
|
}
|
||||||
|
if (item.file_src) {
|
||||||
|
item.file_src = await downloadToLocal(item.file_src);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.items && Array.isArray(item.items)) {
|
||||||
|
for (const subItem of item.items) {
|
||||||
|
if (subItem.item_image) {
|
||||||
|
subItem.item_image = await downloadToLocal(subItem.item_image);
|
||||||
|
}
|
||||||
|
if (subItem.item_video_src) {
|
||||||
|
subItem.item_video_src = await downloadToLocal(subItem.item_video_src);
|
||||||
|
}
|
||||||
|
if (subItem.item_icon) {
|
||||||
|
subItem.item_icon = await downloadToLocal(subItem.item_icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of ['content', 'additional_content', 'description', 'p1', 'p2', 'p3']) {
|
||||||
|
if (item[key]) {
|
||||||
|
const urls = extractImageUrlsFromHtml(item[key]);
|
||||||
|
let html = item[key];
|
||||||
|
for (const url of urls) {
|
||||||
|
const localUrl = await downloadToLocal(url);
|
||||||
|
html = html.replaceAll(url, localUrl);
|
||||||
|
}
|
||||||
|
item[key] = html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPageData({
|
||||||
|
slug_list,
|
||||||
|
page = 1,
|
||||||
|
page_size,
|
||||||
|
downloadFiles = false
|
||||||
|
}) {
|
||||||
|
try {
|
||||||
|
if (!Array.isArray(slug_list)) {
|
||||||
|
throw new Error('slug_list must be an array');
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = { slug_list: JSON.stringify(slug_list), page };
|
||||||
|
if (page_size) params.page_size = page_size;
|
||||||
|
|
||||||
|
const response = await axios.get(
|
||||||
|
`${JINGROW_SERVER_URL}/api/method/jsite.api.v1.get_page_data`,
|
||||||
|
{ params }
|
||||||
|
);
|
||||||
|
|
||||||
|
const message = response.data.message;
|
||||||
|
if (message?.error) {
|
||||||
|
const errorMsg = typeof message.error === 'object' ? JSON.stringify(message.error) : message.error;
|
||||||
|
throw new Error(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = message?.data;
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
if(downloadFiles) {
|
||||||
|
data = await Promise.all(data.map(item => processDataItem(item, downloadFiles)));
|
||||||
|
}
|
||||||
|
} else if (data) {
|
||||||
|
data = await processDataItem(data, downloadFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: message.data,
|
||||||
|
total: message.total,
|
||||||
|
page_info: message.page_info,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in getPageData:", error);
|
||||||
|
return { error: { message: error.message, detail: error?.response?.data || null } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllSlugs() {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`${JINGROW_SERVER_URL}/api/method/jsite.api.v1.get_all_slugs`
|
||||||
|
);
|
||||||
|
const slugs = response.data.message?.data;
|
||||||
|
if (!Array.isArray(slugs)) {
|
||||||
|
console.error('API did not return an array of slugs:', response.data);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
// Filter out slugs that represent the root page, as it's handled by app/page.jsx
|
||||||
|
const filteredSlugs = slugs.filter(slug => {
|
||||||
|
if (!Array.isArray(slug) || slug.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Exclude slugs like [''] or ['/'] which are for the homepage
|
||||||
|
if (slug.length === 1 && (slug[0] === '' || slug[0] === '/')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return filteredSlugs;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching slugs:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user