From b69ff2895ef0cdee41933cf401ef9230adcce42e Mon Sep 17 00:00:00 2001 From: whyour Date: Mon, 10 Jul 2023 23:48:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BE=9D=E8=B5=96=E6=98=AF?= =?UTF-8?q?=E5=90=A6=E5=B7=B2=E7=BB=8F=E5=AE=89=E8=A3=85=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back/config/util.ts | 12 +++++++ back/data/dependence.ts | 24 +++++++++---- back/services/dependence.ts | 66 +++++++++++++++++++++++++++------- back/shared/pLimit.ts | 13 +++---- src/pages/dependence/index.tsx | 32 +++++++---------- src/pages/dependence/modal.tsx | 2 +- 6 files changed, 105 insertions(+), 44 deletions(-) diff --git a/back/config/util.ts b/back/config/util.ts index f183eec3..e6588417 100644 --- a/back/config/util.ts +++ b/back/config/util.ts @@ -399,6 +399,18 @@ export function promiseExec(command: string): Promise { }); } +export function promiseExecSuccess(command: string): Promise { + return new Promise((resolve) => { + exec( + command, + { maxBuffer: 200 * 1024 * 1024, encoding: 'utf8' }, + (err, stdout, stderr) => { + resolve(stdout || ''); + }, + ); + }); +} + export function parseHeaders(headers: string) { if (!headers) return {}; diff --git a/back/data/dependence.ts b/back/data/dependence.ts index 7fd0a6ed..ae202928 100644 --- a/back/data/dependence.ts +++ b/back/data/dependence.ts @@ -4,9 +4,9 @@ import { DataTypes, Model, ModelDefined } from 'sequelize'; export class Dependence { timestamp?: string; id?: number; - status?: DependenceStatus; - type?: DependenceTypes; - name?: number; + status: DependenceStatus; + type: DependenceTypes; + name: string; log?: string[]; remark?: string; @@ -42,19 +42,31 @@ export enum DependenceTypes { export enum InstallDependenceCommandTypes { 'pnpm add -g', - 'pip3 install', + 'pip3 install --disable-pip-version-check --root-user-action=ignore', 'apk add', } +export enum GetDependenceCommandTypes { + 'pnpm ls -g ', + 'pip3 list --disable-pip-version-check --root-user-action=ignore', + 'apk info', +} + +export enum versionDependenceCommandTypes { + '@', + '==', + '=', +} + export enum unInstallDependenceCommandTypes { 'pnpm remove -g', - 'pip3 uninstall -y', + 'pip3 uninstall --disable-pip-version-check --root-user-action=ignore -y', 'apk del', } export interface DependenceInstance extends Model, - Dependence {} + Dependence { } export const DependenceModel = sequelize.define( 'Dependence', { diff --git a/back/services/dependence.ts b/back/services/dependence.ts index 6b76f7b0..8e2638e1 100644 --- a/back/services/dependence.ts +++ b/back/services/dependence.ts @@ -8,11 +8,13 @@ import { DependenceTypes, unInstallDependenceCommandTypes, DependenceModel, + GetDependenceCommandTypes, + versionDependenceCommandTypes, } from '../data/dependence'; import { spawn } from 'cross-spawn'; import SockService from './sock'; import { FindOptions, Op } from 'sequelize'; -import { concurrentRun } from '../config/util'; +import { promiseExecSuccess } from '../config/util'; import dayjs from 'dayjs'; import taskLimit from '../shared/pLimit'; @@ -21,7 +23,7 @@ export default class DependenceService { constructor( @Inject('logger') private logger: winston.Logger, private sockService: SockService, - ) { } + ) {} public async create(payloads: Dependence[]): Promise { const tabs = payloads.map((x) => { @@ -137,9 +139,17 @@ export default class DependenceService { } private async updateLog(ids: number[], log: string): Promise { - const doc = await DependenceModel.findOne({ where: { id: ids } }); - const newLog = doc?.log ? [...doc.log, log] : [log]; - await DependenceModel.update({ log: newLog }, { where: { id: ids } }); + taskLimit.updateDepLog(async () => { + const docs = await DependenceModel.findAll({ where: { id: ids } }); + for (const doc of docs) { + const newLog = doc?.log ? [...doc.log, log] : [log]; + await DependenceModel.update( + { log: newLog }, + { where: { id: doc.id } }, + ); + } + return null; + }); } public installOrUninstallDependency( @@ -155,7 +165,7 @@ export default class DependenceService { : DependenceStatus.removing; await DependenceModel.update({ status }, { where: { id: depIds } }); - const socketMessageType = !force + const socketMessageType = isInstall ? 'installDependence' : 'uninstallDependence'; const depName = dependency.name; @@ -163,7 +173,7 @@ export default class DependenceService { isInstall ? InstallDependenceCommandTypes : unInstallDependenceCommandTypes - )[dependency.type as any]; + )[dependency.type]; const actionText = isInstall ? '安装' : '删除'; const startTime = dayjs(); @@ -175,7 +185,39 @@ export default class DependenceService { message, references: depIds, }); - await this.updateLog(depIds, message); + this.updateLog(depIds, message); + + // 判断是否已经安装过依赖 + if (isInstall) { + const getCommandPrefix = GetDependenceCommandTypes[dependency.type]; + const depVersionStr = versionDependenceCommandTypes[dependency.type]; + const [_depName] = dependency.name.split(depVersionStr); + const depInfo = ( + await promiseExecSuccess( + dependency.type === DependenceTypes.linux + ? `${getCommandPrefix} ${_depName}` + : `${getCommandPrefix} | grep "${_depName}"`, + ) + ).replace(/\s{2,}/, ' '); + + if (depInfo) { + const endTime = dayjs(); + const _message = `检测到已经安装 ${_depName}\n\n${depInfo}\n跳过安装\n\n依赖${actionText}成功,结束时间 ${endTime.format( + 'YYYY-MM-DD HH:mm:ss', + )},耗时 ${endTime.diff(startTime, 'second')} 秒`; + this.sockService.sendMessage({ + type: socketMessageType, + message: _message, + references: depIds, + }); + this.updateLog(depIds, _message); + await DependenceModel.update( + { status: DependenceStatus.installed }, + { where: { id: depIds } }, + ); + return resolve(null); + } + } const cp = spawn(`${depRunCommand} ${depName}`, { shell: '/bin/bash', @@ -187,7 +229,7 @@ export default class DependenceService { message: data.toString(), references: depIds, }); - await this.updateLog(depIds, data.toString()); + this.updateLog(depIds, data.toString()); }); cp.stderr.on('data', async (data) => { @@ -196,7 +238,7 @@ export default class DependenceService { message: data.toString(), references: depIds, }); - await this.updateLog(depIds, data.toString()); + this.updateLog(depIds, data.toString()); }); cp.on('error', async (err) => { @@ -205,7 +247,7 @@ export default class DependenceService { message: JSON.stringify(err), references: depIds, }); - await this.updateLog(depIds, JSON.stringify(err)); + this.updateLog(depIds, JSON.stringify(err)); }); cp.on('close', async (code) => { @@ -221,7 +263,7 @@ export default class DependenceService { message, references: depIds, }); - await this.updateLog(depIds, message); + this.updateLog(depIds, message); let status = null; if (isSucceed) { diff --git a/back/shared/pLimit.ts b/back/shared/pLimit.ts index 07ee17ed..91c83403 100644 --- a/back/shared/pLimit.ts +++ b/back/shared/pLimit.ts @@ -4,6 +4,7 @@ import { AuthDataType, AuthModel } from "../data/auth"; class TaskLimit { private oneLimit = pLimit(1); + private updateLogLimit = pLimit(1); private cpuLimit = pLimit(Math.max(os.cpus().length, 4)); constructor() { @@ -22,15 +23,15 @@ class TaskLimit { } public runWithCpuLimit(fn: () => Promise): Promise { - return this.cpuLimit(() => { - return fn(); - }); + return this.cpuLimit(fn); } public runOneByOne(fn: () => Promise): Promise { - return this.oneLimit(() => { - return fn(); - }); + return this.oneLimit(fn); + } + + public updateDepLog(fn: () => Promise): Promise { + return this.updateLogLimit(fn); } } diff --git a/src/pages/dependence/index.tsx b/src/pages/dependence/index.tsx index 871a86d6..ceeb8ed2 100644 --- a/src/pages/dependence/index.tsx +++ b/src/pages/dependence/index.tsx @@ -33,6 +33,7 @@ import DependenceLogModal from './logModal'; import { useOutletContext } from '@umijs/max'; import { SharedContext } from '@/layouts'; import useTableScrollHeight from '@/hooks/useTableScrollHeight'; +import dayjs from 'dayjs'; const { Text } = Typography; const { Search } = Input; @@ -123,27 +124,20 @@ const Dependence = () => { dataIndex: 'remark', key: 'remark', }, + { + title: '更新时间', + key: 'updatedAt', + dataIndex: 'updatedAt', + render: (text: string) => { + return {dayjs(text).format('YYYY-MM-DD HH:mm:ss')}; + }, + }, { title: '创建时间', - key: 'timestamp', - dataIndex: 'timestamp', - render: (text: string, record: any) => { - const language = navigator.language || navigator.languages[0]; - const time = record.createdAt || record.timestamp; - const date = new Date(time) - .toLocaleString(language, { - hour12: false, - }) - .replace(' 24:', ' 00:'); - return ( - - {date} - - ); + key: 'createdAt', + dataIndex: 'createdAt', + render: (text: string) => { + return {dayjs(text).format('YYYY-MM-DD HH:mm:ss')}; }, }, { diff --git a/src/pages/dependence/modal.tsx b/src/pages/dependence/modal.tsx index 6c3fb9d3..edcc7695 100644 --- a/src/pages/dependence/modal.tsx +++ b/src/pages/dependence/modal.tsx @@ -122,7 +122,7 @@ const DependenceModal = ({ name="name" label="名称" rules={[ - { required: true, message: '请输入依赖名称', whitespace: true }, + { required: true, message: '请输入依赖名称,支持指定版本', whitespace: true }, ]} >