# FogEdge
**Repository Path**: HeimingZ/FogEdge
## Basic Information
- **Project Name**: FogEdge
- **Description**: IoT边缘计算平台的数据分析部分
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 5
- **Created**: 2020-06-02
- **Last Updated**: 2025-11-22
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# FogEdge - 数据分析模块
## 一、概述
### 1.1、介绍
* 本模块实现了时序分析和实时分析两大功能,具体介绍见1.2。本模块的开发基于边缘设备平台能够存储设备部分历史数据这一前提,并在此基础上对设备的历史数据进行分析
* 本数据分析模块并没有设计流数据分析的功能,因为个人认为对流数据的智能分析应当属于规则引擎的一部分,通过在规则引擎模块部署简单的预测模型实现对设备状态的实时分析与预测,再针对预测结果添加规则决定数据转发、数据存储、报警等后续操作
### 1.2、功能点
* 辅助方法:为数据分析模块提供辅助方法
* 获取所有数据通道信息:包括设备信息、设备的所有用于上传数据的数据通道信息,数据通道的所有字段信息
* 时序分析:选择历史数据进行数据可视化
* 折线图分析:指定时间段、间隔时间,使用折线图展示通过不同聚合方式处理后的数据
* 实时分析:使用机器学习模型对设备的最新数据进行分析
* 模型管理:管理PMML机器学习模型,用户可以提前将python、r、spark等产生的模型转换成PMML模型文件,然后上传即可
* 算子管理:管理javascript函数算子,用户可自定义数据预处理方式
* 任务管理:管理实时分析任务,用于匹配模型的输入字段和算子预处理后的结果
## 二、详细设计
* 项目采用分层结构,所有数据分析模块的代码均在**各层的analysis文件夹**下
* 小组代码仓库:https://github.com/XingchiLiu/smart-iot-backend.git
* 个人代码仓库:https://gitee.com/HeimingZ/FogEdge
* 后端基于Java8,使用Springboot、Mybatis、JPA
* 数据库为Mysql5、MongoDB
### 2.1、代码设计
#### 2.1.1、辅助方法
##### a. 获取所有数据通道信息
| 接口名称 | 关联的类 | 接口信息 |
| ---------------------------------------- | --------------------------- | ------------------------------------------------------------ |
| AnalysisUtilController.getAllDevicesInfo | ResultVO
DeviceDataVO | 语法:public ResultVO getAllDevicesInfo()
前置条件:无
后置条件:返回所有设备及其向上传输的数据通道和字段
不变量:数据库状态 |
| AnalysisUtilService.getAllDevicesInfo | DeviceDataVO
DeviceData | 语法:public List\ getAllDevicesInfo()
前置条件:无
后置条件:返回所有设备及其向上传输的数据通道和字段
不变量:数据库状态 |
| DeviceMapper.getAllDevicesInfo | DeviceData | 语法:public List\ getAllDevicesInfo()
前置条件:数据库连接正常
后置条件:返回所有设备及其向上传输的数据通道和字段
不变量:数据库状态 |
#### 2.1.2、时序分析
##### a. 折线图分析
* 监测点MeasurePoint:即设备数据通道中的某个字段
* 度量值Metric:对给定时间段内某数据通道字段值进行聚合操作后得到的结果
* 约束:
* 只能选择DECIMAL类型的数据字段进行时序分析,无法对STRING、BOOLEAN类型的数据进行聚合
* 若某时间段内不存在数据,则该时间点的值为null
| 接口名称 | 关联的类 | 接口信息 |
| --------------------------------------------------- | ------------------------ | ------------------------------------------------------------ |
| TimingAnalysisController.analysisByLineChart | ResultVO
LineChartVO | 语法:public ResultVO analysisByLineChart(TimingAnalysisForm form)
前置条件:表单符合要求,id大于0,时间间隔不为空且大于0,开始时间早于截止时间
后置条件:返回按时间间隔聚合后的数据信息
不变量:数据库状态 |
| TimingAnalysisService.analysisByLineChart | LineChartVO | 语法:public LineChartVO analysisByLineChart(TimingAnalysisForm form)
前置条件:无
后置条件:返回所有设备及其向上传输的数据通道和字段
不变量:数据库状态 |
| TimingAnalysisService.analysisMeasurePoint(private) | LineChartVO.Metric | 语法:private LineChartVO.Metric analysisMeasurePoint(TimingAnalysisForm.MeasurePoint measurePoint, Long intervalMinutes, Integer size, LocalDateTime startTime, LocalDateTime endTime)
前置条件:所有参数均不为null
后置条件:返回该监测点的聚合后的结果
不变量:数据库状态 |
| TimingAnalysisService.aggregateMsg(private) | Double | 语法:private Double aggregateMsg(List\ values, AggregationType type)
前置条件:所有参数均不为null
后置条件:根据聚合类型type将values聚合成一个值
不变量:数据库状态 |
| DeviceMapper.getChannelDataFieldById | ChannelDataField | 语法:public ChannelDataField getChannelDataFieldById(Integer fieldId)
前置条件:数据库连接正常
后置条件:根据字段id获取数据通道的某字段
不变量:数据库状态 |
| DeviceMessageRepo.getMsgByTopicAndTimeInterval | DeviceMessage | 语法:public List getMsgByTopicAndTimeInterval(String topic, LocalDateTime startTime, LocalDateTime endTime)
前置条件:数据库连接正常
后置条件:获取指定时间段内某话题的所有消息
不变量:数据库状态 |
#### 2.1.3、实时分析
##### a. 模型管理
###### 查看所有模型
| 接口名称 | 关联的类 | 接口信息 |
| ---------------------------------------- | -------------------- | ------------------------------------------------------------ |
| OnlineAnalysisController.getAllPMMLModel | ResultVO
ModelVO | 语法:public ResultVO getAllPMMLModel()
前置条件:无
后置条件:返回所有模型的简要信息
不变量:数据库状态 |
| OnlineAnalysisService.getAllPMMLModel | ModelVO
Model | 语法:public List\ getAllPMMLModel()
前置条件:无
后置条件:返回所有模型的简要信息
不变量:数据库状态 |
| ModelMapper.getAllModels | Model | 语法:public List\ getAllModels()
前置条件:数据库连接正常
后置条件:返回所有模型的简要信息
不变量:数据库状态 |
###### 保存模型
* 将模型文件保存到磁盘上,并在数据库中保存模型文件的基本信息(包括模型名、模型描述、模型路径、模型输入字段)
* 所有PMML模型保存在用户目录下的modules文件夹中,因此模型文件名不能相同
| 接口名称 | 关联的类 | 接口信息 |
| ---------------------------------------------- | -------------------------- | ------------------------------------------------------------ |
| OnlineAnalysisController.savePMMLModel | MultipartFile
ResultVO | 语法:public ResultVO savePMMLModel(MultipartFile file, String name, String description)
前置条件:模型名存在且不为空,模型后缀只能为xml或pmml
后置条件:将模型文件保存到磁盘上,并在数据库中记录模型信息
不变量:模型文件内容 |
| OnlineAnalysisService.fileExists | File | 语法:public boolean fileExists(String filename)
前置条件:文件名不为null
后置条件:判断磁盘上是否存在同名文件
不变量:数据库状态、磁盘文件 |
| OnlineAnalysisService.savePMMLModel | MultipartFile
File | 语法:public boolean savePMMLModel(MultipartFile file, String name, String description)
前置条件:所有参数不为null
后置条件:将模型文件保存到磁盘上,并在数据库中记录模型信息
不变量:模型文件内容 |
| OnlineAnalysisService.saveInputFields(private) | ModelField | 语法:private boolean saveInputFields(Integer modelId, String filePath)
前置条件:所有参数不为null
后置条件:读取模型文件,并将模型的输入字段保存到数据库中
不变量:模型文件内容 |
| ModelMapper.insertModel | Model | 语法:public Integer insertModel(Model model)
前置条件:数据库连接正常
后置条件:将模型文件基本信息保存到数据库中
不变量:模型信息 |
| ModelMapper.insertInputFields | ModelField | 语法:public Integer insertInputFields(List\ fields)
前置条件:数据库连接正常
后置条件:将模型的输入字段保存到数据库中
不变量:模型信息 |
###### 删除模型
* 同时删除磁盘上的模型文件和数据库中的模型信息
* 当该模型存在对应任务时无法删除模型
| 接口名称 | 关联的类 | 接口信息 |
| ---------------------------------------------------- | -------- | ------------------------------------------------------------ |
| OnlineAnalysisController.deletePMMLModel | ResultVO | 语法:public ResultVO deletePMMLModel(Integer modelId)
前置条件:id大于0
后置条件:删除磁盘上的模型文件和数据库中的模型信息
不变量:参数 |
| OnlineAnalysisService.deletePMMLModel | | 语法:public boolean deletePMMLModel(Integer modelId)
前置条件:参数不为null
后置条件:删除磁盘上的模型文件和数据库中的模型信息
不变量:参数 |
| OnlineAnalysisService.deletePMMLModelOnDisk(private) | | 语法:private boolean deletePMMLModelOnDisk(String filename)
前置条件:参数不为null
后置条件:删除磁盘上的模型文件
不变量:参数 |
| ModelMapper.deleteModel | | 语法:public Integer deleteModel(Integer modelId)
前置条件:数据库连接正常
后置条件:删除数据库中的面模型信息
不变量:参数 |
###### 获得模型输入字段
| 接口名称 | 关联的类 | 接口信息 |
| ------------------------------------------------ | ------------------------- | ------------------------------------------------------------ |
| OnlineAnalysisController.getPMMLModelInputFields | ResultVO
ModelFieldVO | 语法:public ResultVO getPMMLModelInputFields(Integer modelId)
前置条件:id大于0
后置条件:查询模型的所有输入字段
不变量:数据库状态 |
| OnlineAnalysisService.getPMMLModelInputFields | ModelFieldVO | 语法:public List\ getPMMLModelInputFields(Integer modelId)
前置条件:参数不为null
后置条件:查询模型的所有输入字段
不变量:数据库状态 |
| ModelMapper.getInputFieldsByModelId | ModelField | 语法:public List\ getInputFieldsByModelId(Integer modelId)
前置条件:参数不为null
后置条件:查询模型的所有输入字段
不变量:数据库状态 |
##### b. 算子管理
###### 查看所有算子
| 接口名称 | 关联的类 | 接口信息 |
| --------------------------------------- | ----------------------- | ------------------------------------------------------------ |
| OnlineAnalysisController.getAllOperator | ResultVO
OperatorVO | 语法:public ResultVO getAllOperator()
前置条件:无
后置条件:返回所有算子的简要信息
不变量:数据库状态 |
| OnlineAnalysisService.getAllOperator | OperatorVO
Operator | 语法:public List\ getAllOperator()
前置条件:无
后置条件:返回所有算子的简要信息
不变量:数据库状态 |
| OperatorMapper.getAllOperator | Operator | 语法:public List\ getAllOperator()
前置条件:数据库连接正常
后置条件:返回所有算子的简要信息
不变量:数据库状态 |
###### 创建算子
* 算子代码格式为
* 需要显式支出代码中函数的名称
| 接口名称 | 关联的类 | 接口信息 |
| --------------------------------------- | ------------------------- | ------------------------------------------------------------ |
| OnlineAnalysisController.createOperator | OperatorForm
ResultVO | 语法:public ResultVO createOperator(OperatorForm operatorForm)
前置条件:表单符合要求,算子名称、javascript代码、函数名称不为null
后置条件:将算子信息保存到数据库
不变量:算子信息 |
| OnlineAnalysisService.saveOperator | OperatorForm
Operator | 语法:public boolean saveOperator(OperatorForm operatorForm)
前置条件:表单符合要求
后置条件:将算子信息保存到数据库
不变量:算子信息 |
| OperatorMapper.insertOperator | Operator | 语法:public Integer insertOperator(Operator operator)
前置条件:参数不为null
后置条件:将算子信息保存到数据库
不变量:算子信息 |
###### 修改算子
| 接口名称 | 关联的类 | 接口信息 |
| --------------------------------------- | ------------------------- | ------------------------------------------------------------ |
| OnlineAnalysisController.modifyOperator | OperatorForm
ResultVO | 语法:public ResultVO modifyOperator(Integer operatorId, OperatorForm operatorForm)
前置条件:id大于0,表单符合要求
后置条件:修改数据库中的算子信息
不变量:算子id |
| OnlineAnalysisService.modifyOperator | OperatorForm
Operator | 语法:public boolean modifyOperator(Integer operatorId, OperatorForm operatorForm)
前置条件:参数不为null
后置条件:修改数据库中的算子信息
不变量:算子id |
| OperatorMapper.insertOperator | Operator | 语法:public Integer updateOperator(Operator operator)
前置条件:参数不为null
后置条件:修改数据库中的算子信息
不变量:算子id |
###### 删除算子
| 接口名称 | 关联的类 | 接口信息 |
| --------------------------------------- | -------- | ------------------------------------------------------------ |
| OnlineAnalysisController.deleteOperator | ResultVO | 语法:public ResultVO deleteOperator(Integer operatorId)
前置条件:id大于0
后置条件:删除数据库中的算子信息
不变量:参数 |
| OnlineAnalysisService.deleteOperator | | 语法:public boolean deleteOperator(Integer operatorId)
前置条件:参数不为null
后置条件:删除数据库中的算子信息
不变量:参数 |
| OperatorMapper.deleteOperator | | 语法:public Integer deleteOperator(Integer operatorId)
前置条件:数据库连接正常
后置条件:删除数据库中的算子信息
不变量:参数 |
##### c. 任务管理
###### 查看所有任务
| 接口名称 | 关联的类 | 接口信息 |
| --------------------------------------- | ------------------------------------------- | ------------------------------------------------------------ |
| OnlineAnalysisController.getAllOperator | ResultVO
OnlineAnalysisTaskVO | 语法:public ResultVO getAllTaskBrief()
前置条件:无
后置条件:返回所有算子的简要信息
不变量:数据库状态 |
| OnlineAnalysisService.getAllOperator | OnlineAnalysisTaskVO
OnlineAnalysisTask | 语法:public List\ getAllTask()
前置条件:无
后置条件:返回所有算子的简要信息
不变量:数据库状态 |
| OperatorMapper.getAllOperator | OnlineAnalysisTask | 语法:public List\ getAllTask()
前置条件:数据库连接正常
后置条件:返回所有算子的简要信息
不变量:数据库状态 |
###### 创建任务
* 任务由三部分组成
* 选择一个模型
* 选择一至多个数据通道
* 为模型的每个输入字段选择一个算子,并确定使用被选择数据通道下的哪些字段作为算子的参数
| 接口名称 | 关联的类 | 接口信息 |
| ------------------------------------------- | ----------------------------------- | ------------------------------------------------------------ |
| OnlineAnalysisController.createTask | OnlineAnalysisTaskForm
ResultVO | 语法:public ResultVO createTask(OnlineAnalysisTaskForm taskForm)
前置条件:表单符合要求,id大于0、算子名称不为null
后置条件:将任务信息保存到数据库
不变量:任务信息 |
| OnlineAnalysisService.saveTask | OnlineAnalysisTaskForm | 语法:public boolean saveTask(OnlineAnalysisTaskForm taskForm)
前置条件:表单符合要求
后置条件:将任务信息保存到数据库
不变量:任务信息 |
| OnlineAnalysisMapper.insertTask | OnlineAnalysisTask | 语法:public Integer insertTask(OnlineAnalysisTask task)
前置条件:数据库连接正常
后置条件:将任务的基本信息保存到数据库
不变量:任务信息 |
| OnlineAnalysisMapper.insertTaskDataChannels | | 语法:public Integer insertTaskDataChannels(Integer taskId, List\ channelIds)
前置条件:数据库连接正常
后置条件:将任务对应的数据通道保存到数据库
不变量:任务信息 |
| OnlineAnalysisMapper.insertTaskOperators | TaskOperator | 语法:public Integer insertTaskOperators(List\ taskOperators)
前置条件:数据库连接正常
后置条件:将任务每个输入字段对应的算子保存到数据库
不变量:任务信息 |
| OnlineAnalysisMapper.insertFuncParams | FuncParam | 语法:public Integer insertFuncParams(List\ funcParams)
前置条件:数据库连接正常
后置条件:将任务每个输入字段使用的数据通道字段保存到数据库
不变量:任务信息 |
###### 修改任务
* 数据库层接口与创建任务相同
| 接口名称 | 关联的类 | 接口信息 |
| ----------------------------------- | ----------------------------------- | ------------------------------------------------------------ |
| OnlineAnalysisController.modifyTask | OnlineAnalysisTaskForm
ResultVO | 语法:public ResultVO modifyTask(Integer taskId, OnlineAnalysisTaskForm taskForm)
前置条件:表单符合要求,id大于0、算子名称不为null
后置条件:修改数据库中的任务信息
不变量:任务id |
| OnlineAnalysisService.modifyTask | OnlineAnalysisTaskForm | 语法:public boolean modifyTask(Integer taskId, OnlineAnalysisTaskForm taskForm)
前置条件:表单符合要求
后置条件:修改数据库中的任务信息
不变量:任务id |
| OnlineAnalysisMapper.insertTask | OnlineAnalysisTask | 语法:public Integer insertTask(Integer taskId, OnlineAnalysisTask task)
前置条件:数据库连接正常
后置条件:将任务的基本信息保存到数据库,若taskId不为null则使用该id保存数据
不变量:任务id |
###### 删除任务
| 接口名称 | 关联的类 | 接口信息 |
| ----------------------------------- | -------- | ------------------------------------------------------------ |
| OnlineAnalysisController.deleteTask | ResultVO | 语法:public ResultVO deleteTask(Integer taskId)
前置条件:id大于0
后置条件:删除数据库中的任务信息
不变量:参数 |
| OnlineAnalysisService.deleteTask | | 语法:public boolean deleteTask(Integer taskId)
前置条件:参数不为null
后置条件:删除数据库中的任务信息
不变量:参数 |
| OnlineAnalysisMapper.deleteTask | | 语法:public Integer deleteTask(Integer taskId)
前置条件:数据库连接正常
后置条件:删除数据库中的任务信息
不变量:参数 |
###### 查看任务详情
| 接口名称 | 关联的类 | 接口信息 |
| ------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| OnlineAnalysisController.getTaskDetail | ResultVO
OnlineAnalysisTaskDetailVO | 语法:public ResultVO getTaskDetail(Integer taskId)
前置条件:id大于0
后置条件:获取任务详情
不变量:任务信息 |
| OnlineAnalysisService.getTaskDetail | OnlineAnalysisTaskDetailVO
OnlineAnalysisTaskDetail | 语法:public OnlineAnalysisTaskDetailVO getTaskDetail(Integer taskId)
前置条件:参数不为null
后置条件:获取任务详情
不变量:任务信息 |
| OnlineAnalysisService.getTaskDetailInfo(private) | OnlineAnalysisTaskDetail
OnlineAnalysisTaskDetail.InputFunc | 语法:private OnlineAnalysisTaskDetail getTaskDetailInfo(Integer taskId)
前置条件:参数不为null
后置条件:获取任务详情
不变量:任务信息 |
| OnlineAnalysisMapper.getTaskDetail | OnlineAnalysisTaskDetail | 语法:public OnlineAnalysisTaskDetail getTaskDetail(Integer taskId)
前置条件:参数不为null
后置条件:获取任务除输入字段以外的信息
不变量:任务信息 |
| OnlineAnalysisMapper.getTaskInputFields | OnlineAnalysisTaskDetail.InputFunc | 语法:public List getTaskInputFields(Integer taskId, Integer modelId)
前置条件:参数不为null
后置条件:获取任务输入字段的详细信息
不变量:任务信息 |
###### 执行任务
* 当数据库中不存在数据时任务执行失败
* 数据传入字段需与javascript参数列表匹配
| 接口名称 | 关联的类 | 接口信息 |
| ------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| OnlineAnalysisController.executeTask | ResultVO
OnlineAnalysisTaskResult | 语法:public ResultVO executeTask(Integer taskId)
前置条件:id大于0
后置条件:执行分析任务获取结果
不变量:数据库状态 |
| OnlineAnalysisService.executeTask | OnlineAnalysisTaskResult
DeviceMessage
File
Evaluator
FieldName
FieldValue
InputField
Parameter | 语法:public OnlineAnalysisTaskResult executeTask(Integer taskId)
前置条件:参数不为null
后置条件:执行分析任务获取结果
不变量:数据库状态 |
| OnlineAnalysisService.runJsCode(private) | Parameter
ScriptEngine
Invocable | 语法:private Object runJsCode(String funcName, String jsCode, List\ parameters)
前置条件:参数不为null
后置条件:执行javascript代码获取结果
不变量:数据库状态 |
| OnlineAnalysisService.getTaskDetailInfo(private) | OnlineAnalysisTaskDetail
OnlineAnalysisTaskDetail.InputFunc | 语法:private OnlineAnalysisTaskDetail getTaskDetailInfo(Integer taskId)
前置条件:参数不为null
后置条件:获取任务详情
不变量:数据库状态 |
| DeviceMessageRepo.getLastMsgByTopic | DeviceMessage | 语法:public DeviceMessage getLastMsgByTopic(String topic)
前置条件:参数不为null
后置条件:获取某个话题的最新消息
不变量:数据库状态 |
| OnlineAnalysisMapper.getTaskDetail | OnlineAnalysisTaskDetail | 语法:public OnlineAnalysisTaskDetail getTaskDetail(Integer taskId)
前置条件:参数不为null
后置条件:获取任务除输入字段以外的信息
不变量:数据库状态 |
| OnlineAnalysisMapper.getTaskInputFields | OnlineAnalysisTaskDetail.InputFunc | 语法:public List getTaskInputFields(Integer taskId, Integer modelId)
前置条件:参数不为null
后置条件:获取任务输入字段的详细信息
不变量:数据库状态 |
### 2.2、数据库表设计
##### model表
* **model_id**:模型id
* model_name:模型名称(not null)
* model_description:模型描述
* filename:模型文件名(not null、unique)
```sql
create table model
(
model_id int auto_increment
primary key,
model_name varchar(255) not null,
model_description varchar(255) null,
filename varchar(255) not null,
constraint model_filename_uindex
unique (filename)
);
```
##### model_input_field表
* **field_id**:输入字段id
* model_id:模型id(外键cascade,指向model表)
* field_name:输入字段名称
* data_type:输入字段数据类型
* op_type:输入字段操作类型
```sql
create table model_input_field
(
field_id int auto_increment
primary key,
model_id int not null,
field_name varchar(255) not null,
data_type varchar(255) null,
op_type varchar(255) null,
constraint model_input_field_model_fk
foreign key (model_id) references model (model_id)
on delete cascade
);
```
##### online_analysis_task表
* **task_id**:任务id
* model_id:模型id(外键restrict,指向model表)
* task_name:任务名称
* task_description:任务描述
```sql
create table online_analysis_task
(
task_id int auto_increment
primary key,
model_id int not null,
task_description varchar(255) null,
task_name varchar(255) not null,
constraint online_analysis_task_model_fk
foreign key (model_id) references model (model_id)
);
```
##### oa_task_data_channels表(一对多关系表)
* **task_id**:任务id(外键cascade,指向online_analysis_task表)
* **channel_id**:数据通道id(外键restrict,指向device_channel表)
```sql
create table oa_task_data_channels
(
task_id int not null,
channel_id int not null,
primary key (task_id, channel_id),
constraint oa_task_data_channel_device_channels_id_fk
foreign key (channel_id) references device_channel (id),
constraint oa_task_data_channels_online_analysis_task_task_id_fk
foreign key (task_id) references online_analysis_task (task_id)
on delete cascade
);
```
##### operator表
* **operator_id**:算子id
* operator_name:算子名称
* operator_description:算子描述
* js_code:JavaScript代码
```sql
create table operator
(
operator_id int auto_increment
primary key,
operator_name varchar(255) not null,
operator_description varchar(255) null,
js_code text not null,
func_name varchar(255) not null
);
```
##### oa_task_operators表(一对一关系表)
* **task_id**:任务id(外键cascade,指向online_analysis_task表)
* **input_field_id**:输入字段id(外键restrict,指向model_input_field表)
* operator_id:算子id(外键restrict,指向task_operator表)
```sql
create table oa_task_operators
(
task_id int not null,
input_field_id int not null,
operator_id int not null,
primary key (task_id, input_field_id),
constraint oa_task_operators_model_input_field_field_id_fk
foreign key (input_field_id) references model_input_field (field_id),
constraint oa_task_operators_online_analysis_task_task_id_fk
foreign key (task_id) references online_analysis_task (task_id)
on delete cascade,
constraint oa_task_operators_operator_operator_id_fk
foreign key (operator_id) references operator (operator_id)
);
```
##### oa_task_func_params表(一对多关系表)
* **task_id**:任务id(外键cascade,指向online_analysis_task表)
* **input_field_id**:输入字段id(外键restrict,指向model_input_field表)
* **channel_field_id**:数据通道字段id(外键restrict,指向channel_data_field表)
* param_index:字段在函数中的参数的位置(0表示第一个参数)
```sql
create table oa_task_func_params
(
task_id int not null,
input_field_id int not null,
channel_field_id int not null,
param_index int not null,
primary key (task_id, input_field_id, channel_field_id),
constraint oa_task_func_params_channel_data_field_id_fk
foreign key (channel_field_id) references channel_data_field (id),
constraint oa_task_func_params_model_input_field_field_id_fk
foreign key (input_field_id) references model_input_field (field_id),
constraint oa_task_func_params_online_analysis_task_task_id_fk
foreign key (task_id) references online_analysis_task (task_id)
on delete cascade
);
```
## 三、Restful接口设计
* 可直接通过http://101.37.80.37:8081/swagger-ui.html查看接口api
* 凡是出现[]的地方均可按列表的实际意义理解,可拥有多个值,为减少文档长度故只写了一个
### 3.1、辅助方法
#### 3.1.1、获取所有数据通道信息
* 路径:/analysis/util/device/all
* 方法:GET
* 参数:无
* 返回值:
* fieldType:BOOLEAN(0)、DECIMAL(1)、STRING(2)
* ```json
{
"deviceId": 1,
"deviceName": "device01",
"channels": [
{
"channelId": 1,
"channelName": "channnel01",
"dataFields": [
{
"fieldId": 1,
"fieldName": "field01",
"fieldType": "DECIMAL"
}
]
}
]
}
```
### 3.2、时序分析
#### 3.2.1、折线图分析
* 路径:/analysis/timing/line-chart
* 方法:POST
* 参数:
* 聚类类型:MAX(0)、MIN(1)、AVG(2)、SUM(3)、COUNT(4)
* 时间间隔interval:单位为分钟
* ```json
{
"startTime": "2020-06-01T18:03:33",
"endTime": "2020-06-12T18:03:33",
"intervalMinutes": 120,
"measurePoints": [
{
"deviceId": 1,
"channelId": 1,
"fieldId": 1,
"aggregationType": 0
}
]
}
```
* 返回值:
* ```json
{
"timePoints": ["2020-06-01T18:03:33"],
"metrics": [
{
"deviceId": 1,
"channelId": 1,
"fieldId": 1,
"aggregationType": "AVG",
"values": [6.12]
}
]
}
```
### 3.3、实时分析
#### 3.3.1、查看所有模型
* 路径:/analysis/online/model/all
* 方法:GET
* 参数:无
* 返回值:
* ```json
[
{
"modelId": 1,
"name": "name01",
"description": "description01"
}
]
```
#### 3.3.2、保存模型
* 路径:/analysis/online/model/save
* 方法:POST
* 参数:
* Content-Type:multipart/form-data
* 文件类型为pmml或xml,文件大小限制为10MB以下
* ```json
{
"file": "xxx.xml",
"name": "name01",
"description": "description01"
}
```
* 返回值:成功或失败信息
#### 3.3.3、删除模型
* 路径:/analysis/online/model/delete
* 方法:GET
* 参数:
* modelId:1
* 返回值:成功或失败信息
#### 3.3.4、获得模型输入字段
* 路径:/analysis/online/model/inputs
* 方法:GET
* 参数:
* modelId:1
* 返回值:
* ```json
[
{
"fieldId": 13,
"modelId": 15,
"fieldName": "Sepal.Length",
"dataType": "float",
"opType": "continuous"
}
]
```
#### 3.3.5、查看所有算子
* 路径:/analysis/online/operator/all
* 方法:GET
* 参数:无
* 返回值:
* ```json
[
{
"operatorId": 1,
"name": "operator1",
"description": "demo operator",
"jsCode": "var func1 = function() { print('Hello World'); }",
"funcName": "func1"
}
]
```
#### 3.3.6、创建算子
* 路径:/analysis/online/operator/create
* 方法:POST
* 参数:
* ```json
{
"name": "operator1",
"description": "demo operator",
"jsCode": "var func1 = function() { print('Hello World'); }",
"funcName": "func1"
}
```
* 返回值:成功或失败信息
#### 3.3.7、修改算子
* 路径:/analysis/online/task/modify
* 方法:POST
* 参数:
* operatorId:1
* ```json
{
"name": "operator1",
"description": "demo operator",
"jsCode": "var func1 = function() { print('Hello World'); }",
"funcName": "func1"
}
```
* 返回值:成功或失败信息
#### 3.3.8、删除算子
* 路径:/analysis/online/task/delete
* 方法:GET
* 参数:
* operatorId:1
* 返回值:成功或失败信息
#### 3.3.9、查看所有任务
* 路径:/analysis/online/task/all
* 方法:GET
* 参数:无
* 返回值:
* ```json
[
{
"taskId": 1,
"name": "task1",
"description": "demo task"
}
]
```
#### 3.3.10、查看任务详情
* 路径:/analysis/online/task/detail
* 方法:GET
* 参数:
* taskId:1
* 返回值:
* ```json
{
"code": 1,
"msg": "成功",
"data": {
"taskId": 1,
"name": "task01",
"description": "demo task",
"model": {
"modelId": 1,
"name": "model01",
"description": "demo model"
},
"channels": [
{
"channelId": 1,
"channelName": "device01_channel01"
},
{
"channelId": 2,
"channelName": "device01_channel02"
}
],
"inputFields": [
{
"inputFieldId": 1,
"inputFieldName": "input_field01",
"operator": {
"operatorId": 1,
"name": "operator01",
"description": "x+1",
"jsCode": "var func = function(x) { return x+1; }",
"funcName": "func"
},
"channelFields": [
{
"index": 0,
"fieldId": 1,
"fieldName": "field01"
}
]
}
]
}
}
```
#### 3.3.11、创建任务
* 路径:/analysis/online/task/create
* 方法:POST
* 参数:
* ```json
{
"name": "task1",
"description": "demo task",
"channelIds": [1, 2],
"modelId": 1,
"functions": [
{
"inputFieldId": 1,
"operatorId": 1,
"channelFieldIds": [1, 2]
}
]
}
```
* 返回值:成功或失败信息
#### 3.3.12、修改任务
* 路径:/analysis/online/task/modify
* 方法:POST
* 参数:
* taskId:1
* ```json
{
"name": "task1",
"description": "demo task",
"channelIds": [1, 2],
"modelId": 1,
"functions": [
{
"inputFieldId": 1,
"operatorId": 1,
"channelFieldIds": [1, 2]
}
]
}
```
* 返回值:成功或失败信息
#### 3.3.13、删除任务
* 路径:/analysis/online/task/delete
* 方法:GET
* 参数:
* taskId:1
* 返回值:成功或失败信息
#### 3.3.14、执行任务
* 路径:/analysis/online/task/execute
* 方法:GET
* 参数:
* taskId:1
* 返回值:
* result中的字段数可变
* ```json
{
"result": {
"field1": 0.502469688901231797,
"field2": 0.002279984930201797,
"field...": 0.003965842241264622,
"fieldn": 0.9937541728285336
}
}
```
## 四、使用方式
### 4.1、前端使用方式与注意事项
* 只做了一个前端demo供演示使用,阉割了部分功能,可以通过调用接口体验全部功能
* **前端部署在[47.100.220.26:3000](http://47.100.220.26:3000/DataAnalytics/DataPresentation),可直接使用**
#### 4.1.1、时序分析
##### 折线图分析
* 表单选项
* 设备名称:选择想要观察的设备
* 通道名称:选择想要观察的设备下的数据通道
* 字段名称:选择数据通道中的某一个字段
* 聚合类型:选择使用哪一种方式聚合某一时间间隔内的所有数据
* 时间范围:指定想要观测的时间范围
* 时间间隔:指定执行聚合操作的时间间隔
* 使用注意事项:
* 请选择device1~device6,其他设备未人为制造数据
* 当折线图出现异常时,请重置后重新提交表单
* 当某一时间窗口内不存在数据时默认返回null,所以可能出现折线图间断的状况(返回null的原因是监控数据可能为负,所以不能返回默认数值)
* 该接口/analysis/timing/line-chart实际上可以同时监控多个数据通道的字段,但由于是个demo项目所以为了方便只展示了一个字段

#### 4.1.2、实时分析
##### 新建模型
* 模型名称:自定义模型名称,不能为空
* 模型描述:自定义模型描述,可以为空
* 模型文件:
* 要求模型文件的名称必须以.xml或.pmml结尾,大小不超过10M(假设边缘设备平台不能运行大型机器学习模型)
* others目录下提供了两个已转换好的模型文件,使用了sklearn中的经典的鸢尾花Iris数据集
* 若要使用自己的模型文件,具体转换方式详见4.2

##### 查看所有模型
* 能够删除模型

##### 新建或编辑算子
* 算子名称:自定义算子名称,不能为空
* 算子描述:自定义算子描述,可以为空
* 函数名称:指定javascript代码中的函数名称
* 算子函数:
* 只能使用javascript
* 格式必须为`var 函数名 = function(参数列表) { return 返回值; }`

##### 查看所有算子
* 能够修改和删除算子(查看算子详情可以点修改按钮)

##### 新建任务
* 表单选项
* 任务名称:自定义任务名称,不能为空
* 任务描述:自定义任务描述,可以为空
* 数据通道:选择该任务可用的数据通道
* 模型:选择部署的模型
* 模型输入字段:
* 数据通道字段:选择将数据通道中的哪几个参数传入算子函数(**注意:有顺序!参数个数需和算子参数列表匹配!**)
* 算子:选择使用的算子
* 使用注意事项:
* 请选择device1~device6,其他设备未人为制造数据
* 参数有顺序!!参数的个数需与算子参数列表匹配!!

##### 查看任务详情
* 仅做了简单的展示

##### 执行任务
* 仅做了简单的展示
* 当该任务对应的数据通道尚未上传数据时任务会执行失败

##### 查看所有任务
* 可以删除任务
* 后端有修改任务接口/analysis/online/task/modify,但前端尚未实现

### 4.2、PMML模型生成 - 以python的sklearn为例
[PMML模型转换](https://github.com/jpmml)
* python版本为3.7,需`pip install sklearn2pmml`
* 到处模型的关键代码为`sklearn2pmml(模型名, "文件名.pmml")`
* 详细代码请看others目录中的PMMLModel.py
### 4.3、设备模拟 - 使用python模拟
* python版本为3.7,需`pip install paho-mqtt`
* 脚本为others目录中的publisher.py
* 脚本接受如下命令行参数`python publisher.py sleep_seconds device_id channel_id data_field...`
* sleep_seconds:睡眠时间,即发送消息的间隔时间
* device_id:数据库中该设备的id
* channel_id:数据库中该设备拥有的某个上传数据通道的id
* data_field:数据通道字段,**可以有多个**,均生成0~50间的随机浮点数
## 五、安装部署
* 前端项目部署在47.100.220.26:3000,后端项目部署在101.37.80.37:8081,可直接使用
* 本地测试参考如下
### 5.1、后端部署
* 直接使用smart-iot-backend目录中的代码或从https://github.com/XingchiLiu/smart-iot-backend.git处克隆代码
* 默认部署在本地8081端口,连接远程数据库,若有其他需求可修改src/main/resources/application.yml文件
* 运行
### 5.2、数据库部署
默认连接远程数据库,若有其他需求可修改application.yml文件
* 使用mysql5.7,通过others目录下的iot.sql文件导入初始表结构
* 使用mongodb4,然后通过4.3中的方式启动设备,本地后端会订阅数据并写入数据库
### 5.3、前端部署
* 直接使用smart-iot-frontend目录中的代码或从https://github.com/hkshu12/smart-iot-frontend.git处克隆代码
* 默认连接服务器后端,若要连接本地后端可将src/plugins/axios.js中的修改baseURL修改为http://localhost:8081
* 运行:命令行进入项目根目录
* 运行方式1:`yarn install`后`yarn serve`
* 运行方式2:`npm install`后`npm run serve`
## 六、参考文章
* [PMML简介](https://www.ibm.com/developerworks/cn/opensource/ind-PMML1/)
* [jpmml官方文档](https://github.com/jpmml/jpmml-evaluator)
* [JPMML使用实例](https://blog.csdn.net/imgxr/article/details/80127884)
* [知乎PMML实践1](https://zhuanlan.zhihu.com/p/30378213)
* [知乎PMML实践2](https://zhuanlan.zhihu.com/p/53729084)
* [Java1.8使用ScriptEngine执行JavaScript - NashornScriptEngine](https://www.cnblogs.com/top8/p/6207945.html)