feat: backup plugins

Signed-off-by: MiaoWoo <admin@yumc.pw>
This commit is contained in:
MiaoWoo 2020-06-20 16:40:34 +08:00
parent bf3638dda0
commit 5f0c3bbdd4
5 changed files with 322 additions and 230 deletions

View File

@ -1,3 +1,4 @@
/// <reference types="@ccms/nashorn" />
/// <reference types="@ccms/types/dist/typings/bukkit" /> /// <reference types="@ccms/types/dist/typings/bukkit" />
/// <reference types="@ccms/types/dist/typings/sponge" /> /// <reference types="@ccms/types/dist/typings/sponge" />
/// <reference types="@ccms/types/dist/typings/bungee" /> /// <reference types="@ccms/types/dist/typings/bungee" />
@ -56,7 +57,7 @@ export class MiaoConsole extends interfaces.Plugin {
this.token = Java.type('java.util.UUID').randomUUID().toString() this.token = Java.type('java.util.UUID').randomUUID().toString()
this.logger.console(`§6已生成随机Token: §3${this.token} §c重启后或重新生成后失效!`) this.logger.console(`§6已生成随机Token: §3${this.token} §c重启后或重新生成后失效!`)
} }
global.eventCenter.on('log', (msg) => { process.on('message', (msg) => {
this.logCache.push(msg) this.logCache.push(msg)
if (this.logCache.length > 30) { if (this.logCache.length > 30) {
this.logCache = this.logCache.slice(this.logCache.length - 30, this.logCache.length) 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, { let ProxyAppender = Java.extend(AbstractAppender, {
append: (logEvent) => { append: (logEvent) => {
if (logEvent.level.intLevel() <= Level.INFO.intLevel()) { 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) { if (this.rootLogger) {
let AbstractHandler = Java.type('java.util.logging.Handler') let AbstractHandler = Java.type('java.util.logging.Handler')
let ProxyHandler = Java.extend(AbstractHandler, { let ProxyHandler = Java.extend(AbstractHandler, {
publish: (record) => global.eventCenter.emit('log', record.getMessage()), publish: (record) => process.emit('message', record.getMessage()),
flush: () => { }, flush: () => { },
close: () => { } close: () => { }
}) })
@ -162,7 +163,7 @@ export class MiaoConsole extends interfaces.Plugin {
if (this.rootLogger) { if (this.rootLogger) {
let AppenderBase = Java.type('ch.qos.logback.core.AppenderBase') let AppenderBase = Java.type('ch.qos.logback.core.AppenderBase')
let ProxyAppender = Java.extend(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 = new ProxyAppender()
this.appender.start() this.appender.start()
@ -175,7 +176,7 @@ export class MiaoConsole extends interfaces.Plugin {
disable() { disable() {
if (this.socketIOServer) { if (this.socketIOServer) {
this.socketIOServer.close() this.socketIOServer.close()
global.eventCenter.removeAllListeners('log') process.removeAllListeners('message')
} }
if (this.container.isBound(io.Instance)) { if (this.container.isBound(io.Instance)) {
this.container.unbind(io.Instance) this.container.unbind(io.Instance)
@ -220,7 +221,7 @@ export class MiaoConsole extends interfaces.Plugin {
startSocketIOServer() { startSocketIOServer() {
let namespace = this.socketIOServer.of('/MiaoConsole') 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) => { namespace.on('connect', (client: SocketIOSocket) => {
if (!this.token) { if (!this.token) {
this.logger.console(`§6客户端 §b${client.id} §a请求连接 §4服务器尚未设置 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) setTimeout(() => this.server.dispatchConsoleCommand(cmd), 0)
client.emit('log', `§6命令: §b${cmd} §a执行成功!`) 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) => { client.on('exec', (code) => {
try { try {
client.emit('log', this.runCode(code, client.nsp, client)) 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) => { client.on('ls', (file: string, fn) => {
let dir = fs.file(file); let dir = fs.file(file)
if (!dir.isDirectory()) { if (!dir.isDirectory()) {
return fn(undefined, `${file} 不是一个目录!`) return fn(undefined, `${file} 不是一个目录!`)
} }
@ -309,10 +313,9 @@ export class MiaoConsole extends interfaces.Plugin {
if (this.serverType == "spring") { if (this.serverType == "spring") {
var dbm = container.get(api.database.DataBaseManager) var dbm = container.get(api.database.DataBaseManager)
var db = dbm.getMainDatabase() 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)) + '' return this.task.callSyncMethod(() => tfunc.apply(this, params)) + ''
} }
} }

View File

@ -0,0 +1,32 @@
/// <reference types="@ccms/types" />
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()
}
}

View File

@ -1,10 +1,11 @@
import { plugin as pluginApi, task, server } from '@ccms/api' import { plugin as pluginApi, task, server } from '@ccms/api'
import { Translate } from '@ccms/i18n' 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 { interfaces, plugin, cmd, tab } from '@ccms/plugin'
import * as fs from '@ccms/common/dist/fs' import * as fs from '@ccms/common/dist/fs'
import * as reflect from '@ccms/common/dist/reflect'
import http from '@ccms/common/dist/http' import http from '@ccms/common/dist/http'
let help = [ let help = [
@ -19,7 +20,7 @@ let help = [
'§6/mpm §arun §e<JS代码> §6- §3运行JS代码', '§6/mpm §arun §e<JS代码> §6- §3运行JS代码',
'§6/mpm §adeploy §e<插件名称> §6- §3发布插件', '§6/mpm §adeploy §e<插件名称> §6- §3发布插件',
'§6/mpm §crestart §6- §4重启MiaoScript脚本引擎' '§6/mpm §crestart §6- §4重启MiaoScript脚本引擎'
]; ]
let langMap = { let langMap = {
'main.command.not.exists': '§4未知的子命令: §c{command}', '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 }) @plugin({ name: 'MiaoScriptPackageManager', prefix: 'PM', version: '1.0.1', author: 'MiaoWoo', source: __filename })
export class MiaoScriptPackageManager extends interfaces.Plugin { export class MiaoScriptPackageManager extends interfaces.Plugin {
@inject(pluginApi.PluginManager) @inject(pluginApi.PluginManager)
private pluginManager: pluginApi.PluginManager; private pluginManager: pluginApi.PluginManager
@inject(task.TaskManager) @inject(task.TaskManager)
private taskManager: task.TaskManager; private taskManager: task.TaskManager
@inject(server.ServerType) @inject(server.ServerType)
private serverType: string; private serverType: string
@inject(server.Server) @inject(server.Server)
private server: server.Server private server: server.Server
@inject(pluginApi.PluginFolder) @inject(pluginApi.PluginFolder)
private pluginFolder: string; private pluginFolder: string
private packageCache: any[] = []; private packageCache: any[] = [];
private packageNameCache: string[] = []; private packageNameCache: string[] = [];
private translate: Translate; private translate: Translate
load() { load() {
this.translate = new Translate({ this.translate = new Translate({
@ -78,7 +79,7 @@ export class MiaoScriptPackageManager extends interfaces.Plugin {
@cmd() @cmd()
mpm(sender: any, command: string, args: string[]) { 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) { i18n(sender: any, name: string, params?: any) {
@ -90,64 +91,64 @@ export class MiaoScriptPackageManager extends interfaces.Plugin {
if (!this[cmdKey]) { if (!this[cmdKey]) {
this.i18n(sender, 'main.command.not.exists', { command: args[0] }) this.i18n(sender, 'main.command.not.exists', { command: args[0] })
this.i18n(sender, 'main.command.help.tip', { command }) this.i18n(sender, 'main.command.help.tip', { command })
return; return
} }
args.shift() args.shift()
this[cmdKey](sender, ...args); this[cmdKey](sender, ...args)
} }
cmdhelp(sender: any) { cmdhelp(sender: any) {
this.logger.sender(sender, help); this.logger.sender(sender, help)
} }
cmdload(sender: any, name: string) { cmdload(sender: any, name: string) {
let pluginFile = fs.concat(__dirname + '', name); let pluginFile = fs.concat(__dirname + '', name)
if (!fs.exists(pluginFile)) { if (!fs.exists(pluginFile)) {
this.i18n(sender, 'plugin.not.exists', { name: `${name}(${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') { cmdlist(sender: any, type: string = 'cloud') {
if (type == "i" || type == "install") { if (type == "i" || type == "install") {
this.i18n(sender, 'list.install.header') this.i18n(sender, 'list.install.header')
this.pluginManager.getPlugins().forEach((plugin) => { this.pluginManager.getPlugins().forEach((plugin) => {
this.i18n(sender, 'list.install.body', plugin.description); this.i18n(sender, 'list.install.body', plugin.description)
}) })
} else { } else {
this.i18n(sender, 'list.header') this.i18n(sender, 'list.header')
for (var pkgName in this.packageCache) { 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) { cmdinstall(sender: any, name: string) {
if (!name) { return this.i18n(sender, 'plugin.name.empty') } if (!name) { return this.i18n(sender, 'plugin.name.empty') }
this.download(sender, name); this.download(sender, name)
} }
cmdupdate(sender: any, name: string) { cmdupdate(sender: any, name: string) {
if (name) { if (name) {
this.update(sender, name); this.update(sender, name)
} else { } else {
this.updateRepo(sender) this.updateRepo(sender)
} }
} }
cmdupgrade(sender: any, name: string) { cmdupgrade(sender: any, name: string) {
if (!name) { return this.i18n(sender, 'upgrade.confirm'); } if (!name) { return this.i18n(sender, 'upgrade.confirm') }
if (name == "comfirm") { if (name == "comfirm") {
let enginePath = fs.path(fs.file(fs.concat(root, 'node_modules', '@ccms'))) let enginePath = fs.path(fs.file(fs.concat(root, 'node_modules', '@ccms')))
if (enginePath.startsWith(root)) { if (enginePath.startsWith(root)) {
base.delete(enginePath); base.delete(enginePath)
this.cmdrestart(sender); this.cmdrestart(sender)
} }
} }
if (this.checkPlugin(sender, name)) { if (this.checkPlugin(sender, name)) {
this.update(sender, name); this.update(sender, name)
this.pluginManager.reload(name); this.pluginManager.reload(name)
} }
} }
@ -161,7 +162,7 @@ export class MiaoScriptPackageManager extends interfaces.Plugin {
cmdreload(sender: any, name: string) { cmdreload(sender: any, name: string) {
name = name || this.description.name name = name || this.description.name
if (this.checkPlugin(sender, name)) { if (this.checkPlugin(sender, name)) {
this.pluginManager.reload(name); this.pluginManager.reload(name)
this.i18n(sender, 'plugin.reload.finish', { name }) this.i18n(sender, 'plugin.reload.finish', { name })
} }
} }
@ -184,33 +185,58 @@ export class MiaoScriptPackageManager extends interfaces.Plugin {
return return
} }
try { try {
this.logger.sender(sender, '§6Reloading §3MiaoScript Engine...'); this.logger.sender(sender, '§6Reloading §3MiaoScript Engine...')
ScriptEngineContextHolder.disableEngine(); ScriptEngineContextHolder.disableEngine()
Packages.java.lang.System.gc(); Packages.java.lang.System.gc()
ScriptEngineContextHolder.enableEngine(); ScriptEngineContextHolder.enableEngine()
this.logger.sender(sender, '§3MiaoScript Engine §6Reload §aSuccessful...'); this.logger.sender(sender, '§3MiaoScript Engine §6Reload §aSuccessful...')
} catch (ex) { } catch (ex) {
this.logger.sender(sender, "§3MiaoScript Engine §6Reload §cError! ERR: " + ex); this.logger.sender(sender, "§3MiaoScript Engine §6Reload §cError! ERR: " + ex)
this.logger.sender(sender, this.logger.stack(ex)); this.logger.sender(sender, this.logger.stack(ex))
} }
} }
cmdrun(sender: any, ...args: any[]) { cmdrun(sender: any, ...args: any[]) {
try { try {
let script = args.join(' '); let script = args.join(' ')
this.i18n(sender, 'run.script', { script }) 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 + '' }) this.i18n(sender, 'run.result', { result: result == undefined ? this.translate.translate('run.noresult') : result + '' })
} catch (ex) { } 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) { cmddeploy(sender: any, name: any) {
if (!process.env.AccessToken) { return this.i18n(sender, 'deploy.token.not.exists') } if (!process.env.AccessToken) { return this.i18n(sender, 'deploy.token.not.exists') }
this.taskManager.create(() => { this.taskManager.create(() => {
if (this.checkPlugin(sender, name)) { 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, { let result = http.post("http://ms.yumc.pw/api/plugin/deploy?access_token=" + process.env.AccessToken, {
name, name,
author: plugin.description.author, author: plugin.description.author,
@ -224,7 +250,7 @@ export class MiaoScriptPackageManager extends interfaces.Plugin {
update(sender: any, name: string) { update(sender: any, name: string) {
if (this.checkCloudPlugin(sender, name)) { 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": case "list":
return ["install", "cloud"] return ["install", "cloud"]
case "install": case "install":
return this.packageNameCache; return this.packageNameCache
case "update": case "update":
case "upgrade": case "upgrade":
case "load": case "load":
case "unload": case "unload":
case "reload": case "reload":
case "deploy": case "deploy":
return [...this.pluginManager.getPlugins().keys()]; return [...this.pluginManager.getPlugins().keys()]
} }
} }
} }
updateRepo(sender: any) { updateRepo(sender: any) {
this.taskManager.create(() => { this.taskManager.create(() => {
let result = http.get('http://ms.yumc.pw/api/plugin/list'); let result = http.get('http://ms.yumc.pw/api/plugin/list')
for (const pl of result.data) { this.packageCache[pl.name] = pl; } for (const pl of result.data) { this.packageCache[pl.name] = pl }
this.packageNameCache = Object.keys(this.packageCache); this.packageNameCache = Object.keys(this.packageCache)
this.i18n(sender, 'cloud.update.finish', { length: this.packageNameCache.length }) this.i18n(sender, 'cloud.update.finish', { length: this.packageNameCache.length })
}).async().submit(); }).async().submit()
} }
download(sender: any, name: string, update: boolean = false) { download(sender: any, name: string, update: boolean = false) {

View File

@ -2,58 +2,30 @@
/// <reference types="@ccms/types/dist/typings/tomcat/index" /> /// <reference types="@ccms/types/dist/typings/tomcat/index" />
/// <reference types="@ccms/types/dist/typings/spring/index" /> /// <reference types="@ccms/types/dist/typings/spring/index" />
import { constants, database, plugin } from "@ccms/api" import { constants, database, plugin, web } from "@ccms/api"
import { inject, ContainerInstance, Container, JSClass } from "@ccms/container" import { inject, ContainerInstance, Container } from "@ccms/container"
import { JSPlugin, interfaces, cmd } from "@ccms/plugin" import { JSPlugin, interfaces, cmd } from "@ccms/plugin"
import { DataBase, DataBaseManager } from '@ccms/database' import { DataBase, DataBaseManager } from '@ccms/database'
import { Server, Context, RequestHandler } from '@ccms/web'
import * as fs from '@ccms/common/dist/fs' import * as fs from '@ccms/common/dist/fs'
import * as reflect from '@ccms/common/dist/reflect' import * as reflect from '@ccms/common/dist/reflect'
import * as querystring from 'querystring'
const WebProxyBeanName = 'webServerProxy' @JSPlugin({ name: 'MiaoSpring', prefix: 'MSpring', version: '1.0.1', author: 'MiaoWoo', servers: [constants.ServerType.Spring], source: __filename })
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 })
export class MiaoSpring extends interfaces.Plugin { 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) @inject(ContainerInstance)
private container: Container private container: Container
@inject(plugin.PluginInstance)
private context: any
@inject(plugin.PluginManager) @inject(plugin.PluginManager)
private pluginManager: plugin.PluginManager private pluginManager: plugin.PluginManager
@inject(database.DataBaseManager) @inject(database.DataBaseManager)
private databaseManager: DataBaseManager private databaseManager: DataBaseManager
@inject(web.Server)
private webServer: Server
private ResponseEntity = org.springframework.http.ResponseEntity
private beanFactory: any
private mainDatabase: DataBase private mainDatabase: DataBase
private interceptors: InterceptorAdapter[] = [] private mappings: Set<string>
private requestMapping: { [key: string]: RequestHandler } = {}
@cmd() @cmd()
mspring(sender: any) { mspring(sender: any) {
@ -62,144 +34,24 @@ export class MiaoSpring extends interfaces.Plugin {
} }
load() { load() {
this.beanFactory = this.context.getAutowireCapableBeanFactory()
this.mainDatabase = this.databaseManager.getMainDatabase() this.mainDatabase = this.databaseManager.getMainDatabase()
this.mappings = new Set()
} }
enable() { enable() {
this.registryWebBean()
this.registryDefault() this.registryDefault()
this.registryPages() this.registryPages()
this.registryDatabase() 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) { registryMapping(path: string, handler: RequestHandler) {
this.requestMapping[path] = handler this.mappings.add(path)
this.webServer.registryMapping(path, handler)
} }
registryDefault() { 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'] 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', name: 'RedirectHandle',
preHandle: (ctx: Context) => { preHandle: (ctx: Context) => {
const index = foundMap.indexOf(ctx.request.getRequestURI()) 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', name: 'StaticHandle',
preHandle: (ctx: Context) => { preHandle: (ctx: Context) => {
let type = ctx.header['Accept'] || '' 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 { try {
return { status: 200, data: this.runCode(ctx.body + ''), msg: '代码执行成功!' } return { status: 200, data: this.runCode(ctx.body + ''), msg: '代码执行成功!' }
} catch (error) { } catch (error) {
return { status: 500, data: console.stack(error, false), msg: '代码执行异常!' } 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: '插件列表获取成功!' } 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: '插件名称不得为空!' } } if (!ctx.params.name) { return { status: 400, msg: '插件名称不得为空!' } }
}) })
} }
private registryPages() { 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') } } 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 name = decodeURIComponent(`${ctx.params.name}`)
let varable = undefined let varable = undefined
if (!name) { return { status: 400, msg: '名称不能为空!' } } if (!name) { return { status: 400, msg: '名称不能为空!' } }
@ -265,20 +117,20 @@ Handle Time : ${preHandleTime + execTime + postHandleTime}ms
} }
return { status: 0, data: JSON.parse(content) } 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 let body = ctx.body
if (typeof body.content !== "string") { body.content = JSON.stringify(body.content) } 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) this.mainDatabase.update("INSERT INTO `pages`(`type`, `name`, `content`) VALUES (?, ?, ?)", body.type || 1, body.name, body.content)
return { status: 0, msg: `${body.name} 新增成功!` } 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 不能为空!' } } if (!ctx.params.id) { return { status: 400, msg: 'ID 不能为空!' } }
const body = ctx.body const body = ctx.body
if (typeof body.content !== "string") { body.content = JSON.stringify(body.content) } 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) this.mainDatabase.update("UPDATE `pages` SET `name` = ?, `content` = ? WHERE id = ?", body.name, body.content, ctx.params.id)
return { status: 0, msg: `${body.name} 更新成功!` } 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: '页面 名称 不能为空!' } } 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) this.mainDatabase.update("UPDATE `pages` SET `name` = CONCAT(name, '_deleted'), deleted = 1 WHERE name = ?", ctx.params.name)
return { status: 0, msg: `${ctx.params.name} 删除成功!` } return { status: 0, msg: `${ctx.params.name} 删除成功!` }
@ -288,17 +140,17 @@ Handle Time : ${preHandleTime + execTime + postHandleTime}ms
private configTable = "config" private configTable = "config"
private registryDatabase() { 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) } 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 let name = ctx.params.name
if (!name) { return { status: 400, msg: '名称不能为空!' } } 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) 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} 不存在!` } } if (!result.length) { return { status: 404, msg: `配置 ${name} 不存在!` } }
return { status: 0, data: result[0] } 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 let body = ctx.body
if (!body.name) { return { status: 400, msg: '名称不能为空!' } } if (!body.name) { return { status: 400, msg: '名称不能为空!' } }
this.mainDatabase.update( this.mainDatabase.update(
@ -307,7 +159,7 @@ Handle Time : ${preHandleTime + execTime + postHandleTime}ms
) )
return { status: 0, msg: `配置 ${body.name} 新增成功!` } 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 不能为空!' } } if (!ctx.params.id) { return { status: 400, msg: 'ID 不能为空!' } }
let body = ctx.body let body = ctx.body
this.mainDatabase.update( this.mainDatabase.update(
@ -327,7 +179,6 @@ Handle Time : ${preHandleTime + execTime + postHandleTime}ms
'pluginManager' 'pluginManager'
] ]
let params = [ let params = [
this.beanFactory,
this.mainDatabase, this.mainDatabase,
reflect, reflect,
this.container, this.container,
@ -341,7 +192,6 @@ return eval(${JSON.stringify(code)});`)
} }
disable() { disable() {
Object.keys(this.requestMapping).forEach((r) => delete this.requestMapping[r]) Object.keys(this.mappings).forEach((r) => this.webServer.unregistryMapping(r))
this.beanFactory.destroySingleton(WebProxyBeanName)
} }
} }

View File

@ -0,0 +1,181 @@
/// <reference types="@ccms/types/dist/typings/rabbitmq" />
/// <reference types="@ccms/types/dist/typings/spring/amqp" />
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<string>(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}`
}
}