code-center/src/routes/page/module/cache-file.ts

175 lines
5.9 KiB
TypeScript

import { useFileStore } from '@abearxiong/use-file-store';
import { PageModel } from '../models/index.ts';
import { ContainerModel } from '@/routes/container/models/index.ts';
import { Op } from 'sequelize';
import { getContainerData } from './get-container.ts';
import path from 'path';
import fs from 'fs';
import { getHTML, getDataJs } from './file-template.ts';
import { minioClient } from '@/app.ts';
import { bucketName } from '@/modules/minio.ts';
import { getContentType } from '@/utils/get-content-type.ts';
import archiver from 'archiver';
import { CustomError } from '@kevisual/router';
import { nanoid } from 'nanoid';
export const cacheFile = useFileStore('cache-file', {
needExists: true,
});
export const getDeck = async (page: PageModel) => {
const { data } = page;
const { nodes = [], edges } = data;
const containerList = nodes
.map((item) => {
const { data } = item;
return data?.cid;
})
.filter((item) => item);
const quchong = Array.from(new Set(containerList));
const containers = await ContainerModel.findAll({
where: {
id: {
[Op.in]: quchong,
},
},
});
const pageData = {
page,
containerList: containers,
};
return pageData;
};
export const cachePage = async (page: PageModel, opts: { tokenUser: any; key; version }) => {
const _result = await getDeck(page);
const result = getContainerData(_result);
const key = 'data-' + nanoid(6);
const html = getHTML({ rootId: page.id, title: page?.publish?.key, dataKey: key });
const dataJs = getDataJs(result);
const htmlPath = path.resolve(cacheFile, `${page.id}.html`);
const dataJsPath = path.resolve(cacheFile, `${page.id}.js`);
fs.writeFileSync(htmlPath, html);
fs.writeFileSync(dataJsPath, dataJs);
const minioHTML = await uploadMinio({ ...opts, path: `index.html`, filePath: htmlPath });
const minioData = await uploadMinio({ ...opts, path: `${key || 'data'}.js`, filePath: dataJsPath });
return [
{
name: 'index.html',
path: minioHTML,
},
{
name: `${key || 'data'}.js`,
path: minioData,
},
];
};
export const uploadMinioContainer = async ({ tokenUser, key, version, code, filePath }) => {
if ((filePath as string).includes('..')) {
throw new CustomError('file path is invalid');
}
const minioKeyVersion = `${tokenUser.username}/${key}/${version}`;
const minioPath = path.join(minioKeyVersion, filePath);
console.log('minioPath', minioPath);
// const isHTML = filePath.endsWith('.html');
const name = minioPath.replace(minioKeyVersion + '/', '');
await minioClient.putObject(bucketName, minioPath, code, code.length, {
'Content-Type': getContentType(filePath),
'app-source': 'user-app',
'Cache-Control': 'no-cache', // 缓存一年
});
return {
name,
path: minioPath,
};
};
export const uploadMinio = async ({ tokenUser, key, version, path, filePath }) => {
const minioPath = `${tokenUser.username}/${key}/${version}/${path}`;
const isHTML = filePath.endsWith('.html');
await minioClient.fPutObject(bucketName, minioPath, filePath, {
'Content-Type': getContentType(filePath),
'app-source': 'user-app',
'Cache-Control': isHTML ? 'no-cache' : 'max-age=31536000, immutable', // 缓存一年
});
fs.unlinkSync(filePath); // 删除临时文件
return minioPath;
};
export const uploadMinioTemp = async ({ tokenUser, filePath, path }) => {
const minioPath = `${tokenUser.username}/temp/${path}`;
const isHTML = filePath.endsWith('.html');
await minioClient.fPutObject(bucketName, minioPath, filePath, {
'Content-Type': getContentType(filePath),
'app-source': 'user-app',
'Cache-Control': isHTML ? 'no-cache' : 'max-age=31536000, immutable', // 缓存一年
});
fs.unlinkSync(filePath); // 删除临时文件
return minioPath;
};
export const getZip = async (page: PageModel, opts: { tokenUser: any }) => {
const _result = await getDeck(page);
const result = getContainerData(_result);
const html = getHTML({ rootId: page.id, title: page?.publish?.key });
const dataJs = getDataJs(result);
const zip = archiver('zip', {
zlib: { level: 9 },
});
// 创建 zip 文件的输出流
const zipCache = path.join(cacheFile, `${page.id}.zip`);
if (checkFileExistsSync(zipCache)) {
throw new CustomError('page is on uploading');
}
return await new Promise((resolve, reject) => {
const output = fs.createWriteStream(zipCache);
// 监听事件
output.on('close', async () => {
console.log(`Zip file has been created successfully. Total size: ${zip.pointer()} bytes.`);
let time = (new Date().getTime() / 1000).toFixed(0);
const name = page.title || page.id;
const minioPath = await uploadMinioTemp({ ...opts, filePath: zipCache, path: `${name + '-' + time}.zip` });
resolve(minioPath);
});
output.on('end', () => {
console.log('Data has been drained.'); // 数据已被耗尽
throw new CustomError('Data has been drained.');
});
zip.on('warning', (err) => {
if (err.code === 'ENOENT') {
console.warn('File not found:', err);
} else {
throw err;
}
});
zip.on('error', (err) => {
throw err;
});
// 通过管道将 zip 数据流输出到指定文件
zip.pipe(output);
// 添加 HTML 字符串作为文件到 zip 中
zip.append(html, { name: 'index.html' });
// 添加 JavaScript 字符串作为文件到 zip 中
zip.append(dataJs, { name: 'data.js' });
zip.append(JSON.stringify(page), { name: 'app.config.json5' });
// 可以继续添加更多内容,文件或目录等
// zip.append('Another content', { name: 'other.txt' });
// 结束归档(必须调用,否则 zip 文件无法完成)
zip.finalize();
});
};
export const checkFileExistsSync = (filePath: string) => {
try {
// 使用 F_OK 检查文件或目录是否存在
fs.accessSync(filePath, fs.constants.F_OK);
return true;
} catch (err) {
return false;
}
};