java物联网第四天 智慧农业物联网下

564 篇文章 136 订阅
3 篇文章 4 订阅

5.5萤石云API播放接入指南介绍

海康威视的摄像头我们是可以自己编程开发进行远程控制的,海康威视提供了一套api供我们远程调用,也提供了一个网站去管理我们自己的网络摄像头,这个网站就是萤石云

https://www.ys7.com/

在这个网站可以对你购买的摄像头进行管理,进行开发

注册-登录后找到开发者服务,在这里可以添加你自己的摄像头并且进行管理

点击接口测试,就可以进行功能的调试了

image-20190623145602301

image-20190623145746498

详细开发api在https://open.ys7.com/doc/zh/

AccessToken:要根据这个token进行视频显示及控制,但是这个token七天 就会过期,所以需要每七天去调用接口重新获取一次

根据appKey和secret获取accessToken

  • 接口功能

    该接口用于管理员账号根据appKey和secret获取accessToken,appKey和secret可以在官网-开发者服务-我的应用中找到

    注意:获取到的accessToken有效期是7天,请在即将过期或者接口报错10002时重新获取,请勿频繁调用,频繁调用将会被拉入限制黑名单

  • 请求地址

    https://open.ys7.com/api/lapp/token/get

  • 请求方式

    POST

  • 请求参数

参数名类型描述是否必选
appKeyStringappKeyY
appSecretStringappSecretY

UIKit简介
UI组件(UIKit)是萤石开放平台推出的含UI二次开发组件,全称Ezviz UIKit(简称EZUIKit),可以通过三行代码集成视频功能,有四个版本:iOS、Android、JS(Web/H5)、ActiveX(IE)。

UIKit自带播放器,只需使用“萤石播放链接(URL)”即可实现实时视频预览、录像回放,播放URL可以在“开发者服务中心”的“设备管理”中获取,也可以按照规则自己拼接

uikit非常方便,大部分情况下直接引入这个js,使用三行代码即可完成视频的展示

下载地址:https://open.ys7.com/mobile/download.html

代码示例——demo-live.html

// 首先引入js
    <script src="../ezuikit.js"></script>
    <script src="../js/jquery.min.js"></script>
// 页面创建video标签
  <video
    id="myplayer"
    src="ezopen://open.ys7.com/f01018a141094b7fa138b9d0b856507b.live"
    width="400"
    height="300"
    poster="[这里可以填入封面图片URL]"
    [autoplay]
    controls
    playsInline
    webkit-playsinline
  >
  </video>
// 开始初始化直播源地址
        var player = new EZUIKit.EZUIPlayer('myPlayer');
// 播放
          player.play();
// 结束
          player.stop();

5.5.3.萤石云云台控制API介绍

image-20190623150754859

http接口:—》设备:里面有很多大家会用到的接口

image-20190623150946410

接口调试方法:使用接口调试工具postman,debugapi进行

image-20190623151142135

前三个参数从萤石云-》我的设备中获取

后两个参数代表操作的方式

开始云台控制

  • 接口功能

    对设备进行开始云台控制,开始云台控制之后必须先调用停止云台控制接口才能进行其他操作,包括其他方向的云台转动

  • 请求地址

    https://open.ys7.com/api/lapp/device/ptz/start

  • 请求方式

    POST

  • 子账户token请求所需最小权限

    "Permission":"Ptz" "Resource":"Cam:序列号:通道号"

  • 请求参数

参数名类型描述是否必选
accessTokenString授权过程获取的access_tokenY
deviceSerialString设备序列号,存在英文字母的设备序列号,字母需为大写Y
channelNoint通道号Y
directionint操作命令:0-上,1-下,2-左,3-右,4-左上,5-左下,6-右上,7-右下,8-放大,9-缩小,10-近焦距,11-远焦距Y
speedint云台速度:0-慢,1-适中,2-快Y

预置点:可以设置摄像头开机后的初始位置,预置点序号,C6设备是1-12该参数需要开发者自行保存

如果开发者自己没有摄像头,需要甲方提供对应的设备,并且萤石云的账号是需要交个开发者的,因为开发需要的参数都在萤石云管理界面可以查询到。甲方要做的就是保持要测试的摄像头24小时开机

5.5.4.项目中对接海康威视摄像头

1)要有摄像头的数据库表

2)要有摄像头的管理界面

3)当用户登录以后,打开摄像头的监控页面时,去数据库根据用户找到对应的摄像头,并将摄像头数据返回

4)在页面根据摄像头的信息使用uikit进行渲染

image-20190623205709622

购买摄像头之后,在萤石云申请对应的秘钥,方便下一步进行网络控制。

根据appKey和secret获取accessToken接口

而播放以及控制必须要的三个参数为accessToken,deviceSerial,channelNo

播放地址url

实现步骤:

1)

2)要从数据库把摄像头的信息取回来

3)页面上定义一个vedio标签,其中的src属性指定播放路径

4)在js中让代码执行var player1 = new EZUIPlayer(‘player1’);

可能会遇到的问题:通常是播放的token过期,需要重新去官网申请

5.5.5.使用阿里图标库制作摄像头云台对摄像头进行控制

image-20190623211227443

这里主要想让大家学习的是如何使用第三方的图标库

阿里图标库:https://www.iconfont.cn/

使用说明:https://blog.csdn.net/sanlingwu/article/details/83097641

在这里插入图片描述

<div class="layui-text-bottom layui-row vedio-button-select">
    <button lay-submit="" lay-filter="add" onclick="control(0)"></button>
    <button  lay-submit="" lay-filter="add" onclick="control(1)"></button>
    <button  lay-submit="" lay-filter="add" onclick="control(2)"></button>
    <button  lay-submit="" lay-filter="add" onclick="control(3)"></button>

    <button  lay-submit="" lay-filter="add" onclick="stop()">停止</button>
    <button lay-submit="" lay-filter="add" onclick="control(8)">放大</button>
    <button lay-submit="" lay-filter="add" onclick="control(9)">缩小</button>
    <button lay-submit="" lay-filter="add" onclick="control(10)">近焦</button>
    <button lay-submit="" lay-filter="add" onclick="control(11)">远焦</button>
    <button lay-submit="" lay-filter="add" onclick="capture()">抓拍</button>
    <div style="float: left;">
        <select id="speed3" name="speed" lay-verify="required" class='vedio-select layui-btn layui-btn-normal layui-col-sm2 form-control input-sm' data-bv-notempty='true'>
            <option value="0">慢速</option>
            <option value="1">正常</option>
            <option value="2">快速</option>
        </select>
        <!--<select id="moving3" name="moving" lay-verify="required" class='vedio-select layui-btn layui-btn-normal layui-col-sm2 form-control input-sm' data-bv-notempty='true'>
            <option value="0">步进</option>
            <option value="1">连动</option>
        </select>-->
    </div>
</div>
//开始控制
//0-上,1-下,2-左,3-右,4-左上,5-左下,6-右上,7-右下,8-放大,9-缩小,10-近焦距,11-远焦距
//默认为步进,点一下就停止
var control=function (direction) {
    data.direction=direction;
    data.speed='0';
    layui.use(['jquery', 'layer'], function(){
        var $ = layui.$
            ,layer = layui.layer;
        var speed = $("#speed").val();
        if (speed != null && speed != '') {
            data.speed=speed;
        }

        var moving = $("#moving").val();
        if (moving != null&& moving != '') {
            //连动
            if (moving == 1) {
                $.post('https://open.ys7.com/api/lapp/device/ptz/start',data,function(res){
                    if(res.code==200){
                        layer.msg(res.msg, {time: 2000});
                    }else{
                        layer.msg(res.msg, {time: 2000});
                    }
                },'json');

            } else {
                //步进,动一下就停
                $.post('https://open.ys7.com/api/lapp/device/ptz/start',data,function(res){

                },'json');
                $.post('https://open.ys7.com/api/lapp/device/ptz/stop',data,function(res){
                    if(res.code==200){
                        layer.msg(res.msg, {time: 2000});
                    }else{
                        layer.msg(res.msg, {time: 2000});
                    }
                },'json');
            }
        }
        return false;
    });
}

如果摄像头支持抓拍,可以使用如下代码:

//抓拍
var capture=function () {
    layui.use(['jquery', 'layer'], function(){
        var $ = layui.$
            ,layer = layui.layer;
        $.post('https://open.ys7.com/api/lapp/device/capture',data,function(res){
            if(res.code==200){
                layer.msg(res.msg, {time: 2000});
                //抓图成功后将这个地址传递到后台,下载下来保存到阿里云
                console.log(res.data.picUrl)
                $.get('/vedios/savePic?picUrl='+res.data.picUrl,function (res) {
                    ayer.msg('抓图成功', {time: 2000});
                })
            }else{
                layer.msg(res.msg, {time: 2000});
            }
        },'json');
    })
}

抓拍的后台逻辑:

/**
 * 抓取图片,并上传oss
 * https://open.ys7.com/doc/zh/book/index/device_option.html#device_option-api4
 * 请求地址:https://open.ys7.com/api/lapp/device/capture
 * 请求方式:POST
 * 请求参数:accessToken=at.12xp95k63bboast3aq0g5hg22q468929&deviceSerial=427734888&channelNo=2
 * 返回数据
 * {
 *     "data": {
 *         "picUrl": "http://img.ys7.com//group2/M00/74/22/CmGdBVjBVDCAaFNZAAD4cHwdlXA833.jpg"
 *     },
 *     "code": "200",
 *     "msg": "操作成功!"
 * }
 * @param
 * @return
 *
 * http://localhost:9001/vedios/savePic?picUrl=111
 */
@GetMapping("/savePic")
@ApiOperation(value = "将抓取的图片保存到阿里云")
public void capture(String picUrl)throws Exception{
    //System.out.println(picUrl);
    HttpGet httpGet = new HttpGet("https://img13.360buyimg.com/n5/jfs/t17446/352/1415143705/203490/68cdb4ee/5ac74800N0824a445.jpg");
    httpGet.setConfig(requestConfig);
    CloseableHttpResponse httpResponse = httpClient.execute(httpGet);
    HttpEntity httpEntity = httpResponse.getEntity();

    if (httpEntity!=null) {
        System.out.println("ContentType:"+httpEntity.getContentType().getValue());
        InputStream inputStream = httpEntity.getContent();

        FileService fileService = fileServiceFactory.getFileService("ALIYUN");
        //InputStream转换MultipartFile
        MultipartFile multipartFile = new MultipartFileDto("temp.jpg","temp.jpg",httpEntity.getContentType().getValue(), inputStream);
        fileService.upload(multipartFile);
    }
}

将file包装成MultipartFile

package com.topwulian.dto;

import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

/**
 * @Author: szz
 * @Date: 2019/1/16 下午4:33
 * @Version 1.0
 *
 * 负责将InputStream转换MultipartFile,可以少引一个jar包,本来用的是spring-test-4.3.9中的MockMultipartFile,直接提取出来使用
 */
public class MultipartFileDto implements MultipartFile {
    private final String name;

    private String originalFilename;

    private String contentType;

    private final byte[] content;


    /**
     * Create a new MultipartFileDto with the given content.
     * @param name the name of the file
     * @param content the content of the file
     */
    public MultipartFileDto(String name, byte[] content) {
        this(name, "", null, content);
    }

    /**
     * Create a new MultipartFileDto with the given content.
     * @param name the name of the file
     * @param contentStream the content of the file as stream
     * @throws IOException if reading from the stream failed
     */
    public MultipartFileDto(String name, InputStream contentStream) throws IOException {
        this(name, "", null, FileCopyUtils.copyToByteArray(contentStream));
    }

    /**
     * Create a new MultipartFileDto with the given content.
     * @param name the name of the file
     * @param originalFilename the original filename (as on the client's machine)
     * @param contentType the content type (if known)
     * @param content the content of the file
     */
    public MultipartFileDto(String name, String originalFilename, String contentType, byte[] content) {
        this.name = name;
        this.originalFilename = (originalFilename != null ? originalFilename : "");
        this.contentType = contentType;
        this.content = (content != null ? content : new byte[0]);
    }

    /**
     * Create a new MultipartFileDto with the given content.
     * @param name the name of the file
     * @param originalFilename the original filename (as on the client's machine)
     * @param contentType the content type (if known)
     * @param contentStream the content of the file as stream
     * @throws IOException if reading from the stream failed
     */
    public MultipartFileDto(String name, String originalFilename, String contentType, InputStream contentStream)
            throws IOException {

        this(name, originalFilename, contentType, FileCopyUtils.copyToByteArray(contentStream));
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public String getOriginalFilename() {
        return this.originalFilename;
    }

    @Override
    public String getContentType() {
        return this.contentType;
    }

    @Override
    public boolean isEmpty() {
        return (this.content.length == 0);
    }

    @Override
    public long getSize() {
        return this.content.length;
    }

    @Override
    public byte[] getBytes() throws IOException {
        return this.content;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return new ByteArrayInputStream(this.content);
    }

    @Override
    public void transferTo(File dest) throws IOException, IllegalStateException {
        FileCopyUtils.copy(this.content, dest);
    }

}

5.5.6.摄像头不支持远程截图的另外一种玩法

如果你购买摄像头不支持远程截图,就需要其他办法

在摄像头购买后,可以在局域网中对它进行配置,我购买的摄像头支持配置截图并且上传到windows的ftp服务器这样一个功能,所以我只能迂回作战,先使用内置的上传ftp的功能将图片传到ftp上,再去监控ftp服务器,把最新截取的图片地址保存到云服务器的数据库,这样就能管理每个摄像头所截取的图片啦。
在这里插入图片描述

在这里插入图片描述

5.5.7.监控ftp中设备截图变化程序的制作

通过查看萤石接口平台:https://open.ys7.com/doc/zh/book/index/device_option.html#device_option-api4

得到摄像头截图的接口:

设备抓拍图片

  • 接口功能

    抓拍设备当前画面,该接口仅适用于IPC或者关联IPC的DVR设备,该接口并非预览时的截图功能。海康型号设备可能不支持萤石协议抓拍功能,使用该接口可能返回不支持或者超时。

    注意:设备抓图能力有限,请勿频繁调用,频繁调用将会被拉入限制黑名单,建议调用的间隔为4s左右

  • 请求地址

    https://open.ys7.com/api/lapp/device/capture

  • 请求方式

    POST

  • 子账户token请求所需最小权限

    "Permission":"Capture" "Resource":"Cam:序列号:通道号"

  • 请求参数

参数名类型描述是否必选
accessTokenString授权过程获取的access_tokenY
deviceSerialString设备序列号,存在英文字母的设备序列号,字母需为大写Y
channelNoint通道号,IPC设备填写1Y
  • HTTP请求报文
POST /api/lapp/device/capture HTTP/1.1
Host: open.ys7.com
Content-Type: application/x-www-form-urlencoded

accessToken=at.12xp95k63bboast3aq0g5hg22q468929&deviceSerial=427734888&channelNo=1
  • 返回数据
{
    "data": {
        "picUrl": "https://img.ys7.com//group2/M00/74/22/CmGdBVjBVDCAaFNZAAD4cHwdlXA833.jpg"
    },
    "code": "200",
    "msg": "操作成功!"
}
  • 返回字段
字段名类型描述
picUrlString抓拍后的图片路径,图片保存有效期为2小时
  • 返回码
返回码返回消息描述
200操作成功请求成功
10001参数错误参数为空或格式不正确
10002accessToken异常或过期重新获取accessToken
10005appKey异常appKey被冻结
10051无权限进行抓图设备不属于当前用户或者未分享给当前用户
20002设备不存在
20006网络异常检查设备网络状况,稍后再试
20007设备不在线检查设备是否在线
20008设备响应超时操作过于频繁或者设备不支持萤石协议抓拍
20014deviceSerial不合法
20032该用户下该通道不存在检查设备是否包含该通道
49999数据异常接口调用异常
60017设备抓图失败设备返回失败
60020不支持该命令确认设备是否支持抓图

但是,由于我们购买的摄像头经过测试,不支持远程抓图。

思路:打开摄像头的局域网配置中心,发现可以进行ftp截图,并且传到云服务器上,准备一台云服务器,安装nginx,将设备的截图定时传到nginx的文件目录下面,由nginx映射成http服务,同时把设备上传的图片跟摄像进行绑定(要在数据库维护摄像头跟上传图片的对应关系)。

image-20190707150359079

前置工作:需要在摄像头的配置界面把你要上传到的ftp服务器地址配好

在云服务器上可以通过nginx配置一个ftp服务器,并且配置一个http服务器,可以在服务器上安装一个宝塔,通过宝塔可视化安装配置ftp,http服务器

宝塔网站:https://www.bt.cn/

我们服务器的程序,主要是监控ftp文件夹截图的变化,监控到变化后,要把文件的http地址保存到云平台的服务器的数据库中,跟摄像头建立对应关系

如何使用java程序监控文件夹下面文件的变化呢?

第一通过jdk内置的监控程序,第二通过apache的开源组件

程序ftp_image就可以监控任意文件夹文件的变化

<dependencies>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.6</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>${fastjson.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>${mybatis.version}</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>${druid.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
    </dependency>
</dependencies>

application.yml

线程池

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置线程池核心容量
        executor.setCorePoolSize(10);
        // 设置线程池最大容量
        executor.setMaxPoolSize(20);
        // 设置任务队列长度
        executor.setQueueCapacity(200);
        // 设置线程超时时间
        executor.setKeepAliveSeconds(60);
        // 设置线程名称前缀
        executor.setThreadNamePrefix("taskExecutor-");
        // 设置任务丢弃后的处理策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncUncaughtExceptionHandler() {
            @Override
            public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
                System.out.println("线程异常");
            }
        };
    }
}

配置类

@Component
public class FtpConfig {
    // 自动注入业务服务
    @Autowired
    private VedioImagesDao vedioImagesDao;

    @Autowired
    private VedioDao vedioDao;
    @Bean
    public ReYoFileMonitor ftp(){
        //http://pic.topwulian.com/C32944171/2019_5_19-2019_5_19/C32944171_20190518171652865_TIMING.jpg
        ReYoFileMonitor m =null;
        try {
            m=new ReYoFileMonitor(5000);
            m.monitor("/Users/szz/ftp", new ReYoFileListener(vedioImagesDao,vedioDao));
            m.start();
        }catch (Exception e){
            System.out.println("由于监听器在独立的线程中执行,一旦异常发生将导致线程退出,所以如果希望监听线程不中断,应在线程中捕获所有异常。");
        }
        return m;//
    }
}

监视器类

import java.io.File;

import org.apache.commons.io.monitor.FileAlterationListener;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;

/**
 * <B>创 建 人:</B>AdministratorReyoAut <BR>
 * <B>创建时间:</B>2017年12月23日 下午9:26:08<BR>
 *
 * @author ReYo
 * @version 1.0
 */

public class ReYoFileMonitor {

   FileAlterationMonitor monitor = null;

   public ReYoFileMonitor(long interval) throws Exception {
      monitor = new FileAlterationMonitor(interval);
   }

   public void monitor(String path, FileAlterationListener listener) {
      FileAlterationObserver observer = new FileAlterationObserver(new File(path));
      monitor.addObserver(observer);
      observer.addListener(listener);
   }

   public void stop() throws Exception {
      monitor.stop();
   }

   public void start() throws Exception {
      monitor.start();
   }

}

FileAlterationListener,FileAlterationMonitor,FileAlterationObserver都是commons-io包下面专门用来做文件监控的

监视到文件变化后要做的是事情,需要一个监听类

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.topwulian.dao.VedioDao;
import com.topwulian.dao.VedioImagesDao;
import com.topwulian.model.Vedio;
import com.topwulian.model.VedioImages;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationObserver;


/**
 * <B>创 建 人:</B>AdministratorReyoAut <BR>
 * <B>创建时间:</B>2017年12月23日 下午9:25:21<BR>
 *
 * @author ReYo
 * @version 1.0
 */
//@Component
public class ReYoFileListener extends FileAlterationListenerAdaptor {

    private VedioImagesDao vedioImagesDao;

    private VedioDao vedioDao;

    public ReYoFileListener(VedioImagesDao vedioImagesDao, VedioDao vedioDao) {
        this.vedioImagesDao = vedioImagesDao;
        this.vedioDao = vedioDao;
    }

    ReYoFileMonitor monitor = null;

    @Override
    public void onStart(FileAlterationObserver observer) {
        // System.out.println("onStart");
    }

    @Override
    public void onDirectoryCreate(File directory) {
        System.out.println("onDirectoryCreate:" + directory.getAbsolutePath());

    }

    @Override
    public void onDirectoryChange(File directory) {
        System.out.println("onDirectoryChange:" + directory.getAbsolutePath());
    }

    @Override
    public void onDirectoryDelete(File directory) {
        System.out.println("onDirectoryDelete:" + directory.getAbsolutePath());
    }

    @Override
    public void onFileCreate(File file) {
        try {
            String absolutePath = file.getAbsolutePath();
            System.out.println("onFileCreate:" + absolutePath);
            //c:\wwwroot\pic\C32944171\2019_5_19-2019_5_19\C32944171_20190518171652865_TIMING.jpg
            //System.out.println("将文件处理存入数据库。。。");
            String[] split = absolutePath.split("\\\\");
            Map<String, Object> params=new HashMap<>();
            params.put("deviceSerial",split[3]);
            List<Vedio> vedios = vedioDao.list(params, null, null);
            if (!vedios.isEmpty()&&split.length>5) {
                Vedio vedio = vedios.get(0);
                VedioImages vedioImages = new VedioImages();
                vedioImages.setPath(absolutePath);
                vedioImages.setDeviceSerial(vedio.getDeviceSerial());
                vedioImages.setFarmId(vedio.getFarmId());
                String urlPrefix = "http://pic.topwulian.com/";
                String url=urlPrefix+split[3]+"/"+split[4]+"/"+split[5];
                vedioImages.setUrl(url);
                vedioImages.setVedioId(vedio.getId().intValue());
                vedioImagesDao.save(vedioImages);
                System.out.println("文件存入数据库。。。");
            }
        }catch (Exception e){
            System.out.println("防止异常导致线程退出。。。");
        }
    }

    @Override
    public void onFileChange(File file) {
        System.out.println("onFileCreate : " + file.getAbsolutePath());
    }

    @Override
    public void onFileDelete(File file) {
        System.out.println("onFileDelete :" + file.getAbsolutePath());
    }

    @Override
    public void onStop(FileAlterationObserver observer) {
        // System.out.println("onStop");
    }

}

回顾:在commons-io包中有文件监控的类,主要的方法就是监控文件新增后做哪些事情

5.5.8.服务器可视化运维-宝塔面板的使用介绍

通常,服务于不懂linux命令的运维人员,对于小公司,小项目来说,项目完成了,就希望能稳定运行,不要出错,但是一些日常维护,比如程序重启,重新部署;如果购买了一台新的服务器,需要安装redis,安装tomcat,安装jdk环境,安装nginx,安装php环境,安装mysql。。。谁来安装?

可以进行服务器的监控:cpu,内存,硬盘(一定要关注硬盘空间,切记!!!)

可以通过宝塔无代码安装

他的免费版本非常强大,已经足够使用了,所以我们在运维过程中,不需要花钱。

宝塔可以安装在任意的服务器上

在宝塔面板的软件商店可以安装常用的软件,查看软件的运行状态,并且进行重启

5.5.9.使用SpringTask定时调用萤石云接口获取摄像头最新token

海康的摄像头要想在网页中播放并且进行控制,需要几个参数,其中token一周会过期一次,所以需要我们自己写代码,每周重新根据接口去获取最新的摄像头对应的token,并且更新到数据库,这样,当你刷新页面时,就会把最新的token查询出来,在页面渲染

1)如何去调接口?

https://open.ys7.com/doc/zh/book/index/user.html

注意:获取到的accessToken有效期是7天,请在即将过期或者接口报错10002时重新获取,请勿频繁调用,频繁调用将会被拉入限制黑名单

  • https://open.ys7.com/api/lapp/token/get

  • 请求方式

    POST

  • 请求参数

参数名类型描述是否必选
appKeyStringappKeyY
appSecretStringappSecretY
  • 返回字段
字段名类型描述
accessTokenString获取的accessToken
expireTimelong具体过期时间,精确到毫秒

2)如何做定时?

定时框架可以用quartz,但项目中这个模块用的是SpringTask

/**
 * 定时为萤石云账号获取视频设备的accessToken,因为这个token一周会过期一次
 * 接口参考地址:https://open.ys7.com/doc/zh/book/index/user.html
 * 每周一23点15分执行任务
 * @param
 * @return
 */
@Scheduled(cron = "0 15 23 ? * MON")
@Transactional
public void getAccessTokenAtEveryWeek()throws Exception{

    //萤石云获取摄像头控制key的接口,一周需要执行一次
    String url="https://open.ys7.com/api/lapp/token/get";

    //得到所有的萤石云开发账号
    List<SysYs7Account> sysYs7AccountList = sysYs7AccountDao.list(null, null, null);
    for (SysYs7Account sysYs7Account : sysYs7AccountList) {
        Map<String, Object> param=new HashMap<>();
        param.put("appKey",sysYs7Account.getAppKey());
        param.put("appSecret",sysYs7Account.getAppSecret());
        //这个接口不能频繁请求,为防止多账号情况下的请求次数过多,需要sleep
        Thread.sleep(30000);
        HttpResult httpResult = httpAPIService.doPost(url, param);
        Map mapMsg = JSON.parseObject(httpResult.getBody(), Map.class);
        Data data = JSON.parseObject(mapMsg.get("data").toString(),Data.class);
        sysYs7Account.setAccessToken(data.getAccessToken());
        //更新数据库
        sysYs7AccountDao.update(sysYs7Account);

        //根据userId查询该用户所拥有的全部摄像头,并将它们的accessKey也一起更新
        Map<String, Object> vedioParam = new HashMap<>();
        vedioParam.put("", sysYs7Account.getUserId());
        List<Vedio> vedioList = vedioDao.list(vedioParam, null, null);
        for (Vedio vedio : vedioList) {
            vedio.setAccessToken(sysYs7Account.getAccessToken());
            vedio.setUpdateTime(new Date());
            vedioDao.update(vedio);
        }
    }
}

发送http请求的工具类

import com.topwulian.dto.HttpResult;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
public class HttpAPIService {
 
    @Autowired
    private CloseableHttpClient httpClient;
 
    @Autowired
    private RequestConfig config;
 
 
    /**
     * 不带参数的get请求,如果状态码为200,则返回body,如果不为200,则返回null
     * 
     * @param url
     * @return
     * @throws Exception
     */
    public String doGet(String url) throws Exception {
        // 声明 http get 请求
        HttpGet httpGet = new HttpGet(url);
 
        // 装载配置信息
        httpGet.setConfig(config);
 
        // 发起请求
        CloseableHttpResponse response = this.httpClient.execute(httpGet);
 
        // 判断状态码是否为200
        if (response.getStatusLine().getStatusCode() == 200) {
            // 返回响应体的内容
            return EntityUtils.toString(response.getEntity(), "UTF-8");
        }
        return null;
    }
 
    /**
     * 带参数的get请求,如果状态码为200,则返回body,如果不为200,则返回null
     * 
     * @param url
     * @return
     * @throws Exception
     */
    public String doGet(String url, Map<String, Object> map) throws Exception {
        URIBuilder uriBuilder = new URIBuilder(url);
 
        if (map != null) {
            // 遍历map,拼接请求参数
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                uriBuilder.setParameter(entry.getKey(), entry.getValue().toString());
            }
        }
 
        // 调用不带参数的get请求
        return this.doGet(uriBuilder.build().toString());
 
    }
 
    /**
     * 带参数的post请求
     * 
     * @param url
     * @param map
     * @return
     * @throws Exception
     */
    public HttpResult doPost(String url, Map<String, Object> map) throws Exception {
        // 声明httpPost请求
        HttpPost httpPost = new HttpPost(url);
        // 加入配置信息
        httpPost.setConfig(config);
 
        // 判断map是否为空,不为空则进行遍历,封装from表单对象
        if (map != null) {
            List<NameValuePair> list = new ArrayList<NameValuePair>();
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                list.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString()));
            }
            // 构造from表单对象
            UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(list, "UTF-8");
 
            // 把表单放到post里
            httpPost.setEntity(urlEncodedFormEntity);
        }
 
        // 发起请求
        CloseableHttpResponse response = this.httpClient.execute(httpPost);
        return new HttpResult(response.getStatusLine().getStatusCode(), EntityUtils.toString(
                response.getEntity(), "UTF-8"));
    }
 
    /**
     * 不带参数post请求
     * 
     * @param url
     * @return
     * @throws Exception
     */
    public HttpResult doPost(String url) throws Exception {
        return this.doPost(url, null);
    }
}

5.6.1.IOS移动端适配的特殊处理

在页面进行h5适配的时候,iphone对于iframe不能很好地渲染,上下和左右的滚动条是无效的,这个只有在真机上才能发现,通过浏览器调试是发现不了的

操作步骤:以index.html页面进行

1)index页面中的iframe:

2)样式

<style>
          .scroll-wrapper{
              -webkit-overflow-scrolling: touch;
              overflow: scroll;
          }
          iframe{
              width: 100%;
              height: 100%;
          }

</style>

3)js调用

<script>
    var device = layui.device();//可以获取设备的信息
    if (device.ios) {
        $("#scroll-wrapper").addClass("scroll-wrapper");
    }
</script>

image-20190707160803821

5.6.2.没有硬件时如何模拟硬件端给程序发数据以方便测试?

开发快的官方提供了ssdk-demo程序,这个ssdk-demo程序既可以接收数据,也可以发送数据,我们启动项目后,可以使用这个demo给我们的程序发消息,接收到消息后就可以进行解析,调试了。

public static final String DOMAIN = "ip";//需要在开发快的官网申请
public static final String APP_KEY = "xxx";//需要在开发快的官网申请
public static final String SECRET_KEY = "xxx";//需要在开发快的官网申请
public static final String UID = "xxx";//发送消息的id
public static final String RECEIVE_UID = "xxx";//接收消息的id
//如果你只有一个id,就可以自己给自己发消息

5.6.3.将数据库导出成excel格式的设计文档

5.7.1.其他业务介绍

硬件传感器如何采购:传感器强烈建议大家使用成熟的产品,这种比较贵,但他很靠谱,一般不会出问题

你也可以在淘宝上采购,这个很便宜,几百块钱一套,但消息指令的发送,接收都需要自己编程处理,很坑的地方在于,天天出问题,无信号,无故重启,断电来电后不启动,风吹雨淋出故障,没有防雷击功能

6.ElasticSearch数据快速搜索

我们项目中的大量传感器的采集数据:每套传感器设备每天的采集量是7乘以12乘以24,一个农场仅仅部署一套是远远不够的 ,起码要3-5套。目前本项目已经实施的农场有5个,都是依赖于同一套系统,数据如果继续放在mysql,是承受不了的,需要能实时检索,并做大量的统计。

6.1.2.数据采集LogStash&数据可视化-Kibana的友好展示

ElasticSearch做搜索非常厉害,但是数据哪里来,这是需要使用LogStash进行采集的,可以从日志,消息队列,数据库进行数据的采集。官方也提供了开箱即用的安装包,我们只需要下载安装包,然后配置数据源,将数据保存到ES中即可。

数据的展示就需要使用Kibana,Kibana也可以做ES的管理。

需要注意的是,大家在安装配置ELK的时候,要统一他们三者之间的版本。

ELK运行是依赖于java运行时环境的,由于ELK消耗的服务器资源相对较多,需要服务器的配置相对较高,也需要配置多台服务器,建议搭建一个高可用的集群。

6.2.1.Solr跟ES比较以及不采用Solr的原因大揭秘

两个框架底层使用的都是lucene,而es出来的比较晚,他们的更新速度都相对比较快。

es的搜索速度回更快,他的变成也更友好,es可以做到近实时的搜索,当数据量大的时候,当有大量的新增数据,搜索服务都需要先做分词,然后再存入搜索库,在性能比较上,es表现地非常好。

solr完全免费,而es高级功能部分收费(权限)

6.3.1.邮件告警-JavaMail邮件收发

项目中是可以对环境监测的异常数据进行报警的,我们每次采集到的数据,都会跟对应传感器的阈值进行比对,如果超出范围,并且连续三次都超出,那我们就认为数据是异常的,这时需要进行预警,预警方式可以直接在系统中发邮件,发短信对于特别的紧急的又没人处理,也可以拨打电话。

对于发邮件,项目中使用了JavaMail进行处理,现在JavaMail有针对于springboot项目的起步依赖。

代码:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

在yml中配置邮件账号密码,服务器地址等信息

MailService中进行邮件的发送

@Override
public void sendMail(String toUser, String subject, String text) throws MessagingException {
   MimeMessage message = javaMailSender.createMimeMessage();

   MimeMessageHelper helper = new MimeMessageHelper(message, true);
   helper.setFrom(serverMail);
   helper.setTo(toUser);
   helper.setSubject(subject);
   helper.setText(text, true);

   javaMailSender.send(message);
}

6.3.2.使用Quartz实现邮件的定时收取

1)需要一个定时任务,本脚手架工程已经内置了quartz作为定时任务框架,你可以编写一个方法,然后在界面进行配置执行时间的规则,这样该方法就能运行了

quartz.properties

quartz要想使用详细的配置规则,是需要依赖数据库表的

image-20190707205512647

JobServiceImpl

package com.topwulian.service.impl;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import com.topwulian.dao.JobDao;
import com.topwulian.job.SpringBeanJob;
import com.topwulian.model.JobModel;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import com.topwulian.service.JobService;

@Service
public class JobServiceImpl implements JobService {

   private static final Logger log = LoggerFactory.getLogger("adminLogger");

   @Autowired
   private Scheduler scheduler;
   @Autowired
   private ApplicationContext applicationContext;
   private static final String JOB_DATA_KEY = "JOB_DATA_KEY";
   @Autowired
   private JobDao jobDao;

   @Override
   public void saveJob(JobModel jobModel) {
      checkJobModel(jobModel);
      String name = jobModel.getJobName();

      JobKey jobKey = JobKey.jobKey(name);
      JobDetail jobDetail = JobBuilder.newJob(SpringBeanJob.class).storeDurably()
            .withDescription(jobModel.getDescription()).withIdentity(jobKey).build();

      jobDetail.getJobDataMap().put(JOB_DATA_KEY, jobModel);

      CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobModel.getCron());
      CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(name).withSchedule(cronScheduleBuilder)
            .forJob(jobKey).build();

      try {
         boolean exists = scheduler.checkExists(jobKey);
         if (exists) {
            scheduler.rescheduleJob(new TriggerKey(name), cronTrigger);
            scheduler.addJob(jobDetail, true);
         } else {
            scheduler.scheduleJob(jobDetail, cronTrigger);
         }

         JobModel model = jobDao.getByName(name);
         if (model == null) {
            jobDao.save(jobModel);
         } else {
            jobDao.update(jobModel);
         }
      } catch (SchedulerException e) {
         log.error("新增或修改job异常", e);
      }
   }

   private void checkJobModel(JobModel jobModel) {
      String springBeanName = jobModel.getSpringBeanName();
      boolean flag = applicationContext.containsBean(springBeanName);
      if (!flag) {
         throw new IllegalArgumentException("bean:" + springBeanName + "不存在,bean名如userServiceImpl,首字母小写");
      }

      Object object = applicationContext.getBean(springBeanName);
      Class<?> clazz = object.getClass();
      if (AopUtils.isAopProxy(object)) {
         clazz = clazz.getSuperclass();
      }

      String methodName = jobModel.getMethodName();
      Method[] methods = clazz.getDeclaredMethods();

      Set<String> names = new HashSet<>();
      Arrays.asList(methods).parallelStream().forEach(m -> {
         Class<?>[] classes = m.getParameterTypes();
         if (classes.length == 0) {
            names.add(m.getName());
         }
      });

      if (names.size() == 0) {
         throw new IllegalArgumentException("该bean没有无参方法");
      }

      if (!names.contains(methodName)) {
         throw new IllegalArgumentException("未找到无参方法" + methodName + ",该bean所有方法名为:" + names);
      }
   }

   @Override
   public void doJob(JobDataMap jobDataMap) {
      JobModel jobModel = (JobModel) jobDataMap.get(JOB_DATA_KEY);

      String beanName = jobModel.getSpringBeanName();
      String methodName = jobModel.getMethodName();
      Object object = applicationContext.getBean(beanName);

      try {
         log.info("job:bean:{},方法名:{}", beanName, methodName);
         Method method = object.getClass().getDeclaredMethod(methodName);
         method.invoke(object);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   /**
    * 删除job
    * 
    * @throws SchedulerException
    */
   @Override
   public void deleteJob(Long id) throws SchedulerException {
      JobModel jobModel = jobDao.getById(id);

      if (jobModel.getIsSysJob() != null && jobModel.getIsSysJob()) {
         throw new IllegalArgumentException("该job是系统任务,不能删除,因为此job是在代码里初始化的,删除该类job请先确保相关代码已经去除");
      }

      String jobName = jobModel.getJobName();
      JobKey jobKey = JobKey.jobKey(jobName);

      scheduler.pauseJob(jobKey);
      scheduler.unscheduleJob(new TriggerKey(jobName));
      scheduler.deleteJob(jobKey);

      jobModel.setStatus(0);
      jobDao.update(jobModel);
   }

}

2)要对邮箱中的邮件进行收取

https://www.cnblogs.com/a591378955/p/8031683.html

获取邮箱中的邮件

public class StoreMail {
 final static String USER = "robot"; // 用户名
 final static String PASSWORD = "password520"; // 密码
 public final static String MAIL_SERVER_HOST = "mail.***.com"; // 邮箱服务器
 public final static String TYPE_HTML = "text/html;charset=UTF-8"; // 文本内容类型
 public final static String MAIL_FROM = "[email protected]"; // 发件人
 public final static String MAIL_TO = "[email protected]"; // 收件人
 public final static String MAIL_CC = "[email protected]"; // 抄送人
 public final static String MAIL_BCC = "[email protected]"; // 密送人
 public static void main(String[] args) throws Exception {
  // 创建一个有具体连接信息的Properties对象
  Properties prop = new Properties();
  prop.setProperty("mail.debug", "true");
  prop.setProperty("mail.store.protocol", "pop3");
  prop.setProperty("mail.pop3.host", MAIL_SERVER_HOST);
  // 1、创建session
  Session session = Session.getInstance(prop);
  // 2、通过session得到Store对象
  Store store = session.getStore();
  // 3、连上邮件服务器
  store.connect(MAIL_SERVER_HOST, USER, PASSWORD);
  // 4、获得邮箱内的邮件夹
  Folder folder = store.getFolder("inbox");
  folder.open(Folder.READ_ONLY);
  // 获得邮件夹Folder内的所有邮件Message对象
  Message[] messages = folder.getMessages();
  for (int i = 0; i < messages.length; i++) {
   String subject = messages[i].getSubject();
   String from = (messages[i].getFrom()[0]).toString();
   System.out.println("第 " + (i + 1) + "封邮件的主题:" + subject);
   System.out.println("第 " + (i + 1) + "封邮件的发件人地址:" + from);
  }
  // 5、关闭
  folder.close(false);
  store.close();
 }
}

6.3.3.短信、电话平台介绍

阿里大于-》可以发国际短信,6毛一条,开通要求很严格,有段时间必须是企业

腾讯云短信:每个月送100条

网易短信:开通时送20条

其他平台:

大部分平台都支持语音验证码,对于收不到短信的用户,应该给他提供语音验证码的通道

具体的用法各个平台都有自己开发的sdk提供下载,可以进行参考,但一定要注意,需要使用最新的sdk,因为老版本的往往有安全漏洞,这种安全漏洞是涉及到钱的。

6.4.1.权限框架介绍

Shiro和SpringSecurity他们的使用场景

在开发中Shiro用的稍微多一点,其实这两个权限框架的原理都是类似的。

有两种权限控制的方式:

​ 粗粒度:我只判断是否登录,在访问页面之前判断该用户是否有权限,权限只判断一次

​ 细粒度:细粒度指的是在访问每个方法之前,都会进行权限校验,控制的非常精细

权限的具体实现:

​ 写代码做逻辑判断:用的很少

​ 把规则写在配置文件中:对于页面的访问,大部分情况下用于粗粒度

​ 规则定义在表中:用户表,角色表,权限表,菜单表,他们之间都是多对多的关系。

参考:https://blog.csdn.net/Qsir/article/details/72628127

6.4.2.项目中Shiro跟SpringBoot的整合

对于整合,一个项目甚至于说一个公司,只需要配置一次即可。表的设计也只需要最开始配置好。

大家在工作中,遇到最多的是如何使用权限控制?

​ 如何控制某些页面,某些方法只有特定的权限或角色才能访问?

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>${shiro.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>${shiro.version}</version>
</dependency>

shiro-ehcache缓存jar包的作用:用户在访问某个方法时,需要查询数据库看是否拥有访问这个方法的权限,如果没有缓存,用户每访问一次该方法,都要去数据库查询,效率太低,这时我们可以把当前用户这次登陆时拥有的角色,权限存入内存,就不需要频繁地访问数据库了。

没有使用SpringBoot前,需要在xml文件中配置权限框架的拦截规则,有了SpringBoot,我们把配置改在了Bean中进行。

在com.topwulian.config.ShiroConfig类中定义了权限的配置规则,这个一般配好后不需要大家更改,而且每个项目的配置方法是一致的,代表着这个配置文件是通用的。

ShiroFilterFactoryBean是一个工厂类,项目启动创建一次,他里面配置了拦截规则
filterChainDefinitionMap包含了所要过滤的规则,key就是你要对哪些资源进行拦截,value代表拦截的规则
value中的anon代表不需要登录也可以访问。
authc这个代表必须登录后才能访问
SecurityManager权限管理器
用户拥有的权限要在访问页面,方法的时候进行判断,判断的工作我们都看不到,由权限管理器完成
MyShiroRealm是权限的作用域:我们的权限是在这个类中被管理的
HashedCredentialsMatcher凭证匹配器,你要使用的加密方式,比对数据库保存的加密后的密码和登录的时候输入的密码
常用的加密方式md5,sha1,sha5,BCrypt

通过配置文件或者配置类的方法进行的权限控制,我们通常叫他粗粒度的权限控制

那该如何进行细粒度的权限控制呢?

只需要在你要控制的方法上面加上对应的注解就可以了

@RequiresPermissions("sys:menu:query")

而这个权限就是配置在数据库中的一个字符串

6.5.1.代码生成器

最大的好处是可以把单表的增删改查一键生成,不需要自己写任何代码,包括页面都会生成

用到了模板(controller,service,dao,xml,html,js,css)

生成完毕拷贝到工程中对应的目录下面即可

image-20190717211243747

com.topwulian.controller.GenerateController

com.topwulian.utils.TemplateUtil

原理就是模板加数据生成代码

模板的编写非常有难度

代码生成器有很多种,比较好的是vue+elementUI的模板

6.6.1.Docker

Docker教程:https://www.runoob.com/docker/docker-tutorial.html

对于我们的物联网项目,可能会有多个客户,这时候会要求他们之间的数据相对独立,程序开发保持一致,随着物联网设备的增加,如果只部署一个客户端可能会处理不了太多的请求,我们希望可以部署多份,要做到环境的隔离,每个程序都有自己的redis,自己的消息队列,可以使用docker来解决这个问题。

安装docker:需要注意的问题,在windows中安装docker后会跟虚拟机冲突

在docker中安装依赖,安装redis,mq,程序,数据库

6.7.1.MongoDB

MongoDB他是最像关系型数据库的非关系型数据库软件

对于我们采集来的数据,其实有部分不一定非要存到mysql中,而且采集到的数据没有太复杂的逻辑关系,可以把采集来的数据放到非关系型数据库中,非常推荐大家使用MongoDB,在跟SpringBoot整合后,很难发现他的操作方式跟mysql有什么区别

MongoDB的安装配置略微有些复杂,现在建议大家安装的版本是3.7,要注意的是安装完毕后需要配置数据的存储路径,还要注意磁盘的占用问题。

在安装过程中肯定会遇到各种各样的问题,大家遇到的问题越多,你能学到的也就越多。

数据采集表,设备表,设备阈值

6.8.1.实用爬虫程序制作

https://gitee.com/szz715/httpclient-xiecheng.git

有同学让我帮忙爬取一个医药网站所有的药品类目,爬取携程酒店数据,爬取网站的所有pdf资源

爬虫对于我们的农业项目的作用和意义?

农业项目是需要大量气象数据支撑的,某一地区的气象历史数据从何而来?来自于当地气象站的网站,我们可以爬取下来进行使用。

爬虫实际上就是模拟人类的浏览行为,打开每一个页面,并且把页面上的感兴趣的数据解析并保存下来

1)模拟用户行为

用户的行为就是一个http请求:可以采用httpclient发送http请求

得到响应页面的数据

2)对页面进行解析

jsoup webmagic

3)把数据存到数据库

jdbc

要注意每个页面的页面布局不同,需要写不同的解析代码

7.1.1.Druid连接池可视化监控

主要用到了Druid内置的可视监控模块druid/index.html

image-20190721144130886

大家重点关注的就是sql监控,如果项目在运行的过程中,出现了卡顿,延迟比较严重的情况,可以打开这个页面,实时查看哪些sql的执行比较缓慢,并且可以迅速定位问题。

7.1.2.Alibaba ToolKit一键上云跟持续集成的区别

项目开发完是需要部署到服务器上面的,通常的部署本地开发-》打包-》通过ftp工具将jar包上传到服务器-》用ssh工具登录到服务器-》运行这个程序。

这种方法不是很方便,操作的部署比较多,需要安装一些第三方工具,需要对linux的命令比较熟悉,以上部署一个比较熟练的开发,在5分钟之内可以搞定。

但是,我的需求经常发生变化,这就意味着需要经常重新部署这个项目,这样,你改完代码后,重新花5分钟做重复的事情,这样的事情做多了,你会很烦。

既然是程序员,可不可以把这个做成自动的,我只需要在本地点一个按钮,后续动作全部自动帮你完成?

以往的做法,使用持续集成的CI工具,比如jenkins去部署,但是使用jenkins需要服务器资源,需要在服务器安装maven仓库,安装git,安装jenkins及它的插件,要在linux服务器上装这些程序花的时间成本更高,而且还要保证这些程序不能挂掉。

对于一个人战斗的项目来说,成本实在太高了!

引入一键上云神器!

对于独立开发者,没有那么多的运维资料,这种方案是最方便的,整个过程只需要半分钟

7.1.3.阿里云监控配置实战

项目上线后,如果保证程序不挂掉,数据库一直可以访问,以及比如域名一直可以访问

7.1.4.SpringBoot系列-配置多环境配置文件

我们的项目是在本地开发,服务器部署,本地和服务器的配置肯定是不一样的,数据库,redis,开发快,阿里云,mq这些配置本地跟服务器肯定是不一样的,前面说了,可以通过上云神器一键部署,但是在部署前,需要更改这些配置,改完之后再部署,这个时间又要几分钟。而且部署完后我又要继续开发,意味着我又要把配置信息改回来,麻烦不?

这个时候就要使用多配置文件了,开发环境使用开发环境的配置,部署使用服务器的配置,测试使用测试配置

7.1.5.Linux MySQL自动备份和数据恢复-Crontab

项目上线后,需要定时对数据库备份,切记切记,数据库一般不会出问题,一旦出问题,就是大问题,如果没有备份,想哭都哭不出来!!!

一定要有好的意识,好的习惯,必须备份

7.1.6.代码管理GitLab、码云、github

小项目可以使用码云或github,做的项目的代码不希望公开,可以使用私有项目

对于github,在2019年之前私有项目是收费的,但是被微软收购后,私有项目也免费

对于大的项目,或者你认为代码放在公有库不安全,可以自己搭建gitlab私有仓库

像我们的项目,最多的时候,开发也有五个

7.1.7.域名申请和备案的流程

项目在上线后,还是要做域名访问,通常也需要我们来帮客户申请,建议大家使用阿里云的万网域名申请

注意百度搜索的阿里云地址,不要进入假网站https://cn.aliyun.com/

进行注册登录,搜索域名注册

个人注册,需要准备身份证,公司注册,需要营业执照和法人身份证

搜索你想要注册的域名,并且进行购买,花钱的事情非常容易,支付宝直接扫码付款,新用户有优惠券

注册完的域名不能使用,为啥,根据监管局要求,注册后必须要备案,很痛苦,我备案都是7-20天左右

需要阿里云给你邮寄一个备案的背景贴纸,你站在贴纸下,拍照,上传,打印承诺书,签字,上传,然后等待阿里云客服给你打电话确认

急不得,出了什么问题就按他要求的去整改即可

8.1.1.消息队列的重试机制

项目中使用了mq,mq他发送消息可能会失败,失败了之后会进行重试操作,尽量避免消息的丢失,这里以activemq为例,会首先发送一次,如果失败,重试六次,如果六次后依然失败,就认为消息发不出去了,会把消息放到死信队列,等待用户手动处理。

8.1.2.ActiveMQ死信产生的原因及使用方案

https://blog.csdn.net/m0_37609579/article/details/82216332

比较熟悉,以上部署一个比较熟练的开发,在5分钟之内可以搞定。

但是,我的需求经常发生变化,这就意味着需要经常重新部署这个项目,这样,你改完代码后,重新花5分钟做重复的事情,这样的事情做多了,你会很烦。

既然是程序员,可不可以把这个做成自动的,我只需要在本地点一个按钮,后续动作全部自动帮你完成?

以往的做法,使用持续集成的CI工具,比如jenkins去部署,但是使用jenkins需要服务器资源,需要在服务器安装maven仓库,安装git,安装jenkins及它的插件,要在linux服务器上装这些程序花的时间成本更高,而且还要保证这些程序不能挂掉。

对于一个人战斗的项目来说,成本实在太高了!

引入一键上云神器!

对于独立开发者,没有那么多的运维资料,这种方案是最方便的,整个过程只需要半分钟

7.1.3.阿里云监控配置实战

项目上线后,如果保证程序不挂掉,数据库一直可以访问,以及比如域名一直可以访问

7.1.4.SpringBoot系列-配置多环境配置文件

我们的项目是在本地开发,服务器部署,本地和服务器的配置肯定是不一样的,数据库,redis,开发快,阿里云,mq这些配置本地跟服务器肯定是不一样的,前面说了,可以通过上云神器一键部署,但是在部署前,需要更改这些配置,改完之后再部署,这个时间又要几分钟。而且部署完后我又要继续开发,意味着我又要把配置信息改回来,麻烦不?

这个时候就要使用多配置文件了,开发环境使用开发环境的配置,部署使用服务器的配置,测试使用测试配置

7.1.5.Linux MySQL自动备份和数据恢复-Crontab

项目上线后,需要定时对数据库备份,切记切记,数据库一般不会出问题,一旦出问题,就是大问题,如果没有备份,想哭都哭不出来!!!

一定要有好的意识,好的习惯,必须备份

7.1.6.代码管理GitLab、码云、github

小项目可以使用码云或github,做的项目的代码不希望公开,可以使用私有项目

对于github,在2019年之前私有项目是收费的,但是被微软收购后,私有项目也免费

对于大的项目,或者你认为代码放在公有库不安全,可以自己搭建gitlab私有仓库

像我们的项目,最多的时候,开发也有五个

7.1.7.域名申请和备案的流程

项目在上线后,还是要做域名访问,通常也需要我们来帮客户申请,建议大家使用阿里云的万网域名申请

注意百度搜索的阿里云地址,不要进入假网站https://cn.aliyun.com/

进行注册登录,搜索域名注册

个人注册,需要准备身份证,公司注册,需要营业执照和法人身份证

搜索你想要注册的域名,并且进行购买,花钱的事情非常容易,支付宝直接扫码付款,新用户有优惠券

注册完的域名不能使用,为啥,根据监管局要求,注册后必须要备案,很痛苦,我备案都是7-20天左右

需要阿里云给你邮寄一个备案的背景贴纸,你站在贴纸下,拍照,上传,打印承诺书,签字,上传,然后等待阿里云客服给你打电话确认

急不得,出了什么问题就按他要求的去整改即可

8.1.1.消息队列的重试机制

项目中使用了mq,mq他发送消息可能会失败,失败了之后会进行重试操作,尽量避免消息的丢失,这里以activemq为例,会首先发送一次,如果失败,重试六次,如果六次后依然失败,就认为消息发不出去了,会把消息放到死信队列,等待用户手动处理。

8.1.2.ActiveMQ死信产生的原因及使用方案

https://blog.csdn.net/m0_37609579/article/details/82216332

  • 0
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

办公模板库 素材蛙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值