mirror of
https://github.com/crystelf/crystelf-core.git
synced 2025-10-14 05:19:19 +00:00
feat:系统模块
This commit is contained in:
parent
3e3f8029e6
commit
d91f900f49
@ -24,6 +24,7 @@
|
||||
"axios": "^1.10.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"simple-git": "^3.28.0",
|
||||
"ssh2": "^1.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
25
pnpm-lock.yaml
generated
25
pnpm-lock.yaml
generated
@ -32,6 +32,9 @@ dependencies:
|
||||
rxjs:
|
||||
specifier: ^7.8.1
|
||||
version: 7.8.2
|
||||
simple-git:
|
||||
specifier: ^3.28.0
|
||||
version: 3.28.0
|
||||
ssh2:
|
||||
specifier: ^1.16.0
|
||||
version: 1.16.0
|
||||
@ -1159,6 +1162,18 @@ packages:
|
||||
'@jridgewell/sourcemap-codec': 1.5.4
|
||||
dev: true
|
||||
|
||||
/@kwsites/file-exists@1.1.1:
|
||||
resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==}
|
||||
dependencies:
|
||||
debug: 4.4.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@kwsites/promise-deferred@1.1.1:
|
||||
resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==}
|
||||
dev: false
|
||||
|
||||
/@lukeed/csprng@1.1.0:
|
||||
resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==}
|
||||
engines: {node: '>=8'}
|
||||
@ -5601,6 +5616,16 @@ packages:
|
||||
engines: {node: '>=14'}
|
||||
dev: true
|
||||
|
||||
/simple-git@3.28.0:
|
||||
resolution: {integrity: sha512-Rs/vQRwsn1ILH1oBUy8NucJlXmnnLeLCfcvbSehkPzbv3wwoFWIdtfd6Ndo6ZPhlPsCZ60CPI4rxurnwAa+a2w==}
|
||||
dependencies:
|
||||
'@kwsites/file-exists': 1.1.1
|
||||
'@kwsites/promise-deferred': 1.1.1
|
||||
debug: 4.4.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/sisteransi@1.0.5:
|
||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||
dev: true
|
||||
|
@ -2,8 +2,10 @@ import { Module } from '@nestjs/common';
|
||||
import { RootModule } from './root/root.module';
|
||||
import { AppConfigModule } from './config/config.module';
|
||||
import { PathModule } from './core/path/path.module';
|
||||
import { SystemModule } from './core/system/system.module';
|
||||
import { ToolsModule } from './core/tools/tools.module';
|
||||
|
||||
@Module({
|
||||
imports: [RootModule, AppConfigModule, PathModule],
|
||||
imports: [RootModule, AppConfigModule, PathModule, SystemModule, ToolsModule],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
0
src/core/auto-update/auto-update.module.ts
Normal file
0
src/core/auto-update/auto-update.module.ts
Normal file
94
src/core/auto-update/auto-update.service.ts
Normal file
94
src/core/auto-update/auto-update.service.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { readFileSync } from 'fs';
|
||||
import simpleGit, { SimpleGit } from 'simple-git';
|
||||
import { PathService } from '../path/path.service';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
@Injectable()
|
||||
export class AutoUpdateService {
|
||||
private readonly logger = new Logger(AutoUpdateService.name);
|
||||
private readonly git: SimpleGit;
|
||||
private readonly repoPath: string;
|
||||
|
||||
constructor(
|
||||
@Inject(PathService)
|
||||
private readonly pathService: PathService,
|
||||
) {
|
||||
this.git = simpleGit();
|
||||
this.repoPath = this.pathService.get('root');
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有远程更新
|
||||
*/
|
||||
async checkForUpdates(): Promise<boolean> {
|
||||
try {
|
||||
this.logger.log('检查仓库更新中...');
|
||||
|
||||
const status = await this.git.status();
|
||||
if (status.ahead > 0) {
|
||||
this.logger.warn('检测到本地仓库有未提交的更改,跳过更新');
|
||||
return false;
|
||||
}
|
||||
|
||||
this.logger.log('正在获取远程仓库信息...');
|
||||
await this.git.fetch();
|
||||
|
||||
const localBranch = status.current;
|
||||
const diffSummary = await this.git.diffSummary([
|
||||
`${localBranch}..origin/${localBranch}`,
|
||||
]);
|
||||
|
||||
if (diffSummary.files.length > 0) {
|
||||
this.logger.log('检测到远程仓库有更新!');
|
||||
|
||||
if (localBranch) {
|
||||
this.logger.log('正在拉取远程代码...');
|
||||
await this.git.pull('origin', localBranch);
|
||||
} else {
|
||||
this.logger.error('当前分支名称未知,无法执行拉取操作。');
|
||||
return false;
|
||||
}
|
||||
|
||||
this.logger.log('代码更新成功,开始更新依赖...');
|
||||
await this.updateDependencies();
|
||||
|
||||
this.logger.log('自动更新流程完成');
|
||||
return true;
|
||||
} else {
|
||||
this.logger.log('远程仓库没有新变化');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('检查仓库更新失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动安装依赖和构建
|
||||
*/
|
||||
private async updateDependencies(): Promise<void> {
|
||||
try {
|
||||
this.logger.log('执行 pnpm install...');
|
||||
await execAsync('pnpm install', { cwd: this.repoPath });
|
||||
this.logger.log('依赖安装完成');
|
||||
|
||||
const pkgPath = this.pathService.get('package');
|
||||
const pkgJson = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
||||
|
||||
if (pkgJson.scripts?.build) {
|
||||
this.logger.log('检测到 build 脚本,执行 pnpm build...');
|
||||
await execAsync('pnpm build', { cwd: this.repoPath });
|
||||
this.logger.log('构建完成');
|
||||
} else {
|
||||
this.logger.log('未检测到 build 脚本,跳过构建');
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('更新依赖或构建失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { AppConfigService } from '../../config/config.service';
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
@ -9,7 +8,7 @@ export class PathService {
|
||||
private readonly baseDir: string;
|
||||
private readonly logger = new Logger(PathService.name);
|
||||
|
||||
constructor(private readonly configService: AppConfigService) {
|
||||
constructor() {
|
||||
this.baseDir = path.join(__dirname, '../../..');
|
||||
this.initializePaths();
|
||||
}
|
||||
|
10
src/core/system/system.module.ts
Normal file
10
src/core/system/system.module.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SystemService } from './system.service';
|
||||
import { PathModule } from '../path/path.module';
|
||||
|
||||
@Module({
|
||||
imports: [PathModule],
|
||||
providers: [SystemService],
|
||||
exports: [SystemService],
|
||||
})
|
||||
export class SystemModule {}
|
53
src/core/system/system.service.ts
Normal file
53
src/core/system/system.service.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { PathService } from '../path/path.service';
|
||||
|
||||
@Injectable()
|
||||
export class SystemService {
|
||||
private readonly logger = new Logger(SystemService.name);
|
||||
private readonly restartFile: string;
|
||||
|
||||
constructor(
|
||||
@Inject(PathService)
|
||||
private readonly pathService: PathService,
|
||||
) {
|
||||
this.restartFile = path.join(
|
||||
this.pathService.get('temp'),
|
||||
'restart.timestamp',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重启前保存时间戳
|
||||
*/
|
||||
private markRestartTime(): void {
|
||||
const now = Date.now();
|
||||
fs.writeFileSync(this.restartFile, now.toString(), 'utf-8');
|
||||
this.logger.debug(`记录重启时间戳: ${now}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查重启时间戳
|
||||
*/
|
||||
checkRestartTime(): number | null {
|
||||
if (fs.existsSync(this.restartFile)) {
|
||||
const prev = Number(fs.readFileSync(this.restartFile, 'utf-8'));
|
||||
const duration = ((Date.now() - prev) / 1000 - 5).toFixed(2);
|
||||
fs.unlinkSync(this.restartFile);
|
||||
this.logger.debug(`检测到重启,耗时: ${duration}秒`);
|
||||
return Number(duration);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重启服务
|
||||
*/
|
||||
async restart(): Promise<void> {
|
||||
this.markRestartTime();
|
||||
this.logger.warn('服务即将重启..');
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
4
src/core/tools/retry-options.interface.ts
Normal file
4
src/core/tools/retry-options.interface.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface RetryOptions {
|
||||
maxAttempts: number;
|
||||
initialDelay: number;
|
||||
}
|
8
src/core/tools/tools.module.ts
Normal file
8
src/core/tools/tools.module.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ToolsService } from './tools.service';
|
||||
|
||||
@Module({
|
||||
providers: [ToolsService],
|
||||
exports: [ToolsService],
|
||||
})
|
||||
export class ToolsModule {}
|
51
src/core/tools/tools.service.ts
Normal file
51
src/core/tools/tools.service.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { RetryOptions } from './retry-options.interface';
|
||||
|
||||
@Injectable()
|
||||
export class ToolsService {
|
||||
private readonly logger = new Logger(ToolsService.name);
|
||||
|
||||
/**
|
||||
* 异步重试
|
||||
* @param operation
|
||||
* @param options
|
||||
*/
|
||||
async retry<T>(
|
||||
operation: () => Promise<T>,
|
||||
options: RetryOptions,
|
||||
): Promise<T> {
|
||||
let attempt = 0;
|
||||
let lastError: any;
|
||||
|
||||
while (attempt < options.maxAttempts) {
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
attempt++;
|
||||
|
||||
if (attempt < options.maxAttempts) {
|
||||
const delay = options.initialDelay * Math.pow(2, attempt - 1);
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.error('重试失败', lastError);
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从一个可迭代列表中随机选择一个对象
|
||||
*/
|
||||
getRandomItem<T>(list: T[]): T {
|
||||
return list[Math.floor(Math.random() * list.length)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取随机数
|
||||
*/
|
||||
getRandomDelay(min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import { Logger } from '@nestjs/common';
|
||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||
import { ResponseInterceptor } from './common/interceptors/response.interceptor';
|
||||
import { AllExceptionsFilter } from './common/filters/all-exception.filter';
|
||||
import { SystemService } from './core/system/system.service';
|
||||
|
||||
async function bootstrap() {
|
||||
Logger.log('晶灵核心初始化..');
|
||||
@ -11,6 +12,11 @@ async function bootstrap() {
|
||||
app.setGlobalPrefix('api');
|
||||
app.useGlobalInterceptors(new ResponseInterceptor());
|
||||
app.useGlobalFilters(new AllExceptionsFilter());
|
||||
const systemService = app.get(SystemService);
|
||||
const restartDuration = systemService.checkRestartTime();
|
||||
if (restartDuration) {
|
||||
new Logger('System').warn(`重启完成!耗时 ${restartDuration} 秒`);
|
||||
}
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('晶灵核心')
|
||||
.setDescription('为晶灵提供API服务')
|
||||
|
Loading…
x
Reference in New Issue
Block a user