From 2d281a13d1aed7a5f9a9e9f28653a2da5e00a0a0 Mon Sep 17 00:00:00 2001 From: MiaoWoo Date: Tue, 26 May 2020 15:53:41 +0800 Subject: [PATCH] feat: add spring support Signed-off-by: MiaoWoo --- packages/api/src/console.ts | 2 +- packages/api/src/constants.ts | 3 +- packages/api/src/interfaces/server/index.ts | 1 + packages/core/src/index.ts | 5 ++ packages/plugin/src/decorators.ts | 72 +++++++++-------- packages/plugin/src/manager.ts | 11 ++- packages/spring/.npmignore | 1 + packages/spring/package.json | 31 ++++++++ packages/spring/src/command.ts | 23 ++++++ packages/spring/src/console.ts | 41 ++++++++++ packages/spring/src/event.ts | 24 ++++++ packages/spring/src/index.ts | 14 ++++ packages/spring/src/server.ts | 50 ++++++++++++ packages/spring/src/task.ts | 88 +++++++++++++++++++++ packages/spring/tsconfig.json | 7 ++ 15 files changed, 333 insertions(+), 40 deletions(-) create mode 120000 packages/spring/.npmignore create mode 100644 packages/spring/package.json create mode 100644 packages/spring/src/command.ts create mode 100644 packages/spring/src/console.ts create mode 100644 packages/spring/src/event.ts create mode 100644 packages/spring/src/index.ts create mode 100644 packages/spring/src/server.ts create mode 100644 packages/spring/src/task.ts create mode 100644 packages/spring/tsconfig.json diff --git a/packages/api/src/console.ts b/packages/api/src/console.ts index 4c970b1f..0ed4e5c1 100644 --- a/packages/api/src/console.ts +++ b/packages/api/src/console.ts @@ -140,7 +140,7 @@ export class MiaoScriptConsole implements Console { if (trace.className.startsWith('<')) { let { fileName, lineNumber } = this.readSourceMap(trace.fileName, trace.lineNumber) if (fileName.startsWith(root)) { fileName = fileName.split(root)[1] } - cache.push(` §e->§c ${fileName} => §4${trace.methodName}:${lineNumber}`) + cache.push(` §e->§c ${fileName}:${lineNumber} => §4${trace.methodName}`) } else { let className = trace.className; var fileName = trace.fileName as string; diff --git a/packages/api/src/constants.ts b/packages/api/src/constants.ts index b4bc54a0..a210564d 100644 --- a/packages/api/src/constants.ts +++ b/packages/api/src/constants.ts @@ -11,6 +11,7 @@ export namespace constants { Bukkit = 'bukkit', Sponge = 'sponge', Nukkit = 'nukkit', - Bungee = 'bungee' + Bungee = 'bungee', + Spring = 'spring' } } diff --git a/packages/api/src/interfaces/server/index.ts b/packages/api/src/interfaces/server/index.ts index 9132a26f..1e05f7ba 100644 --- a/packages/api/src/interfaces/server/index.ts +++ b/packages/api/src/interfaces/server/index.ts @@ -33,5 +33,6 @@ export namespace server { getNettyPipeline(): any; getRootLogger(): any; sendJson(sender: string | any, json: object | string): void; + tabComplete?(sender: string | any, input: string, index?: number); } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index cd1cc9d6..2f62a78c 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -80,6 +80,11 @@ function detectServer(): constants.ServerType { return constants.ServerType.Bungee } catch (ex) { } + try { + Java.type("org.springframework.boot.SpringApplication") + return constants.ServerType.Spring + } catch (ex) { + } throw Error('Unknow Server Type...') } diff --git a/packages/plugin/src/decorators.ts b/packages/plugin/src/decorators.ts index 1772379c..a7346d07 100644 --- a/packages/plugin/src/decorators.ts +++ b/packages/plugin/src/decorators.ts @@ -1,4 +1,4 @@ -import { injectable, decorate } from "@ccms/container"; +import { injectable, decorate } from "@ccms/container" import { interfaces } from './interfaces' import { METADATA_KEY } from './constants' import { getPluginMetadatas, getPluginCommandMetadata, getPluginListenerMetadata, getPluginTabCompleterMetadata, getPluginConfigMetadata, getPluginStageMetadata, getPluginSources } from './utils' @@ -9,17 +9,17 @@ import { getPluginMetadatas, getPluginCommandMetadata, getPluginListenerMetadata */ export function plugin(metadata: interfaces.PluginMetadata) { return function (target: any) { - metadata.target = target; - metadata.type = "ioc"; - decorate(injectable(), target); - Reflect.defineMetadata(METADATA_KEY.plugin, metadata, target); - const previousMetadata: Map = getPluginMetadatas(); - previousMetadata.set(metadata.name, metadata); - Reflect.defineMetadata(METADATA_KEY.plugin, previousMetadata, Reflect); - const previousSources: Map = getPluginSources(); - previousSources.set(metadata.source.toString(), metadata); - Reflect.defineMetadata(METADATA_KEY.souece, previousSources, Reflect); - }; + metadata.target = target + metadata.type = "ioc" + decorate(injectable(), target) + Reflect.defineMetadata(METADATA_KEY.plugin, metadata, target) + const previousMetadata: Map = getPluginMetadatas() + previousMetadata.set(metadata.name, metadata) + Reflect.defineMetadata(METADATA_KEY.plugin, previousMetadata, Reflect) + const previousSources: Map = getPluginSources() + previousSources.set(metadata.source.toString(), metadata) + Reflect.defineMetadata(METADATA_KEY.souece, previousSources, Reflect) + } } /** @@ -28,13 +28,13 @@ export function plugin(metadata: interfaces.PluginMetadata) { */ export function cmd(metadata: interfaces.CommandMetadata = {}) { return function (target: any, key: string, value: any) { - metadata.name = metadata.name || key; - metadata.executor = key; + metadata.name = metadata.name || key + metadata.executor = key metadata.paramtypes = Reflect.getMetadata("design:paramtypes", target, key) const previousMetadata: Map = getPluginCommandMetadata(target) - previousMetadata.set(metadata.name, metadata); - Reflect.defineMetadata(METADATA_KEY.cmd, previousMetadata, target.constructor); - }; + previousMetadata.set(metadata.name, metadata) + Reflect.defineMetadata(METADATA_KEY.cmd, previousMetadata, target.constructor) + } } /** @@ -43,14 +43,14 @@ export function cmd(metadata: interfaces.CommandMetadata = {}) { */ export function tab(metadata: interfaces.CommandMetadata = {}) { return function (target: any, key: string, value: any) { - metadata.name = metadata.name || (key.startsWith('tab') ? key.split('tab', 2)[1] : key); - if (!metadata.name) { return; } - metadata.executor = key; + metadata.name = metadata.name || (key.startsWith('tab') ? key.split('tab', 2)[1] : key) + if (!metadata.name) { return } + metadata.executor = key metadata.paramtypes = Reflect.getMetadata("design:paramtypes", target, key) const previousMetadata: Map = getPluginTabCompleterMetadata(target) previousMetadata.set(metadata.name, metadata) - Reflect.defineMetadata(METADATA_KEY.tab, previousMetadata, target.constructor); - }; + Reflect.defineMetadata(METADATA_KEY.tab, previousMetadata, target.constructor) + } } /** @@ -59,30 +59,32 @@ export function tab(metadata: interfaces.CommandMetadata = {}) { */ export function listener(metadata: interfaces.ListenerMetadata = {}) { return function (target: any, key: string, value: any) { - metadata.name = metadata.name || key; - metadata.executor = key; + metadata.name = metadata.name || key + metadata.executor = key const previousMetadata: interfaces.ListenerMetadata[] = getPluginListenerMetadata(target) - Reflect.defineMetadata(METADATA_KEY.listener, [metadata, ...previousMetadata], target.constructor); - }; + Reflect.defineMetadata(METADATA_KEY.listener, [metadata, ...previousMetadata], target.constructor) + } } -export function config(metadata: interfaces.ConfigMetadata = { version: 1, format: 'yml' }) { +export function config(metadata: interfaces.ConfigMetadata = {}) { return function (target: any, key: string) { - metadata.name = metadata.name || key; - metadata.variable = key; + metadata.name = metadata.name || key + metadata.variable = key + metadata.version = metadata.version ?? 1 + metadata.format = metadata.format ?? 'yml' const previousMetadata: Map = getPluginConfigMetadata(target) - previousMetadata.set(metadata.name, metadata); - Reflect.defineMetadata(METADATA_KEY.config, previousMetadata, target.constructor); + previousMetadata.set(metadata.name, metadata) + Reflect.defineMetadata(METADATA_KEY.config, previousMetadata, target.constructor) } } function stage(metadata: interfaces.ExecMetadata = {}, stage: string) { return function (target: any, key: string, value: any) { - metadata.name = metadata.name || key; - metadata.executor = key; + metadata.name = metadata.name || key + metadata.executor = key const previousMetadata: interfaces.ExecMetadata[] = getPluginStageMetadata(target, stage) - Reflect.defineMetadata(METADATA_KEY.stage[stage], [metadata, ...previousMetadata], target.constructor); - }; + Reflect.defineMetadata(METADATA_KEY.stage[stage], [metadata, ...previousMetadata], target.constructor) + } } export function load(metadata: interfaces.ExecMetadata = {}) { diff --git a/packages/plugin/src/manager.ts b/packages/plugin/src/manager.ts index a1e43b28..2d00d5e3 100644 --- a/packages/plugin/src/manager.ts +++ b/packages/plugin/src/manager.ts @@ -30,8 +30,8 @@ export class PluginManagerImpl implements plugin.PluginManager { private pluginMetadataMap: Map initialize() { - if (this.pluginInstance !== null && this.initialized !== true) { - // 如果plugin不等于null 则代表是正式环境 + if (this.pluginInstance === undefined) { throw new Error("Can't found Plugin Instance!") } + if (this.initialized !== true) { console.i18n('ms.plugin.initialize', { plugin: this.pluginInstance, loader: Thread.currentThread().contextClassLoader }) console.i18n('ms.plugin.event.map', { count: this.EventManager.mapEventName().toFixed(0), type: this.serverType }) this.pluginRequireMap = new Map() @@ -262,7 +262,12 @@ export class PluginManagerImpl implements plugin.PluginManager { console.i18n("ms.plugin.manager.config.save.default", { plugin: plugin.description.name, name: config.name, format: config.format }) } else { plugin[config.variable] = configFactory.load(base.read(configFile)) - plugin[config.variable].save = () => base.save(configFile, configFactory.dump(plugin[config.variable])) + plugin[config.variable].save = () => { + let result = configFactory.dump(plugin[config.variable]) + base.save(configFile, result) + console.debug(`[${plugin.description.name}] Save Config ${config.variable} to file ${configFile} result ${result}`) + } + console.debug(`[${plugin.description.name}] Load Config ${config.variable} from file ${configFile} result ${JSON.stringify(plugin[config.variable])}`) } } catch (error) { console.i18n("ms.plugin.manager.config.load.error", { plugin: plugin.description.name, name: config.name, format: config.format, error }) diff --git a/packages/spring/.npmignore b/packages/spring/.npmignore new file mode 120000 index 00000000..b4359f69 --- /dev/null +++ b/packages/spring/.npmignore @@ -0,0 +1 @@ +../../.npmignore \ No newline at end of file diff --git a/packages/spring/package.json b/packages/spring/package.json new file mode 100644 index 00000000..17fbf1ee --- /dev/null +++ b/packages/spring/package.json @@ -0,0 +1,31 @@ +{ + "name": "@ccms/spring", + "version": "0.6.7", + "description": "MiaoScript spring package", + "keywords": [ + "miaoscript", + "minecraft", + "bukkit", + "sponge" + ], + "author": "MiaoWoo ", + "homepage": "https://github.com/circlecloud/ms.git", + "license": "ISC", + "main": "dist/index.js", + "scripts": { + "clean": "rimraf dist", + "watch": "tsc --watch", + "build": "yarn clean && tsc", + "test": "echo \"Error: run tests from root\" && exit 1" + }, + "devDependencies": { + "reflect-metadata": "^0.1.13", + "rimraf": "^3.0.2", + "typescript": "^3.9.2" + }, + "dependencies": { + "@ccms/api": "^0.6.7", + "@ccms/common": "^0.6.7", + "@ccms/container": "^0.6.7" + } +} diff --git a/packages/spring/src/command.ts b/packages/spring/src/command.ts new file mode 100644 index 00000000..ab05d5af --- /dev/null +++ b/packages/spring/src/command.ts @@ -0,0 +1,23 @@ +import '@ccms/nashorn' + +import { command, plugin } from '@ccms/api' +import { inject, provideSingleton, postConstruct } from '@ccms/container' + +@provideSingleton(command.Command) +export class SpringCommand extends command.Command { + @inject(plugin.PluginInstance) + private pluginInstance: any + + protected create(plugin: any, command: string) { + console.console('§4Spring暂不支持create命令!') + } + protected remove(plugin: any, command: string) { + console.console('§4Spring暂不支持remove命令!') + } + protected onCommand(plugin: any, command: any, executor: Function) { + console.console('§4Spring暂不支持onCommand!') + } + protected onTabComplete(plugin: any, command: any, tabCompleter: Function) { + console.console('§4Spring暂不支持onTabComplete!') + } +} diff --git a/packages/spring/src/console.ts b/packages/spring/src/console.ts new file mode 100644 index 00000000..94e664b1 --- /dev/null +++ b/packages/spring/src/console.ts @@ -0,0 +1,41 @@ +import { MiaoScriptConsole } from '@ccms/api' + +var colorMap = [] +colorMap['0'] = '38;5;0' +colorMap['1'] = '38;5;4' +colorMap['2'] = '38;5;2' +colorMap['3'] = '38;5;6' +colorMap['4'] = '38;5;1' +colorMap['5'] = '38;5;5' +colorMap['6'] = '38;5;3' +colorMap['7'] = '38;5;7' +colorMap['8'] = '38;5;8' +colorMap['9'] = '38;5;12' +colorMap['a'] = '38;5;10' +colorMap['b'] = '38;5;14' +colorMap['c'] = '38;5;9' +colorMap['d'] = '38;5;13' +colorMap['e'] = '38;5;11' +colorMap['f'] = '38;5;15' +colorMap['r'] = '0' +colorMap['l'] = '1' +colorMap['n'] = '4' +var regexMap = [] +for (const c in colorMap) { + regexMap[colorMap[c]] = new RegExp(`§${c}`, "g") +} +function mcColor2ANSI(str: string) { + for (const regex in regexMap) { + str = str.replace(regexMap[regex], `\u001b[${regex}m`) + } + return str; +} + +export class SpringConsole extends MiaoScriptConsole { + sender(sender: any, ...args: any[]) { + this.console(args.join(' ')) + } + console(...args: string[]): void { + this.logger.info(mcColor2ANSI(args.join(' ') + '§r')) + } +} diff --git a/packages/spring/src/event.ts b/packages/spring/src/event.ts new file mode 100644 index 00000000..b4fb81eb --- /dev/null +++ b/packages/spring/src/event.ts @@ -0,0 +1,24 @@ +import { event, plugin } from '@ccms/api' +import { inject, provideSingleton } from '@ccms/container' + +@provideSingleton(event.Event) +export class SpringEvent extends event.Event { + @inject(plugin.PluginInstance) + private pluginInstance: any + + constructor() { + super(''); + } + mapEventName() { + return 0; + } + isValidEvent(clazz: any): boolean { + throw new Error("Method not implemented."); + } + register(eventCls: any, exec: Function, priority: any, ignoreCancel: boolean) { + throw new Error("Method not implemented."); + } + unregister(event: any, listener: any): void { + throw new Error("Method not implemented."); + } +} diff --git a/packages/spring/src/index.ts b/packages/spring/src/index.ts new file mode 100644 index 00000000..d70928b4 --- /dev/null +++ b/packages/spring/src/index.ts @@ -0,0 +1,14 @@ +import { server, plugin } from '@ccms/api' +import { Container } from '@ccms/container' + +import { SpringConsole } from './console'; +import './event'; +import './server'; +import './command'; +import './task'; + +const BeanKit = Java.type('com.sixi.micro.common.kits.BeanKit') + +export default function SpringImpl(container: Container) { + container.bind(server.Console).toConstantValue(SpringConsole) +} diff --git a/packages/spring/src/server.ts b/packages/spring/src/server.ts new file mode 100644 index 00000000..d8dee048 --- /dev/null +++ b/packages/spring/src/server.ts @@ -0,0 +1,50 @@ +import { server } from '@ccms/api' +import { provideSingleton } from '@ccms/container'; +import { NativePluginManager } from '@ccms/api/dist/interfaces/server/native_plugin'; + +@provideSingleton(server.Server) +export class SpringServer implements server.Server { + constructor() { + } + getVersion(): string { + return "SpringFramework" + } + getPlayer(name: string) { + throw new Error("Method not implemented."); + } + getOnlinePlayers(): any[] { + throw new Error("Method not implemented."); + } + getConsoleSender() { + return undefined; + } + getService(service: string) { + throw new Error("Method not implemented."); + } + dispatchCommand(sender: any, command: string): boolean { + console.console('§4Spring暂不支持dispatchCommand!') + return false; + } + dispatchConsoleCommand(command: string): boolean { + console.console('§4Spring暂不支持dispatchConsoleCommand!') + return false; + } + getPluginsFolder(): string { + throw new Error("Method not implemented."); + } + getNativePluginManager(): NativePluginManager { + throw new Error("Method not implemented."); + } + getNettyPipeline() { + return base.getInstance().getAutowireCapableBeanFactory() + } + getRootLogger() { + return global.logger + } + sendJson(sender: any, json: string | object): void { + throw new Error("Method not implemented."); + } + tabComplete(sender: any, input: string, index?: number) { + throw new Error("Method not implemented."); + } +} diff --git a/packages/spring/src/task.ts b/packages/spring/src/task.ts new file mode 100644 index 00000000..9548a824 --- /dev/null +++ b/packages/spring/src/task.ts @@ -0,0 +1,88 @@ +import { task, plugin } from '@ccms/api' +import { inject, provideSingleton } from '@ccms/container' + +const AtomicBoolean = Java.type("java.util.concurrent.atomic.AtomicBoolean") +const Thread = Java.type('java.lang.Thread') +const ThreadPoolExecutor = Java.type('java.util.concurrent.ThreadPoolExecutor') +const ThreadPoolTaskExecutor = Java.type('org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor') + +let executor: any +let tasks: { [key: number]: task.Cancelable } = {} +let taskId = 0 + +@provideSingleton(task.TaskManager) +export class SpringTaskManager implements task.TaskManager { + @inject(plugin.PluginInstance) + private pluginInstance: any + + constructor() { + executor = new ThreadPoolTaskExecutor() + executor.setCorePoolSize(10) + executor.setMaxPoolSize(100) + executor.setQueueCapacity(500) + executor.setKeepAliveSeconds(60) + executor.setThreadNamePrefix("@ccms/spring-") + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()) + executor.initialize() + } + + create(func: Function): task.Task { + if (Object.prototype.toString.call(func) !== "[object Function]") { throw TypeError('第一个参数 Task 必须为 function !') } + return new SpringTask(this.pluginInstance, func) + } + callSyncMethod(func: Function): any { + return func() + } + disable() { + Object.values(tasks).forEach((task) => task.cancel()) + executor.shutdown(); + } +} + +export class SpringTask extends task.Task { + public id = taskId++ + private running = new AtomicBoolean(true) + + run() { + if (this.laterTime > 0) { + try { + Thread.sleep(this.laterTime) + } catch (ex) { + Thread.currentThread().interrupt() + } + } + while (this.running.get()) { + try { + this.func() + } catch (t) { + console.error("Task exec error:", t) + console.ex(t) + } + // If we have a interval of 0 or less, only run once + if (this.interval <= 0) { break } + try { + Thread.sleep(this.interval) + } catch (ex) { + Thread.currentThread().interrupt() + } + } + this.cancel() + } + + cancel(): any { + var wasRunning = this.running.getAndSet(false) + if (wasRunning) { + tasks[this.id] = undefined + } + } + + submit(): task.Cancelable { + tasks[this.id] = this + executor.execute(this.run.bind(this)) + return { + cancel: () => { + return this.cancel() + } + } + } +} diff --git a/packages/spring/tsconfig.json b/packages/spring/tsconfig.json new file mode 100644 index 00000000..7aae5d2b --- /dev/null +++ b/packages/spring/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": "src", + "outDir": "dist" + } +} \ No newline at end of file