175 lines
5.9 KiB
TypeScript
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;
|
|
}
|
|
};
|