1-前言

在和@MagicalSheep一起运营着一个MC服务器(服务器地址),服务器分为 核心服(登陆大厅)生存服建筑服小游戏服 四大部分,同时还有 KafkaMiraiBotShootBack 等附属服务。虽然各服务都有自己的启动脚本,但每次重启服务器后都要进控制台把这些脚本运行一遍实在是太烦了,而且受服务器硬件限制,不可能同时运行多个服务器,因此我便萌生了搭建一个服务器Web控制台的想法。


2-构思

我设想中,该Web控制台应当实现各服务的人工启停(只需要按button即可)、运行情况的监控,以及服务器负载的监控等功能。后端还应该使用MySQL实现登录功能,以防止非管理员恶意启停服务。

2.1-服务器负载监控

提供监视面板,每5s(可更改)刷新一次服务器的负载情况,显示服务器的CPU占用、内存占用、网络情况等。

2.2-服务的监控

提供控制面板,将各服务的运行情况可视化的表现出来,使用按钮控制各服务的启动与停止,并弹窗提示错误信息(如**服务未能成功启动的原因等)。

2.3-MySQL实现登录功能

提供登录验证页面,登录后跳转至Web控制台。提供注册页面,用户注册并进入Web控制台。


3-实现

3.1-后端开发

后端我选择了Spring Boot作为开发框架,这是一套基于Spring框架的开源框架,大大降低了SpringAPP的搭建与开发难度。

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.
—— Spring Boot 官网文档

Spring Boot 具有 Spring 一切优秀特性,Spring 能做的事,Spring Boot 都可以做,而且使用更加简单,功能更加丰富,性能更加稳定而健壮。
—— C语言中文网 『Spring Boot框架入门教程』

3.1.1-服务器负载监控

由Client定时请求服务器的负载情况,Server收到请求后获取服务器的CPU负载(%)、Memory占用(TotalMemory、FreeMemory)、IP地址、服务端口等信息,以json的形式返回给Client.

相关代码如下:

@RestController
@RequestMapping("/json/server")
public class Server_Info
{
    //服务器信息类
    public static class ServerInfo
    {
        private static String LocalHostIP;
        private static int LocalHostPort;

        public ServerInfo()
        {
            LocalHostIP = "N/A";
            LocalHostPort = 8080;
        }

        /*标准get、set方法略*/
    }

    //服务器负载类
    public static class ServerLoad
    {
        private static int CPU_load;
        private static long TotalMEM;
        private static long FreeMEM;

        public ServerLoad()
        {
            CPU_load = 0;
            TotalMEM = 1;
            FreeMEM = 0;
        }

        /*标准get、set方法略*/

        public void refresh()
        {//刷新负载数据
            final OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
            double cpuLoad = osmxb.getCpuLoad();
            setCPU_load((int) (cpuLoad * 100));
            setTotalMEM(osmxb.getTotalMemorySize());
            setFreeMEM(osmxb.getFreeMemorySize());
        }
    }

    //新建类对象
    public static ServerLoad load = new ServerLoad();
    public static ServerInfo info = new ServerInfo();

    @RequestMapping("/load")
    public ServerLoad getLoad()
    {
        load.refresh(); //刷新负载数据
        return load;
    }

    @RequestMapping("/info")
    public ServerInfo getInfo()
    {
        return info;
    }
}

值得注意的是:class ServerLoad中的get方法是不能省略的,如果省略,将导致返回的该class无法正常解析成json形式,发生Http 406错误.

3.1.2-各服务的监控

由Client定时请求服务器中各服务的运行情况,Server收到请求后获取服务器中各服务的运行情况(可配置监控哪些服务),以json列表的形式返回给Client.
如果用户点击了“启动服务”/“停止服务”按钮,Client将向Server发送请求,Server收到请求后查询对应服务的运行情况,并启动或停止该服务,然后向Client发送更新后的服务监控列表.

3.1.2.1-服务监视

使用开源库oshi作为依赖,调用其中的getProcesses()方法,获取进程列表,然后再通过比对各进程“启动命令行(CommandLine)”与监视条目,找出其中的被监视进程并将其状态返回给Web端。

3.1.3-MySQL登录功能

(尚未开发)

3.2-前端开发

前端我选择了Vue作为开发框架,这是一套用于构建用户界面的渐进式框架。个人感觉比起纯手撸Html、CSS和JavaScript,还是直接使用Vue框架来的快些。
API通信方面,我选用了axios这一网络请求库。该库体积小,使用简单,对开发者很是友好。

//公共service部分
import axios from "axios";

const service = axios.create({
  baseURL: "http://localhost:8003/api/json",
  timeout: 5000
}); //axios对象,设置公共地址和超时

export default service;

(由于前期还没开发登录功能,所以登陆页面就是个跳转页面。而控制台页面是一个单页应用)

image.png
图源:https://zhuanlan.zhihu.com/p/56621185

3.2.1-服务器负载监控

转到该页面时会自动进行刷新,之后会根据用户设定的间隔定期请求服务器负载情况。收到服务器反馈后则将结果可视化的显示在页面上。相关代码如下:

<!--html部分-->
<div id="LoadMonitor">
  <h2>负载监控页面</h2>
  <p>DebugMSG {{ Debug }}</p>
  <b id="autoRefreshStatus" class="autoRefreshStatus-running">●</b><b>自动刷新 自动刷新间隔:</b>
  <select id="SelInterval" v-on:change="newInterval">
    <option value="1">1s</option>
    <option value="2">2s</option>
    <option value="5" selected="true">5s</option>
    <option value="10">10s</option>
    <option value="30">30s</option>
    <option value="60">1min</option>
  </select>
  <button v-on:click="ToggleAutoRefresh">暂停/启用自动刷新</button>
  <button v-on:click="refresh">立即刷新</button>
  <br/>
  <p>服务器CPU占用: {{ cpu_load }}%</p>
  <div id="CpuLoadBar">
    <div id="CpuLoadBar_base">
      <div id="CpuLoadBar_use"></div>
    </div>
  </div>
  <p>服务器内存占用: {{ mem_load }}% ( {{ free_mem }}MB(free)/ {{ total_mem }}MB(total))</p>
  <div id="MemLoadBar">
    <div id="MemLoadBar_base">
      <div id="MemLoadBar_use"></div>
    </div>
  </div>
</div>
//向服务器发送请求的实现:
export const getServerLoad = () => {
  return service({
    url: '/server/load',
    method: 'get'
  })
}

//刷新函数
async refresh() {
  let tmp = await getServerLoad();
  this.cpu_load = tmp.data.cpu_load;
  CpuLoadBar_use.style.width = this.cpu_load + '%';
    
  //可视化处理略

  this.total_mem = (tmp.data.totalMEM / 1048576).toFixed(2);
  this.free_mem = (tmp.data.freeMEM / 1048576).toFixed(2);
  
  //可视化处理略(注意处理总内存返回值为0的情况)
}

//定时器相关代码
var interval = setInterval(this.refresh, IntervalTime * 1000);//初始化并运行
clearInterval(interval);//清除定时器
IntervalTime = SelInterval.value;//更新刷新时间间隔
interval = setInterval(this.refresh, IntervalTime * 1000);//(重新)设定定时器