feat(mqtt): 实现MQTT客户端,支持系统资源监控和关机控制

添加新的MQTT客户端实现,具有系统资源使用情况监测和远程关机功能。客户端通过MQTT协议与服务器交换信息,订阅特定主题以接收控制命令,并发布系统资源使用情况。此功能允许在远程系统上执行关机操作,并监测CPU及内存使用情况。

- 实现PCInfo结构体以存储系统资源使用信息。
- 实现Command结构体以解析接收到的控制命令。
- 编写onMessageReceived处理函数以响应接收到的消息。
- 实现executeShutdown函数以处理不同操作系统的关机逻辑。
- 主函数中初始化MQTT客户端,加载环境变量,订阅主题,并循环发布系统资源信息。
This commit is contained in:
Wuqiyang312 2024-08-22 17:39:53 +08:00
commit 6274a2bac0
14 changed files with 276 additions and 0 deletions

3
.env Normal file
View File

@ -0,0 +1,3 @@
MQTT_BROKER=tcp://127.0.0.1:1883
MQTT_CLIENT_ID=local
MQTT_TOPIC=pc/0

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

6
.idea/misc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/pc_mqtt.iml" filepath="$PROJECT_DIR$/pc_mqtt.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

3
app/pc_mqtt/.env Executable file
View File

@ -0,0 +1,3 @@
MQTT_BROKER=tcp://127.0.0.1:1883
MQTT_CLIENT_ID=local
MQTT_TOPIC=pc/0

BIN
app/pc_mqtt/client Executable file

Binary file not shown.

33
app/pc_mqtt/install.sh Executable file
View File

@ -0,0 +1,33 @@
#!/bin/bash
# 确保脚本以root权限运行
if [ "$(id -u)" -ne "0" ]; then
echo "This script must be run as root." 1>&2
exit 1
fi
# 设置变量
SERVICE_FILE="/etc/systemd/system/mqtt-info-tool.service"
BINARY_FILE="/opt/app/pc_mqtt/client"
# 确保Go程序可执行
chmod +x $BINARY_FILE
# 安装systemd服务文件
echo "Copying systemd service file to /etc/systemd/system..."
cp ./mqtt-info-tool.service $SERVICE_FILE
# 重新加载systemd配置
echo "Reloading systemd daemon..."
systemctl daemon-reload
# 启动服务并设置为开机自启
echo "Starting MQTT Info Tool service..."
systemctl start mqtt-info-tool
systemctl enable mqtt-info-tool
# 打印服务状态
echo "Service status:"
systemctl status mqtt-info-tool
echo "Installation completed successfully."

View File

@ -0,0 +1,16 @@
[Unit]
Description=MQTT PC Information Tool
After=network.target
[Service]
Type=simple
ExecStart=/opt/app/pc_mqtt/client
WorkingDirectory=/opt/app/pc_mqtt
EnvironmentFile=/opt/app/pc_mqtt/.env
Restart=always
User=root
Group=root
[Install]
WantedBy=multi-user.target

BIN
client Executable file

Binary file not shown.

132
client.go Normal file
View File

@ -0,0 +1,132 @@
package main
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"runtime"
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/joho/godotenv"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/mem"
)
// PCInfo 定义 PC 使用信息的结构。
type PCInfo struct {
CPUUsage float64 `json:"cpu_usage"` // CPU
MemUsage float64 `json:"mem_usage"` // Memory
}
// Command 定义接收命令的结构。
type Command struct {
Action string `json:"action"` // Action 待执行
}
// loadEnv 加载 .env 文件以读取环境变量。
func loadEnv() {
err := godotenv.Load(".env")
if err != nil {
fmt.Println("Error loading .env file")
}
}
// getPCInfo 检索当前 PC 使用信息。
func getPCInfo() (PCInfo, error) {
cpuPercentages, err := cpu.Percent(0, false)
if err != nil {
return PCInfo{}, err
}
memInfo, err := mem.VirtualMemory()
if err != nil {
return PCInfo{}, err
}
// 保留2位小数
return PCInfo{
CPUUsage: float64(int(cpuPercentages[0]*100)) / 100,
MemUsage: float64(int(memInfo.UsedPercent*100)) / 100,
}, nil
}
// onMessageReceived 处理从 MQTT 接收的消息。
func onMessageReceived(client mqtt.Client, msg mqtt.Message) {
var cmd Command
err := json.Unmarshal(msg.Payload(), &cmd)
if err != nil {
fmt.Println("Error decoding JSON command:", err)
return
}
if cmd.Action == "shutdown" {
fmt.Println("Shutdown command received. Shutting down...")
executeShutdown()
}
}
// executeShutdown 根据操作系统执行关机命令。
func executeShutdown() {
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("shutdown", "/s", "/t", "0")
case "linux", "darwin":
cmd = exec.Command("shutdown", "-h", "now")
default:
fmt.Println("Shutdown not supported on this OS")
return
}
err := cmd.Run()
if err != nil {
fmt.Println("Failed to execute shutdown:", err)
}
}
func main() {
loadEnv()
broker := os.Getenv("MQTT_BROKER")
clientID := os.Getenv("MQTT_CLIENT_ID")
topic := os.Getenv("MQTT_TOPIC")
opts := mqtt.NewClientOptions()
opts.AddBroker(broker)
opts.SetClientID(clientID)
opts.SetDefaultPublishHandler(onMessageReceived)
client := mqtt.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
fmt.Println(token.Error())
return
}
// 订阅主题以接收命令
client.Subscribe(topic, 0, nil)
for {
pcInfo, err := getPCInfo()
if err != nil {
fmt.Println("Error getting PC info:", err)
continue
}
pcInfoJSON, err := json.Marshal(pcInfo)
if err != nil {
fmt.Println("Error encoding PC info to JSON:", err)
continue
}
token := client.Publish(topic, 0, false, pcInfoJSON)
token.Wait()
fmt.Println("Message published:", string(pcInfoJSON))
time.Sleep(10 * time.Second) // 控制发送频率例如每10秒发送一次
}
// 完成后断开 MQTT 客户端连接
// client.Disconnect(250)
}

21
go.mod Normal file
View File

@ -0,0 +1,21 @@
module pc_mqtt
go 1.23.0
require (
github.com/eclipse/paho.mqtt.golang v1.5.0
github.com/joho/godotenv v1.5.1
github.com/shirou/gopsutil v3.21.11+incompatible
)
require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.8.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
)

31
go.sum Normal file
View File

@ -0,0 +1,31 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eclipse/paho.mqtt.golang v1.5.0 h1:EH+bUVJNgttidWFkLLVKaQPGmkTUfQQqjOsyvMGvD6o=
github.com/eclipse/paho.mqtt.golang v1.5.0/go.mod h1:du/2qNQVqJf/Sqs4MEL77kR8QTqANF7XU7Fk0aOTAgk=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

9
pc_mqtt.iml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>