如何实现Web应用、网站状态的监控?

news2025/1/15 13:00:32

如何实现Web应用、网站状态的监控?

  • 关键词:网站监控,服务器监控,页面性能监控,用户体验监控
  • 本文通过代码分析、网站应用介绍网站状态监控的方式
  • 下文主要分为网站应用、技术实现两部分

一、网站应用

  • 现在网络上已经存在一些Web网站监控的服务,虽然功能五花八门,但限制较大,需付费使用
  • 本文介绍的技术运行网站见下方地址,不会关闭,可以直接使用
  • 一个朴实无华且免费的WEB网站监控工具
  • 先看下效果
    在这里插入图片描述

1. 打开网站

  • 【传送门】
https://www.xujian.tech/monitor

2. 微信扫码登录

  • 这里通过微信扫码取得小程序openid,利用openid标记用户,不涉及隐私
    在这里插入图片描述
  • 扫码完成后会自动跳转到系统

3. 进入监控表

  • 进入系统后,选中左侧菜单进入监控表页面
    在这里插入图片描述

4. 添加监控器

  • 监控器支持POST、GET两种请求方式
  • GET请求时,如有参数,请直接放置在地址中
  • POST请求时,如有参数,请在表单中填写JSON键值对对象
  • Header如果有需要,也可按JSON对象方式填写
  • 仅需如下三步,即可完成设置
  • 提交后,点击刷新即可在页面上看到监控器记录(此时还未执行)
    在这里插入图片描述

5. 说明和操作

5.1 关于成功率
  • 初次时显示“未执行”,执行正确计算
5.2 关于监控频率
  • 每次执行完成计算下一次执行时间,默认30分钟一次(免费用户暂不支持自定义频率)
  • 计时器每5分钟执行一次,发现监控器执行时间小于当前时间的,就执行请求
  • 所以监控频率并非严格按照30分钟一次
5.2 相关操作
  • 新增/编辑监控器后,可以点击“立即执行”进行一次请求,观察设置是否正确
  • 需要修改时,可点击“编辑”按钮对监控器内容进行修改
  • 点击运行记录,可查看近期运行的情况
  • 运行记录中,点击结果复制,可以复制运行的结果(当返回内容大于512b的时候,只存储前512个内容)
  • 需要邮件通知的用户,可点击右上角头像设置邮箱,在系统异常的时候,会通过邮件进行提示,邮件内容如下:
    在这里插入图片描述

6. 功能拓展

  • 如果有更多建议、合作,请在本文下方留言
  • 或按网站提示添加作者

二、技术实现

1. 技术栈

  • 实现一个监控器需前端、后端、数据库、缓存等技术
  • 本站主要应用了以下技术:
序号技术所属端
1VUE前端
2Vue Element Admin前端
3Java后端
4MySQL 数据库后端
5Redis缓存后端
6Nginx运维
7MyBaits-plus后端

2. 核心代码

  • 实现web应用监控的核心是定期按规则进行请求,并将结果记录,遇到错误时发送邮件提醒
  • 本文以在Spring Boot中实现为例,除Spring Boot基础依赖外,还需添加如下依赖
	<!--发送邮件-->
	<dependency>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-mail</artifactId>
	</dependency>
	<!--糊涂工具,实现网络请求、工具类等-->
	<dependency>
	    <groupId>cn.hutool</groupId>
	    <artifactId>hutool-all</artifactId>
	    <version>5.8.20</version>
	</dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.0</version>
    </dependency>
2.1 监控器实体
  • 记录监控器基本属性、执行时间、统计结果等
  • 下方代码含实体和下次执行时间计算方法
@Data
@Builder
@TableName("m_monitor")
public class MMonitor {

    @JsonFormat(shape = JsonFormat.Shape.STRING)
    private Long id;
    //监控器名称
    private String name;
    //创建时间
    private Date createdAt;
    //下次运行时间,定时器筛选时的重要指标
    private Date nextRunAt;
    //上次运行时间
    private Date lastRunAt;
    //计时器类型,1=分钟,2=小时,3=天
    private Integer timerType;
    //是否已伤处,1=已删除,0=正常
    private Integer isDeleted;
    //计时器步长,整数值
    private Integer timerLength;
    //监控器状态,1可用,0停用
    private Integer status;
    //用户微信小程序openid
    private String openid;
    //请求URL
    private String toUrl;
    //请求方法,GET、POST
    private String toMethod;
    //请求参数,JSON Object
    private String toParams;
    //请求header,JSON Object
    private String toHeaders;
    //返回结果包含为成功,当toResultCode不等于200的时候生效
    private String toResult;
    //返回结果code为成功,200表示用code,其他表示包含字符串
    private Integer toResultCode;
    //POST请求体格式,0=json,1=form
    private Integer toBodyType;
    // 最新一次运行情况:1正常,0异常
    private Integer runStatus;
    // 最新一次运行情况中文,成功、失败、失败原因
    private String runResult;
    // 运行成功次数
    private Integer countSucceed;
    // 总运行次数
    private Integer countAll;

    //分钟数最短步长,即最短30分钟一次
    private static final int MIN_MINUTE_LENGTH = 30;

    // 下次执行时间计算方法
    public void calNextRunAt(){
        if(this.lastRunAt == null){
            this.lastRunAt = new Date();
        }
        timerType = timerType == null ? 1 : timerType;
        if(this.timerLength == null || this.timerLength < 1){
            this.timerLength = 30;
        }
        int addMinute = 0;
        switch (timerType){
            case 1:
                addMinute = timerLength;
                break;
            case 2:
                addMinute = 60 * timerLength;
                break;
            case 3:
                addMinute = 60 * 24 * timerLength;
                break;
        }
        addMinute = Math.max(addMinute,MIN_MINUTE_LENGTH);
        this.nextRunAt = new Date(System.currentTimeMillis() + 1000L * 60 * addMinute);
    }
}

2.2 计时器
  • 利用Spring Boot的Scheduled定时器实现
@Component
@Slf4j
public class MonitorTimerTask {

    @Resource
    MMonitorMapper monitorMapper;
    @Autowired
    MonitorService monitorService;

    @Scheduled(cron="0 0/5 * * * *")
    public void exec(){
        List<MMonitor> monitorList = monitorMapper.selectList(
                new LambdaQueryWrapper<MMonitor>()
                        .isNotNull(MMonitor::getNextRunAt)
                        .lt(MMonitor::getNextRunAt,DateUtil.formatDateTime(new Date()))
                        .eq(MMonitor::getStatus,1)
                        .eq(MMonitor::getIsDeleted,0)
                );
        log.info(String.format("符合执行条件的监控器有%d个", monitorList.size()));
        for (MMonitor mMonitor : monitorList) {
            monitorService.run(mMonitor);
        }
    }
}
  • 定时器不生效?记得在SpringBootApplication上添加注解:@EnableScheduling
2.3 按规则进行请求
  • 即按监控器的toXX字段配置的内容填充请求参数,进行请求!
  • 本段不多说,直接上代码

    /**
     * 运行监控器的方法实现,运行从这里开始
     * */
    @Override
    public void run(MMonitor monitor) {
        long timestamp = System.currentTimeMillis();

        if(monitor.getIsDeleted() != null && monitor.getIsDeleted() == 1){
            return;
        }
        Date date = new Date();
        boolean isSucceed = false;
        String resultMsg = "成功";
        String requestResult = "";
        int code = 0;

        try{
            HttpResponse httpResponse = null;
            if(monitor.getToMethod() != null && monitor.getToMethod().equalsIgnoreCase("GET")){
                HttpRequest httpRequest =  HttpRequest.get(monitor.getToUrl());
                setHeaders(httpRequest,monitor);
                httpResponse = httpRequest.execute(false);
            }else{
                HttpRequest httpRequest =  HttpRequest.post(monitor.getToUrl());
                setHeaders(httpRequest,monitor);
                setPostParams(httpRequest,monitor);
                httpResponse = httpRequest.execute(false);
            }
            code = httpResponse.getStatus();
            requestResult = httpResponse.body();
            if(monitor.getToResultCode() == 200){
                isSucceed = code == 200;
                if(!isSucceed){
                    throw new Exception("返回结果HTTP CODE不为200");
                }
            }else {
                isSucceed = requestResult.contains(monitor.getToResult());
                if(!isSucceed){
                    throw new Exception("返回结果缺少包含内容");
                }
            }
        }catch (Exception e){
            isSucceed = false;
            resultMsg = e.getMessage();
        }

        if(isSucceed){
            monitor.setCountSucceed(monitor.getCountSucceed() + 1);
        }
        monitor.setCountAll(monitor.getCountAll() + 1);
        monitor.setRunStatus(isSucceed ? 1 : 0);
        monitor.setRunResult(resultMsg);
        monitor.calNextRunAt();
        monitor.setLastRunAt(date);
        monitorMapper.updateById(monitor);

        timestamp = System.currentTimeMillis() - timestamp;
        log.info(monitor.getName() + String.format("检查完毕,耗时%dms.", timestamp));

        if(requestResult != null && requestResult.length() > 512){
            requestResult = requestResult.substring(0,511) + "...";
        }

        MRunRecord runRecord = MRunRecord.builder()
                .monitorId(monitor.getId())
                .runCode(code)
                .runResult(requestResult)
                .runAt(date)
                .timeSpent(timestamp)
                .openid(monitor.getOpenid())
                .runStatus(isSucceed ? 1: 0)
                .build();
        mRunRecordMapper.insert(runRecord);
        sendEmail(runRecord,monitor);
    }
    
    /**
     * 发送邮件
     * */
    private void sendEmail(MRunRecord runRecord,MMonitor monitor){
        if(runRecord.getRunStatus() != null && runRecord.getRunStatus() == 1){
            return;
        }
        new Thread(() -> {
            MUser user = userMapper.selectOne(new LambdaQueryWrapper<MUser>().eq(MUser::getOpenid,runRecord.getOpenid()).orderByDesc(MUser::getId).last(" LIMIT 1"));
            if(user == null || StrUtil.isBlank(user.getEmail()) || user.getEmail().length() < 5 || !user.getEmail().contains("@")){
                return;
            }
            String subject = "【亚特技术Web监控】【监控异常】" + monitor.getName();
            String text =
                    "----------------详情登录网站查看----------------\n" +
                    "-------------------请求内容-------------------\n" +
                    "URL:" + monitor.getToUrl() + "\n" +
                    "Method:" + monitor.getToMethod() + "\n" +
                    "-------------------返回内容-------------------\n" +
                    "HttpCode:" + runRecord.getRunCode() + "\n" +
                    "Result:" + runRecord.getRunResult() + "\n";
            eMailUtils.sendTextMailMessage(user.getEmail(), subject, text);
        }).start();
    }

    /**
     * 分析规则设置Post参数
     * */
    private void setPostParams(HttpRequest httpRequest,MMonitor monitor){
        if(monitor.getToBodyType() != null && monitor.getToBodyType() == 1){
            //application/json 方式请求
            httpRequest.contentType("application/x-www-form-urlencoded;charset=GBK");
            try{
                if(!JSONUtil.isTypeJSONObject(monitor.getToParams())){
                    return;
                }
                JSONObject joParams = new JSONObject(monitor.getToParams());
                Map<String, Object> paramsMap = new HashMap<>();
                for (String key : joParams.keySet()) {
                    paramsMap.put(key,joParams.getStr(key));
                }
                httpRequest.form(paramsMap);
            }catch (Exception e){

            }
        }else if(monitor.getToBodyType() != null && monitor.getToBodyType() == 0){
            httpRequest.contentType("application/json");
            httpRequest.body(monitor.getToParams());
        }
    }

    /**
     * 分析规则设置header
     * */
    private void setHeaders(HttpRequest httpRequest,MMonitor monitor){
        try{
            if(!JSONUtil.isTypeJSONObject(monitor.getToHeaders())){
                return;
            }
            JSONObject joHeader = new JSONObject(monitor.getToHeaders());
            Map<String,String> headerMap = new HashMap<>();
            for (String key : joHeader.keySet()) {
                headerMap.put(key,joHeader.getStr(key));
            }
            httpRequest.addHeaders(headerMap);
        }catch (Exception e){

        }
    }

三、结尾说明

  • 第一部分说的网站已经可用了,欢迎试用、欢迎长期使用、欢迎联系合作、欢迎定制功能
  • 第二部分给出了核心内容,但这部分实际上不是实现整个网站最耗时的:前端开发工作也是费力不讨好的
  • 本人同时还提供Java开发一对一教学,有需要的添加微信:xujian_cq详聊
  • 欢迎点赞、收藏、评论

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1016616.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue ——08、路由嵌套,参数传递及重定向

路由嵌套&#xff0c;参数传递及重定向 一、路由嵌套二、参数传递第一种方式&#xff1a;第二种方式&#xff1a; 三、重定向————————创作不易&#xff0c;如觉不错&#xff0c;随手点赞&#xff0c;关注&#xff0c;收藏(*&#xffe3;︶&#xffe3;)&#xff0c;谢…

【C++杂货铺】继承由浅入深详细总结

文章目录 一、继承的概念及定义1.1 继承的概念1.2 继承定义1.2.1 定义格式1.2.2 继承方式和访问限定符1.2.3 继承基类成员访问方式的变化 二、基类和派生类对象赋值转换三、继承中的作用域四、派生类中的默认成员函数4.1 默认构造函数4.2 拷贝构造函数4.3 赋值运算符重载函数4.…

03使用Spring基于XML的方式注册第一个组件

基于XML的方式注册第一个组件 开发步骤 第一步: 创建Maven工程配置生成的pom.xml文件, 添加spring context基础依赖和junit依赖(注意根据Spring官方文档描述,Spring6需要JDK版本17) 当添加Spring的基础依赖spring context之后,Maven会自动关联并引入其他依赖spring aop, spr…

前端生态系统:构建现代Web应用的完整指南

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 前端开发生态系统是一个…

Selenium常见问题解析

1、元素定位失败&#xff1a; 在使用Selenium自动化测试时&#xff0c;最常见的问题之一是无法正确地定位元素&#xff0c;这可能导致后续操作失败。解决方法包括使用不同的定位方式&#xff08;如xpath、CSS selector、id等&#xff09;&#xff0c;等待页面加载完全后再进行…

[NLP] LLM---<训练中文LLama2(二)>扩充LLama2词表构建中文tokenization

使用SentencePiece的除了从0开始训练大模型的土豪和大公司外&#xff0c;大部分应该都是使用其为当前开源的大模型扩充词表&#xff0c;比如为LLama扩充通用中文词表&#xff08;通用中文词表&#xff0c;或者 垂直领域词表&#xff09;。 LLaMA 原生tokenizer词表中仅包含少量…

asp.net+sqlserver医院体检信息管理系统

一、源码描述 这是一款简洁十分美观的ASP.NETsqlserver源码&#xff0c;界面十分美观&#xff0c;功能也比较全面&#xff0c;比较适合 作为毕业设计、课程设计、使用&#xff0c;感兴趣的朋友可以下载看看哦 二、功能介绍 该源码功能十分的全面&#xff0c;具体介绍如下&…

PyTorch深度学习实战——基于ResNet模型实现猫狗分类

PyTorch深度学习实战——基于ResNet模型实现猫狗分类 0. 前言1. ResNet 架构2. 基于预训练 ResNet 模型实现猫狗分类相关链接 0. 前言 从 VGG11 到 VGG19&#xff0c;不同之处仅在于网络层数&#xff0c;一般来说&#xff0c;神经网络越深&#xff0c;它的准确率就越高。但并非…

Linux学习第12天:基于API函数的字符设备驱动开发:一字一符总见情

本节学习的内容主要为基于LinuxAPI函数的字符设备驱动的开发&#xff0c;还包括在驱动模块加载的时候如何自动创建设备节点。总结的脑图如下&#xff1a; 一、驱动原理 1.分配和释放设备号 申请设备号函数&#xff1a; int alloc_chrdev_region(dev_t *dev, unsigned basemin…

改进YOLOv5小目标检测:构建多尺度骨干和特征增强模块,提升小目标检测

构建多尺度骨干和特征增强模块,提升小目标检测 背景代码使用配置文件如下🔥🔥🔥 提升小目标检测,创新提升 🔥🔥🔥 测试在小目标数据集进行提点 👉👉👉: 新设计的创新想法,包含详细的代码和说明,具备有效的创新组合 🐤🐤🐤 1. 本文包含两个创新改…

SQL优化--count优化

select count(*) from tb_user ;在之前的测试中&#xff0c;我们发现&#xff0c;如果数据量很大&#xff0c;在执行count操作时&#xff0c;是非常耗时的。 MyISAM 引擎把一个表的总行数存在了磁盘上&#xff0c;因此执行 count(*) 的时候会直接返回这个 数&#xff0c;效率很…

档案管理系统设计与实现

摘 要 近年来&#xff0c;随着企业彼此间的竞争日趋激烈&#xff0c;信息技术在企业的发展中占据着越来越重要的地位。在企业的运输生产中&#xff0c;档案已成为企业运输经营中不可或缺的一部分&#xff0c;为管理者进行管理决策和进行各种经营活动提供了重要的依据&#xf…

前后端分离--Vue的入门基础版

目录 一.前后端分离 二.Vue的简介 三.Vue的入门案例 四.Vue的生命周期 一.前后端分离 前后端分离是一种软件架构模式&#xff0c;将应用程序的前端&#xff08;用户界面&#xff09;和后端&#xff08;数据处理和业务逻辑&#xff09;独立开发、独立部署。在前后端分离的架…

【数据结构】AVL树的删除(解析有点东西哦)

文章目录 前言一、普通二叉搜索树的删除1. 删除结点的左右结点都不为空2. 删除结点的左结点为空&#xff0c;右节点不为空3. 删除结点的右结点为空&#xff0c;左节点不为空4. 删除结点的左右结点都不为空 二、AVL树的删除1. 删除结点&#xff0c;整棵树的高度不变化1.1 parent…

RISV-V架构的寄存器介绍

1、RISC-V的通用寄存器 &#xff08;1&#xff09;在编写汇编代码时&#xff0c;使用寄存器的ABI名字&#xff0c;一般不直接使用寄存器的编号&#xff1b; &#xff08;2&#xff09;x0-x31是用来做整形运算的寄存器&#xff0c;f0-f31是用来做浮点数运算的寄存器&#xff1b;…

傅里叶变换应用 (01/2):频域和相位

一、说明 我努力理解傅里叶变换&#xff0c;直到我将这个概念映射到现实世界的直觉上。这是一系列技术性越来越强的解释中的第一篇文章。我希望直觉也能帮助你&#xff01; 二、傅里叶变换中频域简介 声音是一种机械波&#xff0c;是空气中的振动或其他介质。音符对应于波的频率…

【LeetCode75】第五十七题 电话号码的字母组合

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 给我们按下的按键&#xff0c;让我们返回对应按键可能产生的所有可能。 这是一道很经典的递归题&#xff0c;我们首先先拿一个数组把每个…

day45:C++ day5,运算符重载剩余部分、静态成员、继承

#include <iostream> #include <cstring> #define pi 3.14 using namespace std;class Shape { protected:double round;double area; public://无参构造Shape():round(40),area(100){cout<<"Shape::无参构造函数&#xff0c;默认周长为40&#xff0c;面…

C语言入门Day_21 函数的使用

目录 前言&#xff1a; 1.变量作用域 2.代码执行顺序 3.易错点 4.思维导图 前言&#xff1a; 我们是先定义函数&#xff0c;再调用函数。完成了函数的定义以后&#xff0c;我们就可以开始调用函数了&#xff0c;让我们来回顾一下&#xff1a; 调用函数分为两部分&#…

1131. 绝对值表达式的最大值

1131. 绝对值表达式的最大值 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;求方向一次遍历两度统计 参考代码&#xff1a;求方向一次遍历两度统计 原题链接&#xff1a; 1131. 绝对值表达式的最大值 https://leetcode.cn/problems/maximum-of-absolute-val…