细节优化
This commit is contained in:
parent
0634aa026b
commit
ba4fc37be3
|
@ -54,26 +54,9 @@ class PluginsLoader {
|
||||||
|
|
||||||
let packageErr = []
|
let packageErr = []
|
||||||
for (let File of files) {
|
for (let File of files) {
|
||||||
let { name: appName, path: appPath } = File
|
|
||||||
try {
|
try {
|
||||||
let tmp = await import(File.path)
|
let tmp = await import(File.path)
|
||||||
// 如果当前是一个插件
|
if (tmp.apps) tmp = { ...tmp.apps }
|
||||||
if (tmp.apps) {
|
|
||||||
// 插件主目录,在这里this.watch里已经做了判断所以不用捕获异常
|
|
||||||
appName = `${appName}/apps`
|
|
||||||
// 插件路径
|
|
||||||
appPath = appPath.replace(/\/index\.js$/, "");
|
|
||||||
// 逐个遍历插件app里的js
|
|
||||||
for (let app in tmp.apps) {
|
|
||||||
// 插件目录的功能
|
|
||||||
const curPlugin = tmp.apps[app]
|
|
||||||
// 当前目录的功能名 (apps/hello.js)
|
|
||||||
const curAppName = curPlugin.name
|
|
||||||
// 监听插件
|
|
||||||
this.watch(appName, `${curAppName}.js`)
|
|
||||||
}
|
|
||||||
tmp = { ...tmp.apps }
|
|
||||||
}
|
|
||||||
let isAdd = false
|
let isAdd = false
|
||||||
lodash.forEach(tmp, (p, i) => {
|
lodash.forEach(tmp, (p, i) => {
|
||||||
if (!p.prototype) return
|
if (!p.prototype) return
|
||||||
|
@ -87,7 +70,7 @@ class PluginsLoader {
|
||||||
this.collectTask(plugin.task)
|
this.collectTask(plugin.task)
|
||||||
this.priority.push({
|
this.priority.push({
|
||||||
class: p,
|
class: p,
|
||||||
key: `${appName}/${p.name}.js`,
|
key: File.name,
|
||||||
name: plugin.name,
|
name: plugin.name,
|
||||||
priority: plugin.priority
|
priority: plugin.priority
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,28 +1,23 @@
|
||||||
import Renderer from '../renderer/Renderer.js'
|
import Renderer from '../renderer/loader.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 暂时保留对手工引用puppeteer.js的兼容
|
* 暂时保留对手工引用puppeteer.js的兼容
|
||||||
* 后期会逐步废弃
|
* 后期会逐步废弃
|
||||||
* 只提供截图及分片截图功能
|
* 只提供截图及分片截图功能
|
||||||
*/
|
*/
|
||||||
export default {
|
let renderer = Renderer.getRenderer('puppeteer')
|
||||||
// 截图
|
renderer.screenshot = async (name, data) => {
|
||||||
async screenshot (name, data = {}) {
|
|
||||||
let renderer = Renderer.getRenderer()
|
|
||||||
let img = await renderer.render(name, data)
|
let img = await renderer.render(name, data)
|
||||||
return img ? segment.image(img) : img
|
return img ? segment.image(img) : img
|
||||||
},
|
}
|
||||||
|
renderer.screenshots = async (name, data) => {
|
||||||
// 分片截图
|
|
||||||
async screenshots (name, data = {}) {
|
|
||||||
let renderer = Renderer.getRenderer()
|
|
||||||
data.multiPage = true
|
data.multiPage = true
|
||||||
let imgs = await renderer.render(name, data) || []
|
let imgs = await renderer.render(name, data) || []
|
||||||
let ret = []
|
let ret = []
|
||||||
for (let img of imgs) {
|
for (let img of imgs) {
|
||||||
ret.push(img ? segment.image(img) : img)
|
ret.push(img ? segment.image(img) : img)
|
||||||
}
|
}
|
||||||
return ret.length > 0 ? ret : false
|
return ret.length > 0 ? ret : false
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default renderer
|
|
@ -1,40 +1,82 @@
|
||||||
|
import template from 'art-template'
|
||||||
|
import chokidar from 'chokidar'
|
||||||
|
import path from 'node:path'
|
||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
import yaml from 'yaml'
|
|
||||||
import lodash from 'lodash'
|
|
||||||
import cfg from '../config/config.js'
|
|
||||||
import { Data } from '#miao'
|
|
||||||
|
|
||||||
let rendererBackends = {}
|
export default class Renderer {
|
||||||
|
/**
|
||||||
|
* 渲染器
|
||||||
|
* @param data.id 渲染器ID
|
||||||
|
* @param data.type 渲染器类型
|
||||||
|
* @param data.render 渲染器入口
|
||||||
|
*/
|
||||||
|
constructor(data) {
|
||||||
|
/** 渲染器ID */
|
||||||
|
this.id = data.id || 'renderer'
|
||||||
|
/** 渲染器类型 */
|
||||||
|
this.type = data.type || 'image'
|
||||||
|
/** 渲染器入口 */
|
||||||
|
this.render = this[data.render || 'render']
|
||||||
|
this.dir = './temp/html'
|
||||||
|
this.html = {}
|
||||||
|
this.watcher = {}
|
||||||
|
this.createDir(this.dir)
|
||||||
|
}
|
||||||
|
|
||||||
async function registerRendererBackends () {
|
/** 创建文件夹 */
|
||||||
const subFolders = fs.readdirSync(`${process.cwd()}/renderers`, { withFileTypes: true }).filter((dirent) => dirent.isDirectory())
|
createDir(dirname) {
|
||||||
for (let subFolder of subFolders) {
|
if (fs.existsSync(dirname)) {
|
||||||
let name = subFolder.name
|
return true
|
||||||
const rendererFn = await Data.importDefault(`/renderers/${name}/index.js`)
|
} else {
|
||||||
let configFile = `./renderers/${name}/config.yaml`
|
if (this.createDir(path.dirname(dirname))) {
|
||||||
let rendererCfg = {}
|
fs.mkdirSync(dirname)
|
||||||
if (fs.existsSync(configFile)) {
|
return true
|
||||||
try {
|
|
||||||
rendererCfg = yaml.parse(fs.readFileSync(configFile, 'utf8'))
|
|
||||||
} catch (e) {
|
|
||||||
rendererCfg = {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let renderer = rendererFn(rendererCfg)
|
}
|
||||||
if (!renderer.id || !renderer.type || !renderer.render || !lodash.isFunction(renderer.render)) {
|
|
||||||
logger.warn('渲染后端 ' + (renderer.id || subFolder.name) + ' 不可用')
|
/** 模板 */
|
||||||
|
dealTpl(name, data) {
|
||||||
|
let { tplFile, saveId = name } = data
|
||||||
|
let savePath = `./temp/html/${name}/${saveId}.html`
|
||||||
|
|
||||||
|
/** 读取html模板 */
|
||||||
|
if (!this.html[tplFile]) {
|
||||||
|
this.createDir(`./temp/html/${name}`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.html[tplFile] = fs.readFileSync(tplFile, 'utf8')
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`加载html错误:${tplFile}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.watch(tplFile)
|
||||||
}
|
}
|
||||||
rendererBackends[renderer.id] = renderer
|
|
||||||
logger.info(`加载渲染后端 ${renderer.id}`)
|
data.resPath = `./resources/`
|
||||||
|
|
||||||
|
/** 替换模板 */
|
||||||
|
let tmpHtml = template.render(this.html[tplFile], data)
|
||||||
|
|
||||||
|
/** 保存模板 */
|
||||||
|
fs.writeFileSync(savePath, tmpHtml)
|
||||||
|
|
||||||
|
logger.debug(`[图片生成][使用模板] ${savePath}`)
|
||||||
|
|
||||||
|
return savePath
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 监听配置文件 */
|
||||||
|
watch(tplFile) {
|
||||||
|
if (this.watcher[tplFile]) return
|
||||||
|
|
||||||
|
const watcher = chokidar.watch(tplFile)
|
||||||
|
watcher.on('change', path => {
|
||||||
|
delete this.html[tplFile]
|
||||||
|
logger.mark(`[修改html模板] ${tplFile}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.watcher[tplFile] = watcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await registerRendererBackends()
|
|
||||||
|
|
||||||
export default {
|
|
||||||
getRenderer () {
|
|
||||||
// TODO 渲染器降级
|
|
||||||
return rendererBackends[cfg.renderer?.name || 'puppeteer']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import yaml from 'yaml'
|
||||||
|
import lodash from 'lodash'
|
||||||
|
import cfg from '../config/config.js'
|
||||||
|
import { Data } from '#miao'
|
||||||
|
import Renderer from './Renderer.js'
|
||||||
|
|
||||||
|
/** 全局变量 Renderer */
|
||||||
|
global.Renderer = Renderer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载渲染器
|
||||||
|
*/
|
||||||
|
class RendererLoader {
|
||||||
|
constructor() {
|
||||||
|
this.renderers = new Map()
|
||||||
|
this.dir = './renderers'
|
||||||
|
// TODO 渲染器热加载
|
||||||
|
this.watcher = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async init() {
|
||||||
|
const render = new RendererLoader()
|
||||||
|
await render.load()
|
||||||
|
return render
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
const subFolders = fs.readdirSync(this.dir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory())
|
||||||
|
for (let subFolder of subFolders) {
|
||||||
|
let name = subFolder.name
|
||||||
|
try {
|
||||||
|
const rendererFn = await Data.importDefault(`${this.dir}/${name}/index.js`)
|
||||||
|
let configFile = `${this.dir}/${name}/config.yaml`
|
||||||
|
let rendererCfg = fs.existsSync(configFile) ? yaml.parse(fs.readFileSync(configFile, 'utf8')) : {}
|
||||||
|
let renderer = rendererFn(rendererCfg)
|
||||||
|
if (!renderer.id || !renderer.type || !renderer.render || !lodash.isFunction(renderer.render)) {
|
||||||
|
logger.warn('渲染后端 ' + (renderer.id || subFolder.name) + ' 不可用')
|
||||||
|
}
|
||||||
|
this.renderers.set(renderer.id, renderer)
|
||||||
|
logger.info(`加载渲染后端 ${renderer.id}`)
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`渲染后端 ${name} 加载失败`)
|
||||||
|
logger.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getRenderer(name = cfg.renderer?.name || 'puppeteer') {
|
||||||
|
// TODO 渲染器降级
|
||||||
|
return this.renderers.get(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default await RendererLoader.init()
|
|
@ -32,7 +32,7 @@
|
||||||
"node-xlsx": "^0.23.0",
|
"node-xlsx": "^0.23.0",
|
||||||
"oicq": "link:lib/modules/oicq",
|
"oicq": "link:lib/modules/oicq",
|
||||||
"pm2": "^5.3.0",
|
"pm2": "^5.3.0",
|
||||||
"puppeteer": "^21.1.1",
|
"puppeteer": "^21.2.1",
|
||||||
"redis": "^4.6.8",
|
"redis": "^4.6.8",
|
||||||
"sequelize": "^6.33.0",
|
"sequelize": "^6.33.0",
|
||||||
"sqlite3": "^5.1.6",
|
"sqlite3": "^5.1.6",
|
||||||
|
|
|
@ -10,13 +10,5 @@ import Puppeteer from './lib/puppeteer.js'
|
||||||
*/
|
*/
|
||||||
export default function (config) {
|
export default function (config) {
|
||||||
// TODO Puppeteer待简化重构
|
// TODO Puppeteer待简化重构
|
||||||
const PuppeteerRender = new Puppeteer(config)
|
return new Puppeteer(config)
|
||||||
|
|
||||||
return {
|
|
||||||
id: 'puppeteer',
|
|
||||||
type: 'image',
|
|
||||||
async render (name, data) {
|
|
||||||
return await PuppeteerRender.screenshot(name, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,20 +1,22 @@
|
||||||
import fs from 'node:fs'
|
import Renderer from '../../../lib/renderer/Renderer.js'
|
||||||
import os from 'node:os'
|
import os from 'node:os'
|
||||||
import lodash from 'lodash'
|
import lodash from 'lodash'
|
||||||
import template from 'art-template'
|
|
||||||
import chokidar from 'chokidar'
|
|
||||||
import puppeteer from 'puppeteer'
|
import puppeteer from 'puppeteer'
|
||||||
// 暂时保留对原config的兼容
|
// 暂时保留对原config的兼容
|
||||||
import cfg from '../../../lib/config/config.js'
|
import cfg from '../../../lib/config/config.js'
|
||||||
import { Data } from '#miao'
|
import { Data } from '#miao'
|
||||||
|
import path from 'path'
|
||||||
const _path = process.cwd()
|
|
||||||
|
|
||||||
// mac地址
|
// mac地址
|
||||||
let mac = ''
|
let mac = ''
|
||||||
|
|
||||||
export default class PuppeteerRenderer {
|
export default class Puppeteer extends Renderer {
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
|
super({
|
||||||
|
id: 'puppeteer',
|
||||||
|
type: 'image',
|
||||||
|
render: 'screenshot'
|
||||||
|
})
|
||||||
this.browser = false
|
this.browser = false
|
||||||
this.lock = false
|
this.lock = false
|
||||||
this.shoting = []
|
this.shoting = []
|
||||||
|
@ -23,7 +25,7 @@ export default class PuppeteerRenderer {
|
||||||
/** 截图次数 */
|
/** 截图次数 */
|
||||||
this.renderNum = 0
|
this.renderNum = 0
|
||||||
this.config = {
|
this.config = {
|
||||||
headless: Data.def(config.headless, "new"),
|
headless: Data.def(config.headless, 'new'),
|
||||||
args: Data.def(config.args, [
|
args: Data.def(config.args, [
|
||||||
'--disable-gpu',
|
'--disable-gpu',
|
||||||
'--disable-setuid-sandbox',
|
'--disable-setuid-sandbox',
|
||||||
|
@ -39,22 +41,6 @@ export default class PuppeteerRenderer {
|
||||||
/** chromium其他路径 */
|
/** chromium其他路径 */
|
||||||
this.config.wsEndpoint = config.puppeteerWS || cfg?.bot?.puppeteer_ws
|
this.config.wsEndpoint = config.puppeteerWS || cfg?.bot?.puppeteer_ws
|
||||||
}
|
}
|
||||||
|
|
||||||
this.html = {}
|
|
||||||
this.watcher = {}
|
|
||||||
this.createDir('./temp/html')
|
|
||||||
}
|
|
||||||
|
|
||||||
createDir(dir) {
|
|
||||||
if (!fs.existsSync(dir)) {
|
|
||||||
let dirs = dir.split('/')
|
|
||||||
for (let idx = 1; idx <= dirs.length; idx++) {
|
|
||||||
let temp = dirs.slice(0, idx).join('/')
|
|
||||||
if (!fs.existsSync(temp)) {
|
|
||||||
fs.mkdirSync(temp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -123,7 +109,7 @@ export default class PuppeteerRenderer {
|
||||||
} else {
|
} else {
|
||||||
logger.info(`[Chromium] ${this.browser.wsEndpoint()}`)
|
logger.info(`[Chromium] ${this.browser.wsEndpoint()}`)
|
||||||
if (process.env.pm_id && this.browserMacKey) {
|
if (process.env.pm_id && this.browserMacKey) {
|
||||||
//缓存一下实例30天
|
// 缓存一下实例30天
|
||||||
const expireTime = 60 * 60 * 24 * 30
|
const expireTime = 60 * 60 * 24 * 30
|
||||||
await redis.set(this.browserMacKey, this.browser.wsEndpoint(), { EX: expireTime })
|
await redis.set(this.browserMacKey, this.browser.wsEndpoint(), { EX: expireTime })
|
||||||
}
|
}
|
||||||
|
@ -140,15 +126,19 @@ export default class PuppeteerRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取Mac地址
|
// 获取Mac地址
|
||||||
getMac() {
|
getMac () {
|
||||||
const mac = '00:00:00:00:00:00'
|
const mac = '00:00:00:00:00:00'
|
||||||
try {
|
try {
|
||||||
const network = os.networkInterfaces()
|
const network = os.networkInterfaces()
|
||||||
for (const a in network)
|
for (const a in network) {
|
||||||
for (const i of network[a])
|
for (const i of network[a]) {
|
||||||
if (i.mac && i.mac != mac)
|
if (i.mac && i.mac != mac) {
|
||||||
return i.mac
|
return i.mac
|
||||||
} catch (e) {}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
return mac
|
return mac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +163,9 @@ export default class PuppeteerRenderer {
|
||||||
const pageHeight = data.multiPageHeight || 4000
|
const pageHeight = data.multiPageHeight || 4000
|
||||||
|
|
||||||
let savePath = this.dealTpl(name, data)
|
let savePath = this.dealTpl(name, data)
|
||||||
if (!savePath) return false
|
if (!savePath) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
let buff = ''
|
let buff = ''
|
||||||
let start = Date.now()
|
let start = Date.now()
|
||||||
|
@ -184,7 +176,7 @@ export default class PuppeteerRenderer {
|
||||||
try {
|
try {
|
||||||
const page = await this.browser.newPage()
|
const page = await this.browser.newPage()
|
||||||
let pageGotoParams = lodash.extend({ timeout: 120000 }, data.pageGotoParams || {})
|
let pageGotoParams = lodash.extend({ timeout: 120000 }, data.pageGotoParams || {})
|
||||||
await page.goto(`file://${_path}${lodash.trim(savePath, '.')}`, pageGotoParams)
|
await page.goto('file://' + path.resolve(savePath), pageGotoParams)
|
||||||
let body = await page.$('#container') || await page.$('body')
|
let body = await page.$('#container') || await page.$('body')
|
||||||
|
|
||||||
// 计算页面高度
|
// 计算页面高度
|
||||||
|
@ -238,7 +230,9 @@ export default class PuppeteerRenderer {
|
||||||
} else {
|
} else {
|
||||||
buff = await page.screenshot(randData)
|
buff = await page.screenshot(randData)
|
||||||
}
|
}
|
||||||
if (num > 2) await Data.sleep(200)
|
if (num > 2) {
|
||||||
|
await Data.sleep(200)
|
||||||
|
}
|
||||||
this.renderNum++
|
this.renderNum++
|
||||||
|
|
||||||
/** 计算图片大小 */
|
/** 计算图片大小 */
|
||||||
|
@ -251,7 +245,6 @@ export default class PuppeteerRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
page.close().catch((err) => logger.error(err))
|
page.close().catch((err) => logger.error(err))
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[图片生成][${name}] 图片生成失败:${error}`)
|
logger.error(`[图片生成][${name}] 图片生成失败:${error}`)
|
||||||
/** 关闭浏览器 */
|
/** 关闭浏览器 */
|
||||||
|
@ -275,51 +268,6 @@ export default class PuppeteerRenderer {
|
||||||
return data.multiPage ? ret : ret[0]
|
return data.multiPage ? ret : ret[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 模板 */
|
|
||||||
dealTpl(name, data) {
|
|
||||||
let { tplFile, saveId = name } = data
|
|
||||||
let savePath = `./temp/html/${name}/${saveId}.html`
|
|
||||||
|
|
||||||
/** 读取html模板 */
|
|
||||||
if (!this.html[tplFile]) {
|
|
||||||
this.createDir(`./temp/html/${name}`)
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.html[tplFile] = fs.readFileSync(tplFile, 'utf8')
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`加载html错误:${tplFile}`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
this.watch(tplFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
data.resPath = `${_path}/resources/`
|
|
||||||
|
|
||||||
/** 替换模板 */
|
|
||||||
let tmpHtml = template.render(this.html[tplFile], data)
|
|
||||||
|
|
||||||
/** 保存模板 */
|
|
||||||
fs.writeFileSync(savePath, tmpHtml)
|
|
||||||
|
|
||||||
logger.debug(`[图片生成][使用模板] ${savePath}`)
|
|
||||||
|
|
||||||
return savePath
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 监听配置文件 */
|
|
||||||
watch(tplFile) {
|
|
||||||
if (this.watcher[tplFile]) return
|
|
||||||
|
|
||||||
const watcher = chokidar.watch(tplFile)
|
|
||||||
watcher.on('change', path => {
|
|
||||||
delete this.html[tplFile]
|
|
||||||
logger.mark(`[修改html模板] ${tplFile}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.watcher[tplFile] = watcher
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 重启 */
|
/** 重启 */
|
||||||
restart() {
|
restart() {
|
||||||
/** 截图超过重启数时,自动关闭重启浏览器,避免生成速度越来越慢 */
|
/** 截图超过重启数时,自动关闭重启浏览器,避免生成速度越来越慢 */
|
||||||
|
|
Loading…
Reference in New Issue