记录一下科创开发

把过程中碰到的主要问题都在这里记录

用到的工具:Xshell7, Xftp7

IP: 39.103.142.138

阿里云:

提跃宇

Tee964708596.

docker容器启动失败

在进行硬件数据上云过程中,需要用到broker,但是服务器里部署的emqx无法正常启动

没办法,完全从零开始玩docker,碰到的问题目前都无法靠自己解决

记录一下常用指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
docker ps -a
//查看所有容器运行情况

docker logs --tail=1000 容器名
//查看容器日志

df -h
//查看磁盘使用情况

docker info
//查看docker版本问题

docker start 容器名
//启动容器

docker inspect 容器名/ID
//查看容器的详细信息

MQTT协议中间用到的Broker: emqx无法正常启动,一启动就掉,看status为1秒前退出

查看log

去emqx github issues找到了类似问题vm.args needs to have a -name parameter.,貌似是说磁盘空间占满了

查看磁盘占用情况:

试着进入对应目录删除log文件,但是还是没什么效果

去咸鱼解决问题,花30块找了个老哥也没解决问题,找出问题就没下文了

咸鱼老哥说安装的镜像太多,导致磁盘占用高

但是我看阿里云服务器是120G的磁盘,不应该占满啊

因为服务器到期原因,被迫重启

安装所需依赖

总共是3个文件夹:

rain-cloud-vue:前端项目

rain-iot-device:虚拟设备

rain-iot-server:后端项目

首先该做的事就是,进入每个文件夹下,运行npm install指令,安装所需依赖

package.json文件是一个Node.js项目的核心文件,它包含了项目的元数据和依赖列表。你可以通过运行以下命令来安装package.json文件中列出的所有依赖:

1
>npm install

这个命令会读取package.json文件,然后下载并安装所有列出的依赖到node_modules目录。如果你的项目中已经有一个package-lock.json文件,那么npm install命令会根据这个文件来安装依赖,以确保你安装的依赖版本和其他开发者安装的版本一致。

在你安装依赖之后,你应该就可以正常运行你的项目了。

本地启动项目

因为之前没有用过JavaScript,nodejs这些东西,所以绝大部分工作是问的bing

首先得本地启动现有的项目,步骤如下:

首先rain-iot-server路径下输入npm start,这样可以启动服务器

因为在package.json 文件中有一个 scripts 部分,并且在这个部分中有一个 start 命令,所以需要通过在命令行中输入 npm start 来启动项目

然后再在rain-cloud-vue路径下输入npm run serve,这样就可以正常打开前端

因为在package.json 文件中有一个 scripts 部分,并且在这个部分中有一个 serve 命令,所以需要通过在命令行中输入 npm run serve 来启动项目

能在后端的控制台看到全部的HTTP请求

我记得后端控制台输出需要调用console.log函数,为什么我没在get函数里看到对应的函数,却仍能在控制台看到对应的GET请求?

这是因为Express.js框架自动记录了所有的HTTP请求。在你的代码中,你使用了morgan中间件,这是一个HTTP请求记录器中间件。当你调用app.use(logger('dev'))时,你其实是在告诉Express.js使用morgan来记录所有的HTTP请求。

morgan会自动在控制台输出每个HTTP请求的信息,包括HTTP方法(如GET或POST)、请求的URL、HTTP状态码、响应时间等。所以,即使你没有在路由处理函数中调用console.log(),你仍然可以在控制台看到对应的GET请求。

哈希路由

在分析代码时,我有点疑惑后端的GET方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @router GET
* @desc 获取所有产品列表
*/
router.get("/", function (req, res) {
// TODO 根据查询参数;分页查询
// var productId = req.params.productId
console.log('req.params', req.params);
Product.find(function (err, products) {
if (err) {
res.send(err)
} else {
res.json(products.map(function (product) {
return product.toJSONObject()
}))
}
})
})

我访问的界面明明是 http://192.168.32.129:8080/#/product/productList,那为什么这里的get函数参数是/根路径?

你提到的http://192.168.32.129:8080/#/product/productList是一个使用了哈希路由(hash routing)的URL。哈希路由是一种在前端应用(如单页应用)中常见的路由策略。

在这种策略中,URL的#后面的部分(如/product/productList)会被前端应用用来决定要显示哪个页面或组件,而不是发送到后端服务器。因此,当你访问http://192.168.32.129:8080/#/product/productList时,你的浏览器实际上发送的请求URL是http://192.168.32.129:8080/

然后,前端应用会查看#/product/productList,并根据这个路径渲染对应的页面或组件。在你的例子中,这可能是产品列表页面。

所以,当你点击“显示产品列表”的按钮时,前端应用可能会发送一个到/api/v1/products的GET请求来获取产品数据,这个请求会被后端的router.get("/")路由处理。

你需要查看前端的路由文件。在一个使用Vue.js的前端项目中,路由通常在router.js或者router/index.js文件中定义。这个文件中会定义一个VueRouter实例,其中包含了一个routes数组。这个数组中的每个对象都代表一个路由,包括路径(path)、组件(component)等属性。

例如,你可能会看到类似下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>import Vue from 'vue'
>import Router from 'vue-router'
>import ProductList from '@/components/ProductList'

>Vue.use(Router)

>export default new Router({
routes: [
{
path: '/product/productList',
name: 'ProductList',
component: ProductList
},
// 其他路由...
]
>})

在这个例子中,当用户访问#/product/productList时,前端应用会显示ProductList组件。

当你点击“显示产品列表”的按钮时,ProductList组件可能会发送一个GET请求到/api/v1/products来获取产品数据。这个请求会被后端的router.get("/")路由处理。

路由路径

我点击产品列表界面,前端给后端发送的HTTP请求是:

GET /api/v1/products?column=createTime&order=desc&field=id,productId,productName,activeStatus,description,createTime,updateTime&pageNo=1&pageSize=10

后端一开始的app.js有如下定义:

1
app.use('/api/v1', apiv1)

这就设置了基础路径,于是所有以 /api/v1 开头的请求都会被路由到 apiv1

所以,当在 apiv1 中定义一个 router.get("/") 路由时,它实际上是处理 /api/v1 的 GET 请求

然后在routes/apiv1/index.js中有如下定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const router = require('express').Router()
const productRouter = require('./product')
const deviceRouter = require('./devices');
const tokensRouter = require('./tokens')
const webHookeRouter = require('./emqx_web_hook')
const messageRouter = require('./messages')


router.use('/products', productRouter);
router.use('/devices', deviceRouter);
router.use('/tokens', tokensRouter);
router.use('/emqx_web_hook', webHookeRouter)
router.use('/messages', messageRouter)

module.exports = router;

所有以 /products 开头的请求都会被路由到 productRouter

本地安装EMQX

我在虚拟机上安装了EMQX以后才能正常运行程序,这里是EMXQ的文档

运行以下命令启动 EMQX。

1
>./emqx/bin/emqx start

现在您可通过浏览器访问 http://localhost:18083/(localhost 可替换为您的实际 IP 地址)以访问 EMQX Dashboard 管理控制台,进行设备连接与相关指标监控管理。

默认用户名及密码:

admin

public

目前已经改密码为leizhibo1

运行以下命令停止 EMQX。

1
>./emqx/bin/emqx stop

后续如需卸载 EMQX,您可直接删除 EMQX 目录即可完成卸载。

这个Dashboard就是可以监测EMQX状态的,端口是18083

虚拟机截图,可能有些不清楚

然后需要把rain-iot-device/sdk/iotDevice.js里EMQX的端口重新设置为1883而不是11883,这里可能是学姐本人打错了:

1
constructor({ serverAddress = "127.0.0.1:1883", productId, deviceId, secret, clientID, storePath } = {}) {

EMQX 3.2.0

你说得对,但是版本安装错了,应该安装3.2.0

最新版已经不自带MongoDB的插件了

我在官网下载目录找到了这个版本,下载发现确实有了我要的插件,无敌了

但是我草你妈,这个版本的EMQX不能正常部署

查了一堆,终于在官方的一个问答中找到了建议:Windows建议用docker

你说得对,但是我现在要用docker了

虚拟机装docker装不上?我服了

这下得捯饬捯饬Linux虚拟机了,毕竟老师花了8000买了一年,我也有使用权限,狠狠造;但是不能像之前一个学长一样,部署东西把本来好好的项目整报废了,而且这个项目还是跟我这个逼科创相关的

Erlang runtime

这个我真的操你妈了,安装这玩意快给我整死了

./configure提示缺东西:

1
2
3
4
5
6
7
8
9
10
configure: WARNING: Can not find wx/stc/stc.h  -g -Wall -O2 -fPIC -g -O2 -Wno-deprecated-declarations -fomit-frame-pointer -fno-strict-aliasing  -D_GNU_SOURCE -D_THREAD_SAFE -D_REENTRANT -I/usr/lib64/wx/include/gtk3-unicode-3.0 -I/usr/include/wx-3.0 -D_FILE_OFFSET_BITS=64 -DWXUSINGDLL -D__WXGTK__ -pthread
configure: WARNING: Can not link wx program are all developer packages installed?

*********************************************************************
********************** APPLICATIONS INFORMATION *******************
*********************************************************************

wx : Can not link the wx driver, wx will NOT be useable

*********************************************************************

然后我找到stc.h这个文件了,在/usr/include/wx-3.0/wx/stc目录下

我给他添加到环境变量以后还是找不到这个文件,搞了他妈的一下午一晚上,搞得我想杀人

终于,找到了类似问题:

Erlang Build Fails: Can not link the wx driver, wx will NOT be useable - Stack Overflow

结果竟然是他吗的没装C++?

I got the same error while building erlang on CentOS 7. I too was missing the C++ compiler. After doing yum install gcc-c++, I was able to build erlang successfully.

我真是你妈草了

运行yum install gcc-c++就能成功安装C++

然后再运行./configure就能解决了

还得是stackoverflow,毕竟你问bing的时候,bing不知道你装没装C++

修改记录

前端后端添加删除产品功能

单个删除按钮

我在前端的产品列表界面,即rain-cloud-vue/src/views/product/productList.vue中:

修改了HTML文件,修改了handleDelete函数代入的参数,使点击按钮绑定handleDelete函数后正常工作:

1
<el-button type="danger" size="mini" plain @click="handleDelete(scope.row.productId)">删除</el-button>

修改了import文件,使deleteAction被包括进来:

1
import { getAction, postAction, deleteAction } from "@/api/axios";

修改了url的定义,添加了delete选项:

1
2
3
4
5
url: {
list: "/products",
add: "/products",
delete: "/products",
},

重写了删除函数handleDelete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
handleDelete(id) {
console.log('productId:', id);
this.$confirm('此操作将删除该产品及其下所有设备, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteAction(this.url.delete + '/' + id)
.then(() => {
this.$message({
type: 'success',
message: '删除成功!'
});
// 在这里重新获取数据
this.loadData();
})
.catch(() => {
this.$message({
type: 'error',
message: '删除失败!'
});
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},


我在后端的产品路由,即rain-iot-server/routes/apiv1/product.js中:

添加了后端处理删除请求的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @router DELETE /:productId
* @desc 删除产品
* @access public
*/
router.delete("/:productId", function (req, res) {
const productId = req.params.productId;
Product.findOneAndRemove({ product_id: productId }, function (err) {
if (err) {
console.log(err);
res.status(500).send(err);
} else {
res.json({
message: '产品删除成功!'
});
}
});
});

批量删除

修改了处理选中产品行的函数:

1
2
3
4
handleSelectionChange(val) {
this.selectedRows = val.map(row => row.productId);
// console.log(val);
},

重写了批量删除产品的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
handleBatchDelete() {
if (this.selectedRows.length <= 0) {
this.$message.warning("请选择一条记录!");
return;
} else {
this.$confirm('此操作将删除该产品及其下所有设备, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const deletePromises = this.selectedRows.map(productId => deleteAction(this.url.delete + '/' + productId));
Promise.all(deletePromises)
.then(() => {
this.$message({
type: 'success',
message: '批量删除产品成功!'
});
// 在这里重新获取数据
this.loadData();
})
.catch(() => {
this.$message({
type: 'error',
message: '批量删除失败!'
});
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
},

虚拟设备更新EMQX端口

需要把rain-iot-device/sdk/iotDevice.js里EMQX的端口重新设置为1883而不是11883,这里可能是学姐本人打错了:

不过貌似不是打错了,因为后面很多次出现的端口都是11883,我不清楚,因为我本地11883出错了,而1883是正常的

1
constructor({ serverAddress = "127.0.0.1:1883", productId, deviceId, secret, clientID, storePath } = {}) {

这样才能正常连接虚拟设备,并且可以在EMQX本地的dashboard中看到设备连接

前端正常显示设备的创建时间

rain-cloud-vue/src/views/device/deviceDetail.vue文件中,可以看到“创建时间”一栏是写死的“2022年3月27日17:33:31”,于是就希望显示真正的创建时间,做如下修改:

rain-cloud-vue/src/views/device/deviceDetail.vue文件中的computed部分添加一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
formattedCreateTime() {
console.log('formData.createTime:', this.formData.createTime);
if (this.formData.createTime) {
let date = new Date(this.formData.createTime);
console.log('date:', date);
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
let hours = date.getHours();
let minutes = date.getMinutes();
let seconds = date.getSeconds();
let formattedDate = `${year}${month}${day}${hours}:${minutes}:${seconds}`;
console.log('formattedDate:', formattedDate);
return formattedDate;
} else {
return '';
}
},

这个函数用于将ISO格式的日期(2024-03-28T01:18:35.253Z),转化为人更好阅读的2024-03-28T01:18:35.253Z

然后将这个返回的字符串显示在对应位置:

1
2
3
4
5
6
7
8
9
<el-descriptions-item>
<template slot="label">
<i class="el-icon-time"></i>
创建时间
</template>
<!-- 2022年3月27日17:33:31 -->
<!-- {{ formData.createTime }} -->
{{ formattedCreateTime }}
</el-descriptions-item>

{{}}包括起来是Vue里的一种用法:

在Vue.js中,{{}}被称为文本插值,它用于将数据绑定到视图。当你在{{}}中写入一个变量名时,Vue.js会自动将这个变量的值插入到HTML中。

设备列表界面

前端设备总列表显示启用数字不正确

还能有这种问题?我是真不明白,你是怎么写错的,能不能对自己的毕设上点心?

rain-iot-server/controllers/deviceController.js里的getAllDevices函数修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const getAllDevices = function (req, res) {
Device.find(async function (err, devices) {
if (err) {
res.send(err)
} else {
const result = []
let deviceTotalNum = devices.length;
let deviceEnableNum = 0;
let deviceOnlineNum = 0;
for (const device of devices) {
//在此修改,多一个判断条件
if (device.status == 'active') {
deviceEnableNum++
}
const connection = await Connection.findOne({ device: device._id })
if (connection != null) {
const connected = connection.toJSONObject().connected
result.push(Object.assign(device.toJSONObject(), {
connectStatus: connected
}))
if (connected) {
deviceOnlineNum++
}
} else {
result.push(device.toJSONObject())
}
}
res.json(Object.assign({}, {
data: result,
deviceTotalNum,
deviceEnableNum,
deviceOnlineNum
}))
}
})
}

前端设备列表启用设备数目即时刷新

rain-cloud-vue/src/views/device/deviceList.vue文件中,将handleEnable函数修改为以下形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 切换开关启用设备
*/
handleEnable(row) {
console.log("call switch");
console.log(row.enableStatus);
if (row.enableStatus) {
// 启用 /active
putAction(
`${this.url.active}/${row.productId}/${row.deviceId}/active`
).then((res) => {
console.log(res);
this.$message.success("启用成功!");
//添加刷新
this.loadData(1);
});
} else {
// /suspend
row.connectStatus = false
putAction(
`${this.url.suspend}/${row.productId}/${row.deviceId}/suspend`
).then((res) => {
console.log(res);
this.$message.success("禁用成功!");
//添加刷新
this.loadData(1);
});
}
},

批量删除功能重写

源代码写的有问题,做以下修改:

rain-cloud-vue/src/views/device/deviceList.vue文件中,将handleBatchDelete函数修改为以下形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
handleBatchDelete() {
if (this.selectedRows.length <= 0) {
this.$message.warning("请至少选择一条记录!");
return;
} else {
this.$confirm("是否确认删除选中数据?", "确认删除", {
type: "warning",
}).then(() => {
let deletePromises = this.selectedRows.map(row => deleteAction(`${this.url.delete}/${row.productId}/${row.deviceId}`));
Promise.all(deletePromises)
.then(() => {
this.$message({
type: "success",
message: "批量删除成功",
});
// 删除后刷新
this.loadData(1);
})
.catch((error) => {
console.log(error);
this.$message.error("批量删除失败");
});
});
}
},





参考资料

vm.args needs to have a -name parameter.