Development_Document - yumao233/MCSManager-v8.7 GitHub Wiki

项目开发参考文档

本页面将告诉你此项目的基本知识与开发常识,适用于想对本项目进行修改,二次开发,新增功能的任何技术人员。

前提

开发者必须掌握 Javascript 语言,Vue.js 框架概念,以及后端的 Express.js 基本知识。

当前代码依靠 MCSManager 8.6.12 进行描述,未来版本代码可能会有细节上的变化。


Github 项目地址: https://github.com/Suwings/MCSManager

使用 HTTPAPI 文档 :https://github.com/Suwings/MCSManager/wiki/API-Documentation

后端目录解释

  • core 包含文件读写数据模型,进程类,服务器管理类,用户类等
  • model 主要作用是在路由层与 core 层之间有一个辅助作用。
  • helper 服务层,用于辅助路由层业务逻辑代码以及公共代码。
  • route 路由层,所有 http/ws 请求业务逻辑代码均在此层。
  • route/*.js 路由层,所有 http 请求业务逻辑代码均在此层。
  • route/websocket/*.js 路由层,所有 ws 请求业务逻辑代码均在此层。
  • route/websocket/console/*.js 路由层,进程输出与命令执行相关逻辑均在此处。
  • onlinefs 文件管理嵌入式小程序,文件在线管理后端相关代码。
  • ftpd FTP 嵌入式小程序,FTP 相关代码。

前端目录解释

  • public 所有前端代码,前端所有HTML代码均无需后端渲染,通过纯粹的请求(http/ws)来获取数据。
  • public/common 公共资源库,存放 js,css,依赖库,图片,或其他公开资源。
  • public/login 登陆页面
  • public/onlinefs_public 文件管理嵌入式小程序,前端相关代码。
  • public/template/*.html 基本页面,网页可以加载这些html,每一个html均为一个独立的子页面。
  • public/template/component/*.html 基本页面,网页可以加载这些html,每一个html均为一个独立的子页面。
  • public/template/dialog/*.html 弹出式对话框页面,可以在基本页面弹出一个对话框。
  • public/template/other/*.html 其他,与项目无直接关系的相关页面。

面板前端框架

整体前端网页是基于 Vue.js 自研的框架,非常适用于使用 Websocket 通信的单页Web应用(single page web application,SPA)。

整个网页一般情况下无需刷新,整个网站仅有一个标准 URL,目前的多样化的 URL 均是伪装 URL。

输入图片说明

您可以现在自己再仔细看看面板的网页,仔细研究便会发现,从始至终,整个页面均没有进行过刷新,你所看见的网页切换均只是右侧的子页面局部刷新,动态加载不同的 html 的结果。


子页面的生命周期 (重要)

前面我已经讲过,整个页面是 无需刷新 的,页面的变化均是 子页面动态加载的结果

但是为了避免子页面的全局变量泛滥,又或者值域冲突,每一个子页面均有一个生命周期。

最简单的例子

仔细去观察每个 public/template/*.html 文件,你会发现均有以下代码:

这是 public/template/center.html 文件

// 子页面被创建事件,触发此事件时,子页面元素已经完全加载完毕。
MI.rListener('onload', function () {
    // ... 子页面创建后,执行的代码 ...
});

// 子页面被销毁事件,触发此事件时,子页面元素还存在,但即将被销毁。
MI.rListener('onend', function () {
    // ... 子页面消耗后,执行的代码,一般用来释放资源 ...
})

所有子页面代码 必须写在这两个事件函数 内,写在外面话会影响整个面板,成为全局变量。

两个 生命周期事件函数直接如果要传递变量 ,可以 使用 PAGE 这个子页面临时全局变量 ,每个子页面都会初始化此全局变量。

PAGE.name = "xxxx" // 在事件函数中直接使用便可,此临时全局变量一定存在。


面板请求与响应基本概念

接下来我将会告诉你当前项目的基本通信原理。

整个网页均由 http+ws 模式进行传输数据,并且绝大部分均由 ws 来进行传输,所以不一定非得局限于 http 模式,即整个网页可以由后端自主发送数据,而无需前端请求,前端也可以随时随地的发送数据。


最简单的请求模板

后端路由监听(需写在route/websockt/下):

const { WebSocketObserver } = require('../../model/WebSocketModel');
// 定义 route/path 路由,待前端请求此路由时会触发
WebSocketObserver().listener('my/path', (data) => {
 const body = data.body; // 前端请求后的请求数据
    // 更多业务逻辑代码....
    response.wsSend(data.ws, 'mcping/config', "..data.."); // 后端响应数据的方法
});

前端请求方法(需写在 public/*/*.html<script> 区域):

// 通过 WS.sendMsg 全局方法发送数据,此方法是 ws 传输数据,并非 http。
// 参数1 路由层的路径,即对应的 my/path
// 参数2 请求体,这里发送了服务器名字,PAGE 是子页面的临时全局变量。
// 参数3 后端下一个响应回调,请求之后的下一个响应一定触发。
WS.sendMsg('my/path', PAGE.selectServerName, function (obj) {
    var responseValue = obj.ResponseValue; // 后端响应体
    // responseValue 为 "..data.."
    // 前端业务逻辑代码
});

一段实际代码的讲解

接下来我将举例一段项目中实际存在的代码供你学习项目是如何进行通信的。

后端路由 route\websocket\mcping.js 37 行左右

// 获取配置路由器,用于响应 mcping/config 路径的请求。
// 其中 data.body 代表请求体,即前端发送来的字符串。
WebSocketObserver().listener('mcping/config', (data) => {
    const serverName = data.body || ""; // 获取请求体中的服务器名字
    if (serverName) { 
        const mcserver = serverModel.ServerManager().getServer(serverName); // 通过名字获取服务器实例
        // 响应给前端实例数据模型下的 mcpingConfig 相关数据。
        // 用此方法来向前端发送数据。
        response.wsSend(data.ws, 'mcping/config', mcserver.dataModel.mcpingConfig); 
    }
});

前端请求 public\template\dialog\pingmc_config.html 49 行左右

 WS.sendMsg('mcping/config', PAGE.listenServername, function (obj) {
        // obj.ResponseValue 是后端发送来的响应消息头
        var remoteMCPingConfig = obj.ResponseValue;
        // 经典的 Vue.js 框架运用方法
        new Vue({
            el: "#PingmcContainer",
            data: {
                mcpingName: remoteMCPingConfig.mcpingName || "",
                mcpingHost: remoteMCPingConfig.mcpingHost || "",
                mcpingPort: remoteMCPingConfig.mcpingPort || "",
                mcpingMotd: remoteMCPingConfig.mcpingMotd || ""
            },
            methods: {
                ok: function () {
                    // 用户点击保存按钮时触发,前端发送消息到 mcping/config_save 路由。
                    // 发送的数据是 参数2,切记要进行 json 转文本,此方法并不会自动转换对象成文本。
                    WS.sendMsg('mcping/config_save', JSON.stringify({
                        mcpingServerName: PAGE.listenServername,
                        mcpingConfig: {
                            mcpingName: this.mcpingName.trim() || "Minecraft Server",
                            mcpingHost: this.mcpingHost.trim() || "localhost",
                            mcpingPort: this.mcpingPort.trim() || "",
                            mcpingMotd: this.mcpingMotd.trim() || ""
                        }
                    }));
                }
            }
        });
    });

功能界面如下: 输入图片说明


小结

到目前为止,此教程已经简单的告诉您面板的通信原理与前端页面框架的大致使用方法,如您有相关兴趣,建议您仔细阅读源代码,多阅读现有代码实现方式。

值得注意的是: 因为面板是从2017一直维护至今,Nodejs 也从当初的 8.X 到现在的 14.X。因此有部分陈旧代码因为历史遗留问题依然在使用,相关的使用方法已经不再推荐,但依然可用并且兼容。


前端网页全局方法

发送数据到后端指定路由控制器(重要)

// 发送一段 json 文本到 server/console/command 路由。
WS.sendMsg('server/console/command', JSON.stringify({
    command: 'help',
    serverName: this.serverName
}));

子页面跳转

// 参数1 必填,跳转的html路径。
// 参数2 必填,跳转页面前请求的路由地址,不推荐使用,建议填写 'genuser/home' 即可。
// 参数3 选填,服务器名字,用于刷新网页后从 URL 获取服务器名字。
RES.redirectPage('./template/component/properties.html', 'genuser/home', serverName);

弹出对话框

TOOLS.popWind({
    style: { // 对话框附加式样
        maxWidth: "600px",
        top: "24%" // 建议根据对话框的预估高度来修改相对顶部距离
    },
    title: "用户的密匙设置", // 对话框标题
    template: "template/dialog/apikey.html" // 对话框对应的 html 文件
});

弹出底部蓝色提示框

// 弹出提示框,显示3秒,淡入淡出。若消息过多,会形成队列逐一展示。
TOOLS.pushMsgWindow('提示的消息');

刷新子页面

// 右侧子页面将会重新加载,不会导致整个网页刷新。
PAGE.refresh();

定义网页 URL

// 定义页面地址,即网页 URL 虚假地址,刷新网页后依然可以跳转到现有网页的办法。
// 参数1 必填,HTML文件相对地址
// 参数2 必填,跳转页面前请求的路由地址,不推荐使用,建议填写 'genuser/home' 即可。
// 参数3 选填,服务器名字,用于刷新网页后从 URL 获取服务器名字。
TOOLS.definePage('template/gen_home', 'genuser/home', '');

网页 URL 定位器

// 这段代码的作用是打开文件管理网页
// 使用 MCSERVER.URL() 方法,来获取路径,这样做的目的是方便统一使用。
var path = MCSERVER.URL("fs_auth/auth/" + encodeURIComponent(serverName));
window.open(path);

旧版的 Vue 实例管理器(不推荐使用)

VIEW_MODEL.newVue('ServerList', {
    el: '#ServerList',
    methods: {
        // 一些方法的代码
    }
});
// 相信您一定在一些 html 文件里面见过这样 new Vue 对象的方法,这属于历史遗留问题。
// 采用此方法创建的 Vue 对象将会分配到 ViewModel 层专门进行管理和共享,从而实现子页面数据跨界共享。
// VIEW_MODEL.newVue() 的实质就是 new Vue(),只是将数据托管给了 ViewModel 层。
// 但事实上并无需要如此复杂,现在版本已经不再推荐使用这个方法,建议只用于阅读,不用于使用。

前端常用的方法便是这些,绝大部分功能纵使您不清楚相关 API ,也可以通过原生 Javascript 语言来完成。这些 API 中,十分重要的方法便是第一个,它是与后端交互数据的主要方法。


后端网页全局方法

创建路由控制器

// route\websocket\docker.js
// 监听来自 docker/config 路径的前端请求。
WebSocketObserver().listener('docker/config', (data) => {
    // data.body 请求体,数据不可信
    // data.WsSession.username 请求者的用户名,数据可信
    if (!permssion.isMaster(data.WsSession)) return; // 权限检查,判定此 ws 链接是否是管理员
    const serverName = data.body || "";
    if (serverName) {
        // 取得服务器实例
        const mcserver = serverModel.ServerManager().getServer(serverName); 
        // 返回服务器的 Docker 配置信息给前端
        response.wsSend(data.ws, 'docker/config', mcserver.dataModel.dockerConfig);
    }
});

前端请求路由控制器,前面已经说过,但这里再说明一下:

// public\template\dialog\docker_config.html
// 请求刚刚写的路由器,获取服务器的 Docker 配置
WS.sendMsg('docker/config', serverName, function (obj) { 
    var responseValue = obj.ResponseValue; // 响应消息体,一般用于给予简短的消息,这里是 docker 配置。
    var body = obj.body; // 响应文本,一般用于给予非常长的纯文本,这里是空。
});

权限控制

const permssion = require('../../helper/Permission');
// 根据 data 的 WsSession 类来判定是否为管理,返回 bool。(仅限于 ws 协议)
permssion.isMaster(data.WsSession);
// 根据 userName 来判定是否能够控制 serverName 服务器。返回 bool。
permssion.isCanServer(userName, serverName)
// 通过 Express 框架的 req 和 res 变量来判断是否已经登陆。返回 bool。
permission.needLogin(req, res);
// 通过 Express 框架的 req 和 res 变量来判断是否为管理员。返回 bool。
permission.IsSessionMaster(req, res);

服务器相关

// 需要导入的模块
const serverModel = require('../../model/ServerModel');
// 通过服务器名字获取服务器实例
const server = serverModel.ServerManager().getServer(serverName);
// 获取所有服务器信息
const value = serverModel.ServerManager().getServerList();
// 创建服务器
serverModel.createServer(serverName, config);
config.addCmd;  // 附加参数 [] 类型
config.java;    // Java 环境变量,一般默认就行
config.jarName; // Jar 文件名
config.Xmx;     // 最大内存
config.Xms;     // 最小内存
config.ie;      // 输入编码
config.oe;      // 输出编码
config.timeLimitDate;   // 到期时间
config.cwd;             // 服务端目录
config.highCommande;    // 自定义启动命令
// 删除服务器
serverModel.deleteServer(serverName);
// 获取所有服务器实例(不是信息数据,是对象实例)
const servers = serverModel.ServerManager().getServerObjects();
// 执行命令到指定服务器
serverModel.sendCommand(serverName, 'stop');
// 强制性结束服务器
serverModel.ServerManager().getServer(serverName).kill();
// 重启服务器
serverModel.restartServer(serverName);
// 开启服务器
serverModel.startServer(serverName);
// 停止服务器
serverModel.stopServer(serverName);
// 获取/修改服务器更多详情
const server = serverModel.ServerManager().getServer(serverName);
server.dataModel.dockerConfig // dockerConfig 为 Docker 配置,对其操作即可。
server.dataModel.mcpingConfig // mcpingConfig 为 mcping 协议配置,对其操作即可。
server.dataModel.autoRestart // autoRestart 为是否开启崩溃自动重启,bool型。
server.dataModel.schedule       // schedule 为此服务器的计划任务列表
server.properties            // properties 为MC主流服务端的配置表
// 更多信息,请直接输出 server.dataModel 查看一次便知。
// 补充,dataModel 变量下所有数据,均与配置文件 *.json 完全对应。

用户相关

const userModel = require('../../model/UserModel');
// 获取用户列表
const userNameList = userModel.userCenter().getUserList();
// 注册用户
userModel.register(username, password);
// 删除用户
userModel.deleteUser(username)
// 获取用户实例
const user = userCenter().get(username);
// 获取/修改用户更多详情
const user = userModel.userCenter().get(userName); // 获取用户实例
user.dataModel.allowedServer; // allowedServer 为此用户可控服务器列表,对其修改便可。
user.dataModel.apikey         // apikey 为 string 类型,存放的是 API key
// 更多信息,请直接输出 server.dataModel 查看一次便知。
// 补充,dataModel 变量下所有数据,均与配置文件 *.json 完全对应。

写在最后

控制面板大部分函数均已在此教程中写明,虽然这并不是全部,但是已经足以解决绝大部分需求。

欢迎您对此面板进行改造,升级又或商用,此项目均不收取任何费用与分成 ,完全供您打造,若您有兴趣,则可以在此面板基础之上打造出属于您自己的专业面板。

但是,您必须保留原作者声明与相关版权声明,不得二次开发后声明 “自研”,“原创” 等字样。

Github 项目地址:https://github.com/Suwings/MCSManager

⚠️ **GitHub.com Fallback** ⚠️