refactor: 优化检查机制

This commit is contained in:
ningmengchongshui 2024-06-15 09:27:19 +08:00
parent 4fe35676f3
commit 54aa32d0e4
14 changed files with 354 additions and 494 deletions

View File

@ -1,10 +0,0 @@
{
"apps": [
{
"name": "Miao-Yunzai",
"script": "./index.js",
"max_memory_restart": "512M",
"restart_delay": 60000
}
]
}

View File

@ -1,6 +1,5 @@
import '../src/init/modules.js'
import '../src/init/logger.js'
import '../src/init/config.js' import '../src/init/config.js'
import '../src/init/logger.js'
import '../src/init/redis.js' import '../src/init/redis.js'
import './tailwindcss.js' import './tailwindcss.js'
import Koa from 'koa' import Koa from 'koa'

View File

@ -5,8 +5,24 @@ module.exports = {
name: 'Miao-Yunzai', name: 'Miao-Yunzai',
script: './index.js', script: './index.js',
args: argv, args: argv,
max_memory_restart: '512M', // 超时时间内进程仍未终止,则 PM2 将强制终止该进程
restart_delay: 60000 kill_timeout: 5000,
// 发送意外重启
autorestart: true,
// 进程到达指定内存时重启
max_memory_restart: '2G',
// 进程重启之间的延迟时间
restart_delay: 5000,
// 进程重启之间的最大延迟时间
restart_delay_max: 10000,
// 将 PM2 进程列表自动保存到文件中
autodump: true,
// 不监听文件变化
watch: false,
env: {
NODE_ENV: 'production'
}
} }
] ]
} }
console.log('module.exports', module.exports)

View File

@ -1,37 +0,0 @@
import { execAsync, readJSON } from "./utils.js"
/**
*
* @returns
*/
export async function checkRun() {
/**
*
*/
if (process.argv[1].includes('pm2')) return
/**
*
*/
if (process.argv[1].includes('test')) return
/**
*
*/
const cfg = readJSON('./config/pm2/pm2.json')
/**
*
*/
if (!cfg) return
/**
*
*/
execAsync(`pm2 show ${cfg.apps[0].name}`).then((status) => {
/**
*
*/
if (status.stdout.includes('online')) {
logger.mark('检测到后台正在运行')
logger.mark('已停止后台进程,防止重复运行')
execAsync(`pm2 stop ${cfg.apps[0].name}`).catch(logger.error)
}
}).catch(() => { })
}

View File

@ -1,107 +0,0 @@
import log4js from 'log4js'
import chalk from 'chalk'
import cfg from './config.js'
import { mkdirSync } from 'node:fs'
/**
*
* @returns
*/
function createLog() {
// log4js.levels.levels[5].level = Number.MAX_VALUE
// log4js.levels.levels.sort((a, b) => a.level - b.level)
log4js.configure({
appenders: {
console: {
type: 'console',
layout: {
type: 'pattern',
pattern: '%[[MYZ-V4][%d{hh:mm:ss.SSS}][%4.4p]%] %m'
}
},
command: {
type: 'dateFile', // 可以是console,dateFile,file,Logstash等
filename: 'logs/command', // 将会按照filename和pattern拼接文件名
pattern: 'yyyy-MM-dd.log',
numBackups: 15,
alwaysIncludePattern: true,
layout: {
type: 'pattern',
pattern: '[%d{hh:mm:ss.SSS}][%4.4p] %m'
}
},
error: {
type: 'file',
filename: 'logs/error.log',
alwaysIncludePattern: true,
layout: {
type: 'pattern',
pattern: '[%d{hh:mm:ss.SSS}][%4.4p] %m'
}
}
},
categories: {
default: { appenders: ['console'], level: cfg.bot.log_level },
command: { appenders: ['console', 'command'], level: 'warn' },
error: { appenders: ['console', 'command', 'error'], level: 'error' }
}
})
const defaultLogger = log4js.getLogger('message')
const commandLogger = log4js.getLogger('command')
const errorLogger = log4js.getLogger('error')
/**
* error日志等级
*/
const logger = {
trace() {
defaultLogger.trace.call(defaultLogger, ...arguments)
},
debug() {
defaultLogger.debug.call(defaultLogger, ...arguments)
},
info() {
defaultLogger.info.call(defaultLogger, ...arguments)
},
// warn及以上的日志采用error策略
warn() {
commandLogger.warn.call(defaultLogger, ...arguments)
},
error() {
errorLogger.error.call(errorLogger, ...arguments)
},
fatal() {
errorLogger.fatal.call(errorLogger, ...arguments)
},
mark() {
errorLogger.mark.call(commandLogger, ...arguments)
}
}
return logger
}
/**
*
*/
export function loggerInit() {
/**
*
*/
mkdirSync('./logs', {
'recursive': true
})
/**
* logger
*/
global.logger = createLog() as any
logger.chalk = chalk
logger.red = chalk.red
logger.green = chalk.green
logger.yellow = chalk.yellow
logger.blue = chalk.blue
logger.magenta = chalk.magenta
logger.cyan = chalk.cyan
}

View File

@ -1,76 +0,0 @@
import cfg from "./config.js"
import { execAsync, sleep } from "./utils.js"
import { createClient } from "redis"
/**
* redis客户端
* @returns
*/
export async function redisInit() {
const rc = cfg.redis
const redisUn = rc.username || ""
let redisPw = rc.password ? `:${rc.password}` : ""
if (rc.username || rc.password) {
redisPw += "@"
}
const redisUrl = `redis://${redisUn}${redisPw}${rc.host}:${rc.port}/${rc.db}`
let client = createClient({ url: redisUrl })
try {
logger.info(`正在连接 ${logger.blue(redisUrl)}`)
await client.connect()
} catch (err) {
logger.error(`Redis 错误:${logger.red(err)}`)
const cmd = "redis-server --save 900 1 --save 300 10 --daemonize yes" + await aarch64()
logger.info("正在启动 Redis...")
await execAsync(cmd)
await sleep(1000)
try {
client = createClient({ url: redisUrl })
await client.connect()
} catch (err) {
logger.error(`Redis 错误:${logger.red(err)}`)
logger.error(`请先启动 Redis${logger.blue(cmd)}`)
process.exit()
}
}
client.on("error", async err => {
logger.error(`Redis 错误:${logger.red(err)}`)
const cmd = "redis-server --save 900 1 --save 300 10 --daemonize yes" + await aarch64()
logger.error(`请先启动 Redis${cmd}`)
process.exit()
})
/** 全局变量 redis */
global.redis = client as any
logger.info("Redis 连接成功")
return client
}
/**
*
* @returns
*/
async function aarch64() {
if (process.platform == "win32") return ""
return await execAsync("uname -m").then(async arch => {
if (arch.stdout && arch.stdout.includes("aarch64")) {
/** 判断redis版本 */
let v = await execAsync("redis-server -v")
if (v.stdout) {
const data = v.stdout.match(/v=(\d)./)
/** 忽略arm警告 */
if (data && Number(data[1]) >= 6) {
return " --ignore-warnings ARM64-COW-BUG"
}
}
}
return ''
})
}

View File

@ -1,42 +1,7 @@
import fs from 'node:fs'
import { exec } from 'child_process'
import { join } from 'path'
/** /**
* *
* @param ms * @param ms
*/ */
export function sleep(ms: number) { export function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms)) return new Promise(resolve => setTimeout(resolve, ms))
} }
/**
*
* @param cmd
* @returns
*/
export function execAsync(cmd: string): Promise<{
stdout: string
stderr: string
}> {
return new Promise((resolve, reject) => {
exec(cmd, (error, stdout, stderr) => {
if (error) reject(error)
resolve({ stdout, stderr })
})
})
}
/**
*
* @param dir
* @returns
*/
export function readJSON(dir: string) {
try {
const cfg = fs.readFileSync(join(process.cwd(), dir), 'utf-8')
return JSON.parse(cfg)
} catch {
return false
}
}

View File

@ -1,104 +1,5 @@
import './init/modules.js'
import './init/config.js' import './init/config.js'
import './init/logger.js' import './init/logger.js'
import './init/redis.js' import './init/redis.js'
import { promises } from 'node:fs' import './init/process.js'
import yaml from 'yaml' import './init/run.js'
import { BOT_NAME } from './config'
import { CONFIG_INIT_PATH } from './config/system.js'
import { checkRun } from './config/check.js'
/**
*
*/
logger.mark(`${BOT_NAME} 启动中...`)
/**
*
*/
process.title = BOT_NAME
/**
*
*/
process.env.TZ = 'Asia/Shanghai'
/**
*
*/
process.on('SIGHUP', () => process.exit())
/**
*
*/
process.on('uncaughtException', error => {
if (typeof logger == 'undefined') console.log(error)
else logger.error(error)
})
/**
* Promise错误
*/
process.on('unhandledRejection', error => {
if (typeof logger == 'undefined') console.log(error)
else logger.error(error)
})
/**
* 退
*/
process.on('exit', async () => {
if (typeof redis != 'undefined') {
await redis.save()
}
if (typeof logger == 'undefined') {
console.log(`${BOT_NAME} 已停止运行`)
} else {
logger.mark(logger.magenta(`${BOT_NAME} 已停止运行`))
}
})
/**
*
*/
let title = BOT_NAME
//
const qq = await promises
.readFile(`./${CONFIG_INIT_PATH}qq.yaml`, 'utf-8')
.then(yaml.parse)
.catch(() => null)
/**
*
*/
if (qq) {
title += `@${qq.qq || ''}`
switch (qq.platform) {
case 1: {
title += ' 安卓手机'
break
}
case 2: {
title += ' aPad'
break
}
case 3: {
title += ' 安卓手表'
break
}
case 4: {
title += ' MacOS'
break
}
case 5: {
title += ' iPad'
break
}
case 6: {
title += ' Tim'
break
}
default: {
break
}
}
}
/**
*
*/
process.title = title
/**
*
*/
await checkRun()

View File

@ -1,2 +1,101 @@
import { loggerInit } from '../config/log' import log4js from 'log4js'
loggerInit() import chalk from 'chalk'
import cfg from '../config/config.js'
import { mkdirSync } from 'node:fs'
/**
*
* @returns
*/
function createLog() {
// log4js.levels.levels[5].level = Number.MAX_VALUE
// log4js.levels.levels.sort((a, b) => a.level - b.level)
log4js.configure({
appenders: {
console: {
type: 'console',
layout: {
type: 'pattern',
pattern: '%[[MYZ-V4][%d{hh:mm:ss.SSS}][%4.4p]%] %m'
}
},
command: {
type: 'dateFile', // 可以是console,dateFile,file,Logstash等
filename: 'logs/command', // 将会按照filename和pattern拼接文件名
pattern: 'yyyy-MM-dd.log',
numBackups: 15,
alwaysIncludePattern: true,
layout: {
type: 'pattern',
pattern: '[%d{hh:mm:ss.SSS}][%4.4p] %m'
}
},
error: {
type: 'file',
filename: 'logs/error.log',
alwaysIncludePattern: true,
layout: {
type: 'pattern',
pattern: '[%d{hh:mm:ss.SSS}][%4.4p] %m'
}
}
},
categories: {
default: { appenders: ['console'], level: cfg.bot.log_level },
command: { appenders: ['console', 'command'], level: 'warn' },
error: { appenders: ['console', 'command', 'error'], level: 'error' }
}
})
const defaultLogger = log4js.getLogger('message')
const commandLogger = log4js.getLogger('command')
const errorLogger = log4js.getLogger('error')
/**
* error日志等级
*/
const logger = {
trace() {
defaultLogger.trace.call(defaultLogger, ...arguments)
},
debug() {
defaultLogger.debug.call(defaultLogger, ...arguments)
},
info() {
defaultLogger.info.call(defaultLogger, ...arguments)
},
// warn及以上的日志采用error策略
warn() {
commandLogger.warn.call(defaultLogger, ...arguments)
},
error() {
errorLogger.error.call(errorLogger, ...arguments)
},
fatal() {
errorLogger.fatal.call(errorLogger, ...arguments)
},
mark() {
errorLogger.mark.call(commandLogger, ...arguments)
}
}
return logger
}
/**
*
*/
mkdirSync('./logs', {
recursive: true
})
/**
* logger
*/
global.logger = createLog() as any
logger.chalk = chalk
logger.red = chalk.red
logger.green = chalk.green
logger.yellow = chalk.yellow
logger.blue = chalk.blue
logger.magenta = chalk.magenta
logger.cyan = chalk.cyan

View File

@ -1,11 +0,0 @@
import { existsSync } from 'fs'
import { join } from 'path'
const node_modules = join(process.cwd(), './node_modules')
/**
* node_modules
*/
if (!existsSync(node_modules)) {
console.log('未安装依赖。。。。')
console.log('请先运行命令pnpm install -P 安装依赖')
process.exit()
}

94
src/init/process.ts Normal file
View File

@ -0,0 +1,94 @@
import { promises } from 'node:fs'
import yaml from 'yaml'
import { BOT_NAME, CONFIG_INIT_PATH } from '../config/system.js'
/**
*
*/
logger.mark(`${BOT_NAME} 启动中...`)
/**
*
*/
process.title = BOT_NAME
/**
*
*/
process.env.TZ = 'Asia/Shanghai'
/**
*
*/
process.on('SIGHUP', () => process.exit())
/**
*
*/
process.on('uncaughtException', error => {
if (typeof logger == 'undefined') console.log(error)
else logger.error(error)
})
/**
* Promise错误
*/
process.on('unhandledRejection', error => {
if (typeof logger == 'undefined') console.log(error)
else logger.error(error)
})
/**
* 退
*/
process.on('exit', async () => {
if (typeof redis != 'undefined') {
await redis.save()
}
if (typeof logger == 'undefined') {
console.log(`${BOT_NAME} 已停止运行`)
} else {
logger.mark(logger.magenta(`${BOT_NAME} 已停止运行`))
}
})
/**
*
*/
let title = BOT_NAME
//
const qq = await promises
.readFile(`./${CONFIG_INIT_PATH}qq.yaml`, 'utf-8')
.then(yaml.parse)
.catch(() => null)
/**
*
*/
if (qq) {
title += `@${qq.qq || ''}`
switch (qq.platform) {
case 1: {
title += ' 安卓手机'
break
}
case 2: {
title += ' aPad'
break
}
case 3: {
title += ' 安卓手表'
break
}
case 4: {
title += ' MacOS'
break
}
case 5: {
title += ' iPad'
break
}
case 6: {
title += ' Tim'
break
}
default: {
break
}
}
}
/**
*
*/
process.title = title

View File

@ -1,2 +1,71 @@
import { redisInit } from '../config/redis' import cfg from '../config/config.js'
import { execAsync, sleep } from '../utils/common.js'
import { createClient } from 'redis'
/**
* redis客户端
* @returns
*/
async function redisInit() {
const rc = cfg.redis
const redisUn = rc.username || ''
let redisPw = rc.password ? `:${rc.password}` : ''
if (rc.username || rc.password) {
redisPw += '@'
}
const redisUrl = `redis://${redisUn}${redisPw}${rc.host}:${rc.port}/${rc.db}`
let client = createClient({ url: redisUrl })
try {
logger.info(`正在连接 ${logger.blue(redisUrl)}`)
await client.connect()
} catch (err) {
logger.error(`Redis 错误:${logger.red(err)}`)
const cmd =
'redis-server --save 900 1 --save 300 10 --daemonize yes' +
(await aarch64())
logger.info('正在启动 Redis...')
await execAsync(cmd)
await sleep(1000)
try {
client = createClient({ url: redisUrl })
await client.connect()
} catch (err) {
logger.error(`Redis 错误:${logger.red(err)}`)
logger.error(`请先启动 Redis${logger.blue(cmd)}`)
process.exit()
}
}
client.on('error', async err => {
logger.error(`Redis 错误:${logger.red(err)}`)
const cmd =
'redis-server --save 900 1 --save 300 10 --daemonize yes' +
(await aarch64())
logger.error(`请先启动 Redis${cmd}`)
process.exit()
})
/** 全局变量 redis */
global.redis = client as any
logger.info('Redis 连接成功')
return client
}
/**
*
* @returns
*/
async function aarch64() {
if (process.platform == 'win32') return ''
return await execAsync('uname -m').then(async arch => {
if (arch.stdout && arch.stdout.includes('aarch64')) {
/** 判断redis版本 */
let v = await execAsync('redis-server -v')
if (v.stdout) {
const data = v.stdout.match(/v=(\d)./)
/** 忽略arm警告 */
if (data && Number(data[1]) >= 6) {
return ' --ignore-warnings ARM64-COW-BUG'
}
}
}
return ''
})
}
await redisInit() await redisInit()

67
src/init/run.ts Normal file
View File

@ -0,0 +1,67 @@
import { join } from 'path'
import { createRequire } from 'module'
import { existsSync } from 'fs'
import pm2 from 'pm2'
const require = createRequire(import.meta.url)
/**
*
* @returns
*/
export function checkRun() {
return new Promise((resolve, reject) => {
if (process.argv[1].includes('pm2')) {
resolve(true)
}
if (process.argv[1].includes('test')) {
resolve(true)
}
// 不是生产运行
if (process.env.NODE_ENV != 'production') {
const dir = join(process.cwd(), 'pm2.config.cjs')
if (!existsSync(dir)) {
reject(false)
}
const cfg = require('../../pm2.config.cjs')
pm2.connect(err => {
if (err) {
reject(err)
return
}
//
pm2.list((err, processList) => {
if (err) {
pm2.disconnect()
reject(err)
return
}
const app = processList.find(p => p.name === cfg.apps[0].name)
if (app && app.pm2_env.status === 'online') {
console.log('检测到后台正在运行')
// 关闭
pm2.stop(cfg.apps[0].name, err => {
if (err) {
reject(err)
} else {
console.log('已停止后台进程,防止重复运行')
}
pm2.disconnect()
resolve(true)
})
} else {
// 断开连接
pm2.disconnect()
resolve(true)
}
})
})
} else {
resolve(true)
}
})
}
await checkRun().catch(err => {
// 打印错误
console.error(err)
// 关闭进程
process.exit(2)
})

109
trss.js
View File

@ -1,109 +0,0 @@
console.log("正迁移到 TRSS-Yunzai")
import fs from "node:fs"
import { execSync } from "child_process"
import YAML from "yaml"
function exec(cmd) { try {
console.log(`执行命令 [${cmd}]`)
console.log(execSync(cmd).toString())
return true
} catch (err) {
console.error("执行", cmd, "失败", err)
return false
}}
function rm(file) { try {
if (!fs.existsSync(file)) return true
return process.platform == "win32" ?
exec(`rd /s /q "${file.replace(/\//g, "\\")}"`) :
exec(`rm -rf "${file}"`)
} catch (err) {
console.error("删除", file, "错误", err)
return false
}}
function mv(file, target) { try {
if (!fs.existsSync(file)) return false
if (fs.existsSync(target)) rm(target)
return fs.renameSync(file, target)
} catch (err) {
console.error("移动", file, target, "错误", err)
return false
}}
function readYaml(file) { try {
if (!fs.existsSync(file)) return {}
return YAML.parse(fs.readFileSync(file, "utf-8"))
} catch (err) {
console.error("读取", file, "错误", err)
return {}
}}
function writeYaml(file, data) { try {
return fs.writeFileSync(file, YAML.stringify(data), "utf-8")
} catch (err) {
console.error("写入", file, "错误", err)
return false
}}
const bot = readYaml("config/config/bot.yaml")
const qq = readYaml("config/config/qq.yaml")
exec("git remote add trss https://gitee.com/TimeRainStarSky/Yunzai")
exec("git fetch trss main")
exec("git clean -df")
mv("config/config", "config/config_miao")
exec("git reset --hard")
exec("git checkout --track trss/main")
rm("plugins/ICQQ-Plugin")
exec("git clone --depth 1 --single-branch https://gitee.com/TimeRainStarSky/Yunzai-ICQQ-Plugin plugins/ICQQ-Plugin")
if (qq.qq) writeYaml("config/ICQQ.yaml", { bot, token: [`${qq.qq}:${qq.pwd}:${qq.platform}`] })
rm("plugins/genshin")
exec("git clone --depth 1 --single-branch https://gitee.com/TimeRainStarSky/Yunzai-genshin plugins/genshin")
exec("pnpm install --force")
console.log("迁移完成,请查看教程 启动协议端\nhttps://gitee.com/TimeRainStarSky/Yunzai\n输入 node miao 回 Miao-Yunzai")
fs.writeFileSync("miao.js", `console.log("正迁移到 Miao-Yunzai")
import fs from "node:fs"
import { execSync } from "child_process"
function exec(cmd) { try {
console.log(\`执行命令 [\${cmd}]\`)
console.log(execSync(cmd).toString())
return true
} catch (err) {
console.error("执行", cmd, "失败", err)
return false
}}
function rm(file) { try {
if (!fs.existsSync(file)) return true
return process.platform == "win32" ?
exec(\`rd /s /q "\${file.replace(/\\//g, "\\\\")}"\`) :
exec(\`rm -rf "\${file}"\`)
} catch (err) {
console.error("删除", file, "错误", err)
return false
}}
function mv(file, target) { try {
if (!fs.existsSync(file)) return false
if (fs.existsSync(target)) rm(target)
return fs.renameSync(file, target)
} catch (err) {
console.error("移动", file, target, "错误", err)
return false
}}
mv("config/config_miao", "config/config")
exec("git reset --hard")
exec("git clean -df")
rm("plugins/ICQQ-Plugin")
rm("plugins/genshin")
exec("git checkout master")
exec("git branch -D main")
exec("pnpm install --force")
console.log("迁移完成")`, "utf-8")