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

如何实现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详聊
  • 欢迎点赞、收藏、评论
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
智能宿舍监控系统是一种集成了多种技术的web开发项目。 首先,我们可以采用前端开发技术来构建用户界面。通过使用HTML、CSS和JavaScript等技术,我们可以设计出直观、易用的页面,供用户使用。 其次,对于后端开发,我们可以选择使用合适的编程语言和框架来实现系统的功能。例如,我们可以使用Python语言和Django框架来处理系统的逻辑和数据库交互。通过编写后端代码,我们可以实现宿舍智能设备的监控和管理功能。 此外,为了实现宿舍监控系统的实时性和稳定性,我们可以使用一些其他技术。例如,我们可以使用WebSocket来实时传输监控数据,以确保用户可以及时获得最新的宿舍状态。同时,我们还可以使用缓存技术来提高系统的响应速度,减轻服务器的压力。 在数据存储方面,我们可以选择使用关系型数据库或者NoSQL数据库来存储宿舍相关的信息。通过使用数据库技术,我们可以将宿舍状态、设备管理信息、用户数据等存储在可靠的数据源中,并且可以方便地进行查询和更新。 最后,为了保证系统的安全性,我们可以采取一些安全措施。例如,使用HTTPS协议来进行数据传输加密,使用身份验证机制来确保只有授权的用户可以访问系统。 总而言之,智能宿舍监控系统的web开发涉及到前后端技术的应用、实时数据传输、数据库设计以及安全措施的实施等方面。通过合理地使用这些技术,我们可以开发出一个功能完善、易用且安全可靠的智能宿舍监控系统。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值