feat:系统模块

This commit is contained in:
Jerry 2025-07-25 16:03:12 +08:00
parent 3e3f8029e6
commit d91f900f49
12 changed files with 256 additions and 3 deletions

View File

@ -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
View File

@ -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

View File

@ -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 {}

View 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);
}
}
}

View File

@ -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();
}

View 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 {}

View 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);
}
}

View File

@ -0,0 +1,4 @@
export interface RetryOptions {
maxAttempts: number;
initialDelay: number;
}

View File

@ -0,0 +1,8 @@
import { Module } from '@nestjs/common';
import { ToolsService } from './tools.service';
@Module({
providers: [ToolsService],
exports: [ToolsService],
})
export class ToolsModule {}

View 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;
}
}

View File

@ -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服务')