前端利用fetch实现服务器健康检查

发布时间 2023-10-02 22:54:10作者: jsper

最近公司赛事较多,一些大型赛事部署了多台服务器,为了实时了解的运行状态,保障服务器正常运行,我用前端实现了一个服务器健康检查程序,可设置自动轮询检查或手动检查。

使用fetch发送ajax请求(服务器需要设置允许跨域),判断请求状态和结果来得出正常、超时、连接失败状态。代码使用vue3了浏览器版前端框架。

效果:

 代码:

<!doctype html>
<head>
    <meta charset="utf-8">
    <title>服务器健康检查</title>
    <meta name="keywords" content="" />
    <meta name="description" content="" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
    <style>
        * {
            cursor: default;
            box-sizing: border-box;
        }
        html,body {
            display: flex;flex-direction: column; align-items: center;
        }
        #app {
            width:800px;
        }
        .controls {
            display: flex; justify-content: flex-end;
        }
        .mode-switch {
            width:100px;height:30px;border-radius: 6px;display: flex;overflow: hidden;border:1px solid rgb(57, 166, 255);
        }
        .mode-switch-item {
            width:100%; display: flex; justify-content: center; align-items: center;
        }
        .mode-switch-item-active {
            background-color: rgb(57, 166, 255);color:white;
        }

        .button {
            background-color: rgb(57, 166, 255);border: none;color:white;display: flex;padding: 6px 10px;
        }
        .button:active {
            transform: scale(0.95);
        }

        .tab-header {
            display: flex; align-items: center;
        }
        .tab-header-item {
            padding: 4px 8px; border: 1px solid #ccc; border-top-left-radius: 6px; border-top-right-radius: 6px;
        }
        .tab-header-item-active {
            border: 1px solid rgb(57, 166, 255); background-color: rgb(57, 166, 255);color:white;
        }

        .tab-bodyer {
            width:100%;border-top:10px solid rgb(57, 166, 255);overflow-y: auto;
        }
        .tab-bodyer-item {
            display: none;
        }
        .tab-bodyer-item-active {
            display: flex; flex-direction: column;
        }

        .tab-bodyer-item-server {
            width:100%;height:60px;border:1px solid rgb(57, 166, 255); background-color: rgb(207, 233, 255);border-radius: 6px;margin-top:10px;
            display: flex;justify-content: space-between;align-items: center;
        }
        
        /* loading效果 开始 */
        .circular-box {
            animation: loading-rotate 2s linear infinite;
        }
        
        .circle {
            stroke-dasharray: 900;
            stroke-dashoffset: -3;
            animation: loading-dash 1.5s ease-in-out infinite;
        }
        @keyframes loading-dash {
            0% {
                stroke-dasharray: 1, 200;
                stroke-dashoffset: 0;
            }
            50% {
                stroke-dasharray: 90, 150;
                stroke-dashoffset: -40;
            }
            100% {
                stroke-dasharray: 90, 150;
                stroke-dashoffset: -120;
            }
        }
        @keyframes loading-rotate {
            100% {
                transform: rotate(360deg);
            }
        }
        /* loading效果 结束 */
    
        .check-status {
            display: flex; justify-content: flex-start; align-items: center;
        }
    </style>
    <script src="lib/vue.global.prod.js"></script>
    <script>

    </script>
</head>
<body>
    <div id="app">
        <h2>服务器健康检查</h2>
        <div class="controls">
            <div class="mode-switch">
                <div :class="['mode-switch-item', mode === 'Auto' ? 'mode-switch-item-active' : '']" @click="switchMode('Auto')">自动</div>
                <div :class="['mode-switch-item', mode === 'Manual' ? 'mode-switch-item-active' : '']" @click="switchMode('Manual')">手动</div>
            </div>
            <button class="button" @click="start" :disable="status === 'Start'" style="margin-left:100px;">
                <div v-if="status === 'Stoped'">开始</div>
                <div v-if="status === 'Started'">进行中</div>
            </button>
            <button class="button" @click="stop" :disable="status === 'Stoped'" style="margin-left:10px;">停止</button>
        </div>
        <div style="margin-top:20px;">
            <div class="tab-header">
                <div v-for="(group, index) in serverGroups" :class="['tab-header-item', currentTabIndex === index ? 'tab-header-item-active' : '']" @click="switchGroup(index)">
                    {{ group.groupName }}
                </div>
            </div>
            <div class="tab-bodyer">
                <div v-for="(group, index) in serverGroups" :class="['tab-bodyer-item', currentTabIndex === index ? 'tab-bodyer-item-active' : '']">
                    <div v-for="(server, ii) in group.servers" class="tab-bodyer-item-server">
                        <div style="width:100%;padding-left:20px;">
                            <div style="font-size:20px;font-weight: bold;">{{ server.name }}</div>
                            <div style="color:#444;">{{ server.url }}</div>
                        </div>
                        <div style="width:200px;display: flex;justify-content: flex-start;align-items: center;">
                            <div v-if="server.checkStatus === 'checking'" class="check-status">
                                <svg style="width:26px;height:26px;" viewBox="0 0 50 50" class="circular-box">
                                    <circle class="circle" cx="25" cy="25" r="20" fill="none" stroke-width="2" stroke="blue"></circle>
                                </svg>
                                <div style="margin-left:10px;">
                                    检查中...
                                </div>
                            </div>
                            <div v-if="server.checkStatus === 'fail'" class="check-status">
                                <svg xmlns="http://www.w3.org/2000/svg" style="width:30px;height:30px;" viewBox="0 0 24 24" stroke-width="1.5" stroke="#ff2825" fill="none" stroke-linecap="round" stroke-linejoin="round">
                                    <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
                                    <path d="M18 6l-12 12" />
                                    <path d="M6 6l12 12" />
                                </svg>
                                <div style="margin-left:2px;">
                                    连接失败
                                </div>
                            </div>
                            <div v-if="server.checkStatus === 'timeout'" class="check-status">
                                <svg xmlns="http://www.w3.org/2000/svg" style="width:30px;height:30px;" viewBox="0 0 24 24" stroke-width="1.5" stroke="#ff2825" fill="none" stroke-linecap="round" stroke-linejoin="round">
                                    <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
                                    <path d="M20.986 12.502a9 9 0 1 0 -5.973 7.98" />
                                    <path d="M12 7v5l3 3" />
                                    <path d="M19 16v3" />
                                    <path d="M19 22v.01" />
                                </svg>
                                <div style="margin-left:6px;">
                                    超时
                                </div>
                            </div>
                            <div v-if="server.checkStatus === 'success'" class="check-status">
                                <svg xmlns="http://www.w3.org/2000/svg" style="width:30px;height:30px;" viewBox="0 0 24 24" stroke-width="1.5" stroke="#00b341" fill="none" stroke-linecap="round" stroke-linejoin="round">
                                    <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
                                    <path d="M5 12l5 5l10 -10" />
                                </svg>
                                <div style="margin-left:2px;">
                                    正常
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>



        
    </div>
    <script>
        const { createApp, ref, onMounted } = Vue
        createApp({
            setup() {
                const serverGroups = ref([
                    {
                        groupName: 'tplus',
                        servers: [
                            {
                                name: '601',
                                url: 'http://tplus601.seentao.com/tplus/tapi/v1/customlogo/getLogo',
                                checkStatus: 'none'
                            }, {
                                name: '602',
                                url: 'http://tplus602.seentao.com/tplus/tapi/v1/customlogo/getLogo',
                                checkStatus: 'none'
                            }, {
                                name: '603',
                                url: 'http://tplus603.seentao.com/tplus/tapi/v1/customlogo/getLogo',
                                checkStatus: 'none'
                            }, {
                                name: '604',
                                url: 'http://tplus604.seentao.com/tplus/tapi/v1/customlogo/getLogo',
                                checkStatus: 'none'
                            }, {
                                name: '605',
                                url: 'http://tplus605.seentao.com/tplus/tapi/v1/customlogo/getLogo',
                                checkStatus: 'none'
                            }
                        ]
                    },
                    {
                        groupName: 't3',
                        servers: [
                            {
                                name: 't01',
                                url: 'https://t3.seentao.com/portal/portal.jsp',
                                checkStatus: 'success'
                            }, {
                                name: 't02',
                                url: 'https://t3.seentao.com/portal/portal.jsp',
                                checkStatus: 'fail'
                            }, {
                                name: 't03',
                                url: 'https://t3.seentao.com/portal/portal.jsp',
                                checkStatus: 'timeout'
                            }, {
                                name: 't03',
                                url: 'https://t3.seentao.com/portal/portal.jsp',
                                checkStatus: 'checking'
                            },
                        ]
                    }
                ])

                const mode = ref('Auto')
                const currentTabIndex = ref(0)
                let autoCheckIntval = null
                const status = ref('Stoped')
                function start() {
                    console.log('start')
                    if (mode.value === 'Manual') {
                        checkCurrentServersGroup()
                    } else {
                        checkCurrentServersGroup()
                        autoCheckIntval = setInterval(checkCurrentServersGroup, 10 * 1000)
                    }
                    status.value = 'Started'
                }
                
                function stop() {
                    clearInterval(autoCheckIntval)
                    status.value = 'Stoped'
                }

                function checkCurrentServersGroup()  {
                    console.log('checked...')
                    serverGroups.value[currentTabIndex.value].servers.forEach(server => {
                        server.checkStatus = 'checking'
                        const controller = new AbortController(); // 超时处理
                        const signal = controller.signal;
                        setTimeout(() => {
                            controller.abort(); // 放弃请求
                            server.checkStatus = 'timeout'
                        }, 5000);
                        // (get、post、跨域)请求参考:https://blog.csdn.net/m0_71469120/article/details/130795756
                        fetch(server.url, {
                            method: 'get',
                            headers: {
                                'Origin': 'https://tplus.seentao.com/' // 设置请求的来源,后端需要在响应头中添加Access-Control-Allow-Origin,包含此值,支持跨域。
                            },
                            responseType: 'json',
                            signal: signal // 超时处理配置
                        })
                        .then(response => {
                            if (response.ok) { // HTTP状态码200-299范围
                                server.checkStatus = 'success'
                                return response.json()
                            } else { // 其他的都是发生了错误
                                server.checkStatus = 'fail'
                            }
                        })
                        .then(result => {
                            console.info('res:', result);
                        })
                        .catch(error => {
                            console.error('Error:', error);
                        })
                    });
                }

                function switchMode(modeValue) {
                    mode.value = modeValue
                }

                function switchGroup(index) {
                    currentTabIndex.value = index
                }

                

                return {
                    serverGroups, mode, currentTabIndex, status, start, stop, switchMode, switchGroup
                }
            }
        }).mount('#app')
    </script>
</body>