feat: 下载page到本地
This commit is contained in:
@@ -21,15 +21,15 @@ export class User extends Model {
|
||||
declare needChangePassword: boolean;
|
||||
declare description: string;
|
||||
declare data: UserData;
|
||||
declare type: string; // user | org
|
||||
declare type: string; // user | org | visitor
|
||||
declare owner: string;
|
||||
declare orgId: string;
|
||||
declare email: string;
|
||||
async createToken(uid?: string) {
|
||||
const { id, username } = this;
|
||||
const { id, username, type } = this;
|
||||
const expireTime = 60 * 60 * 24 * 7; // 7 days
|
||||
const now = new Date().getTime();
|
||||
const token = await createToken({ id, username, uid }, config.tokenSecret);
|
||||
const token = await createToken({ id, username, uid, type }, config.tokenSecret);
|
||||
return { token, expireTime: now + expireTime };
|
||||
}
|
||||
static async verifyToken(token: string) {
|
||||
|
||||
@@ -9,10 +9,13 @@ app
|
||||
.route({
|
||||
path: 'chat-prompt',
|
||||
key: 'list',
|
||||
// middleware: ['auth'],
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const chatPrompt = await ChatPrompt.findAll({
|
||||
order: [['updatedAt', 'DESC']],
|
||||
// 列出被删除的
|
||||
// paranoid: false,
|
||||
});
|
||||
ctx.body = chatPrompt;
|
||||
})
|
||||
@@ -78,6 +81,7 @@ app
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
middleware: ['auth'],
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const id = ctx.query.id;
|
||||
@@ -93,6 +97,7 @@ app
|
||||
.route({
|
||||
path: 'chat-prompt',
|
||||
key: 'getByKey',
|
||||
middleware: ['auth'],
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const { key } = ctx.query.data || {};
|
||||
|
||||
@@ -9,6 +9,9 @@ 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 '@abearxiong/router';
|
||||
|
||||
export const cacheFile = useFileStore('cache-file', {
|
||||
needExists: true,
|
||||
});
|
||||
@@ -72,3 +75,80 @@ export const uploadMinio = async ({ tokenUser, key, version, path, filePath }) =
|
||||
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('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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
import { ContainerModel } from '../container/models/index.ts';
|
||||
import { Op } from 'sequelize';
|
||||
import { AppListModel, AppModel } from '../app-manager/index.ts';
|
||||
import { cachePage } from './module/cache-file.ts';
|
||||
import { cachePage, getZip } from './module/cache-file.ts';
|
||||
import _ from 'lodash';
|
||||
import semver from 'semver';
|
||||
|
||||
@@ -67,3 +67,26 @@ app
|
||||
}
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
app
|
||||
.route({
|
||||
path: 'page',
|
||||
key: 'download',
|
||||
middleware: ['auth'],
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const tokenUser = ctx.state.tokenUser;
|
||||
const { id } = ctx.query;
|
||||
const page = await PageModel.findByPk(id);
|
||||
if (!page) {
|
||||
throw new CustomError('page not found');
|
||||
}
|
||||
try {
|
||||
const files = await getZip(page, { tokenUser });
|
||||
ctx.body = files;
|
||||
} catch (e) {
|
||||
console.log('error', e);
|
||||
throw new CustomError(e.message || 'download error');
|
||||
}
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ContainerModel } from '@/routes/container/models/index.ts';
|
||||
|
||||
import { ChatPrompt } from '@/models/chat-prompt.ts';
|
||||
const recoverData = async () => {
|
||||
const data = {
|
||||
id: '868970a4-8cab-4141-a73c-cc185fd17508',
|
||||
@@ -24,4 +24,24 @@ const recoverData = async () => {
|
||||
const r = await ContainerModel.create(data);
|
||||
};
|
||||
|
||||
recoverData();
|
||||
// recoverData();
|
||||
|
||||
const revoverId = async () => {
|
||||
const id = 'e235576e-eb48-4b5c-8385-9b8ada4a137f';
|
||||
// const cp = await ChatPrompt.findByPk(id);
|
||||
const cp = await ChatPrompt.findAll({
|
||||
paranoid: false,
|
||||
});
|
||||
console.log(
|
||||
cp.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
// @ts-ignore
|
||||
deletedAt: item.deletedAt,
|
||||
};
|
||||
}),
|
||||
);
|
||||
// cp 被删除了,复原
|
||||
await ChatPrompt.restore({ where: { id } });
|
||||
};
|
||||
revoverId();
|
||||
|
||||
@@ -6,6 +6,7 @@ export const getContentType = (filePath: string) => {
|
||||
'.html': 'text/html',
|
||||
'.js': 'text/javascript',
|
||||
'.css': 'text/css',
|
||||
'.txt': 'text/plain',
|
||||
'.json': 'application/json',
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpg',
|
||||
@@ -13,6 +14,7 @@ export const getContentType = (filePath: string) => {
|
||||
'.svg': 'image/svg+xml',
|
||||
'.wav': 'audio/wav',
|
||||
'.mp4': 'video/mp4',
|
||||
'.zip': 'application/octet-stream',
|
||||
};
|
||||
return contentType[extname] || 'application/octet-stream';
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user