mirror of
https://github.com/whyour/qinglong.git
synced 2026-11-06 17:34:34 +08:00
refactor: resolve script history service through TypeDI
This commit is contained in:
+14
-25
@@ -9,11 +9,11 @@ import * as fs from 'fs/promises';
|
||||
import { celebrate, Joi } from 'celebrate';
|
||||
import path, { join, parse } from 'path';
|
||||
import ScriptService from '../services/script';
|
||||
import ScriptHistoryService from '../services/scriptHistory';
|
||||
import { t } from '../shared/i18n';
|
||||
import multer from 'multer';
|
||||
import { writeFileWithLock } from '../shared/utils';
|
||||
import {
|
||||
ScriptHistory,
|
||||
ScriptHistoryError,
|
||||
HistoryUnavailableError,
|
||||
} from '../shared/scriptHistory';
|
||||
@@ -120,12 +120,6 @@ export default (app: Router) => {
|
||||
},
|
||||
);
|
||||
|
||||
const historyService = () =>
|
||||
new ScriptHistory(
|
||||
config.scriptPath,
|
||||
join(config.dataPath, 'script-history'),
|
||||
config.blackFileList,
|
||||
);
|
||||
const historyError = (error: unknown, res: Response, next: NextFunction) => {
|
||||
if (error instanceof ScriptHistoryError) {
|
||||
return res.status(error.status).send({
|
||||
@@ -148,7 +142,8 @@ export default (app: Router) => {
|
||||
celebrate({ query: Joi.object(historyQuery).unknown(true) }),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const data = await historyService().list(
|
||||
const service = Container.get(ScriptHistoryService);
|
||||
const data = await service.list(
|
||||
(req.query.path as string) || '',
|
||||
req.query.filename as string,
|
||||
);
|
||||
@@ -168,7 +163,8 @@ export default (app: Router) => {
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const data = await historyService().detail(
|
||||
const service = Container.get(ScriptHistoryService);
|
||||
const data = await service.detail(
|
||||
(req.query.path as string) || '',
|
||||
req.query.filename as string,
|
||||
req.query.id as string,
|
||||
@@ -191,12 +187,8 @@ export default (app: Router) => {
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { path = '', filename, id, expectedHash } = req.body;
|
||||
const data = await historyService().restore(
|
||||
path,
|
||||
filename,
|
||||
id,
|
||||
expectedHash,
|
||||
);
|
||||
const service = Container.get(ScriptHistoryService);
|
||||
const data = await service.restore(path, filename, id, expectedHash);
|
||||
res.send({ code: 200, data });
|
||||
} catch (error) {
|
||||
historyError(error, res, next);
|
||||
@@ -304,7 +296,8 @@ export default (app: Router) => {
|
||||
join(config.bakPath, originFilename.replace(/\//g, '')),
|
||||
);
|
||||
}
|
||||
const data = await historyService().save(path, filename, content, {
|
||||
const service = Container.get(ScriptHistoryService);
|
||||
const data = await service.save(path, filename, content, {
|
||||
skipHistory: req.body.skipHistory,
|
||||
expectedHash: req.body.expectedHash,
|
||||
});
|
||||
@@ -357,15 +350,11 @@ export default (app: Router) => {
|
||||
message: t('暂无权限'),
|
||||
});
|
||||
}
|
||||
const data = await historyService().save(
|
||||
path || '',
|
||||
filename,
|
||||
content,
|
||||
{
|
||||
skipHistory: req.body.skipHistory,
|
||||
expectedHash: req.body.expectedHash,
|
||||
},
|
||||
);
|
||||
const service = Container.get(ScriptHistoryService);
|
||||
const data = await service.save(path || '', filename, content, {
|
||||
skipHistory: req.body.skipHistory,
|
||||
expectedHash: req.body.expectedHash,
|
||||
});
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
return historyError(e, res, next);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import path from 'path';
|
||||
import { Service } from 'typedi';
|
||||
import config from '../config';
|
||||
import { ScriptHistory } from '../shared/scriptHistory';
|
||||
|
||||
/** Panel configuration and lifecycle; the storage core stays independently testable. */
|
||||
@Service()
|
||||
export default class ScriptHistoryService extends ScriptHistory {
|
||||
constructor() {
|
||||
super(
|
||||
config.scriptPath,
|
||||
path.join(config.dataPath, 'script-history'),
|
||||
config.blackFileList,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
位置:`<QL_DATA_DIR>/script-history/<SHA-256(规范相对路径)>.br`。目录位于脚本目录之外,避免进入脚本树、订阅和任务扫描。目录权限 `0700`,历史文件权限 `0600`。目录随整个数据目录备份。
|
||||
|
||||
每个脚本仅一个 Brotli 压缩 JSON 包,存储完整的旧内容快照以及时间、来源、内容哈希。新文件内容不再重复存储,只保存头版本元数据,供下一次留档标注来源。包内重复文本共享压缩窗口,不依赖补丁链,也不需要额外数据库、文件监听、后台任务或常驻缓存。使用 Node 内置 zlib 异步压缩,Brotli quality=4、window=4 MiB,不新增依赖。
|
||||
每个脚本仅一个 Brotli 压缩 JSON 包,存储完整的旧内容快照以及时间、来源、内容哈希。新文件内容不再重复存储,只保存头版本元数据,供下一次留档标注来源。面板服务通过 TypeDI 注册为单例,路由使用 `Container.get(ScriptHistoryService)` 获取;构造实例只初始化配置,首次发生保存时才创建历史目录。包内重复文本共享压缩窗口,不依赖补丁链,也不需要额外数据库、文件监听、后台任务或常驻缓存。使用 Node 内置 zlib 异步压缩,Brotli quality=4、window=4 MiB,不新增依赖。
|
||||
|
||||
| 限制 | 默认值 |
|
||||
| --- | --- |
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
require('reflect-metadata');
|
||||
const assert = require('node:assert/strict');
|
||||
const test = require('node:test');
|
||||
const fs = require('node:fs/promises');
|
||||
@@ -5,6 +6,7 @@ const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const express = require('express');
|
||||
const { errors } = require('celebrate');
|
||||
const { Container } = require('typedi');
|
||||
const load = require('../helpers/load-security-module.cjs');
|
||||
const { resolveFileAccess } = load(
|
||||
path.join(__dirname, '../../back/shared/fileAccess.ts'),
|
||||
@@ -29,7 +31,7 @@ test('script routes save, list, preview and restore, with validation and conflic
|
||||
return resolveFileAccess(scripts, [dir || '', name]);
|
||||
}
|
||||
}
|
||||
const api = load(path.join(__dirname, '../../back/api/script.ts'), {
|
||||
const mocks = {
|
||||
'../config': { default: config, __esModule: true },
|
||||
'fs/promises': {
|
||||
...fs,
|
||||
@@ -54,7 +56,30 @@ test('script routes save, list, preview and restore, with validation and conflic
|
||||
},
|
||||
'../services/script': { default: ScriptService, __esModule: true },
|
||||
'../shared/i18n': { t: (value) => value },
|
||||
typedi: { Container: { get: () => new ScriptService() } },
|
||||
};
|
||||
const cache = new Map();
|
||||
const api = load(
|
||||
path.join(__dirname, '../../back/api/script.ts'),
|
||||
mocks,
|
||||
cache,
|
||||
);
|
||||
const { default: ScriptHistoryService } = load(
|
||||
path.join(__dirname, '../../back/services/scriptHistory.ts'),
|
||||
mocks,
|
||||
cache,
|
||||
);
|
||||
Container.set(ScriptService, new ScriptService());
|
||||
t.after(() => {
|
||||
Container.remove(ScriptHistoryService);
|
||||
Container.remove(ScriptService);
|
||||
});
|
||||
assert.ok(Container.has(ScriptHistoryService));
|
||||
const historyService = Container.get(ScriptHistoryService);
|
||||
assert.strictEqual(Container.get(ScriptHistoryService), historyService);
|
||||
// An empty history read must not create storage or other persistent resources.
|
||||
assert.deepEqual((await historyService.list('', 'test.js')).versions, []);
|
||||
await assert.rejects(fs.stat(path.join(root, 'script-history')), {
|
||||
code: 'ENOENT',
|
||||
});
|
||||
const app = express();
|
||||
app.use(express.json({ limit: '50mb' }));
|
||||
|
||||
Reference in New Issue
Block a user