mirror of
https://github.com/whyour/qinglong.git
synced 2025-05-22 14:26:07 +08:00
写入文件增加文件锁,避免竞争条件引起文件内容异常
This commit is contained in:
parent
7d43b14f81
commit
05f8bbd26e
|
@ -7,6 +7,7 @@ import { celebrate, Joi } from 'celebrate';
|
|||
import { join } from 'path';
|
||||
import { SAMPLE_FILES } from '../config/const';
|
||||
import ConfigService from '../services/config';
|
||||
import { writeFileWithLock } from '../shared/utils';
|
||||
const route = Router();
|
||||
|
||||
export default (app: Router) => {
|
||||
|
@ -77,7 +78,7 @@ export default (app: Router) => {
|
|||
if (name.startsWith('data/scripts/')) {
|
||||
path = join(config.rootPath, name);
|
||||
}
|
||||
await fs.writeFile(path, content);
|
||||
await writeFileWithLock(path, content);
|
||||
res.send({ code: 200, message: '保存成功' });
|
||||
} catch (e) {
|
||||
return next(e);
|
||||
|
|
|
@ -8,6 +8,7 @@ import { celebrate, Joi } from 'celebrate';
|
|||
import path, { join, parse } from 'path';
|
||||
import ScriptService from '../services/script';
|
||||
import multer from 'multer';
|
||||
import { writeFileWithLock } from '../shared/utils';
|
||||
const route = Router();
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
|
@ -156,7 +157,7 @@ export default (app: Router) => {
|
|||
await rmPath(originFilePath);
|
||||
}
|
||||
}
|
||||
await fs.writeFile(filePath, content);
|
||||
await writeFileWithLock(filePath, content);
|
||||
return res.send({ code: 200 });
|
||||
} catch (e) {
|
||||
return next(e);
|
||||
|
@ -182,7 +183,7 @@ export default (app: Router) => {
|
|||
path: string;
|
||||
};
|
||||
const filePath = join(config.scriptPath, path, filename);
|
||||
await fs.writeFile(filePath, content);
|
||||
await writeFileWithLock(filePath, content);
|
||||
return res.send({ code: 200 });
|
||||
} catch (e) {
|
||||
return next(e);
|
||||
|
@ -261,7 +262,7 @@ export default (app: Router) => {
|
|||
let { filename, content, path } = req.body;
|
||||
const { name, ext } = parse(filename);
|
||||
const filePath = join(config.scriptPath, path, `${name}.swap${ext}`);
|
||||
await fs.writeFile(filePath, content || '', { encoding: 'utf8' });
|
||||
await writeFileWithLock(filePath, content || '');
|
||||
|
||||
const scriptService = Container.get(ScriptService);
|
||||
const result = await scriptService.runScript(filePath);
|
||||
|
|
|
@ -10,6 +10,7 @@ import { load } from 'js-yaml';
|
|||
import config from './index';
|
||||
import { TASK_COMMAND } from './const';
|
||||
import Logger from '../loaders/logger';
|
||||
import { writeFileWithLock } from '../shared/utils';
|
||||
|
||||
export * from './share';
|
||||
|
||||
|
@ -170,7 +171,7 @@ export async function fileExist(file: any) {
|
|||
|
||||
export async function createFile(file: string, data: string = '') {
|
||||
await fs.mkdir(path.dirname(file), { recursive: true });
|
||||
await fs.writeFile(file, data);
|
||||
await writeFileWithLock(file, data);
|
||||
}
|
||||
|
||||
export async function handleLogPath(
|
||||
|
|
|
@ -3,6 +3,7 @@ import path from 'path';
|
|||
import os from 'os';
|
||||
import Logger from './logger';
|
||||
import { fileExist } from '../config/util';
|
||||
import { writeFileWithLock } from '../shared/utils';
|
||||
|
||||
const rootPath = process.env.QL_DIR as string;
|
||||
let dataPath = path.join(rootPath, 'data/');
|
||||
|
@ -99,46 +100,46 @@ export default async () => {
|
|||
// 初始化文件
|
||||
|
||||
if (!confFileExist) {
|
||||
await fs.writeFile(confFile, await fs.readFile(sampleConfigFile));
|
||||
await writeFileWithLock(confFile, await fs.readFile(sampleConfigFile));
|
||||
}
|
||||
|
||||
await fs.writeFile(jsNotifyFile, await fs.readFile(sampleNotifyJsFile));
|
||||
await fs.writeFile(pyNotifyFile, await fs.readFile(sampleNotifyPyFile));
|
||||
await writeFileWithLock(jsNotifyFile, await fs.readFile(sampleNotifyJsFile));
|
||||
await writeFileWithLock(pyNotifyFile, await fs.readFile(sampleNotifyPyFile));
|
||||
|
||||
if (!scriptNotifyJsFileExist) {
|
||||
await fs.writeFile(
|
||||
await writeFileWithLock(
|
||||
scriptNotifyJsFile,
|
||||
await fs.readFile(sampleNotifyJsFile),
|
||||
);
|
||||
}
|
||||
|
||||
if (!scriptNotifyPyFileExist) {
|
||||
await fs.writeFile(
|
||||
await writeFileWithLock(
|
||||
scriptNotifyPyFile,
|
||||
await fs.readFile(sampleNotifyPyFile),
|
||||
);
|
||||
}
|
||||
|
||||
if (!TaskBeforeFileExist) {
|
||||
await fs.writeFile(TaskBeforeFile, await fs.readFile(sampleTaskShellFile));
|
||||
await writeFileWithLock(TaskBeforeFile, await fs.readFile(sampleTaskShellFile));
|
||||
}
|
||||
|
||||
if (!TaskBeforeJsFileExist) {
|
||||
await fs.writeFile(
|
||||
await writeFileWithLock(
|
||||
TaskBeforeJsFile,
|
||||
'// The JavaScript code that executes before the JavaScript task execution will execute.',
|
||||
);
|
||||
}
|
||||
|
||||
if (!TaskBeforePyFileExist) {
|
||||
await fs.writeFile(
|
||||
await writeFileWithLock(
|
||||
TaskBeforePyFile,
|
||||
'# The Python code that executes before the Python task execution will execute.',
|
||||
);
|
||||
}
|
||||
|
||||
if (!TaskAfterFileExist) {
|
||||
await fs.writeFile(TaskAfterFile, await fs.readFile(sampleTaskShellFile));
|
||||
await writeFileWithLock(TaskAfterFile, await fs.readFile(sampleTaskShellFile));
|
||||
}
|
||||
|
||||
Logger.info('✌️ Init file down');
|
||||
|
|
|
@ -21,6 +21,7 @@ import { spawn } from 'cross-spawn';
|
|||
import dayjs from 'dayjs';
|
||||
import pickBy from 'lodash/pickBy';
|
||||
import omit from 'lodash/omit';
|
||||
import { writeFileWithLock } from '../shared/utils';
|
||||
|
||||
@Service()
|
||||
export default class CronService {
|
||||
|
@ -601,7 +602,7 @@ export default class CronService {
|
|||
}
|
||||
});
|
||||
|
||||
await fs.writeFile(config.crontabFile, crontab_string);
|
||||
await writeFileWithLock(config.crontabFile, crontab_string);
|
||||
|
||||
execSync(`crontab ${config.crontabFile}`);
|
||||
await CrontabModel.update({ saved: true }, { where: {} });
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
} from '../data/env';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import { FindOptions, Op } from 'sequelize';
|
||||
import { writeFileWithLock } from '../shared/utils';
|
||||
|
||||
@Service()
|
||||
export default class EnvService {
|
||||
|
@ -225,8 +226,8 @@ export default class EnvService {
|
|||
}
|
||||
}
|
||||
}
|
||||
await fs.writeFile(config.envFile, env_string);
|
||||
await fs.writeFile(config.jsEnvFile, js_env_string);
|
||||
await fs.writeFile(config.pyEnvFile, py_env_string);
|
||||
await writeFileWithLock(config.envFile, env_string);
|
||||
await writeFileWithLock(config.jsEnvFile, js_env_string);
|
||||
await writeFileWithLock(config.pyEnvFile, py_env_string);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Subscription } from '../data/subscription';
|
|||
import { formatUrl } from '../config/subscription';
|
||||
import config from '../config';
|
||||
import { fileExist, rmPath } from '../config/util';
|
||||
import { writeFileWithLock } from '../shared/utils';
|
||||
|
||||
@Service()
|
||||
export default class SshKeyService {
|
||||
|
@ -25,13 +26,12 @@ export default class SshKeyService {
|
|||
if (_exist) {
|
||||
config = await fs.readFile(this.sshConfigFilePath, { encoding: 'utf-8' });
|
||||
} else {
|
||||
await fs.writeFile(this.sshConfigFilePath, '');
|
||||
await writeFileWithLock(this.sshConfigFilePath, '');
|
||||
}
|
||||
if (!config.includes(this.sshConfigHeader)) {
|
||||
await fs.writeFile(
|
||||
await writeFileWithLock(
|
||||
this.sshConfigFilePath,
|
||||
`${this.sshConfigHeader}\n\n${config}`,
|
||||
{ encoding: 'utf-8' },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -41,10 +41,14 @@ export default class SshKeyService {
|
|||
key: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await fs.writeFile(path.join(this.sshPath, alias), `${key}${os.EOL}`, {
|
||||
await writeFileWithLock(
|
||||
path.join(this.sshPath, alias),
|
||||
`${key}${os.EOL}`,
|
||||
{
|
||||
encoding: 'utf8',
|
||||
mode: '400',
|
||||
});
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error('生成私钥文件失败', error);
|
||||
}
|
||||
|
@ -74,12 +78,9 @@ export default class SshKeyService {
|
|||
this.sshPath,
|
||||
alias,
|
||||
)}\n StrictHostKeyChecking no\n${proxyStr}`;
|
||||
await fs.writeFile(
|
||||
await writeFileWithLock(
|
||||
`${path.join(this.sshPath, `${alias}.config`)}`,
|
||||
config,
|
||||
{
|
||||
encoding: 'utf8',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -102,7 +103,11 @@ export default class SshKeyService {
|
|||
await this.generateSingleSshConfig(alias, host, proxy);
|
||||
}
|
||||
|
||||
public async removeSSHKey(alias: string, host: string, proxy?: string): Promise<void> {
|
||||
public async removeSSHKey(
|
||||
alias: string,
|
||||
host: string,
|
||||
proxy?: string,
|
||||
): Promise<void> {
|
||||
await this.removePrivateKeyFile(alias);
|
||||
await this.removeSshConfig(alias);
|
||||
}
|
||||
|
|
27
back/shared/utils.ts
Normal file
27
back/shared/utils.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { lock } from 'proper-lockfile';
|
||||
import { writeFile, open } from 'fs/promises';
|
||||
import { fileExist } from '../config/util';
|
||||
|
||||
export async function writeFileWithLock(
|
||||
path: string,
|
||||
content: string | Buffer,
|
||||
options: Parameters<typeof writeFile>[2] = {},
|
||||
) {
|
||||
if (typeof options === 'string') {
|
||||
options = { encoding: options };
|
||||
}
|
||||
if (!(await fileExist(path))) {
|
||||
const fileHandle = await open(path, 'w');
|
||||
fileHandle.close();
|
||||
}
|
||||
const release = await lock(path, {
|
||||
retries: {
|
||||
retries: 10,
|
||||
factor: 2,
|
||||
minTimeout: 100,
|
||||
maxTimeout: 3000,
|
||||
},
|
||||
});
|
||||
await writeFile(path, content, { encoding: 'utf8', ...options });
|
||||
await release();
|
||||
}
|
|
@ -6,6 +6,7 @@ import fs from 'fs';
|
|||
import config from './config';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import { writeFileWithLock } from './shared/utils';
|
||||
|
||||
const tokenFile = path.join(config.configPath, 'token.json');
|
||||
|
||||
|
@ -25,16 +26,7 @@ async function getToken() {
|
|||
}
|
||||
|
||||
async function writeFile(data: any) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.writeFile(
|
||||
tokenFile,
|
||||
`${JSON.stringify(data)}${os.EOL}`,
|
||||
{ encoding: 'utf8' },
|
||||
() => {
|
||||
resolve();
|
||||
},
|
||||
);
|
||||
});
|
||||
await writeFileWithLock(tokenFile, `${JSON.stringify(data)}${os.EOL}`);
|
||||
}
|
||||
|
||||
getToken();
|
||||
|
|
|
@ -103,7 +103,8 @@
|
|||
"request-ip": "3.3.0",
|
||||
"ip2region": "2.3.0",
|
||||
"keyv": "^5.2.3",
|
||||
"@keyv/sqlite": "^4.0.1"
|
||||
"@keyv/sqlite": "^4.0.1",
|
||||
"proper-lockfile": "^4.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"moment": "2.30.1",
|
||||
|
@ -138,6 +139,7 @@
|
|||
"@types/sockjs-client": "^1.5.1",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/request-ip": "0.0.41",
|
||||
"@types/proper-lockfile": "^4.1.4",
|
||||
"@uiw/codemirror-extensions-langs": "^4.21.9",
|
||||
"@uiw/react-codemirror": "^4.21.9",
|
||||
"@umijs/max": "^4.3.36",
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
lockfileVersion: '6.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
sqlite3: git+https://github.com/whyour/node-sqlite3.git#v1.0.3
|
||||
|
||||
|
@ -97,6 +101,9 @@ dependencies:
|
|||
p-queue-cjs:
|
||||
specifier: 7.3.4
|
||||
version: 7.3.4
|
||||
proper-lockfile:
|
||||
specifier: ^4.1.2
|
||||
version: 4.1.2
|
||||
protobufjs:
|
||||
specifier: ^7.4.0
|
||||
version: 7.4.0
|
||||
|
@ -201,6 +208,9 @@ devDependencies:
|
|||
'@types/nodemailer':
|
||||
specifier: ^6.4.4
|
||||
version: 6.4.17
|
||||
'@types/proper-lockfile':
|
||||
specifier: ^4.1.4
|
||||
version: 4.1.4
|
||||
'@types/qrcode.react':
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.5
|
||||
|
@ -4634,6 +4644,12 @@ packages:
|
|||
resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==}
|
||||
dev: true
|
||||
|
||||
/@types/proper-lockfile@4.1.4:
|
||||
resolution: {integrity: sha512-uo2ABllncSqg9F1D4nugVl9v93RmjxF6LJzQLMLDdPaXCUIDPeOJ21Gbqi43xNKzBi/WQ0Q0dICqufzQbMjipQ==}
|
||||
dependencies:
|
||||
'@types/retry': 0.12.5
|
||||
dev: true
|
||||
|
||||
/@types/qrcode.react@1.0.5:
|
||||
resolution: {integrity: sha512-BghPtnlwvrvq8QkGa1H25YnN+5OIgCKFuQruncGWLGJYOzeSKiix/4+B9BtfKF2wf5ja8yfyWYA3OXju995G8w==}
|
||||
dependencies:
|
||||
|
@ -4683,6 +4699,10 @@ packages:
|
|||
'@types/node': 17.0.45
|
||||
dev: false
|
||||
|
||||
/@types/retry@0.12.5:
|
||||
resolution: {integrity: sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==}
|
||||
dev: true
|
||||
|
||||
/@types/semver@7.5.8:
|
||||
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
|
||||
dev: true
|
||||
|
@ -12466,6 +12486,14 @@ packages:
|
|||
react-is: 16.13.1
|
||||
dev: true
|
||||
|
||||
/proper-lockfile@4.1.2:
|
||||
resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==}
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
retry: 0.12.0
|
||||
signal-exit: 3.0.7
|
||||
dev: false
|
||||
|
||||
/protobufjs@7.4.0:
|
||||
resolution: {integrity: sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
@ -14041,7 +14069,6 @@ packages:
|
|||
engines: {node: '>= 4'}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/reusify@1.0.4:
|
||||
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
|
||||
|
@ -16174,7 +16201,3 @@ packages:
|
|||
- encoding
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
|
|
@ -3,9 +3,6 @@
|
|||
"target": "es2017",
|
||||
"lib": ["ESNext"],
|
||||
"typeRoots": ["./node_modules/celebrate/lib", "./node_modules/@types"],
|
||||
"paths": {
|
||||
"@/*": ["./back/*"]
|
||||
},
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
|
|
|
@ -22,10 +22,6 @@
|
|||
"allowJs": true,
|
||||
"noEmit": false
|
||||
},
|
||||
"include": ["src/**/*", ".umirc.ts", "typings.d.ts", "back/**/*"],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"static",
|
||||
"data",
|
||||
]
|
||||
"include": ["src/**/*", ".umirc.ts", "typings.d.ts"],
|
||||
"exclude": ["node_modules", "static", "data"]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user