t10015/utils/data.js

390 lines
12 KiB
JavaScript

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 = /<img[^>]+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);
// 并发执行所有下载任务并获取结果
const results = 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 = results.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 = results.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 = results.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,
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"
};
}
}