import axios from 'axios'; import fs from 'fs'; import path from 'path'; const BACKEND_SERVER_URL = process.env.BACKEND_SERVER_URL; const BACKEND_SITE_NAME = process.env.BACKEND_SITE_NAME; const PUBLIC_FILES_DIR = path.join(process.cwd(), 'public/files'); if (!fs.existsSync(PUBLIC_FILES_DIR)) { fs.mkdirSync(PUBLIC_FILES_DIR, { recursive: true }); } // 直接内置鉴权头部生成函数,避免跨文件依赖 function get_jingrow_api_headers() { // 这里应根据实际情况实现获取token等逻辑 const apiKey = process.env.BACKEND_API_KEY; const apiSecret = process.env.BACKEND_API_SECRET; return { 'Authorization': `token ${apiKey}:${apiSecret}`, 'Content-Type': 'application/json' }; } export async function downloadToLocal(fileUrl) { if (!fileUrl) return fileUrl; try { let fullUrl = fileUrl; if (!/^https?:\/\//.test(fileUrl)) { fullUrl = `${BACKEND_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 = /]+src=["']([^"'>]+)["']/g; const urls = []; let match; while ((match = regex.exec(html)) !== null) { urls.push(match[1]); } return urls; } export async function processDataItem(item, downloadFiles) { if (!downloadFiles) return item; // 收集所有需要下载的文件URL const downloadTasks = []; const downloadMap = new Map(); // 用于存储下载结果 // 主表字段下载任务 if (item.image) { downloadTasks.push(downloadToLocal(item.image).then(url => downloadMap.set('image', url))); } if (item.image_1) { downloadTasks.push(downloadToLocal(item.image_1).then(url => downloadMap.set('image_1', url))); } if (item.image_2) { downloadTasks.push(downloadToLocal(item.image_2).then(url => downloadMap.set('image_2', url))); } if (item.video_src) { downloadTasks.push(downloadToLocal(item.video_src).then(url => downloadMap.set('video_src', url))); } if (item.file_src) { downloadTasks.push(downloadToLocal(item.file_src).then(url => downloadMap.set('file_src', url))); } // 处理attachments字段 - 并发下载 if (item.attachments && Array.isArray(item.attachments)) { const attachmentTasks = item.attachments.map(async (attachment, index) => { if (attachment.file_url) { const url = await downloadToLocal(attachment.file_url); return { index, url }; } return null; }); downloadTasks.push(...attachmentTasks.filter(task => task !== null)); } // 处理items字段 - 并发下载 if (item.items && Array.isArray(item.items)) { const itemTasks = item.items.map(async (subItem, itemIndex) => { const subItemDownloads = []; if (subItem.item_image) { subItemDownloads.push(downloadToLocal(subItem.item_image).then(url => ({ field: 'item_image', url }))); } if (subItem.item_video_src) { subItemDownloads.push(downloadToLocal(subItem.item_video_src).then(url => ({ field: 'item_video_src', url }))); } if (subItem.item_icon) { subItemDownloads.push(downloadToLocal(subItem.item_icon).then(url => ({ field: 'item_icon', url }))); } const results = await Promise.all(subItemDownloads); return { itemIndex, results }; }); downloadTasks.push(...itemTasks); } // 处理HTML内容中的图片 - 并发下载 const htmlTasks = []; for (const key of ['content', 'additional_content', 'description', 'p1', 'p2', 'p3']) { if (item[key]) { const urls = extractImageUrlsFromHtml(item[key]); if (urls.length > 0) { htmlTasks.push( Promise.all(urls.map(url => downloadToLocal(url))).then(localUrls => ({ key, urls, localUrls })) ); } } } downloadTasks.push(...htmlTasks); // 并发执行所有下载任务 await Promise.all(downloadTasks); // 更新主表字段 if (downloadMap.has('image')) item.image = downloadMap.get('image'); if (downloadMap.has('image_1')) item.image_1 = downloadMap.get('image_1'); if (downloadMap.has('image_2')) item.image_2 = downloadMap.get('image_2'); if (downloadMap.has('video_src')) item.video_src = downloadMap.get('video_src'); if (downloadMap.has('file_src')) item.file_src = downloadMap.get('file_src'); // 更新attachments字段 if (item.attachments && Array.isArray(item.attachments)) { const attachmentResults = downloadTasks.filter(task => task && typeof task === 'object' && 'index' in task ); for (const result of attachmentResults) { if (result && item.attachments[result.index]) { item.attachments[result.index].file_url = result.url; } } } // 更新items字段 if (item.items && Array.isArray(item.items)) { const itemResults = downloadTasks.filter(task => task && typeof task === 'object' && 'itemIndex' in task ); for (const result of itemResults) { if (result && item.items[result.itemIndex]) { for (const fieldResult of result.results) { item.items[result.itemIndex][fieldResult.field] = fieldResult.url; } } } } // 更新HTML内容 const htmlResults = downloadTasks.filter(task => task && typeof task === 'object' && 'key' in task ); for (const result of htmlResults) { if (result) { let html = item[result.key]; for (let i = 0; i < result.urls.length; i++) { html = html.replaceAll(result.urls[i], result.localUrls[i]); } item[result.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, site_name: BACKEND_SITE_NAME }; if (page_size) params.page_size = page_size; const response = await axios.get( `${BACKEND_SERVER_URL}/api/action/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( `${BACKEND_SERVER_URL}/api/action/jsite.api.v1.get_all_slugs`, { params: { site_name: BACKEND_SITE_NAME } } ); 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 []; } } export async function fetchComponentData(componentName, downloadFiles = true) { try { const res = await axios.get( `${BACKEND_SERVER_URL}/api/action/jsite.api.v1.get_component_data`, { params: { component_name: componentName, site_name: BACKEND_SITE_NAME }, } ); let data = res.data.message?.data || null; if (data && downloadFiles) { data = await processDataItem(data, downloadFiles); } return { data }; } catch (error) { if ( error.response && (error.response.status === 403 || error.response.status === 404) ) { return { data: null }; } console.error( `Error fetching component data for ${componentName}:`, error.message ); return { data: null }; } } export async function fetchListViewData({ pagetype, category, count }) { try { const params = { pagetype, site_name: BACKEND_SITE_NAME }; if (category) params.category = category; if (count !== undefined && count !== null) params.count = String(count); const res = await axios.get( `${BACKEND_SERVER_URL}/api/action/jsite.api.v1.get_listview_data`, { params } ); return { data: res.data.message?.data || [] }; } catch (error) { if ( error.response && (error.response.status === 403 || error.response.status === 404) ) { return { data: [] }; } console.error(`Error fetching list view data:`, error.message); return { data: [] }; } } export async function fetchCategoryData({ pagetype, name }) { try { const res = await axios.get( `${BACKEND_SERVER_URL}/api/action/jsite.api.v1.get_category`, { params: { pagetype, name, site_name: BACKEND_SITE_NAME } } ); return { data: res.data.message?.data || null }; } catch (error) { if ( error.response && (error.response.status === 403 || error.response.status === 404) ) { return { data: null }; } console.error(`Error fetching category data:`, error.message); return { data: null }; } } // 通用获取菜单数据函数 export async function getMenuData() { try { const response = await axios.get( `${BACKEND_SERVER_URL}/api/action/jsite.api.v1.get_menu`, { params: { site_name: BACKEND_SITE_NAME } } ); const items = response.data.message?.data || []; // 递归组装菜单树 function buildMenuTree(items, parent = null, parentPath = "") { return items .filter(item => (item.parent_jsite_menu === parent || (!item.parent_jsite_menu && !parent))) .map(item => { let currentPath = parentPath ? `${parentPath}/${item.slug.replace(/^\//, "")}` : item.slug; if (!currentPath.startsWith('/')) currentPath = '/' + currentPath; return { id: item.name, label: item.title, href: currentPath, position: item.position, order: item.order, icon: item.icon, children: buildMenuTree(items, item.name, currentPath), }; }); } const menuTree = buildMenuTree(items); return { menu: menuTree }; } catch (error) { return { error: error.message, detail: error?.response?.data || null }; } } export async function getSiteSettings() { try { const res = await axios.get( `${BACKEND_SERVER_URL}/api/action/jsite.api.v1.get_site_settings`, { headers: get_jingrow_api_headers(), params: { site_name: BACKEND_SITE_NAME } } ); return res.data?.message?.data || { site_name: "Jsite" }; } catch (e) { return { site_name: "Jsite" }; } }