警务大数据项目实战

  • 专题一 大数据数据存储
  • 1-1 警务大数据之表创建
  • 第1关:Phoenix在Springboot应用
  • 第2关:通过POI事件读取Excel内容
  • 第3关:创建警务大数据表信息
  • 1-2 警务大数据之交换配置
  • 第1关:通过JDBC存储字段信息
  • 第2关:表关系信息存储
  • 第3关:自动交换应用
  • 1-3 警务大数据之离线存储
  • 第1关:Excel数据存储HBase
  • 第2关:文本文件数据存储HBase
  • 1-4 警务大数据之实时存储
  • 第一关:Kafka实时存储HBase
  • 第二关:从HTTP读取数据存入HBase
  • 1-5警务大数据之关系数据库存储
  • 第一关:MySQL支持
  • 第二关:MySQL离线存储
  • 专题二 大数据批处理
  • 2-1 警务大数据之上网相关指标批处理
  • 第一关:频繁更换上网场所
  • 第二关:多次同上网
  • 专题三 实时流计算
  • 3-1 警务大数据之大屏幕实时流数据监控
  • 第一关:车辆相关信息实时计算
  • 第二关:实时数据可视化
  • 专题四 数据可视化
  • 4-1 警务大数据之慧搜
  • 4-2 警务大数据之精准布控
  • 第一关:布控人员查询
  • 第二关:布控人员日志查询


专题一 大数据数据存储

1-1 警务大数据之表创建

第1关:Phoenix在Springboot应用

Phoenix是构建在HBase上的一个SQL层,能让我们用标准的JDBC API 来对数据进行操作。Phoenix主要原理是通过查询引擎将SQL查询转换为一个或多个HBase扫描,并发执行来生成标准的JDBC结果集。
在做警务大数据项目之前,我们先来学会使用Springboot 集成Phoenix,完成HBase表的创建。

package com.educodeer.police1.case1;


import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class Case1 {

	//获取hikariCP连接池
	@Autowired
	DataSource dataSource;

	/**
	 * HBase 表创建
	 * @throws SQLException
	 */
	public void  createTable() throws SQLException {
		/********** Begin *********/
        Connection connection=dataSource.getConnection();
        connection.createStatement().execute("CREATE TABLE TEST (ID INTEGER not null primary key, NAME VARCHAR)");
        /********** end *********/
	}

}

第2关:通过POI事件读取Excel内容

Excel是个人计算机数据处理软件,可以存储大量的数据,当警务需要的大量数据存储在Excel中时应该如何读取呢?本关任务就是编写一个通过 POI事件读取Excel内容的小程序。

package com.educodeer.police1.case1;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener;
import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
import org.apache.poi.hssf.eventusermodel.HSSFListener;
import org.apache.poi.hssf.eventusermodel.HSSFRequest;
import org.apache.poi.hssf.eventusermodel.MissingRecordAwareHSSFListener;
import org.apache.poi.hssf.record.LabelSSTRecord;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.SSTRecord;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;

/**
 * POI的事件模式处理
 *
 */
public class Case2 implements HSSFListener {

	// Records we pick up as we process
	private SSTRecord sstRecord;
	// 存储行记录的容器
	private List<String> rowlist = new ArrayList<String>();

	public void process(InputStream stream) throws IOException {
		POIFSFileSystem fs = new POIFSFileSystem(stream);
		// 添加监听记录的事件
		MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(this);
		// 监听代理
		FormatTrackingHSSFListener formatListener = new FormatTrackingHSSFListener(listener);
		// 创建事件工厂
		HSSFEventFactory factory = new HSSFEventFactory();
		// 注册监听器
		HSSFRequest request = new HSSFRequest();
		request.addListenerForAllRecords(formatListener);
		// 处理基于时间文档流(循环获取每一条Record进行处理)
		factory.processWorkbookEvents(request, fs);
	}

	//记录处理
	@Override
	public void processRecord(Record record) {
		/********** Begin *********/
		int thisColumn = -1;
        switch (record.getSid()) {
            //所有sheet文本单元格的文本内容
            case SSTRecord.sid:
                sstRecord = (SSTRecord) record;
                break;
            //单元格为字符串类型
            case LabelSSTRecord.sid:
                LabelSSTRecord labelSSTRecord = (LabelSSTRecord) record;
                thisColumn = labelSSTRecord.getColumn();
                if (labelSSTRecord != null) {
                    String value = sstRecord.getString(labelSSTRecord.getSSTIndex()).toString().trim();
                    value = value.equals("") ? " " : value;
                    rowlist.add(thisColumn, value);
                } else {
                    rowlist.add(thisColumn, " ");
                }
                break;
            default:
                break;
        }
        if (record.getSid() == -1) {
            System.out.println(rowlist);
            rowlist.clear();
        }
        /********** end *********/
	}
	
	
}

第3关:创建警务大数据表信息

上一关已经介绍了POI事件模式的使用,本关任务是通过POI事件模式来创建警务大数据的相关表。

package com.educodeer.police1.case1;

import com.educodeer.police1.util.Excel2003Reader;
import com.educodeer.police1.util.IRowReader;

import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @Description Case3
 * @DateTime 2023/4/18 10:26
 * 创建HBase表
 */
public class Case3 {
    static StringBuffer buffer = new StringBuffer();

    /**
     * 获取excel的表信息,来创建HBase表
     *
     * @param connection HBase连接
     * @return
     * @throws IOException
     */
    public List<String> createHBaseTable(Connection connection) throws IOException, SQLException {
        /********** Begin *********/
        List<String> tableNames = new ArrayList<>();
        List<String> stringList = new ArrayList<>();
        List<String> primaryKeys = Arrays.asList(new String[]{"DJRSFZH", "ID", "ID", "GMSFHM", "ZJHM", "RZSJ,ZJHM", "RZSJ,ZJHM"});
        Excel2003Reader reader = new Excel2003Reader(new IRowReader() {
            boolean isStart = false;
            boolean flag = false;
            String startName = "名称";
            String startName1 = "数据类型";
            String endName = "表名:";

            @Override
            public void getRows(int sheetIndex, int curRow, List<String> rowlist) {
                if (endName.equals(rowlist.get(0))) {
                    if (flag) {
                        stringList.add(buffer.toString());
                        buffer = new StringBuffer();
                    } else flag = true;
                    tableNames.add(rowlist.get(1));
                    isStart = false;
                } else if (rowlist.get(0).equals(startName) && rowlist.get(1).equals(startName1)) {
                    isStart = true;
                } else if (isStart) {
                    buffer.append(rowlist.get(0)).append(" VARCHAR,");
                }
            }
        });
        reader.process("table1.xls");
        stringList.add(buffer.toString());
        for (int i = 0; i < 7; i++) {
            String str = "CREATE TABLE " + tableNames.get(i) + "(" + stringList.get(i) + "CONSTRAINT pk PRIMARY KEY ("
                    + primaryKeys.get(i) + ") )DATA_BLOCK_ENCODING='DIFF',VERSIONS=3,BLOCKSIZE='32000',MAX_FILESIZE=10000000";
            connection.createStatement().execute(str);
        }
        return tableNames;
        /********** End *********/
    }

}

1-2 警务大数据之交换配置

第1关:通过JDBC存储字段信息

当外接数据和HBase本身字段名不匹配,则需要配置映射关系,来让程序知道如何进行转换,这就是自动交换。在进行自动交换之前我们需要知道外接数据的字段信息,并把外接数据的字段信息进行存储。前面我们通过读取 Excel获取过HBase表的字段信息,接下来我们要通过JDBC的方式获取表字段信息。
本关任务:编写一个程序,通过JDBC的方式获取表字段信息,并存储到 HBase里。

package com.educodeer.police3.case1;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;

public class Case1 {

    /**
     * 初始化数据
     *
     * @param hbaseConnection HBase连接
     * @param mysqlConnection mysql连接
     * @param sqlTableName    sql表名
     * @param hbaseTableName  HBase表名
     * @throws SQLException
     */
    public void init(Connection hbaseConnection, Connection mysqlConnection, String sqlTableName, String hbaseTableName)
            throws SQLException {
        /********** Begin *********/
        ResultSet hbaseSet = hbaseConnection.createStatement().executeQuery("select * from " + hbaseTableName + " limit 1");
        ResultSetMetaData hbaseSetMetaData = hbaseSet.getMetaData();
        ResultSet sqlSet = mysqlConnection.createStatement().executeQuery("select * from " + sqlTableName + " limit 1");
        ResultSetMetaData sqlSetMetaData = sqlSet.getMetaData();
        for (int i = 1; i <= hbaseSetMetaData.getColumnCount(); i++) {
            String str = "UPSERT INTO table_fields(id,name,type,table_name,table_type)" +
                    "VALUES (NEXT VALUE FOR table_fields_sequence," +
                    "'" + hbaseSetMetaData.getColumnName(i) + "'," +
                    "'" + hbaseSetMetaData.getColumnTypeName(i) + "'," +
                    "'" + hbaseSetMetaData.getTableName(i) + "'," +
                    "'1')";
            hbaseConnection.createStatement().execute(str);
        }
        for (int i = 1; i <= sqlSetMetaData.getColumnCount(); i++) {
            String str = "UPSERT INTO table_fields(id,name,type,table_name,table_type)" +
                    "VALUES (NEXT VALUE FOR table_fields_sequence," +
                    "'" + sqlSetMetaData.getColumnName(i) + "'," +
                    "'" + sqlSetMetaData.getColumnTypeName(i) + "'," +
                    "'" + sqlSetMetaData.getTableName(i) + "'," +
                    "'2')";
            hbaseConnection.createStatement().execute(str);
        }
        /********** end *********/
    }

}

第2关:表关系信息存储

当外接数据和HBase本身字段名不匹配,则需要配置映射关系,来让程序知道如何进行转换,这就是自动交换。上一关我们已经学习了通过JDBC的方式获取表字段信息,本关来编写一个存储映射关系的接口,来说明HBase 表和外部数据名称的对应关系。

package com.educodeer.police3.case1;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

public class Case2 {

    /**
     * 保存关联关系
     *
     * @param connection HBase 连接
     * @param map        外接数据名称 和 HBase字段名称映射关系
     * @throws SQLException
     */
    public void saveRelationData(Connection connection, Map<String, String> map) throws SQLException {
        /********** Begin *********/
        ResultSet resultSet = connection.createStatement().executeQuery("select * from table_fields");
        Map<String, Info> sqlMap = new HashMap<>();
        Map<String, Info> hbaseMap = new HashMap<>();
        while (resultSet.next()) {
            Integer id = resultSet.getInt(1);
            String name = resultSet.getString(2);
            String type = resultSet.getString(3);
            String table_name = resultSet.getString(4);
            String table_type = resultSet.getString(5);
            Info info = new Info(id, name, type, table_name, table_type);
            if (table_type.equals("1")){
                hbaseMap.put(name, info);
            }
            else sqlMap.put(name, info);
        }
        for (String key : map.keySet()) {
            String value = map.get(key);
            Info sqlInfo = sqlMap.get(key);
            Info hbaseInfo = hbaseMap.get(value);
            connection.createStatement().execute("UPSERT INTO relate_fields values(" +
                    hbaseInfo.id + "," +
                    sqlInfo.id + "," +
                    "'" + sqlInfo.table_name + "'," +
                    "'" + hbaseInfo.table_name + "')"
            );
        }
        /********** end *********/
    }

    class Info {
        Integer id;
        String name;
        String type;
        String table_name;
        String table_type;

        public Info() {

        }

        public Info(Integer id, String name, String type, String table_name, String table_type) {
            this.id = id;
            this.name = name;
            this.type = type;
            this.table_name = table_name;
            this.table_type = table_type;
        }
    }
}

第3关:自动交换应用

当外接数据和HBase本身字段名不匹配,则需要配置映射关系,来让程序知道如何进行转换,这就是自动交换。上一关我们已经存储了映射关系,本关我们来编写一个自动交换应用的小程序。

package com.educodeer.police3.case1;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

public class Case3 {

    /**
     * @param hbaseConnection HBaseConnection连接
     * @param sqlTableName    外接表表名
     * @param data            数据
     * @throws SQLException
     */
    public void dataStore(Connection hbaseConnection, String sqlTableName, Map<String, String> data) throws SQLException {
        /********** Begin *********/
        //sql列名——>sqlID——>hbaseID>——>hbase列名——>类型转换
        ResultSet tableFieldsResultSet = hbaseConnection.createStatement().executeQuery("select * from table_fields");
        Map<Integer, TableFields> hbaseMap = new HashMap<>();
        Map<String, TableFields> sqlMap = new HashMap<>();
        while (tableFieldsResultSet.next()) {
            TableFields tableFields = new TableFields();
            tableFields.id = tableFieldsResultSet.getInt(1);
            tableFields.name = tableFieldsResultSet.getString(2);
            tableFields.type = tableFieldsResultSet.getString(3);
            tableFields.table_name = tableFieldsResultSet.getString(4);
            tableFields.table_type = tableFieldsResultSet.getString(5);
            if (tableFields.table_type.equals("1")) {
                hbaseMap.put(tableFields.id, tableFields);
            } else sqlMap.put(tableFields.name, tableFields);
        }
        ResultSet relateFieldsResultSet = hbaseConnection.createStatement().executeQuery("select * from relate_fields");
        Map<Integer, RelateFields> relateFieldsMap = new HashMap<>();
        while (relateFieldsResultSet.next()) {
            RelateFields relateFields = new RelateFields();
            relateFields.hbaseID = relateFieldsResultSet.getInt(1);
            relateFields.sqlID = relateFieldsResultSet.getInt(2);
            relateFields.sqlTableName = relateFieldsResultSet.getString(3);
            relateFields.hbaseTableName = relateFieldsResultSet.getString(4);
            relateFieldsMap.put(relateFields.sqlID, relateFields);
        }
        Salary salary = new Salary();
        for (String key : data.keySet()) {
            TableFields sqlTableFields = sqlMap.get(key);
            RelateFields relateFields = relateFieldsMap.get(sqlTableFields.id);
            TableFields hbaseTableFields = hbaseMap.get(relateFields.hbaseID);
            switch (hbaseTableFields.name) {
                case "ID":
                    salary.id = Integer.parseInt(data.get(key));
                    break;
                case "NAME":
                    salary.name = data.get(key);
                    break;
                case "AGE":
                    salary.age = Integer.parseInt(data.get(key));
                    break;
                case "CITY":
                    salary.city = data.get(key);
                    break;
                case "SALARY":
                    salary.salary = Double.parseDouble(data.get(key));
                    break;
            }
        }
        hbaseConnection.createStatement().execute(salary.generateStr());
        /********** end *********/
    }

    class RelateFields {
        Integer hbaseID;
        Integer sqlID;
        String sqlTableName;
        String hbaseTableName;

        public RelateFields() {

        }

        public RelateFields(Integer hbaseID, Integer sqlID, String sqlTableName, String hbaseTableName) {
            this.hbaseID = hbaseID;
            this.sqlID = sqlID;
            this.sqlTableName = sqlTableName;
            this.hbaseTableName = hbaseTableName;
        }
    }

    class TableFields {
        Integer id;
        String name;
        String type;
        String table_name;
        String table_type;

        public TableFields() {
        }

        public TableFields(Integer id, String name, String type, String table_name, String table_type) {
            this.id = id;
            this.name = name;
            this.type = type;
            this.table_name = table_name;
            this.table_type = table_type;
        }
    }

    class Salary {
        Integer id;
        String name;
        Integer age;
        String city;
        Double salary;

        public Salary() {

        }

        public Salary(Integer id, String name, Integer age, String city, Double salary) {
            this.id = id;
            this.name = name;
            this.age = age;
            this.city = city;
            this.salary = salary;
        }

        public String generateStr() {
            return "UPSERT INTO salary values(" + id + ",'" + name + "'," + age + ",'" + city + "'," + salary + ")";
        }
    }

}

1-3 警务大数据之离线存储

第1关:Excel数据存储HBase

Excel是个人计算机数据处理软件,可以存储大量的数据,当警务大量数据存储在Excel中如何读取呢?
本关任务:读取Excel的警务数据,通过Phoenix插件,采用JDBC方式将其存入HBase。

package com.educoder.policeCalculate;

import com.educoder.policeCalculate.util.Excel2003Reader;
import com.educoder.policeCalculate.util.IRowReader;

import java.io.IOException;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class Cs {
    // 请在此处编写代码,读取Excel表中数据,并通过Phoenix插件将其存入HBase
    /********** Begin **********/
    private static Connection connection = null;
    private static List<String> hbaseCol = new ArrayList<String>();
    private static List<String> col = new ArrayList<String>();
    private static List<Integer> index = new ArrayList<Integer>();
    private static List<List> values = new ArrayList<List>();

    static {
        try {
            //连接Phoenxi的服务驱动
            Class.forName("org.apache.phoenix.queryserver.client.Driver");
            connection = DriverManager.getConnection("jdbc:phoenix:thin:url=http://127.0.0.1:8765;serialization=PROTOBUF");
            //获取HBase表T_BKYJ_N_JBXX的所有列
            ResultSet resultSet = connection.createStatement().executeQuery("select * from T_BKYJ_N_JBXX limit 1");
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++)
                hbaseCol.add(metaData.getColumnName(i));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException, SQLException {
        Excel2003Reader excel2003Reader = new Excel2003Reader(new IRowReader() {
            public void getRows(int sheetIndex, int curRow, List<String> rowlist) {
                if (curRow == 0) {
                    for (String str : rowlist)
                        col.add(str.toUpperCase());
                    col.retainAll(hbaseCol);
                    for (int i = 0; i < col.size(); i++)
                        index.add(rowlist.indexOf(col.get(i).toLowerCase()));
                } else {
                    List<String> value = new ArrayList<String>();
                    for (int i = 0; i < index.size(); i++)
                        value.add(rowlist.get(index.get(i)));
                    values.add(value);
                }
            }
        });
        excel2003Reader.process("/root/files/t_bkyj_n_jbxx.xls");
        upsertHBase();
    }

    private static void upsertHBase() {
        StringBuffer buffer = new StringBuffer("UPSERT INTO T_BKYJ_N_JBXX(");
        for (String str : col)
            buffer.append(str).append(",");
        buffer.deleteCharAt(buffer.length() - 1).append(") VALUES(");
        for (String str : col)
            buffer.append("?,");
        buffer.deleteCharAt(buffer.length() - 1).append(")");
        String sql = buffer.toString();
        PreparedStatement preparedStatement = null;
        try {
            preparedStatement = connection.prepareStatement(sql);
            for (List value : values) {
                for (int i = 0; i < value.size(); i++) {
                    preparedStatement.setString(i + 1, value.get(i).toString());
                }
                preparedStatement.execute();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                preparedStatement.close();
                connection.commit();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
    /********** End **********/
}

第2关:文本文件数据存储HBase

CSV是一种通用的、相对简单的文件格式,被用户、商业和科学广泛应用,当警务大量数据存储在CSV中如何读取呢?
本关任务:编写一个Spark程序,读取csv数据,通过Phoenix插件将其存入HBase。

package com.educoder.policeCalculate;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SaveMode;
import org.apache.spark.sql.SparkSession;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CsvToHbase {

    public static void Csv(SparkSession spark) {
        Logger.getLogger("org").setLevel(Level.ERROR);
        // 请在此处编写Spark程序,读取csv文件的数据,并通过Phoenix插件将其存入HBase
        /********** Begin **********/
        //获取hbase表T_BKYJ_N_JBXX的所有列
        List<String> hbaseCol = Arrays.asList(spark.read()
                .option("table", "T_BKYJ_N_JBXX")
                .option("zkUrl", "127.0.0.1:2181")
                .format("org.apache.phoenix.spark")
                .load()
                .columns());
        //获取csv文件所有数据并摘出列名
        Dataset<Row> rowDataset = spark.read()
                .option("header", true)
                .option("encoding", "utf-8")
                .csv("/root/files1/a.csv");
        List<String> csvCol = Arrays.asList(rowDataset.columns());
        //转化为大写并找出共同的列
        List<String> col = new ArrayList<String>();
        for (String str : csvCol)
            col.add(str.toUpperCase());
        col.retainAll(hbaseCol);
        //将csv的数据做成临时视图csvData并写入hbase
        rowDataset.createOrReplaceTempView("csvData");
        spark.sql("select " + col.toString().substring(1, col.toString().length() - 1) + " from csvData")
                .write()
                .mode(SaveMode.Overwrite)
                .format("org.apache.phoenix.spark")
                .option("zkUrl", "127.0.0.1:2181")
                .option("table", "T_BKYJ_N_JBXX")
                .save();
        /********** End **********/
    }

}

1-4 警务大数据之实时存储

第一关:Kafka实时存储HBase

Kafka是由Apache软件基金会开发的一个开源流处理平台,当实时数据存储到 Kafka时,如何进行读取并存储呢?
本关任务:编写一个读取 Kafka 中的名为 test3 的 topic,通过Phoenix插件将其存入HBase的SparkStreaming程序。

package com.educoder.policeCalculate;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.spark.Accumulator;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.VoidFunction;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.JavaDStream;
import org.apache.spark.streaming.api.java.JavaInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import org.apache.spark.streaming.kafka010.ConsumerStrategies;
import org.apache.spark.streaming.kafka010.KafkaUtils;
import org.apache.spark.streaming.kafka010.LocationStrategies;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.*;

public class KafkaToHBase {
    public static void main(String[] args) throws InterruptedException {
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("Step2");
        conf.set("spark.streaming.stopGracefullyOnShutdown", "true");
        JavaSparkContext sc = new JavaSparkContext(conf);
        /********* Begin *********/
        //kafka参数
        Map<String, Object> kafkaParams = new HashMap<String, Object>();
        kafkaParams.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092");
        kafkaParams.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        kafkaParams.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        kafkaParams.put(ConsumerConfig.GROUP_ID_CONFIG, "sparkStreaming1");
        kafkaParams.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
        //Topic以及分区
        TopicPartition topicPartition = new TopicPartition("test3", 0);
        List<TopicPartition> topicPartitionList = new ArrayList<>();
        topicPartitionList.add(topicPartition);
        //偏移量
        Map<TopicPartition, Long> offsets = new HashMap<TopicPartition, Long>();
        offsets.put(topicPartition, 0l);
        //初始化JavaStreamingContext并设置3秒处理一个批次
        JavaStreamingContext javaStreamingContext = new JavaStreamingContext(sc, Durations.seconds(3));
        //使用KafkaUtils对象创建流,使用Assign订阅主题
        JavaInputDStream<ConsumerRecord<String, String>> message = KafkaUtils.createDirectStream(
                javaStreamingContext,
                LocationStrategies.PreferConsistent(),
                ConsumerStrategies.Assign(topicPartitionList, kafkaParams, offsets));
        JavaDStream<String> line = message.map(new Function<ConsumerRecord<String, String>, String>() {
            @Override
            public String call(ConsumerRecord<String, String> stringStringConsumerRecord) throws Exception {
                return stringStringConsumerRecord.value();
            }
        });
        //对数据进行处理
        List<List<String>> values = new ArrayList<List<String>>();
        JavaDStream<List<List<String>>> map = line.map(new Function<String, List<List<String>>>() {
            @Override
            public List<List<String>> call(String s) throws Exception {
                List<String> value = new ArrayList<String>();
                String[] split = s.split(",");
                value.add(split[0]);//id
                value.add(split[1]);//bkid
                value.add(split[2]);//tzlx
                value.add(split[3]);//dxlx
                value.add(split[4]);//tzxx
                value.add(split[5]);//lylx
                value.add(split[6]);//lyb
                value.add(split[7]);//lybid
                value.add(split[8]);//gxsj
                value.add(split[9]);//gldx
                value.add(split[10]);//px
                value.add(split[11]);//sfbd
                values.add(value);
                return values;
            }
        });
        //判断rdd是否累计3次为空
        Accumulator<Integer> a = sc.accumulator(0, "Error Accumulator");
        map.foreachRDD(rdd -> {
            if (rdd.isEmpty()) {
                if (a.value() == 2) {
                    javaStreamingContext.stop(false, false);
                    System.exit(0);
                } else {
                    a.add(1);
                }
            } else {
                rdd.foreachPartition(new VoidFunction<Iterator<List<List<String>>>>() {
                    @Override
                    public void call(Iterator<List<List<String>>> listIterator) throws Exception {
                        while (listIterator.hasNext()) {
                            upsertTable(listIterator.next());
                        }
                    }
                });
            }
        });
        javaStreamingContext.start();
        javaStreamingContext.awaitTermination();
        /********* End *********/
    }

    public static void upsertTable(List<List<String>> values) {
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            connection = DriverManager.getConnection("jdbc:phoenix:127.0.0.1:2181");
            connection.setAutoCommit(false);
            String sql = "UPSERT INTO T_BKYJ_N_TZ(ID,BKID,TZLX,DXLX,TZXX,LYLX,LYB,LYBID,GXSJ,GLDX,PX,SFBD) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)";
            ps = connection.prepareStatement(sql);
            for (List<String> value : values) {
                for (int i = 0; i < value.size(); i++)
                    ps.setString(i + 1, value.get(i));
                ps.addBatch();
            }
            ps.executeBatch();
            ps.clearBatch();
            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

第二关:从HTTP读取数据存入HBase

上一关卡,我们学习到了如何实时读取Kafka的警务数据,那使用HTTP协议的警务数据如何读取呢?
本关任务:采用MyBatis框架,实时读取HTTP数据,用注解的方式,通过Phoenix插件将其存入HBase。

package com.educoder.policeBd.mapper;

import com.educoder.policeBd.bean.TBkyjNTz;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface TBkyjNTzMapper {
    /********** Begin **********/
    //插入HBase
    @Insert("UPSERT INTO T_BKYJ_N_TZ(ID,BKID,TZLX,DXLX,TZXX,LYLX,LYB,LYBID,GXSJ,GLDX,PX,SFBD) VALUES(#{tBkyjNTz.ID},#{tBkyjNTz.BKID},#{tBkyjNTz.TZLX},#{tBkyjNTz.DXLX},#{tBkyjNTz.TZXX},#{tBkyjNTz.LYLX},#{tBkyjNTz.LYB},#{tBkyjNTz.LYBID},#{tBkyjNTz.GXSJ},#{tBkyjNTz.GLDX},#{tBkyjNTz.PX},#{tBkyjNTz.SFBD})")
    void insert(@Param("tBkyjNTz") TBkyjNTz tBkyjNTz);
    /********** End **********/

}
package com.educoder.policeBd.cotroller;

import com.educoder.policeBd.bean.TBkyjNTz;
import com.educoder.policeBd.mapper.TBkyjNTzMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("TBkyjNTz")
public class TBkyjNTzCotroller {
    @Autowired
    private TBkyjNTzMapper tBkyjNTzMapper;
    @RequestMapping("insert")
    //调用mapper层方法将其插入HBase
    public void upsertHbase(List<List<String>> lists) {
        /********** Begin **********/
        for(List<String> list:lists){
            TBkyjNTz tBkyjNTz=new TBkyjNTz();
            tBkyjNTz.setID(list.get(0));
            tBkyjNTz.setBKID(list.get(1));
            tBkyjNTz.setTZLX(list.get(2));
            tBkyjNTz.setDXLX(list.get(3));
            tBkyjNTz.setTZXX(list.get(4));
            tBkyjNTz.setLYLX(list.get(5));
            tBkyjNTz.setLYB(list.get(6));
            tBkyjNTz.setLYBID(list.get(7));
            tBkyjNTz.setGXSJ(list.get(8));
            tBkyjNTz.setGLDX(list.get(9));
            tBkyjNTz.setPX(list.get(10));
            tBkyjNTz.setSFBD(list.get(11));
            tBkyjNTzMapper.insert(tBkyjNTz);
        }
        /********** End **********/
    }
}

1-5警务大数据之关系数据库存储

第一关:MySQL支持

当外界数据为关系型数据库时,我们首先需要把关系型数据库表自动转换成 HBase表。本关任务:把MySQL表转换成HBase表。

package com.educodeer.police2.case1;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class Case1 {

    /**
     * 把mysql表转换成HBase表
     *
     * @param tableName       Mysql表名
     * @param hbaseConnection HBase连接
     * @param mysqlConnection Mysql连接
     * @throws SQLException
     */
    public void mysqlTableCovent(String tableName, Connection hbaseConnection, Connection mysqlConnection) throws SQLException {
        // 请在此处添加代码
        /********** Begin *********/
        ResultSet resultSet = mysqlConnection.createStatement().executeQuery("show full fields from " + tableName);
        List<String> colNames = new ArrayList<String>();
        List<String> colTypes=new ArrayList<String>();
        while (resultSet.next()) {
            String colName = resultSet.getString("Field");
            String colType = resultSet.getString("Type");
            if (colType.contains("int"))
                colType = "INTEGER";
            else colType = "VARCHAR";
            colNames.add(colName);
            colTypes.add(colType);
        }
        StringBuffer sqlBuffer = new StringBuffer("CREATE TABLE ").append(tableName).append("(");
        for (int i = 0; i < colNames.size(); i++) {
            String colName = colNames.get(i);
            String colType = colTypes.get(i);
            if (i == 0)
                sqlBuffer.append(colName).append(" ").append(colType).append(" primary key,");
            else
                sqlBuffer.append(colName).append(" ").append(colType).append(",");
        }
        sqlBuffer.deleteCharAt(sqlBuffer.length() - 1).append(")");
        String sql = sqlBuffer.toString();
        hbaseConnection.createStatement().execute(sql);
        /********** end *********/
    }

}

第二关:MySQL离线存储

当MySQL大量数据需要存储到HBase,如何保证高效稳定的进行传输呢,答案是使用MapReduce,本关任务:编写一个MapReduce把MySQL的内容存储到HBase。

package com.educodeer.police2.case1;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.db.DBConfiguration;
import org.apache.hadoop.mapreduce.lib.db.DBInputFormat;
import org.apache.hadoop.mapreduce.lib.db.DBWritable;
import org.apache.hadoop.mapreduce.lib.output.NullOutputFormat;
import org.apache.hadoop.util.Tool;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.sql.*;

/**
 * * 离线存储mysql数据
 **/
public class Case2 extends Configured implements Tool {
    private static class MyMapper extends Mapper<Object, SqlRecord, NullWritable, NullWritable> {
        @Override
        public void map(Object object, SqlRecord value, Context context) {
            // 请在此处添加代码
            /********** Begin *********/
            String sql = "UPSERT INTO " + value.tableName + "(" + value.columnName.toString() + ") VALUES(" + value.value.toString() + ")";
            Connection connection = null;
            try {
                connection = DriverManager.getConnection("jdbc:phoenix:127.0.0.1:2181");
                connection.createStatement().execute(sql);
                connection.commit();
            } catch (SQLException e) {

            } finally {
                if (connection != null) {
                    try {
                        connection.close();
                    } catch (SQLException e) {

                    }
                }
            }
            /********** end *********/
        }
    }


    /**
     * 在调用时,为单例模式,需考虑赋值方式
     */
    public static class SqlRecord implements Writable, DBWritable {
        //对结果值进行组装,例如 1,2,3,4
        private StringBuffer value = null;
        //对结果名称进行组装,例如id,'name',age,'content'
        private StringBuffer columnName = null;
        private String tableName = null;

        public StringBuffer getValue() {
            return value;
        }

        public StringBuffer getColumnName() {
            return columnName;
        }

        public String getTableName() {
            return tableName;
        }

        @Override
        public void write(PreparedStatement statement) throws SQLException {
        }

        /**
         * * 从 ResultSet 对象中读取表信息
         **/
        @Override
        public void readFields(ResultSet resultSet) throws SQLException {
            // 请在此处添加代码
            /********** Begin *********/
            columnName = new StringBuffer();
            value = new StringBuffer();
            ResultSetMetaData metaData = resultSet.getMetaData();
            tableName = metaData.getTableName(1);
            int count = metaData.getColumnCount();
            for (int i = 0; i < count; i++) {
                columnName.append(metaData.getColumnName(i + 1)).append(",");
                String colType = metaData.getColumnTypeName(i + 1);
                if (colType.equalsIgnoreCase("VARCHAR"))
                    value.append("'").append(resultSet.getString(i + 1)).append("',");
                else value.append(resultSet.getString(i + 1)).append(",");
            }
            columnName.deleteCharAt(columnName.length() - 1);
            value.deleteCharAt(value.length() - 1);
            /********** end *********/
        }

        @Override
        public void write(DataOutput out) throws IOException {
        }

        @Override
        public void readFields(DataInput in) throws IOException {
        }


    }


    @Override
    public int run(String[] args) throws Exception {
        //配置Job
        String tableName = args[0];
        Configuration conf = HBaseConfiguration.create(getConf());
        //配置mysql信息
        conf.set(DBConfiguration.DRIVER_CLASS_PROPERTY, "com.mysql.jdbc.Driver");
        conf.set(DBConfiguration.URL_PROPERTY, "jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true");
        conf.set(DBConfiguration.USERNAME_PROPERTY, "root");
        conf.set(DBConfiguration.PASSWORD_PROPERTY, "123123");
        Job job = Job.getInstance(conf);
        //不需要reduce 设置为0线程
        job.setNumReduceTasks(0);
        //设置mapper节点的执行类
        job.setMapperClass(MyMapper.class);
        //通过查找给定类的来源来设置Jar
        job.setJarByClass(Case2.class);
        //设置输出到null文件
        job.setOutputFormatClass(NullOutputFormat.class);
        //SqlRecord 为mysql输出内容接受类,
        //select * from " + tableName 为 输出内容
        //select count(1) from " + tableName 为输出总数
        DBInputFormat.setInput(job, SqlRecord.class, "select * from " + tableName, "select count(1) from " + tableName);
        return job.waitForCompletion(true) ? 0 : 1;
    }

}

专题二 大数据批处理

2-1 警务大数据之上网相关指标批处理

第一关:频繁更换上网场所

本关卡属于警务大数据中的一个业务指标统计,从网吧的上网人员日志记录中进行批量计算,对上网地点异常的人员进行统计,对有犯罪动机的人员进行监控、预警。
本关任务:统计出触及频繁更换上网场所指标的人员。

package com.yy;

import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SaveMode;
import org.apache.spark.sql.SparkSession;

public class Police {

    public static void main(String[] args) {
        //请在以下提示框内填写你的代码
        /**********Begin**********/
        //zookeeper地址
        String zk = "127.0.0.1:2181";
        //查询sql
        String sql = "SELECT ZJHM AS zjhm, SUBSTR(RZSJ,1,8) AS theday, COUNT(*) AS num " +
                "FROM temp GROUP BY SUBSTR(RZSJ,1,8), ZJHM having COUNT(*)>10";
        //输出结果表表名
        String outputTable = "pfghswd";
        //数据源表名
        String sourceTable = "dw.ZNB_04_121800005";
        //创建SparkSession
        SparkSession sparkSession = SparkSession.builder().appName("police").master("local").getOrCreate();
        Dataset<Row> dwDataset = sparkSession.read()
                .format("org.apache.phoenix.spark")
                .option("zkUrl", zk)
                .option("table", sourceTable)
                .load();
        dwDataset.createOrReplaceTempView("temp");
        //执行查询
        Dataset<Row> sqlDataSet = sparkSession.sql(sql);
        //查询结果导入HBase
        sqlDataSet
                .write()
                .mode(SaveMode.Overwrite)
                .format("org.apache.phoenix.spark")
                .option("zkUrl", zk)
                .option("table", outputTable)
                .save();
        //停止
        sparkSession.stop();
        /**********End**********/

    }
}

第二关:多次同上网

本关卡属于警务大数据中的一个业务指标统计,从网吧的上网人员日志记录中进行批量计算,对上网时间异常的人员进行统计,对有犯罪动机的人员进行监控、预警。
本关任务:统计出触及多次同上网指标的人员。
指标定义:一个月内一同上网超过十次,两个人同一天在同一家网吧上过网即作为一次。

package com.yy;

import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SaveMode;
import org.apache.spark.sql.SparkSession;

public class Police {

    public static void main(String[] args) {
        //请在以下提示框内填写你的代码
        /**********Begin**********/
        //zookeeper地址
        String zk = "127.0.0.1:2181";
        //查询sql
        String sql = "SELECT zjhm1, zjhm2, COUNT(*) AS swcs " +
                "FROM " +
                "(SELECT temp1.ZJHM AS zjhm1, temp2.ZJHM AS zjhm2 " +
                "FROM " +
                "(SELECT CONCAT(SUBSTR(RZSJ, 1, 8), LGMC) AS key1, ZJHM " +
                "FROM temp " +
                "GROUP BY CONCAT(SUBSTR(RZSJ, 1, 8), LGMC), ZJHM) temp1 " +
                "JOIN " +
                "(SELECT CONCAT(SUBSTR(RZSJ, 1, 8), LGMC) AS key2, ZJHM " +
                "FROM temp " +
                "GROUP BY CONCAT(SUBSTR(RZSJ, 1, 8), LGMC), ZJHM) temp2  " +
                "ON temp1.key1 = temp2.key2 " +
                "WHERE temp1.ZJHM != temp2.ZJHM) " +
                "GROUP BY zjhm1, zjhm2 " +
                "HAVING cast(zjhm1 AS STRING) > cast(zjhm2 AS STRING) AND COUNT(*)>20";
        //输出结果表表名
        String outputTable = "dctsw";
        //数据源表名
        String sourceTable = "dw.ZNB_04_121800005";
        //创建SparkSession,master模式设为local
        SparkSession sparkSession = SparkSession.builder().appName("police").master("local").getOrCreate();
        //读取数据,创建临时表
        Dataset<Row> sourceData = sparkSession.read()
                .format("org.apache.phoenix.spark")
                .option("zkUrl", zk)
                .option("table", sourceTable)
                .load();
        sourceData.createOrReplaceTempView("temp");
        //执行查询
        Dataset<Row> outputData = sparkSession.sql(sql);
        //查询结果导入HBase
        outputData
                .write()
                .mode(SaveMode.Overwrite)
                .format("org.apache.phoenix.spark")
                .option("zkUrl", zk)
                .option("table", outputTable)
                .save();
        //停止
        sparkSession.stop();
        /**********End**********/

    }
}

专题三 实时流计算

3-1 警务大数据之大屏幕实时流数据监控

第一关:车辆相关信息实时计算

本关卡属于警务大数据中的实时计算模块,对车辆相关信息进行实时计算,实时计算出车辆在线数、车辆活跃数、致命故障数量、严重故障数量、一般故障数量、轻微故障数量、房车报警数量、旅行车报警数量、桥跑车报警数量、跑车报警数量、敞篷车报警数量等,为下一关的实时数据可视化提供数据基础。
本关任务:编写一个 SparkStructuredStreaming 程序完成车辆相关信息实时统计。

package net.educoder.spark

import org.apache.spark.sql.SparkSession

object KafkaSparkStreaming {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder().master("local").appName("demo").getOrCreate()
    spark.sparkContext.setLogLevel("error")
    /** ******begin ******** */
    val stream = spark
      .readStream
      .format("kafka")
      .option("kafka.bootstrap.servers", "127.0.0.1:9092")
      .option("subscribe", "demo")
      .load()
    val frame = stream.selectExpr("CAST(value AS STRING)")
    import spark.implicits._
    val query = frame.as[String].map(x => {
      val arr = x.split("\t")
      val carType = arr(1).toInt
      val warning = arr(3).toInt
      val fault = arr(4).toInt
      val speed = arr(5).toInt

      val online = 1
      val activeCount = if (speed > 0) 1 else 0
      val fault_4 = if (fault == 4) 1 else 0
      val fault_3 = if (fault == 3) 1 else 0
      val fault_2 = if (fault == 2) 1 else 0
      val fault_1 = if (fault == 1) 1 else 0
      val warning_5 = if (warning == 1 || carType == 5) 1 else 0
      val warning_4 = if (warning == 1 || carType == 4) 1 else 0
      val warning_3 = if (warning == 1 || carType == 3) 1 else 0
      val warning_2 = if (warning == 1 || carType == 2) 1 else 0
      val warning_1 = if (warning == 1 || carType == 1) 1 else 0
      event(online, activeCount, fault_4, fault_3, fault_2, fault_1, warning_5, warning_4, warning_3, warning_2, warning_1, 0)
    })
    query
      .groupBy("flag")
      .sum("onlineCount", "activeCount", "fault_4", "fault_3", "fault_2", "fault_1", "warning_4", "warning_4", "warning_3", "warning_2", "warning_1")
      .map(x => {
        var result = ""
        for (i <- 1 to 11) {
          result = result + x.get(i) + ","
        }
        result.substring(0, result.length - 1)
      })
      .writeStream
      .outputMode("complete")
      .format("kafka")
      .option("kafka.bootstrap.servers", "127.0.0.1:9092")
      .option("checkpointLocation", "/root/sparkStreaming")
      .option("topic", "demo2")
      .start().awaitTermination()
    /** ******end ******** */
  }

  //在线数、活跃数、致命故障数量、严重故障数量、一般故障数量、轻微故障数量、房车报警数量、旅行车报警数量、桥跑车报警数量、跑车报警数量、敞篷车报警数量
  case class event(onlineCount: Int, activeCount: Int, fault_4: Int, fault_3: Int, fault_2: Int, fault_1: Int, warning_5: Int, warning_4: Int, warning_3: Int, warning_2: Int, warning_1: Int, flag: Int)
}

第二关:实时数据可视化

本关卡属于警务大数据中的实时可视化模块,在上一关的数据基础上进行实时数据展示,让车辆监控人员更容易地制定出监控策略、更可靠地保障交通安全、发挥交通基础设施效能、提升交通系统运行效率和管理水平,为通畅的公众出行和可持续的经济发展服务。
本关任务:使用WebSocket完成Kafka数据推送及数据可视化

  1. WebSocketServer
package net.educoder.app.service;

import net.educoder.app.utils.kafkaClient;
import org.springframework.stereotype.Component;

import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;

@ServerEndpoint("/websocket")
@Component
public class WebSocketServer {

    /**
     * 需求如下:
     * 客户端与Websocket服务端建立连接之后,启动 kafkaClient 线程,将当前 session 作为参数传入
     */


    /********** begin **********/
    @OnOpen
    public void onOpen(Session session){
        new Thread(new kafkaClient(session)).start();
    }
    /********** end **********/
}
  1. kafkaClient
package net.educoder.app.utils;

import com.alibaba.fastjson.JSON;
import net.educoder.app.entity.Event;
import net.educoder.app.service.WebSocketServer;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

import javax.websocket.Session;
import java.io.IOException;
import java.util.Arrays;
import java.util.Properties;

public class kafkaClient implements Runnable {

    private Session session;

    public kafkaClient(Session session) {
        this.session = session;
    }

    /********** begin **********/
 @Override  
        public void run() {
            // 1. 创建 Properties 对象  
            Properties props = new Properties();  
            // 2. 配置连接 kafka 的参数  
            /**  
             *      bootstrap.servers:127.0.0.1:9092  
             *      group.id:my_group  
             *      enable.auto.commit:true  
             *      auto.commit.interval.ms:1000  
             *      key.deserializer:org.apache.kafka.common.serialization.StringDeserializer  
             *      value.deserializer:org.apache.kafka.common.serialization.StringDeserializer  
             */  
            props.put("bootstrap.servers", "127.0.0.1:9092");  
            props.put("group.id", "my_group");  
            props.put("enable.auto.commit", "true");  
            props.put("auto.commit.interval.ms", "1000");  
            props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");  
            props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");  
            //3. 创建 KafkaConsumer 对象  
            KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);  
            //4. 订阅名为 demo2 的 topic  
            consumer.subscribe(Arrays.asList("demo2"));  
            //5. 死循环,不断消费订阅的数据  
            while (true) {  
                //6. 使用 KafkaConsumer 拉取数据  
                ConsumerRecords<String, String> records = consumer.poll(100);  
                //7. 遍历数据,将数据封装到 Event 对象中,使用 fastjson 将 Event 对象转换成 JSON 字符串,最后调用 session.getBasicRemote().sendText(String msg); 将数据推送到前端页面  
                /**  
                 *  
                 * kafka消费的数据如下:  
                 *      在线数,活跃数,致命故障数量,严重故障数量,一般故障数量,轻微故障数量,房车报警数量,旅行车报警数量,桥跑车报警数量,跑车报警数量,敞篷车报警数量  
                 *      3608335,1802435,25809,63260,15879,38612,77507,29697,10542,67913,42963  
                 *      1745818,1365579,29449,46912,58208,29464,46830,55611,90398,94499,89332  
                 *      3768443,2243235,32830,12980,26930,61768,44310,20354,11672,91021,52017  
                 *  
                 *  
                 *  Event对象属性如下:  
                 *          private String onlineCount;     在线数  
                 *          private String activeCount;     活跃数  
                 *          private String fault4Count;     致命故障数量  
                 *          private String fault3Count;     严重故障数量  
                 *          private String fault2Count;     一般故障数量  
                 *          private String fault1Count;     轻微故障数量  
                 *          private String warning5Count;   房车报警数量  
                 *          private String warning4Count;   旅行车报警数量  
                 *          private String warning3Count;   桥跑车报警数量  
                 *          private String warning2Count;   跑车报警数量  
                 *          private String warning1Count;   敞篷车报警数量  
                 *  
                 *  Event 对象有参构造如下:  
                 *      public Event(String onlineCount, String activeCount, String fault4Count, String fault3Count, String fault2Count, String fault1Count, String warning5Count, String warning4Count, String warning3Count, String warning2Count, String warning1Count){....}  
                 *  
                 */  
                for (ConsumerRecord<String, String> record : records) {  
                    String value = record.value();  
                    String[] arr = value.split(",");  
                    if (arr.length == 11) {  
                        //在线数、活跃数、致命故障数量、严重故障数量、一般故障数量、轻微故障数量、房车报警数量、旅行车报警数量、桥跑车报警数量、跑车报警数量、敞篷车报警数量  
                        Event event = new Event(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6], arr[7], arr[8], arr[9], arr[10]);  
                        String s = JSON.toJSONString(event);  
                        try {  
                            session.getBasicRemote().sendText(s);  
                        } catch (IOException e) {  
                            e.printStackTrace();  
                        }  
                    }  
                }  
            }  
        }  
    /********** end **********/
}
  1. index.html
<!DOCTYPE html>  
    <html lang="en" xmlns:th="http://www.thymeleaf.org">  
    <head>  
        <meta charset="UTF-8">  
        <title>智慧警务-交通大数据监控平台</title>  
        <link rel="stylesheet" th:href="@{css/common.css}">  
        <link rel="stylesheet" th:href="@{css/map.css}">  
    </head>  
    <body>  
    <div class="data">
        <div class="data-title">  
            <div class="title-left fl"></div>  
            <div class="title-center fl"></div>  
            <div class="title-right fr"></div>  
        </div>  
        <div class="data-content">  
            <div class="con-left fl">  
                <div class="left-top">  
                    <div class="info">  
                        <div class="info-title">实时统计</div>  
                        <img th:src="@{img/bj-1.png}" alt="" class="bj-1">  
                        <img th:src="@{img/bj-2.png}" alt="" class="bj-2">  
                        <img th:src="@{img/bj-3.png}" alt="" class="bj-3">  
                        <img th:src="@{img/bj-4.png}" alt="" class="bj-4">  
                        <div class="info-main">  
                            <div class="info-1">  
                                <div class="info-img fl">  
                                    <img th:src="@{img/info-img-1.png}" alt="">  
                                </div>  
                                <div class="info-text fl">  
                                    <p>车辆总数(辆)</p>  
                                    <p id="CountNum">12,457</p>  
                                </div>  
                            </div>  
                            <div class="info-2">  
                                <div class="info-img fl">  
                                    <img th:src="@{img/info-img-2.png}" alt="">  
                                </div>  
                                <div class="info-text fl">  
                                    <p>当前在线数(辆)</p>  
                                    <p id="OnlineNum">12,457</p>  
                                </div>  
                            </div>  
                            <div class="info-3">  
                                <div class="info-img fl">  
                                    <img th:src="@{img/info-img-3.png}" alt="">  
                                </div>  
                                <div class="info-text fl">  
                                    <p>当前活跃数(辆)</p>  
                                    <p id="activeNum">12,457</p>  
                                </div>  
                            </div>  
                            <div class="info-4">  
                                <div class="info-img fl">  
                                    <img th:src="@{img/info-img-4.png}" alt="">  
                                </div>  
                                <div class="info-text fl">  
                                    <p>当前活跃率(%)</p>  
                                    <p id="liveness">83</p>  
                                </div>  
                            </div>  
                        </div>  
                    </div>  
                    <div class="top-bottom">  
                        <div class="title"></div>  
                        <img th:src="@{img/bj-1.png}" alt="" class="bj-1">  
                        <img th:src="@{img/bj-2.png}" alt="" class="bj-2">  
                        <img th:src="@{img/bj-3.png}" alt="" class="bj-3">  
                        <img th:src="@{img/bj-4.png}" alt="" class="bj-4">  
                        <div id="echarts_1" class="charts"></div>  
                    </div>  
                </div>  
                <div class="left-bottom">  
                    <div class="title"></div>  
                    <img th:src="@{img/bj-1.png}" alt="" class="bj-1">  
                    <img th:src="@{img/bj-2.png}" alt="" class="bj-2">  
                    <img th:src="@{img/bj-3.png}" alt="" class="bj-3">  
                    <img th:src="@{img/bj-4.png}" alt="" class="bj-4">  
                    <div id="echarts_2" class="charts"></div>  
                </div>  
            </div>  
            <div class="con-center fl">  
                <div class="map-num">  
                    <p>实时行驶车辆(辆)</p>  
                    <div class="num" id="runningNum">  
                        <span>1</span>  
                        <span>,</span>  
                        <span>2</span>  
                        <span>3</span>  
                        <span>4</span>  
                        <span>,</span>  
                        <span>5</span>  
                        <span>6</span>  
                        <span>7</span>  
                    </div>  
                </div>  
                <div class="cen-top" id="map"></div>  
                <div class="cen-bottom">  
                    <div class="title"></div>  
                    <img th:src="@{img/bj-1.png}" alt="" class="bj-1">  
                    <img th:src="@{img/bj-2.png}" alt="" class="bj-2">  
                    <img th:src="@{img/bj-3.png}" alt="" class="bj-3">  
                    <img th:src="@{img/bj-4.png}" alt="" class="bj-4">  
                    <div id="echarts_3" class="charts"></div>  
                </div>  
            </div>  
            <div class="con-right fr">  
                <div class="right-top">  
                    <div class="title">汽车故障统计</div>  
                    <img th:src="@{img/bj-1.png}" alt="" class="bj-1">  
                    <img th:src="@{img/bj-2.png}" alt="" class="bj-2">  
                    <img th:src="@{img/bj-3.png}" alt="" class="bj-3">  
                    <img th:src="@{img/bj-4.png}" alt="" class="bj-4">  
                    <div id="echarts_5" class="charts"></div>  
                </div>  
                <div class="right-center">  
                    <div class="title">电池报警车辆统计</div>  
                    <img th:src="@{img/bj-1.png}" alt="" class="bj-1">  
                    <img th:src="@{img/bj-2.png}" alt="" class="bj-2">  
                    <img th:src="@{img/bj-3.png}" alt="" class="bj-3">  
                    <img th:src="@{img/bj-4.png}" alt="" class="bj-4">  
                    <div id="echarts_6" class="charts"></div>  
                </div>  
                <div class="right-bottom">  
                    <div class="title"></div>  
                    <img th:src="@{img/bj-1.png}" alt="" class="bj-1">  
                    <img th:src="@{img/bj-2.png}" alt="" class="bj-2">  
                    <img th:src="@{img/bj-3.png}" alt="" class="bj-3">  
                    <img th:src="@{img/bj-4.png}" alt="" class="bj-4">  
                </div>  
            </div>  
        </div>  
    </div>
    </body>  
    <script th:src="@{js/jquery-2.1.1.min.js}"></script>  
    <script th:src="@{js/echarts.min.js}"></script>  
    <script th:src="@{js/china.js}"></script>  
    <script th:src="@{js/echarts.js}"></script>
    <script type="text/javascript" th:inline="javascript">
        var socket;  
        if (typeof(WebSocket) == "undefined") {  
            console.log("您的浏览器不支持WebSocket");  
        } else {  
            console.log("您的浏览器支持WebSocket");  
            //实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接  
            var href = window.location.href;  
            var arr = href.split(":");  
            var ip = arr[1];  
            var port = arr[2];  
            socket = new WebSocket("ws://" + ip + ":" + port + "/websocket");  
            //打开事件  
            socket.onopen = function () {  
                console.log("Socket 已打开");  
            };  
            //获得消息事件  
            socket.onmessage = function (msg) {  
                /********** begin **********/
                //1.获取 WebSocket 服务端推送过来的数据并将其转换成 JSON 对象并命名为 d  
                var d = JSON.parse(msg.data);
                //2.从 JSON 对象 d 中获取carCount、onlineCount、activeCount、active 并将其分别替换 id 为 CountNum、OnlineNum、activeNum 的文本内容  
                $("#CountNum").text(d.carCount);  
                $("#OnlineNum").text(d.onlineCount);  
                $("#activeNum").text(d.activeCount)  
                $("#liveness").text(d.active)
                var runningNum = numberHandle(d.activeCount);  
                $("#runningNum").html(runningNum)
                //3.汽车故障图表生成,创建一个存储4种故障类型数量的数组(fault4Count、fault3Count、fault2Count、fault1Count),调用 fault(Array arr) 函数生成图表  
                var faultList = new Array(d.fault4Count, d.fault3Count, d.fault2Count, d.fault1Count)  
                fault(faultList)
                //4.汽车电池警告图表生成,创建一个存储5种电池警告类型数量的数组(warning5Count、warning4Count、warning3Count、warning2Count,warning1Count),调用 warning(Array arr) 函数生成图表  
                var warningList = new Array(d.warning5Count, d.warning4Count, d.warning3Count, d.warning2Count, d.warning1Count)  
                warning(warningList)
                /********** end **********/  
            };  
            //关闭事件  
            socket.onclose = function () {  
                console.log("Socket已关闭");  
            };  
            //发生了错误事件  
            socket.onerror = function () {  
                alert("Socket发生了错误");  
            }  
        }
        function numberHandle(str) {  
            var arr = str.split("");  
            var result = "";  
            for (var x = 0; x < arr.length; x++) {  
                result += "<span>" + arr[x] + "</span>"  
            }  
            return result;  
        }
        function fault(dataList) {  
            var myChart = echarts.init(document.getElementById('echarts_5'));  
            var xData = function () {  
                var data = ['致命故障', '严重故障', '一般故障', '轻微故障'];  
                return data;  
            }();  
            option = {  
                tooltip: {  
                    show: "true",  
                    trigger: 'item',  
                    backgroundColor: 'rgba(0,0,0,0.4)', // 背景  
                    padding: [8, 10], //内边距  
                    // extraCssText: 'box-shadow: 0 0 3px rgba(255, 255, 255, 0.4);', //添加阴影  
                    formatter: function (params) {  
                        if (params.seriesName != "") {  
                            return params.name + ' :  ' + params.value + ' 辆';  
                        }  
                    },
                },  
                grid: {  
                    borderWidth: 0,  
                    top: 20,  
                    bottom: 35,  
                    left: 55,  
                    right: 30,  
                    textStyle: {  
                        color: "#fff"  
                    }  
                },  
                xAxis: [{  
                    type: 'category',
                    axisTick: {  
                        show: false  
                    },  
                    axisLine: {  
                        show: true,  
                        lineStyle: {  
                            color: '#363e83',  
                        }  
                    },  
                    axisLabel: {  
                        inside: false,  
                        textStyle: {  
                            color: '#bac0c0',  
                            fontWeight: 'normal',  
                            fontSize: '12',  
                        },  
                    },  
                    data: xData,  
                }, {  
                    type: 'category',  
                    axisLine: {  
                        show: false  
                    },  
                    axisTick: {  
                        show: false  
                    },  
                    axisLabel: {  
                        show: false  
                    },  
                    splitArea: {  
                        show: false  
                    },  
                    splitLine: {  
                        show: false  
                    },  
                    data: xData,  
                }],  
                yAxis: {  
                    type: 'value',  
                    axisTick: {  
                        show: false  
                    },  
                    axisLine: {  
                        show: true,  
                        lineStyle: {  
                            color: '#32346c',  
                        }  
                    },  
                    splitLine: {  
                        show: true,  
                        lineStyle: {  
                            color: '#32346c ',  
                        }  
                    },  
                    axisLabel: {  
                        textStyle: {  
                            color: '#bac0c0',  
                            fontWeight: 'normal',  
                            fontSize: '12',  
                        },  
                        formatter: '{value}',  
                    },  
                },  
                series: [{  
                    type: 'bar',  
                    itemStyle: {  
                        normal: {  
                            show: true,  
                            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{  
                                offset: 0,  
                                color: '#00c0e9'  
                            }, {  
                                offset: 1,  
                                color: '#3b73cf'  
                            }]),  
                            barBorderRadius: 50,  
                            borderWidth: 0,  
                        },  
                        emphasis: {  
                            shadowBlur: 15,  
                            shadowColor: 'rgba(105,123, 214, 0.7)'  
                        }  
                    },  
                    zlevel: 2,  
                    barWidth: '20%',  
                    data: dataList  
                },  
                    {  
                        name: '',  
                        type: 'bar',  
                        xAxisIndex: 1,  
                        zlevel: 1,  
                        itemStyle: {  
                            normal: {  
                                color: '#121847',  
                                borderWidth: 0,  
                                shadowBlur: {  
                                    shadowColor: 'rgba(255,255,255,0.31)',  
                                    shadowBlur: 10,  
                                    shadowOffsetX: 0,  
                                    shadowOffsetY: 2,  
                                },  
                            }  
                        },  
                        barWidth: '20%'  
                    }  
                ]  
            };  
            myChart.setOption(option);  
            window.addEventListener("resize", function () {  
                myChart.resize();  
            });  
        }
        function warning(dataList) {  
            var myChart = echarts.init(document.getElementById('echarts_6'));  
            var xData = function () {  
                var data = ['房车', '旅行轿车', '轿跑车', '跑车', '敞篷车'];  
                return data;  
            }();
            option = {  
                // backgroundColor: "#141f56",  
                tooltip: {  
                    show: "true",  
                    trigger: 'item',  
                    backgroundColor: 'rgba(0,0,0,0.4)', // 背景  
                    padding: [8, 10], //内边距  
                    // extraCssText: 'box-shadow: 0 0 3px rgba(255, 255, 255, 0.4);', //添加阴影  
                    formatter: function (params) {  
                        if (params.seriesName != "") {  
                            return params.name + ' :  ' + params.value + ' 辆';  
                        }  
                    },
                },  
                grid: {  
                    borderWidth: 0,  
                    top: 20,  
                    bottom: 35,  
                    left: 55,  
                    right: 30,  
                    textStyle: {  
                        color: "#fff"  
                    }  
                },  
                xAxis: [{  
                    type: 'category',
                    axisTick: {  
                        show: false  
                    },  
                    axisLine: {  
                        show: true,  
                        lineStyle: {  
                            color: '#363e83',  
                        }  
                    },  
                    axisLabel: {  
                        inside: false,  
                        textStyle: {  
                            color: '#bac0c0',  
                            fontWeight: 'normal',  
                            fontSize: '12',  
                        },  
                    },  
                    data: xData,  
                }, {  
                    type: 'category',  
                    axisLine: {  
                        show: false  
                    },  
                    axisTick: {  
                        show: false  
                    },  
                    axisLabel: {  
                        show: false  
                    },  
                    splitArea: {  
                        show: false  
                    },  
                    splitLine: {  
                        show: false  
                    },  
                    data: xData,  
                }],  
                yAxis: {  
                    type: 'value',  
                    axisTick: {  
                        show: false  
                    },  
                    axisLine: {  
                        show: true,  
                        lineStyle: {  
                            color: '#32346c',  
                        }  
                    },  
                    splitLine: {  
                        show: true,  
                        lineStyle: {  
                            color: '#32346c ',  
                        }  
                    },  
                    axisLabel: {  
                        textStyle: {  
                            color: '#bac0c0',  
                            fontWeight: 'normal',  
                            fontSize: '12',  
                        },  
                        formatter: '{value}',  
                    },  
                },  
                series: [{  
                    type: 'bar',  
                    itemStyle: {  
                        normal: {  
                            show: true,  
                            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{  
                                offset: 0,  
                                color: '#00c0e9'  
                            }, {  
                                offset: 1,  
                                color: '#3b73cf'  
                            }]),  
                            barBorderRadius: 50,  
                            borderWidth: 0,  
                        },  
                        emphasis: {  
                            shadowBlur: 15,  
                            shadowColor: 'rgba(105,123, 214, 0.7)'  
                        }  
                    },  
                    zlevel: 2,  
                    barWidth: '20%',  
                    data: dataList,  
                },  
                    {  
                        name: '',  
                        type: 'bar',  
                        xAxisIndex: 1,  
                        zlevel: 1,  
                        itemStyle: {  
                            normal: {  
                                color: '#121847',  
                                borderWidth: 0,  
                                shadowBlur: {  
                                    shadowColor: 'rgba(255,255,255,0.31)',  
                                    shadowBlur: 10,  
                                    shadowOffsetX: 0,  
                                    shadowOffsetY: 2,  
                                },  
                            }  
                        },  
                        barWidth: '20%'  
                    }  
                ]  
            };  
            // 使用刚指定的配置项和数据显示图表。  
            myChart.setOption(option);  
            window.addEventListener("resize", function () {  
                myChart.resize();  
            });  
        }
    </script>  
    </html>

专题四 数据可视化

4-1 警务大数据之慧搜

4-2 警务大数据之精准布控

第一关:布控人员查询

本关卡是对前端框架layUI的数据表格模块的熟悉、掌握并在此基础上完成布控人员数据渲染,对下一关的人员行为数据展示做铺垫。

本关任务:使用layUI中的数据表格模块完成数据渲染功能

  1. Step1Mapper
package net.educoder.mapper;

import net.educoder.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface Step1Mapper {
    /********** Begin **********/
    @Select("select * from monitoring")
    List<User> findAllUser();

    /********** End **********/

    @Select("select * from monitoring where 1=0")
    void init();
}
  1. Step1ServiceImpl
package net.educoder.service.impl;

import net.educoder.entity.User;
import net.educoder.mapper.Step1Mapper;
import net.educoder.service.Step1Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class Step1ServiceImpl implements Step1Service {


    /********** Begin **********/
    @Autowired
    private Step1Mapper mapper;

    @Override
    public List<User> findMonitoringUser() {
        return mapper.findAllUser();
    }

    /********** End **********/

}
  1. Step1Controller
package net.educoder.controller;

import net.educoder.entity.User;
import net.educoder.service.Step1Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

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

@Controller
public class Step1Controller {
    /********** Begin **********/
    @Autowired
    private Step1Service service;

    @RequestMapping("/monitoringUser")
    @ResponseBody
    public Map<String, Object> monitoringUser() {
        HashMap<String, Object> map = new HashMap<>();
        List<User> list = service.findMonitoringUser();
        map.put("msg", "");
        map.put("code", 0);
        map.put("data", list);
        map.put("count", list.size());
        return map;
    }
    /********** End **********/
}
  1. demo.html(其实没有写html也能通过)
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>精准布控</title>
    <link rel="stylesheet" type="text/css" th:href="@{/jw/css/index.css}">
    <link rel="stylesheet" type="text/css" th:href="@{/jw/css/magic-check.css}">
    <script type="text/javascript" th:src="@{/jw/js/jquery-3.3.1.min.js}"></script>
    <script type="text/javascript" th:src="@{/jw/js/index.js}"></script>
    <link rel="stylesheet" type="text/css" th:href="@{/layui/css/layui.css}">
</head>
<body>
<div class="policeHead">
    <img th:src="@{/jw/images/logo.png}"/>
    <a href="/" class="returnToIndex">返回</a>
    <table id="demo" lay-filter="test"></table>
    <div id="jsmind_container"></div>
</div>

<script type="text/html" id="barDemo">
    <a class="layui-btn layui-btn-xs layui-btn-normal layui-btn-fluid" lay-event="cat">查看</a>
</script>

<script type="text/javascript" th:src="@{/layui/layui.all.js}"></script>
<script th:inline="javaScript">
    layui.use('table', function () {
        var table = layui.table;
        /********** Begin **********/
        table.render({
            elem: '#demo',
            height: 312,
            url: '/monitoringUser',
            page: true,
            cols: [[
                {field: 'id', title: '身份证号码', fixed: 'left'},
                {field: 'trueName', title: '真实姓名'},
                {field: 'monitoringTime', title: '布控时间'},
                {field: 'monitoringRange', title: '布控范围'}
            ]]
        })
        /********** End **********/
    });

</script>
</body>
</html>

第二关:布控人员日志查询

本关卡是警务大数据可视化层的精准布控模块,对人员行为日志数据进行可视化,让警务人员更好地从数据可视化中得出结论并作出相应的决策。

本关任务:完成布控人员日志查询功能。

  1. Step2Mapper
package net.educoder.mapper;

import net.educoder.entity.Event;
import net.educoder.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface Step2Mapper {
    /********** Begin **********/
    @Select("select id, log from eventLog where code = #{code}")
    List<Event> findEventLogByCode(String code);

    @Select("select * from monitoring where id = #{id}")
    User findUserById(String id);
    /********** End **********/
}
  1. Step2ServiceImpl
package net.educoder.service.impl;

import net.educoder.entity.Event;
import net.educoder.entity.JsMind;
import net.educoder.entity.User;
import net.educoder.mapper.Step2Mapper;
import net.educoder.service.Step2Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

@Service
public class Step2ServiceImpl implements Step2Service {

    /********** Begin **********/
    @Autowired
    private Step2Mapper mapper;

    @Override
    public List<JsMind> findEventLogByCode(String id) {
        List<JsMind> jsMinds = new ArrayList<>();
        User user = mapper.findUserById(id);
        List<Event> events = mapper.findEventLogByCode(id);//id作code
        JsMind root = new JsMind("root", "true", "", user.getTrueName());
        jsMinds.add(root);
        for (int i = 0; i < events.size(); i++) {
            JsMind jsMind = new JsMind(String.valueOf(i), "false", "root", events.get(i).getLog());
            jsMinds.add(jsMind);
        }
        return jsMinds;
    }
    /********** End **********/
}
  1. Step2Controller
package net.educoder.controller;

import net.educoder.entity.JsMind;
import net.educoder.service.Step2Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

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

@Controller
public class Step2Controller {

    /********** Begin **********/
    @Autowired
    private Step2Service service;

    @RequestMapping("/jsMindInfo")
    @ResponseBody
    public Map<String, Object> jsMindInfo(String id) {
        Map<String, Object> map = new HashMap<>();
        List<JsMind> jsMinds = service.findEventLogByCode(id);
        map.put("list", jsMinds);
        return map;
    }
    /********** End **********/
}
  1. accurate_control.htm(其实没有写html也能通过)
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>精准布控</title>
    <link rel="stylesheet" type="text/css" th:href="@{/jw/css/index.css}">
    <link rel="stylesheet" type="text/css" th:href="@{/jw/css/magic-check.css}">
    <script type="text/javascript" th:src="@{/jw/js/jquery-3.3.1.min.js}"></script>
    <script type="text/javascript" th:src="@{/jw/js/index.js}"></script>
    <link rel="stylesheet" type="text/css" th:href="@{/layui/css/layui.css}">
    <link rel="stylesheet" type="text/css" th:href="@{/jsmind/jsmind.css}">
    <script type="text/javascript" th:src="@{/jsmind/jsmind.draggable.js}"></script>
    <script type="text/javascript" th:src="@{/jsmind/jsmind.js}"></script>
    <style type="text/css">
        #jsmind_container {
            height: 600px;
            border: solid 1px #ccc;
            /*background:#f4f4f4;*/
            background: #f4f4f4;
        }
    </style>
</head>
<body>
<div class="policeHead">
    <img th:src="@{/jw/images/logo.png}"/>
    <a href="/" class="returnToIndex">返回</a>
    <table id="demo" lay-filter="test"></table>
    <div id="jsmind_container"></div>
</div>

<script type="text/html" id="barDemo">
    <a class="layui-btn layui-btn-xs layui-btn-normal layui-btn-fluid" lay-event="cat">查看</a>
</script>

<script type="text/javascript" th:src="@{/layui/layui.all.js}"></script>
<script th:inline="javaScript">
    layui.use('table', function () {
        var table = layui.table;

        //第一个实例
        table.render({
            elem: '#demo'
            , height: 312
            , url: '/monitoringUser'
            , page: true //开启分页
            , cols: [[ //表头
                {field: 'id', title: '身份证号码', fixed: 'left'}
                , {field: 'trueName', title: '真实姓名'}
                , {field: 'monitoringTime', title: '布控时间'}
                , {field: 'monitoringRange', title: '布控范围'}
                , {fixed: 'right', title: '操作', toolbar: '#barDemo'}
            ]]
        });

        table.on('tool(test)', function (obj) {
            var data = obj.data;
            if (obj.event === 'cat') {
                load_jsmind(data.id)
            }
        });

    });

    var options = {
        container: 'jsmind_container',
        editable: true,
        theme: 'primary'
    }
    var jm = new jsMind(options);

    function load_jsmind(id) {
        /********** Begin **********/
        $.ajax({
            url: "/jsMindInfo",
            data: {"id": id},
            success: function (data) {
                var mind = {
                    "meta": {
                        "name": "example",
                        "author": "hizzgdev@163.com",
                        "version": "0.2"
                    },
                    "format": "node_array",
                    "data": data.list
                };
                jm.show(mind)
            },
            dataType: "json",
            type: "post"
        });
        /********** End **********/
    }
</script>
</body>
</html>