From 5f0c3bbdd42e968c52331f4df49a12cefa4f5fe5 Mon Sep 17 00:00:00 2001 From: MiaoWoo Date: Sat, 20 Jun 2020 16:40:34 +0800 Subject: [PATCH] feat: backup plugins Signed-off-by: MiaoWoo --- packages/plugins/src/MiaoConsole.ts | 23 +- packages/plugins/src/MiaoProtocol.ts | 32 +++ .../plugins/src/MiaoScriptPackageManager.ts | 110 ++++++---- packages/plugins/src/MiaoSpring.ts | 206 +++--------------- packages/plugins/src/SearchRanking.ts | 181 +++++++++++++++ 5 files changed, 322 insertions(+), 230 deletions(-) create mode 100644 packages/plugins/src/MiaoProtocol.ts create mode 100644 packages/plugins/src/SearchRanking.ts diff --git a/packages/plugins/src/MiaoConsole.ts b/packages/plugins/src/MiaoConsole.ts index 34acc38a..744f0a2b 100644 --- a/packages/plugins/src/MiaoConsole.ts +++ b/packages/plugins/src/MiaoConsole.ts @@ -1,3 +1,4 @@ +/// /// /// /// @@ -56,7 +57,7 @@ export class MiaoConsole extends interfaces.Plugin { this.token = Java.type('java.util.UUID').randomUUID().toString() this.logger.console(`§6已生成随机Token: §3${this.token} §c重启后或重新生成后失效!`) } - global.eventCenter.on('log', (msg) => { + process.on('message', (msg) => { this.logCache.push(msg) if (this.logCache.length > 30) { this.logCache = this.logCache.slice(this.logCache.length - 30, this.logCache.length) @@ -130,7 +131,7 @@ export class MiaoConsole extends interfaces.Plugin { let ProxyAppender = Java.extend(AbstractAppender, { append: (logEvent) => { if (logEvent.level.intLevel() <= Level.INFO.intLevel()) { - global.eventCenter.emit('log', logEvent.getMessage().getFormattedMessage()) + process.emit('message', logEvent.getMessage().getFormattedMessage()) } } }) @@ -146,7 +147,7 @@ export class MiaoConsole extends interfaces.Plugin { if (this.rootLogger) { let AbstractHandler = Java.type('java.util.logging.Handler') let ProxyHandler = Java.extend(AbstractHandler, { - publish: (record) => global.eventCenter.emit('log', record.getMessage()), + publish: (record) => process.emit('message', record.getMessage()), flush: () => { }, close: () => { } }) @@ -162,7 +163,7 @@ export class MiaoConsole extends interfaces.Plugin { if (this.rootLogger) { let AppenderBase = Java.type('ch.qos.logback.core.AppenderBase') let ProxyAppender = Java.extend(AppenderBase, { - append: (logEvent) => global.eventCenter.emit('log', logEvent.getFormattedMessage()) + append: (logEvent) => process.emit('message', logEvent.getFormattedMessage()) }) this.appender = new ProxyAppender() this.appender.start() @@ -175,7 +176,7 @@ export class MiaoConsole extends interfaces.Plugin { disable() { if (this.socketIOServer) { this.socketIOServer.close() - global.eventCenter.removeAllListeners('log') + process.removeAllListeners('message') } if (this.container.isBound(io.Instance)) { this.container.unbind(io.Instance) @@ -220,7 +221,7 @@ export class MiaoConsole extends interfaces.Plugin { startSocketIOServer() { let namespace = this.socketIOServer.of('/MiaoConsole') - global.eventCenter.on('log', (msg) => namespace.emit('log', msg)) + process.on('message', (msg) => namespace.emit('log', msg)) namespace.on('connect', (client: SocketIOSocket) => { if (!this.token) { this.logger.console(`§6客户端 §b${client.id} §a请求连接 §4服务器尚未设置 Token 无法连接!`) @@ -249,6 +250,9 @@ export class MiaoConsole extends interfaces.Plugin { setTimeout(() => this.server.dispatchConsoleCommand(cmd), 0) client.emit('log', `§6命令: §b${cmd} §a执行成功!`) }) + client.on('tabComplate', (input, index, callback) => { + callback && callback(this.server.tabComplete(this.server.getConsoleSender(), input, index)) + }) client.on('exec', (code) => { try { client.emit('log', this.runCode(code, client.nsp, client)) @@ -273,7 +277,7 @@ export class MiaoConsole extends interfaces.Plugin { } }) client.on('ls', (file: string, fn) => { - let dir = fs.file(file); + let dir = fs.file(file) if (!dir.isDirectory()) { return fn(undefined, `${file} 不是一个目录!`) } @@ -309,10 +313,9 @@ export class MiaoConsole extends interfaces.Plugin { if (this.serverType == "spring") { var dbm = container.get(api.database.DataBaseManager) var db = dbm.getMainDatabase() - var df = base.getInstance().getAutowireCapableBeanFactory() + var bf = base.getInstance().getAutowireCapableBeanFactory() } -return '§a返回结果: §r'+ eval(${JSON.stringify(code)}); -`) +return '§a返回结果: §r'+ eval(${JSON.stringify(code)});`) return this.task.callSyncMethod(() => tfunc.apply(this, params)) + '' } } diff --git a/packages/plugins/src/MiaoProtocol.ts b/packages/plugins/src/MiaoProtocol.ts new file mode 100644 index 00000000..ed22b400 --- /dev/null +++ b/packages/plugins/src/MiaoProtocol.ts @@ -0,0 +1,32 @@ +/// + +import { task, server, constants } from "@ccms/api"; +import { inject } from "@ccms/container"; +import { plugin, interfaces, cmd } from "@ccms/plugin"; + +import http from '@ccms/common/dist/http' +import * as fs from '@ccms/common/dist/fs' + +@plugin({ name: 'MiaoProtocol', prefix: 'MPTL', version: '1.0.0', author: 'MiaoWoo', servers: [constants.ServerType.Bukkit], source: __filename }) +export class MiaoProtocol extends interfaces.Plugin { + @inject(server.Server) + private server: server.Server; + @inject(task.TaskManager) + private taskManager: task.TaskManager; + + private pipeline: any + + enable() { + let count = 0 + let wait = this.taskManager.create(() => { + this.pipeline = this.server.getNettyPipeline() + if (this.pipeline) { + wait.cancel() + } else if (count++ > 30) { + wait.cancel() + this.logger.console('§cNetty通道注入失败 §4所有功能将无法使用!') + } + }).later(20).timer(40).submit() + } + +} diff --git a/packages/plugins/src/MiaoScriptPackageManager.ts b/packages/plugins/src/MiaoScriptPackageManager.ts index 124e779a..861384ee 100644 --- a/packages/plugins/src/MiaoScriptPackageManager.ts +++ b/packages/plugins/src/MiaoScriptPackageManager.ts @@ -1,10 +1,11 @@ import { plugin as pluginApi, task, server } from '@ccms/api' import { Translate } from '@ccms/i18n' -import { inject } from '@ccms/container'; +import { inject, DefaultContainer as container } from '@ccms/container' import { interfaces, plugin, cmd, tab } from '@ccms/plugin' import * as fs from '@ccms/common/dist/fs' +import * as reflect from '@ccms/common/dist/reflect' import http from '@ccms/common/dist/http' let help = [ @@ -19,7 +20,7 @@ let help = [ '§6/mpm §arun §e §6- §3运行JS代码', '§6/mpm §adeploy §e<插件名称> §6- §3发布插件', '§6/mpm §crestart §6- §4重启MiaoScript脚本引擎' -]; +] let langMap = { 'main.command.not.exists': '§4未知的子命令: §c{command}', @@ -53,20 +54,20 @@ let fallbackMap = langMap @plugin({ name: 'MiaoScriptPackageManager', prefix: 'PM', version: '1.0.1', author: 'MiaoWoo', source: __filename }) export class MiaoScriptPackageManager extends interfaces.Plugin { @inject(pluginApi.PluginManager) - private pluginManager: pluginApi.PluginManager; + private pluginManager: pluginApi.PluginManager @inject(task.TaskManager) - private taskManager: task.TaskManager; + private taskManager: task.TaskManager @inject(server.ServerType) - private serverType: string; + private serverType: string @inject(server.Server) private server: server.Server @inject(pluginApi.PluginFolder) - private pluginFolder: string; + private pluginFolder: string private packageCache: any[] = []; private packageNameCache: string[] = []; - private translate: Translate; + private translate: Translate load() { this.translate = new Translate({ @@ -78,7 +79,7 @@ export class MiaoScriptPackageManager extends interfaces.Plugin { @cmd() mpm(sender: any, command: string, args: string[]) { - this.taskManager.create(() => this.main(sender, command, args)).async().submit(); + this.taskManager.create(() => this.main(sender, command, args)).async().submit() } i18n(sender: any, name: string, params?: any) { @@ -90,64 +91,64 @@ export class MiaoScriptPackageManager extends interfaces.Plugin { if (!this[cmdKey]) { this.i18n(sender, 'main.command.not.exists', { command: args[0] }) this.i18n(sender, 'main.command.help.tip', { command }) - return; + return } args.shift() - this[cmdKey](sender, ...args); + this[cmdKey](sender, ...args) } cmdhelp(sender: any) { - this.logger.sender(sender, help); + this.logger.sender(sender, help) } cmdload(sender: any, name: string) { - let pluginFile = fs.concat(__dirname + '', name); + let pluginFile = fs.concat(__dirname + '', name) if (!fs.exists(pluginFile)) { this.i18n(sender, 'plugin.not.exists', { name: `${name}(${pluginFile})` }) - return; + return } - this.pluginManager.loadFromFile(fs.file(pluginFile)); + this.pluginManager.loadFromFile(fs.file(pluginFile)) } cmdlist(sender: any, type: string = 'cloud') { if (type == "i" || type == "install") { this.i18n(sender, 'list.install.header') this.pluginManager.getPlugins().forEach((plugin) => { - this.i18n(sender, 'list.install.body', plugin.description); + this.i18n(sender, 'list.install.body', plugin.description) }) } else { this.i18n(sender, 'list.header') for (var pkgName in this.packageCache) { - this.i18n(sender, 'list.body', this.packageCache[pkgName]); + this.i18n(sender, 'list.body', this.packageCache[pkgName]) } } } cmdinstall(sender: any, name: string) { if (!name) { return this.i18n(sender, 'plugin.name.empty') } - this.download(sender, name); + this.download(sender, name) } cmdupdate(sender: any, name: string) { if (name) { - this.update(sender, name); + this.update(sender, name) } else { this.updateRepo(sender) } } cmdupgrade(sender: any, name: string) { - if (!name) { return this.i18n(sender, 'upgrade.confirm'); } + if (!name) { return this.i18n(sender, 'upgrade.confirm') } if (name == "comfirm") { let enginePath = fs.path(fs.file(fs.concat(root, 'node_modules', '@ccms'))) if (enginePath.startsWith(root)) { - base.delete(enginePath); - this.cmdrestart(sender); + base.delete(enginePath) + this.cmdrestart(sender) } } if (this.checkPlugin(sender, name)) { - this.update(sender, name); - this.pluginManager.reload(name); + this.update(sender, name) + this.pluginManager.reload(name) } } @@ -161,7 +162,7 @@ export class MiaoScriptPackageManager extends interfaces.Plugin { cmdreload(sender: any, name: string) { name = name || this.description.name if (this.checkPlugin(sender, name)) { - this.pluginManager.reload(name); + this.pluginManager.reload(name) this.i18n(sender, 'plugin.reload.finish', { name }) } } @@ -184,33 +185,58 @@ export class MiaoScriptPackageManager extends interfaces.Plugin { return } try { - this.logger.sender(sender, '§6Reloading §3MiaoScript Engine...'); - ScriptEngineContextHolder.disableEngine(); - Packages.java.lang.System.gc(); - ScriptEngineContextHolder.enableEngine(); - this.logger.sender(sender, '§3MiaoScript Engine §6Reload §aSuccessful...'); + this.logger.sender(sender, '§6Reloading §3MiaoScript Engine...') + ScriptEngineContextHolder.disableEngine() + Packages.java.lang.System.gc() + ScriptEngineContextHolder.enableEngine() + this.logger.sender(sender, '§3MiaoScript Engine §6Reload §aSuccessful...') } catch (ex) { - this.logger.sender(sender, "§3MiaoScript Engine §6Reload §cError! ERR: " + ex); - this.logger.sender(sender, this.logger.stack(ex)); + this.logger.sender(sender, "§3MiaoScript Engine §6Reload §cError! ERR: " + ex) + this.logger.sender(sender, this.logger.stack(ex)) } } cmdrun(sender: any, ...args: any[]) { try { - let script = args.join(' '); + let script = args.join(' ') this.i18n(sender, 'run.script', { script }) - let result = eval(script); + let result = this.runCode(script, sender) this.i18n(sender, 'run.result', { result: result == undefined ? this.translate.translate('run.noresult') : result + '' }) } catch (ex) { - this.logger.sender(sender, this.logger.stack(ex)); + this.logger.sender(sender, this.logger.stack(ex)) } } + private runCode(code: string, sender: any) { + let paramNames = [ + 'sender', + 'reflect', + 'container', + 'pluginManager' + ] + let params = [ + sender, + reflect, + container, + this.pluginManager + ] + let tfunc = new Function( + ...paramNames, + `var api = require('@ccms/api'); +if (this.serverType == "spring") { + var dbm = container.get(api.database.DataBaseManager) + var db = dbm.getMainDatabase() + var df = base.getInstance().getAutowireCapableBeanFactory() +} +return '§a返回结果: §r'+ eval(${JSON.stringify(code)});`) + return tfunc.apply(this, params) + '' + } + cmddeploy(sender: any, name: any) { if (!process.env.AccessToken) { return this.i18n(sender, 'deploy.token.not.exists') } this.taskManager.create(() => { if (this.checkPlugin(sender, name)) { - let plugin: pluginApi.Plugin = this.pluginManager.getPlugins().get(name); + let plugin: pluginApi.Plugin = this.pluginManager.getPlugins().get(name) let result = http.post("http://ms.yumc.pw/api/plugin/deploy?access_token=" + process.env.AccessToken, { name, author: plugin.description.author, @@ -224,7 +250,7 @@ export class MiaoScriptPackageManager extends interfaces.Plugin { update(sender: any, name: string) { if (this.checkCloudPlugin(sender, name)) { - this.download(sender, name, true); + this.download(sender, name, true) } } @@ -236,25 +262,25 @@ export class MiaoScriptPackageManager extends interfaces.Plugin { case "list": return ["install", "cloud"] case "install": - return this.packageNameCache; + return this.packageNameCache case "update": case "upgrade": case "load": case "unload": case "reload": case "deploy": - return [...this.pluginManager.getPlugins().keys()]; + return [...this.pluginManager.getPlugins().keys()] } } } updateRepo(sender: any) { this.taskManager.create(() => { - let result = http.get('http://ms.yumc.pw/api/plugin/list'); - for (const pl of result.data) { this.packageCache[pl.name] = pl; } - this.packageNameCache = Object.keys(this.packageCache); + let result = http.get('http://ms.yumc.pw/api/plugin/list') + for (const pl of result.data) { this.packageCache[pl.name] = pl } + this.packageNameCache = Object.keys(this.packageCache) this.i18n(sender, 'cloud.update.finish', { length: this.packageNameCache.length }) - }).async().submit(); + }).async().submit() } download(sender: any, name: string, update: boolean = false) { diff --git a/packages/plugins/src/MiaoSpring.ts b/packages/plugins/src/MiaoSpring.ts index 61944fb3..7ecf38a3 100644 --- a/packages/plugins/src/MiaoSpring.ts +++ b/packages/plugins/src/MiaoSpring.ts @@ -2,58 +2,30 @@ /// /// -import { constants, database, plugin } from "@ccms/api" -import { inject, ContainerInstance, Container, JSClass } from "@ccms/container" +import { constants, database, plugin, web } from "@ccms/api" +import { inject, ContainerInstance, Container } from "@ccms/container" import { JSPlugin, interfaces, cmd } from "@ccms/plugin" import { DataBase, DataBaseManager } from '@ccms/database' +import { Server, Context, RequestHandler } from '@ccms/web' + import * as fs from '@ccms/common/dist/fs' import * as reflect from '@ccms/common/dist/reflect' -import * as querystring from 'querystring' -const WebProxyBeanName = 'webServerProxy' -const FilterProxyBeanName = 'webFilterProxy' -type RequestHandler = (ctx: Context) => any -interface InterceptorAdapter { - name: string - preHandle?(ctx: Context): void - postHandle?(ctx: Context): void -} - -type RequestHeader = { [key: string]: string | string[] } -type RequestParams = { [key: string]: string | string[] } - -interface Context { - request?: javax.servlet.http.HttpServletRequest - response?: javax.servlet.http.HttpServletResponse - header?: RequestHeader - url?: string - params?: RequestParams - body?: any - result?: any -} - -@JSPlugin({ name: 'MiaoSpring', prefix: 'MSpring', version: '1.0.1', author: 'MiaoWoo', servers: [constants.ServerType.Bukkit], source: __filename }) +@JSPlugin({ name: 'MiaoSpring', prefix: 'MSpring', version: '1.0.1', author: 'MiaoWoo', servers: [constants.ServerType.Spring], source: __filename }) export class MiaoSpring extends interfaces.Plugin { - @JSClass('pw.yumc.MiaoScript.web.WebServerProxy') - private WebServerProxy: any - @JSClass('pw.yumc.MiaoScript.web.WebFilterProxy') - private WebFilterProxy: any - private StreamUtils = org.springframework.util.StreamUtils - private ResponseEntity = org.springframework.http.ResponseEntity - @inject(ContainerInstance) private container: Container - @inject(plugin.PluginInstance) - private context: any @inject(plugin.PluginManager) private pluginManager: plugin.PluginManager @inject(database.DataBaseManager) private databaseManager: DataBaseManager + @inject(web.Server) + private webServer: Server + + private ResponseEntity = org.springframework.http.ResponseEntity - private beanFactory: any private mainDatabase: DataBase - private interceptors: InterceptorAdapter[] = [] - private requestMapping: { [key: string]: RequestHandler } = {} + private mappings: Set @cmd() mspring(sender: any) { @@ -62,144 +34,24 @@ export class MiaoSpring extends interfaces.Plugin { } load() { - this.beanFactory = this.context.getAutowireCapableBeanFactory() this.mainDatabase = this.databaseManager.getMainDatabase() + this.mappings = new Set() } enable() { - this.registryWebBean() this.registryDefault() this.registryPages() this.registryDatabase() } - registryWebBean() { - try { this.beanFactory.destroySingleton(FilterProxyBeanName); this.beanFactory.destroySingleton(WebProxyBeanName) } catch (ex) { } - var WebFilterProxyNashorn = Java.extend(this.WebFilterProxy, { - doFilter: (servletRequest: javax.servlet.http.HttpServletRequest, servletResponse: javax.servlet.http.HttpServletResponse, filterChain: javax.servlet.FilterChain) => { - console.log('WebFilterProxyNashorn', 'doFilter', servletRequest, servletResponse) - filterChain.doFilter(servletRequest, servletResponse) - } - }) - this.beanFactory.registerSingleton(FilterProxyBeanName, new WebFilterProxyNashorn()) - var WebServerProxyNashorn = Java.extend(this.WebServerProxy, { - process: (req: javax.servlet.http.HttpServletRequest, resp: javax.servlet.http.HttpServletResponse) => { - let ctx: Context = { request: req, response: resp } - ctx.url = req.getRequestURI() - // @ts-ignore - ctx.header = { __noSuchProperty__: (name: string) => req.getHeader(name) + '' } - if (req.getQueryString()) { - ctx.url += `?${req.getQueryString()}` - ctx.params = querystring.parse(req.getQueryString()) - } - if (req.getMethod() == "POST") { - ctx.body = this.StreamUtils.copyToString(req.getInputStream(), java.nio.charset.StandardCharsets.UTF_8) - if ((ctx.header['Content-Type'] || '').includes('application/json')) { - try { - ctx.body = JSON.parse(ctx.body) - } catch (error) { - return { - status: 500, - msg: `parse json body error: ${error}`, - path: ctx.url, - error: console.stack(error, false), - timestamp: Date.now() - } - } - } - } - let result = this.process(ctx) - result?.status && resp.setStatus(result.status) - return result - } - }) - this.beanFactory.registerSingleton(WebProxyBeanName, new WebServerProxyNashorn()) - } - - private process(ctx: Context) { - let startTime = Date.now() - for (const interceptor of this.interceptors) { - if (interceptor.preHandle) { - try { - let startTime = Date.now() - ctx.result = interceptor.preHandle(ctx) - let preHandleTime = Date.now() - startTime - if (preHandleTime > 20) { - console.debug(`[WARN] Interceptor ${interceptor.name} preHandle cost time ${preHandleTime}ms!`) - } - if (ctx.result) { return ctx.result } - } catch (error) { - console.ex(error) - return { - status: 500, - msg: `Interceptor ${interceptor.name} preHandle error: ${error}`, - path: ctx.url, - error: console.stack(error, false), - timestamp: Date.now() - } - } - } - } - let preHandleTime = Date.now() - startTime; startTime = Date.now() - ctx.result = this.execRequestHandle(ctx) - let execTime = Date.now() - startTime; startTime = Date.now() - for (const interceptor of this.interceptors) { - if (interceptor.postHandle) { - try { - ctx.result = interceptor.postHandle(ctx) - } catch (error) { - return { - status: 500, - msg: `Interceptor ${interceptor.name} postHandle error: ${error}`, - path: ctx.url, - error: console.stack(error, false), - timestamp: Date.now() - } - } - } - } - let postHandleTime = Date.now() - startTime - console.debug(` -===================== MiaoSpring ===================== -Request URL : ${ctx.url} -preHandle Time : ${preHandleTime}ms -exec Time : ${execTime}ms -Response Body : ${JSON.stringify(Java.asJSONCompatible(ctx.result))} -postHandle Time : ${postHandleTime}ms -Handle Time : ${preHandleTime + execTime + postHandleTime}ms -======================================================`) - return ctx.result - } - - private execRequestHandle(ctx: Context) { - if (!this.requestMapping[ctx.request.getRequestURI()]) { - return { - status: 404, - msg: "requestMapping Not Found!", - path: ctx.url, - timestamp: Date.now() - } - } - try { - return this.requestMapping[ctx.request.getRequestURI()](ctx) - } catch (error) { - return { - status: 500, - msg: '' + error, - path: ctx.url, - error: console.stack(error, false), - timestamp: Date.now() - } - } - } - registryMapping(path: string, handler: RequestHandler) { - this.requestMapping[path] = handler + this.mappings.add(path) + this.webServer.registryMapping(path, handler) } registryDefault() { const foundMap = ["/node_modules/amis/sdk/sdk.js", 'https://houtai.baidu.com/v2/jssdk', "/node_modules/amis/sdk/sdk.css", 'https://houtai.baidu.com/v2/csssdk'] - this.interceptors.push({ + this.webServer.registryInterceptor({ name: 'RedirectHandle', preHandle: (ctx: Context) => { const index = foundMap.indexOf(ctx.request.getRequestURI()) @@ -208,7 +60,7 @@ Handle Time : ${preHandleTime + execTime + postHandleTime}ms } } }) - this.interceptors.push({ + this.webServer.registryInterceptor({ name: 'StaticHandle', preHandle: (ctx: Context) => { let type = ctx.header['Accept'] || '' @@ -225,27 +77,27 @@ Handle Time : ${preHandleTime + execTime + postHandleTime}ms } } }) - this.registryMapping('/api/eval', (ctx: Context) => { + this.webServer.registryMapping('/api/eval', (ctx: Context) => { try { return { status: 200, data: this.runCode(ctx.body + ''), msg: '代码执行成功!' } } catch (error) { return { status: 500, data: console.stack(error, false), msg: '代码执行异常!' } } }) - this.registryMapping('/api/plugin/list', () => { + this.webServer.registryMapping('/api/plugin/list', () => { return { status: 200, data: [...this.pluginManager.getPlugins().values()].map((plugin) => plugin.description), msg: '插件列表获取成功!' } }) - this.registryMapping('/api/plugin/update', (ctx: Context) => { + this.webServer.registryMapping('/api/plugin/update', (ctx: Context) => { if (!ctx.params.name) { return { status: 400, msg: '插件名称不得为空!' } } }) } private registryPages() { - this.registryMapping('/api/page/list', () => { + this.webServer.registryMapping('/api/page/list', () => { return { status: 0, data: { rows: this.mainDatabase.query('SELECT `id`, `type`, `name`, `content` FROM `pages` WHERE `deleted` = 0') } } }) - this.registryMapping('/api/page/get', (ctx: Context) => { + this.webServer.registryMapping('/api/page/get', (ctx: Context) => { let name = decodeURIComponent(`${ctx.params.name}`) let varable = undefined if (!name) { return { status: 400, msg: '名称不能为空!' } } @@ -265,20 +117,20 @@ Handle Time : ${preHandleTime + execTime + postHandleTime}ms } return { status: 0, data: JSON.parse(content) } }) - this.registryMapping('/api/page/add', (ctx: Context) => { + this.webServer.registryMapping('/api/page/add', (ctx: Context) => { let body = ctx.body if (typeof body.content !== "string") { body.content = JSON.stringify(body.content) } this.mainDatabase.update("INSERT INTO `pages`(`type`, `name`, `content`) VALUES (?, ?, ?)", body.type || 1, body.name, body.content) return { status: 0, msg: `${body.name} 新增成功!` } }) - this.registryMapping('/api/page/update', (ctx: Context) => { + this.webServer.registryMapping('/api/page/update', (ctx: Context) => { if (!ctx.params.id) { return { status: 400, msg: 'ID 不能为空!' } } const body = ctx.body if (typeof body.content !== "string") { body.content = JSON.stringify(body.content) } this.mainDatabase.update("UPDATE `pages` SET `name` = ?, `content` = ? WHERE id = ?", body.name, body.content, ctx.params.id) return { status: 0, msg: `${body.name} 更新成功!` } }) - this.registryMapping('/api/page/delete', (ctx: Context) => { + this.webServer.registryMapping('/api/page/delete', (ctx: Context) => { if (!ctx.params.name) { return { status: 400, msg: '页面 名称 不能为空!' } } this.mainDatabase.update("UPDATE `pages` SET `name` = CONCAT(name, '_deleted'), deleted = 1 WHERE name = ?", ctx.params.name) return { status: 0, msg: `${ctx.params.name} 删除成功!` } @@ -288,17 +140,17 @@ Handle Time : ${preHandleTime + execTime + postHandleTime}ms private configTable = "config" private registryDatabase() { - this.registryMapping('/api/config/list', (ctx: Context) => { + this.webServer.registryMapping('/api/config/list', (ctx: Context) => { return { status: 0, data: this.mainDatabase.query('SELECT id, name, label, url, driver, username, password FROM `' + this.configTable + '` WHERE deleted = 0 AND type = ?', ctx.params.type || 0) } }) - this.registryMapping('/api/config/get', (ctx: Context) => { + this.webServer.registryMapping('/api/config/get', (ctx: Context) => { let name = ctx.params.name if (!name) { return { status: 400, msg: '名称不能为空!' } } let result = this.mainDatabase.query('SELECT id, name, label, url, driver, username, password FROM `' + this.configTable + '` WHERE `name` = ?', name) if (!result.length) { return { status: 404, msg: `配置 ${name} 不存在!` } } return { status: 0, data: result[0] } }) - this.registryMapping('/api/config/add', (ctx: Context) => { + this.webServer.registryMapping('/api/config/add', (ctx: Context) => { let body = ctx.body if (!body.name) { return { status: 400, msg: '名称不能为空!' } } this.mainDatabase.update( @@ -307,7 +159,7 @@ Handle Time : ${preHandleTime + execTime + postHandleTime}ms ) return { status: 0, msg: `配置 ${body.name} 新增成功!` } }) - this.registryMapping('/api/config/update', (ctx: Context) => { + this.webServer.registryMapping('/api/config/update', (ctx: Context) => { if (!ctx.params.id) { return { status: 400, msg: 'ID 不能为空!' } } let body = ctx.body this.mainDatabase.update( @@ -327,7 +179,6 @@ Handle Time : ${preHandleTime + execTime + postHandleTime}ms 'pluginManager' ] let params = [ - this.beanFactory, this.mainDatabase, reflect, this.container, @@ -341,7 +192,6 @@ return eval(${JSON.stringify(code)});`) } disable() { - Object.keys(this.requestMapping).forEach((r) => delete this.requestMapping[r]) - this.beanFactory.destroySingleton(WebProxyBeanName) + Object.keys(this.mappings).forEach((r) => this.webServer.unregistryMapping(r)) } } diff --git a/packages/plugins/src/SearchRanking.ts b/packages/plugins/src/SearchRanking.ts new file mode 100644 index 00000000..9a68e08a --- /dev/null +++ b/packages/plugins/src/SearchRanking.ts @@ -0,0 +1,181 @@ +/// +/// + +import { constants, plugin as pluginApi, amqp, server, web } from '@ccms/api' +import { plugin, interfaces, cmd } from '@ccms/plugin' +import { AmqpAdmin, ConnectionFactoryAdapter, AmqpManager } from '@ccms/amqp' +import { inject, Autowired } from '@ccms/container' +import { Server } from '@ccms/web' + +@plugin({ name: SearchRanking.name, version: SearchRanking.version, author: SearchRanking.author, servers: SearchRanking.servers, source: __filename }) +export class SearchRanking extends interfaces.Plugin { + public static version = '1.0.0' + public static author = 'MiaoWoo' + public static servers = [constants.ServerType.Spring] + + @inject(pluginApi.PluginManager) + private pluginManager: pluginApi.PluginManager + @inject(amqp.Manager) + private amqpManager: AmqpManager + @inject(server.Server) + private Server: server.Server + @inject(web.Server) + private webServer: Server + + @Autowired() + private mongoTemplate: any + @Autowired() + private redisTemplate: any + + private amqpAdmin: AmqpAdmin + private listener: org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer + + private readonly exchangeName = 'search.ranking' + private readonly queueName = 'search.ranking' + private readonly routerKey = 'search.ranking' + + load() { + let connection = new ConnectionFactoryAdapter({ + url: 'amqp://rabbitmq.c.sixi.com:5672', + username: 'root', + password: 'SixiMQ2018@' + }) + let template = this.amqpManager.createTemplate(connection) + this.amqpAdmin = this.amqpManager.createAdmin(template) + } + + enable() { + this.webServer.registryMapping('/api/search', (ctx) => { + if (!ctx.params.keyword) { return { status: 400, msg: '查询关键词不得为空!' } } + let keyword = ctx.params.keyword + '' + let type = ctx.params.type == 'sale' ? 'sale' : 'normal' + let time = parseInt(ctx.params.time + '') || 60 * 60 * 24 * 15 + return this.cacheAndSearch(keyword, type as any, time * 1000) + }) + this.webServer.registryMapping('/api/search/ranking', (ctx) => { + if (!ctx.params.keyword || !ctx.params.shopName) { return { status: 400, msg: '查询关键词不得为空!' } } + let keyword = ctx.params.keyword + '' + let shopName = ctx.params.shopName + '' + let type = ctx.params.type == 'sale' ? 'sale' : 'normal' + let time = parseInt(ctx.params.time + '') || 60 * 60 * 24 * 15 + return this.sendSearchRankingCmd(keyword, shopName, type as any, time * 1000) + }) + this.amqpAdmin.declareQueueAndBindExchange(this.queueName, this.exchangeName, this.routerKey) + this.amqpAdmin.declareBinding(this.queueName, 'client.topic.exchange', `cmd_res.${this.routerKey}`) + this.listener = this.amqpAdmin.createContainer(this.queueName, (content, message, channel) => { + let searchResult = JSON.parse(content) + this.redisTemplate.opsForValue().set(searchResult.reqData.cacheKey, searchResult) + this.logger.sender(this.Server.getConsoleSender(), `§6查询任务完成! §b关键词: §r${searchResult.reqData.keywords}`) + }) + this.listener.start() + } + + disable() { + this.listener.stop() + } + + @cmd() + sr(sender: any, cmd: string, args: string[]) { + let cmdKey = 'cmd' + (args[0] || 'help') + if (!this[cmdKey]) { + this.logger.sender(sender, `§4子命令 ${args[0]} 不存在!`) + return + } + args.shift() + this[cmdKey](sender, ...args) + } + + cmdrun(sender: any, ...args: string[]) { + sender.sendMessage(eval(args.join(' '))) + } + + cmdreload() { + this.pluginManager.reload(this) + } + + cmdsend(sender: any, ...args: string[]) { + } + + cmdsearch(sender: any, keyword: string, type: "sale" | "normal" = "sale", time: number) { + this.logger.sender(sender, this.cacheAndSearch(keyword, type, time)?.msg) + } + + private cacheAndSearch(keyword: string, type: string = "sale", time: number = 15 * 24 * 60 * 60 * 1000) { + let cacheKey = this.getCacheKey(keyword, type) + if (this.redisTemplate.hasKey(cacheKey)) { + let lastSearchTime = this.redisTemplate.opsForValue().get(cacheKey) + if (Date.now() - lastSearchTime > time) { + return this.createKeyworkSearch(keyword, type) + } + let lastSearchKey = this.getResultCacheKey(keyword, type, lastSearchTime) + if (!this.redisTemplate.hasKey(lastSearchKey)) { + return { status: 202, msg: `关键词: ${keyword} 排名类型: ${type} 缓存Key: ${lastSearchKey} 查询任务尚未完成...` } + } + return { + status: 200, + msg: `关键词: ${keyword} 排名类型: ${type} 查询时间: ${new Date(lastSearchTime).toLocaleString()} 查询已完成!`, + data: this.redisTemplate.opsForValue().get(lastSearchKey) + } + } + return this.createKeyworkSearch(keyword, type) + } + + private createKeyworkSearch(keyword: string, type: string) { + let cacheDate = Date.now() + this.redisTemplate.opsForValue().set(this.getCacheKey(keyword, type), cacheDate) + this.sendSearchCmd(keyword, type, cacheDate) + return { status: 201, msg: `关键词: ${keyword} 排名类型: ${type} 查询任务以创建...`, createTime: cacheDate } + } + + private createRankingSearch(keyword: string, shopName: string, type: string) { + let cacheDate = Date.now() + this.redisTemplate.opsForValue().set(this.getCacheKey(keyword, type), cacheDate) + this.sendSearchRankingCmd(keyword, shopName, type, cacheDate) + return { status: 201, msg: `关键词: ${keyword} 店铺名称: ${shopName} 排名类型: ${type} 查询任务以创建...`, createTime: cacheDate } + } + + private sendSearchCmd(keywords: string, type: string, dateCache: number) { + this.amqpAdmin.getTemplate().convertAndSend('client.topic.exchange', 'cmd_req', { + cmd: 'scout._1688Search', + data: { + keywords: keywords, + parameter: type == "sale" ? { + descendOrder: true, + sortType: 'va_rmdarkgmv30rt', + button_click: 'top' + } : {}, + cacheKey: this.getResultCacheKey(keywords, type, dateCache) + }, + resRouteSuffix: this.routerKey, + target: {} + }) + } + + private sendSearchRankingCmd(keywords: string, shopName: string, type: string, cacheTime: number) { + this.amqpAdmin.getTemplate().convertAndSend('client.topic.exchange', 'cmd_req', { + cmd: 'scout._1688Ranking', + data: { + keywords: keywords, + selector: { + shopName + }, + cacheKey: this.getResultCacheKey(keywords, type, cacheTime), + cacheTime + }, + resRouteSuffix: this.routerKey, + target: {} + }) + } + + /** + * 获得关键词+类型的上次查询时间 + * @param keywords 关键词 + * @param type 类型 + */ + private getCacheKey(keywords: string, type: string) { + return `SearchRanking:${keywords}:${type}` + } + private getResultCacheKey(keywords: string, type: string, date: number) { + return `SearchRanking:${keywords}:${type}:${date}` + } +}