From 98a96392fa89a5e46089f3d2f2d3ac00b49e44ad Mon Sep 17 00:00:00 2001 From: hamster1963 <1410514192@qq.com> Date: Tue, 3 Oct 2023 15:57:52 +0800 Subject: [PATCH] =?UTF-8?q?update:=20=E5=88=9D=E5=A7=8B=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 1 + .github/dependabot.yml | 16 ++ .github/workflows/GoCompileTest.yml | 52 +++++ .gitignore | 14 ++ Dockerfile | 55 +++++ README.md | 8 + api/data_core/data_core.go | 22 ++ api/data_core/v1/api_data_core.go | 89 ++++++++ go.mod | 41 ++++ go.sum | 95 ++++++++ hack/config.yaml | 10 + internal/boot/boot.go | 170 +++++++++++++++ internal/cmd/cmd.go | 65 ++++++ internal/controller/data_core/data_core.go | 5 + .../controller/data_core/data_core_new.go | 15 ++ .../data_core_v1_get_docker_monitor_sse.go | 40 ++++ .../data_core_v1_get_home_data_sse.go | 39 ++++ .../data_core_v1_get_network_data_sse.go | 63 ++++++ .../data_core/data_core_v1_get_task_list.go | 24 +++ .../data_core_v1_get_uptime_data_sse.go | 39 ++++ .../data_core_v1_get_xui_data_sse.go | 35 +++ .../data_core/data_core_v1_recover_task.go | 25 +++ .../data_core/data_core_v1_stop_task.go | 22 ++ internal/global/g_functions/functions.go | 42 ++++ internal/global/g_index/index.go | 33 +++ internal/global/g_middleware/middleware.go | 191 ++++++++++++++++ internal/logic/.gitkeep | 0 internal/logic/logic.go | 7 + internal/packed/packed.go | 1 + internal/router/r_hamster_router/router.go | 19 ++ main.go | 28 +++ manifest/config/config.yaml | 28 +++ manifest/consts.go | 79 +++++++ tag.sh | 2 + utility/.gitkeep | 0 utility/bin_utils/bin_info.go | 19 ++ utility/docker_utils/docker.go | 57 +++++ utility/ha_utils/ha_entity.go | 119 ++++++++++ utility/network_utils/coffee.go | 68 ++++++ utility/network_utils/home_network.go | 94 ++++++++ utility/network_utils/node_utils.go | 129 +++++++++++ utility/network_utils/proxy_network.go | 204 ++++++++++++++++++ utility/redis_test.go | 143 ++++++++++++ utility/server_utils/nezha.go | 73 +++++++ utility/server_utils/server_cron.go | 34 +++ utility/uptime_utils/kuma.go | 114 ++++++++++ 46 files changed, 2429 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/GoCompileTest.yml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 api/data_core/data_core.go create mode 100644 api/data_core/v1/api_data_core.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 hack/config.yaml create mode 100644 internal/boot/boot.go create mode 100644 internal/cmd/cmd.go create mode 100644 internal/controller/data_core/data_core.go create mode 100644 internal/controller/data_core/data_core_new.go create mode 100644 internal/controller/data_core/data_core_v1_get_docker_monitor_sse.go create mode 100644 internal/controller/data_core/data_core_v1_get_home_data_sse.go create mode 100644 internal/controller/data_core/data_core_v1_get_network_data_sse.go create mode 100644 internal/controller/data_core/data_core_v1_get_task_list.go create mode 100644 internal/controller/data_core/data_core_v1_get_uptime_data_sse.go create mode 100644 internal/controller/data_core/data_core_v1_get_xui_data_sse.go create mode 100644 internal/controller/data_core/data_core_v1_recover_task.go create mode 100644 internal/controller/data_core/data_core_v1_stop_task.go create mode 100644 internal/global/g_functions/functions.go create mode 100644 internal/global/g_index/index.go create mode 100644 internal/global/g_middleware/middleware.go create mode 100644 internal/logic/.gitkeep create mode 100644 internal/logic/logic.go create mode 100644 internal/packed/packed.go create mode 100644 internal/router/r_hamster_router/router.go create mode 100644 main.go create mode 100644 manifest/config/config.yaml create mode 100644 manifest/consts.go create mode 100644 tag.sh create mode 100644 utility/.gitkeep create mode 100644 utility/bin_utils/bin_info.go create mode 100644 utility/docker_utils/docker.go create mode 100644 utility/ha_utils/ha_entity.go create mode 100644 utility/network_utils/coffee.go create mode 100644 utility/network_utils/home_network.go create mode 100644 utility/network_utils/node_utils.go create mode 100644 utility/network_utils/proxy_network.go create mode 100644 utility/redis_test.go create mode 100644 utility/server_utils/nezha.go create mode 100644 utility/server_utils/server_cron.go create mode 100644 utility/uptime_utils/kuma.go diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1fbf887 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* linguist-language=GO \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..15d1381 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gomod" # Golang + directory: "/" # Location of package manifests + schedule: + interval: "daily" + + - package-ecosystem: "github-actions" # GitHub Actions + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/GoCompileTest.yml b/.github/workflows/GoCompileTest.yml new file mode 100644 index 0000000..6a48652 --- /dev/null +++ b/.github/workflows/GoCompileTest.yml @@ -0,0 +1,52 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: 代码编译测试 + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + + build-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.21 + + - name: Get Git Commit Log + id: git-commit-log + run: echo "GIT_COMMIT_LOG=${{ github.sha }}" >> $GITHUB_ENV + + - name: Get current time + uses: josStorer/get-current-time@v2 + id: current-time + with: + format: YYYYMMDD-HH + utcOffset: "+08:00" + + - name: Get Build Time + id: build-time + run: echo "BUILD_TIME=${{ steps.current-time.outputs.readableTime }}" >> $GITHUB_ENV + + - name: Print Environment Variables + run: | + echo "${{env.GIT_COMMIT_LOG}}" + echo "${{env.BUILD_TIME}}" + + - name: Install GF CLI dependencies + run: | + wget -O gf "https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS)_$(go env GOARCH)" && chmod +x gf && ./gf install -y && rm ./gf + + - name: GF CLI Build Binary + run: | + go build + pwd + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6dadcf8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.buildpath +.hgignore.swp +.project +.orig +.swp +.idea/ +.settings/ +.vscode/ +bin/ +**/.DS_Store +main +output/ +manifest/output/ +temp/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c828298 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,55 @@ +FROM golang:1.21-bullseye AS builder + +# 设置构建参数的默认值 +ARG GIT_TAG="unknown" +ARG GIT_COMMIT_LOG="unknown" +ARG BUILD_TIME="unknown" +ARG BUILD_GO_VERSION="github@action golang:1.21-bullseye" + + +WORKDIR /go/src/app +COPY . . + +# 打印构建参数 +RUN echo "GIT_TAG=${GIT_TAG}" +RUN echo "GIT_COMMIT_LOG=${GIT_COMMIT_LOG}" +RUN echo "BUILD_TIME=${BUILD_TIME}" +RUN echo "BUILD_GO_VERSION=${BUILD_GO_VERSION}" + + +# 设置 LDFlags 变量 +ENV LDFLAGS=" \ + -X 'main.GitTag=${GIT_TAG}' \ + -X 'main.GitCommitLog=${GIT_COMMIT_LOG}' \ + -X 'main.BuildTime=${BUILD_TIME}' \ + -X 'main.BuildGoVersion=${BUILD_GO_VERSION}' \ +" + +RUN wget -O gf "https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS)_$(go env GOARCH)" && chmod +x gf && ./gf install -y && rm ./gf +RUN gf build -e "-ldflags \"${LDFLAGS}\" " + +FROM loads/alpine:3.8 + +LABEL maintainer="Hamster " + +############################################################################### +# INSTALLATION +############################################################################### + +# 设置固定的项目路径 +ENV WORKDIR /app/main +COPY --from=builder /go/src/app/temp/release/linux_amd64/service $WORKDIR/service +# 添加应用可执行文件,并设置执行权限 +RUN chmod +x $WORKDIR/service +# 增加端口绑定 +EXPOSE 10401 + +############################################################################### +# START +############################################################################### +WORKDIR $WORKDIR +CMD ["./service"] + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..e027bdf --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# 家庭网络信息采集后端 +监控家中与网络有关的一切 +## 已接入 +- [x] 家庭路由器 +- [x] 家庭NAS +- [x] 家庭服务器 +- [x] 推送中心 +- [x] 代理节点信息 \ No newline at end of file diff --git a/api/data_core/data_core.go b/api/data_core/data_core.go new file mode 100644 index 0000000..fdc9216 --- /dev/null +++ b/api/data_core/data_core.go @@ -0,0 +1,22 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package data_core + +import ( + "context" + + "home-network-watcher/api/data_core/v1" +) + +type IDataCoreV1 interface { + GetXuiDataSSE(ctx context.Context, req *v1.GetXuiDataSSEReq) (res *v1.GetXuiDataSSERes, err error) + GetNetworkDataSSE(ctx context.Context, req *v1.GetNetworkDataSSEReq) (res *v1.GetNetworkDataSSERes, err error) + GetDockerMonitorSSE(ctx context.Context, req *v1.GetDockerMonitorSSEReq) (res *v1.GetDockerMonitorSSERes, err error) + GetUptimeDataSSE(ctx context.Context, req *v1.GetUptimeDataSSEReq) (res *v1.GetUptimeDataSSERes, err error) + GetHomeDataSSE(ctx context.Context, req *v1.GetHomeDataSSEReq) (res *v1.GetHomeDataSSERes, err error) + GetTaskList(ctx context.Context, req *v1.GetTaskListReq) (res *v1.GetTaskListRes, err error) + StopTask(ctx context.Context, req *v1.StopTaskReq) (res *v1.StopTaskRes, err error) + RecoverTask(ctx context.Context, req *v1.RecoverTaskReq) (res *v1.RecoverTaskRes, err error) +} diff --git a/api/data_core/v1/api_data_core.go b/api/data_core/v1/api_data_core.go new file mode 100644 index 0000000..f4dc666 --- /dev/null +++ b/api/data_core/v1/api_data_core.go @@ -0,0 +1,89 @@ +package v1 + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +// GetXuiDataSSEReq 获取 xui 网络数据 Req请求 +type GetXuiDataSSEReq struct { + g.Meta `method:"get" tags:"xui" summary:"获取 xui 网络数据" dc:"获取 xui 网络数据"` +} + +// GetXuiDataSSERes 获取 xui 网络数据 Res返回 +type GetXuiDataSSERes struct { + XuiData interface{} `json:"xuiData" dc:"xui数据"` +} + +// GetNetworkDataSSEReq 获取网络信息-SSE Req请求 +type GetNetworkDataSSEReq struct { + g.Meta `method:"get" tags:"家庭网络" summary:"获取网络信息-SSE" dc:"获取网络信息-SSE"` +} + +// GetNetworkDataSSERes 获取网络信息-SSE Res返回 +type GetNetworkDataSSERes struct { + NodeInfo interface{} `json:"nodeInfo" dc:"节点信息"` + HomeNetwork interface{} `json:"homeNetwork" dc:"家庭网络"` + ProxyNetwork interface{} `json:"proxyNetwork" dc:"科学上网"` + CoffeeInfo interface{} `json:"coffeeInfo" dc:"coffee代理信息"` + ServerInfo interface{} `json:"serverInfo" dc:"服务器信息"` +} + +// GetDockerMonitorSSEReq 获取 Docker 监控数据 Req请求 +type GetDockerMonitorSSEReq struct { + g.Meta `method:"get" tags:"docker" summary:"获取 Docker 监控数据" dc:"获取 Docker 监控数据"` +} + +// GetDockerMonitorSSERes 获取 Docker 监控数据 Res返回 +type GetDockerMonitorSSERes struct { + DockerData interface{} `json:"dockerData" dc:"Docker数据"` +} + +// GetUptimeDataSSEReq 获取 uptime 数据 Req请求 +type GetUptimeDataSSEReq struct { + g.Meta `method:"get" tags:"uptime" summary:"获取 uptime 数据" dc:"获取 uptime 数据"` +} + +// GetUptimeDataSSERes 获取 uptime 数据 Res返回 +type GetUptimeDataSSERes struct { + UptimeData interface{} `json:"uptimeData" dc:"uptime数据"` +} + +// GetHomeDataSSEReq 获取智能家居数据 Req请求 +type GetHomeDataSSEReq struct { + g.Meta `method:"get" tags:"home" summary:"获取智能家居数据" dc:"获取智能家居数据"` +} + +// GetHomeDataSSERes 获取智能家居数据 Res返回 +type GetHomeDataSSERes struct { + HomeData interface{} `json:"homeData" dc:"智能家居数据"` +} + +// GetTaskListReq 获取定时任务列表 Req请求 +type GetTaskListReq struct { + g.Meta `method:"get" tags:"任务" summary:"获取定时任务列表" dc:"获取定时任务列表"` +} + +// GetTaskListRes 获取定时任务列表 Res返回 +type GetTaskListRes struct { + TaskList interface{} `json:"taskList" dc:"定时任务列表"` +} + +// StopTaskReq 停止定时任务 Req请求 +type StopTaskReq struct { + g.Meta `method:"post" tags:"任务" summary:"停止定时任务" dc:"停止定时任务"` + Name string `json:"name" dc:"任务名称" v:"required #请输入 任务名称"` +} + +// StopTaskRes 停止定时任务 Res返回 +type StopTaskRes struct { +} + +// RecoverTaskReq 恢复定时任务 Req请求 +type RecoverTaskReq struct { + g.Meta `method:"post" tags:"任务" summary:"恢复定时任务" dc:"恢复定时任务"` + Name string `json:"name" dc:"任务名称" v:"required #请输入 任务名称"` +} + +// RecoverTaskRes 恢复定时任务 Res返回 +type RecoverTaskRes struct { +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..79e6aaa --- /dev/null +++ b/go.mod @@ -0,0 +1,41 @@ +module home-network-watcher + +go 1.21 + +require ( + github.com/gogf/gf/contrib/drivers/mysql/v2 v2.5.4 + github.com/gogf/gf/contrib/nosql/redis/v2 v2.5.4 + github.com/gogf/gf/v2 v2.5.4 + github.com/golang-jwt/jwt/v5 v5.0.0 + github.com/google/uuid v1.3.1 + github.com/hamster1963/360-router-data-retriever v0.2.8 +) + +require ( + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/clbanning/mxj/v2 v2.7.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/fatih/color v1.15.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/grokify/html-strip-tags-go v0.0.1 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/redis/go-redis/v9 v9.2.1 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + go.opentelemetry.io/otel v1.19.0 // indirect + go.opentelemetry.io/otel/metric v1.19.0 // indirect + go.opentelemetry.io/otel/sdk v1.19.0 // indirect + go.opentelemetry.io/otel/trace v1.19.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a03d2c7 --- /dev/null +++ b/go.sum @@ -0,0 +1,95 @@ +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= +github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/gogf/gf/contrib/drivers/mysql/v2 v2.5.4 h1:xAmYQZEDBDoce/q5s7UTibYHHW0DSTApfmXVC/i0/zI= +github.com/gogf/gf/contrib/drivers/mysql/v2 v2.5.4/go.mod h1:lEgzJw5PLBOEJ4gZHgs1GwsbjyBLBttN2sN63rYRNhk= +github.com/gogf/gf/contrib/nosql/redis/v2 v2.5.4 h1:t4Ls/E022VMk5hc3a4IgWO3xGNEQRXicCTOrFymZdDg= +github.com/gogf/gf/contrib/nosql/redis/v2 v2.5.4/go.mod h1:1P7id3OWFb3lkp1zBKdGqRoVXgYAjATzky++tGh2vxc= +github.com/gogf/gf/v2 v2.5.4 h1:UBCSw8mInkHmEqL0E1LYc6QhSpaNFY/wHcFrTI/rzTk= +github.com/gogf/gf/v2 v2.5.4/go.mod h1:7yf5qp0BznfsYx7Sw49m3mQvBsHpwAjJk3Q9ZnKoUEc= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0= +github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78= +github.com/hamster1963/360-router-data-retriever v0.2.8 h1:sakN7UMIfvMCNrBvXS8oirQ5S1rP/VXK/lu86P6E/ck= +github.com/hamster1963/360-router-data-retriever v0.2.8/go.mod h1:wL8v8dkHUXlHLEjArcbUNnuvsTzhjb+CLOc5knKNLyA= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +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/redis/go-redis/v9 v9.2.0 h1:zwMdX0A4eVzse46YN18QhuDiM4uf3JmkOB4VZrdt5uI= +github.com/redis/go-redis/v9 v9.2.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= +github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +go.opentelemetry.io/otel v1.18.0 h1:TgVozPGZ01nHyDZxK5WGPFB9QexeTMXEH7+tIClWfzs= +go.opentelemetry.io/otel v1.18.0/go.mod h1:9lWqYO0Db579XzVuCKFNPDl4s73Voa+zEck3wHaAYQI= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/metric v1.18.0 h1:JwVzw94UYmbx3ej++CwLUQZxEODDj/pOuTCvzhtRrSQ= +go.opentelemetry.io/otel/metric v1.18.0/go.mod h1:nNSpsVDjWGfb7chbRLUNW+PBNdcSTHD4Uu5pfFMOI0k= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/sdk v1.18.0 h1:e3bAB0wB3MljH38sHzpV/qWrOTCFrdZF2ct9F8rBkcY= +go.opentelemetry.io/otel/sdk v1.18.0/go.mod h1:1RCygWV7plY2KmdskZEDDBs4tJeHG92MdHZIluiYs/M= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.18.0 h1:NY+czwbHbmndxojTEKiSMHkG2ClNH2PwmcHrdo0JY10= +go.opentelemetry.io/otel/trace v1.18.0/go.mod h1:T2+SGJGuYZY3bjj5rgh/hN7KIrlpWC5nS8Mjvzckz+0= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hack/config.yaml b/hack/config.yaml new file mode 100644 index 0000000..d875794 --- /dev/null +++ b/hack/config.yaml @@ -0,0 +1,10 @@ +# https://goframe.org/pages/viewpage.action?pageId=3673173 +gfcli: + build: + name: "service" + arch: "amd64" # all,386,amd64,arm64 + system: "linux" # all,linux,darwin,windows.... + packSrc: "manifest/config" + version: "release" + mod: "" + cgo: 0 diff --git a/internal/boot/boot.go b/internal/boot/boot.go new file mode 100644 index 0000000..48cfd40 --- /dev/null +++ b/internal/boot/boot.go @@ -0,0 +1,170 @@ +package boot + +import ( + "context" + "github.com/gogf/gf/v2/os/gcron" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" + "home-network-watcher/utility/ha_utils" + "home-network-watcher/utility/network_utils" + "home-network-watcher/utility/server_utils" + "home-network-watcher/utility/uptime_utils" +) + +// Boot +// +// @dc: 定时任务启动 +// @return: error +func Boot() (err error) { + _, err = gcron.AddOnce(context.TODO(), "@every 1s", func(ctx context.Context) { + glog.Debug(context.Background(), "定时任务启动中...") + if err := bootMethod(); err != nil { + glog.Fatal(context.Background(), "定时任务启动失败: ", err) + } + glog.Debug(context.Background(), "定时任务启动成功") + }, "开始启动定时任务") + if err != nil { + return err + } + + _, err = gcron.AddOnce(context.TODO(), "@every 15s", func(ctx context.Context) { + glog.Info(context.Background(), "定时任务测试中...") + if err := bootCheck(); err != nil { + glog.Fatal(context.Background(), "定时任务测试失败: ", err) + } else { + glog.Info(context.Background(), "定时任务测试成功") + } + }, "开始测试定时任务") + if err != nil { + return err + } + + return nil +} + +// bootCheck +// +// @Description: 定时任务测试 +// @return error +func bootCheck() (err error) { + return nil +} + +// bootMethod +// @Description: 定时任务启动 +// @return error +func bootMethod() (err error) { + var ctx = gctx.New() + + glog.Notice(ctx, "开始获取科学上网网速") + _, err = gcron.AddSingleton(ctx, "@every 1s", func(ctx context.Context) { + err = network_utils.ProxyNetwork.GetProxyNetwork() + if err != nil { + glog.Warning(ctx, "获取代理速度"+err.Error()) + } + }, "获取代理速度") + if err != nil { + return err + } + + glog.Notice(ctx, "开始获取家庭路由器网速") + _, err = gcron.AddSingleton(ctx, "@every 1s", func(ctx context.Context) { + err = network_utils.NetworkUtils.GetHomeNetwork() + if err != nil { + glog.Warning(ctx, "获取家庭路由器速度"+err.Error()) + } + }, "获取家庭路由器速度") + if err != nil { + return err + } + + glog.Notice(ctx, "开始获取当前代理节点信息") + _, err = gcron.AddSingleton(ctx, "@every 5s", func(ctx context.Context) { + err = network_utils.NodeUtils.GetNodeInfo() + if err != nil { + glog.Warning(ctx, "获取当前代理节点信息"+err.Error()) + } + }, "获取当前代理节点信息") + if err != nil { + return err + } + + // 进行第一次机场信息缓存 + err = network_utils.ProxyProvider.GetSubscribeInfo() + if err != nil { + glog.Warning(ctx, "获取机场订阅信息失败"+err.Error()) + } + + glog.Notice(ctx, "开始获取机场订阅信息") + _, err = gcron.AddSingleton(ctx, "@every 10m", func(ctx context.Context) { + err = network_utils.ProxyProvider.GetSubscribeInfo() + if err != nil { + glog.Warning(ctx, "获取机场订阅信息失败"+err.Error()) + } + }, "开始获取机场订阅信息") + if err != nil { + return err + } + + // 获取服务器信息 + glog.Notice(ctx, "开始获取服务器信息") + _, err = gcron.AddSingleton(ctx, "@every 1s", func(ctx context.Context) { + err = server_utils.Nezha.GetAllServerInfo(ctx) + if err != nil { + glog.Warning(ctx, "获取服务器信息失败"+err.Error()) + } + }, "开始获取服务器信息") + if err != nil { + return err + } + + // 获取 xui 信息 + glog.Notice(ctx, "开始获取 xui 信息") + _, err = gcron.AddSingleton(ctx, "@every 5s", func(ctx context.Context) { + err = network_utils.ProxyNetwork.GetXuiUserList() + if err != nil { + glog.Warning(ctx, "获取 xui 信息失败"+err.Error()) + } + }, "开始获取 xui 信息") + if err != nil { + return err + } + + // 获取服务概览信息 + glog.Notice(ctx, "开始获取服务概览信息") + _, err = gcron.AddSingleton(ctx, "@every 10s", func(ctx context.Context) { + err = server_utils.ServerCron.CronGetDockerAndMonitor(ctx) + if err != nil { + glog.Warning(ctx, "获取服务概览信息失败"+err.Error()) + } + }, "开始获取服务概览信息") + if err != nil { + return err + } + + // 获取 UptimeKuma 信息 + glog.Notice(ctx, "开始获取 UptimeKuma 信息") + _, err = gcron.AddSingleton(ctx, "@every 10s", func(ctx context.Context) { + err = uptime_utils.Kuma.GetUptimeData(ctx) + if err != nil { + glog.Warning(ctx, "获取 UptimeKuma 信息失败"+err.Error()) + } + }, "开始获取 UptimeKuma 信息") + if err != nil { + return err + } + + // 获取智能家居信息 + glog.Notice(ctx, "开始获取智能家居信息") + _, err = gcron.AddSingleton(ctx, "@every 1s", func(ctx context.Context) { + err = ha_utils.HaUtils.GetEntitiesInfo(ctx) + if err != nil { + glog.Warning(ctx, "获取智能家居信息失败"+err.Error()) + } + }, "开始获取智能家居信息") + if err != nil { + return err + } + + return nil +} diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go new file mode 100644 index 0000000..09128f1 --- /dev/null +++ b/internal/cmd/cmd.go @@ -0,0 +1,65 @@ +package cmd + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gres" + "home-network-watcher/internal/boot" + "home-network-watcher/internal/global/g_functions" + "home-network-watcher/internal/global/g_index" + "home-network-watcher/internal/global/g_middleware" + "home-network-watcher/internal/router/r_hamster_router" + binInfo "home-network-watcher/utility/bin_utils" + "runtime" +) + +var ( + Main = gcmd.Command{ + Name: "main", + Usage: "main", + Brief: "start http server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + gres.Dump() + s := g.Server() + + // 性能分析 + runtime.SetMutexProfileFraction(1) // (非必需)开启对锁调用的跟踪 + runtime.SetBlockProfileRate(1) // (非必需)开启对阻塞操作的跟踪 + s.EnablePProf() + + // 统一日志服务 + g_functions.SetDefaultHandler() + // 服务状态码处理 + g_middleware.SMiddlewares.ErrorsStatus(s) + + // 全局中间件 + s.BindMiddlewareDefault( + g_middleware.SMiddlewares.MiddlewareCORS, + g_middleware.SMiddlewares.ResponseHandler, + ) + + s.Group("/", func(group *ghttp.RouterGroup) { + // 首页HTML + group.ALL("/", func(r *ghttp.Request) { + r.Response.Write(g_index.IndexHTML) + }) + group.ALL("/version", func(r *ghttp.Request) { + r.Response.Write(binInfo.VersionString()) + }) + // 接口绑定 + r_hamster_router.BindController(group) + }) + + // 初始化 + if err := boot.Boot(); err != nil { + glog.Fatal(ctx, "初始化任务失败: ", err) + } + + s.Run() + return nil + }, + } +) diff --git a/internal/controller/data_core/data_core.go b/internal/controller/data_core/data_core.go new file mode 100644 index 0000000..13bd9e4 --- /dev/null +++ b/internal/controller/data_core/data_core.go @@ -0,0 +1,5 @@ +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package data_core diff --git a/internal/controller/data_core/data_core_new.go b/internal/controller/data_core/data_core_new.go new file mode 100644 index 0000000..0a86853 --- /dev/null +++ b/internal/controller/data_core/data_core_new.go @@ -0,0 +1,15 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package data_core + +import ( + "home-network-watcher/api/data_core" +) + +type ControllerV1 struct{} + +func NewV1() data_core.IDataCoreV1 { + return &ControllerV1{} +} diff --git a/internal/controller/data_core/data_core_v1_get_docker_monitor_sse.go b/internal/controller/data_core/data_core_v1_get_docker_monitor_sse.go new file mode 100644 index 0000000..0b3d346 --- /dev/null +++ b/internal/controller/data_core/data_core_v1_get_docker_monitor_sse.go @@ -0,0 +1,40 @@ +package data_core + +import ( + "context" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcache" + "home-network-watcher/manifest" + "time" + + "home-network-watcher/api/data_core/v1" +) + +func (c *ControllerV1) GetDockerMonitorSSE(ctx context.Context, _ *v1.GetDockerMonitorSSEReq) (res *v1.GetDockerMonitorSSERes, err error) { + request := g.RequestFromCtx(ctx) + request.Response.Header().Set("Content-Type", "text/event-stream") + request.Response.Header().Set("Cache-Control", "no-cache") + request.Response.Header().Set("Connection", "keep-alive") + request.Response.Header().Set("Access-Control-Allow-Origin", "*") + request.Response.Header().Set("X-Accel-Buffering", "no") + + for { + // 从缓存中获取数据 + dockerData, err := gcache.Get(ctx, manifest.DockerMonitorCacheKey) + if err != nil { + return nil, err + } + res = &v1.GetDockerMonitorSSERes{DockerData: dockerData} + // 发送数据 + request.Response.Writefln("data: " + gjson.New(res).MustToJsonString() + "\n") + request.Response.Flush() + + // 等待10秒或者上下文取消 + select { + case <-time.After(10 * time.Second): + case <-ctx.Done(): + return nil, ctx.Err() + } + } +} diff --git a/internal/controller/data_core/data_core_v1_get_home_data_sse.go b/internal/controller/data_core/data_core_v1_get_home_data_sse.go new file mode 100644 index 0000000..0457954 --- /dev/null +++ b/internal/controller/data_core/data_core_v1_get_home_data_sse.go @@ -0,0 +1,39 @@ +package data_core + +import ( + "context" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcache" + "home-network-watcher/api/data_core/v1" + "home-network-watcher/manifest" + "time" +) + +func (c *ControllerV1) GetHomeDataSSE(ctx context.Context, _ *v1.GetHomeDataSSEReq) (res *v1.GetHomeDataSSERes, err error) { + request := g.RequestFromCtx(ctx) + request.Response.Header().Set("Content-Type", "text/event-stream") + request.Response.Header().Set("Cache-Control", "no-cache") + request.Response.Header().Set("Connection", "keep-alive") + request.Response.Header().Set("Access-Control-Allow-Origin", "*") + request.Response.Header().Set("X-Accel-Buffering", "no") + + for { + // 从缓存中获取数据 + homeData, err := gcache.Get(ctx, manifest.HaEntitiesCacheKey) + if err != nil { + return nil, err + } + res = &v1.GetHomeDataSSERes{HomeData: homeData} + // 发送数据 + request.Response.Writefln("data: " + gjson.New(res).MustToJsonString() + "\n") + request.Response.Flush() + + // 等待1秒或者上下文取消 + select { + case <-time.After(1 * time.Second): + case <-ctx.Done(): + return nil, ctx.Err() + } + } +} diff --git a/internal/controller/data_core/data_core_v1_get_network_data_sse.go b/internal/controller/data_core/data_core_v1_get_network_data_sse.go new file mode 100644 index 0000000..6c8f5ef --- /dev/null +++ b/internal/controller/data_core/data_core_v1_get_network_data_sse.go @@ -0,0 +1,63 @@ +package data_core + +import ( + "context" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcache" + "home-network-watcher/manifest" + "time" + + "home-network-watcher/api/data_core/v1" +) + +func (c *ControllerV1) GetNetworkDataSSE(ctx context.Context, _ *v1.GetNetworkDataSSEReq) (res *v1.GetNetworkDataSSERes, err error) { + request := g.RequestFromCtx(ctx) + request.Response.Header().Set("Content-Type", "text/event-stream") + request.Response.Header().Set("Cache-Control", "no-cache") + request.Response.Header().Set("Connection", "keep-alive") + request.Response.Header().Set("Access-Control-Allow-Origin", "*") + request.Response.Header().Set("X-Accel-Buffering", "no") + + for { + // 从缓存中获取数据 + nodeInfo, err := gcache.Get(ctx, manifest.ProxyNodeCacheKey) + if err != nil { + return nil, err + } + homeNetwork, err := gcache.Get(ctx, manifest.HomeNetworkCacheKey) + if err != nil { + return nil, err + } + proxyNetwork, err := gcache.Get(ctx, manifest.ProxyNetworkCacheKey) + if err != nil { + return nil, err + } + coffeeInfo, err := gcache.Get(ctx, manifest.ProxySubscribeCacheKey) + if err != nil { + return nil, err + } + serverInfo, err := gcache.Get(ctx, manifest.ServerDataCacheKey) + if err != nil { + return nil, err + } + res = &v1.GetNetworkDataSSERes{ + NodeInfo: nodeInfo, + HomeNetwork: homeNetwork, + ProxyNetwork: proxyNetwork, + CoffeeInfo: coffeeInfo, + ServerInfo: serverInfo, + } + // 发送数据 + request.Response.Writefln("data: " + gjson.New(res).MustToJsonString() + "\n") + request.Response.Flush() + + // 等待1秒或者上下文取消 + select { + case <-time.After(1 * time.Second): + case <-ctx.Done(): + return nil, ctx.Err() + } + } + +} diff --git a/internal/controller/data_core/data_core_v1_get_task_list.go b/internal/controller/data_core/data_core_v1_get_task_list.go new file mode 100644 index 0000000..894a20d --- /dev/null +++ b/internal/controller/data_core/data_core_v1_get_task_list.go @@ -0,0 +1,24 @@ +package data_core + +import ( + "context" + "github.com/gogf/gf/v2/os/gcron" + + "home-network-watcher/api/data_core/v1" +) + +// GetTaskList 获取任务列表 +func (c *ControllerV1) GetTaskList(_ context.Context, _ *v1.GetTaskListReq) (res *v1.GetTaskListRes, err error) { + var taskList []map[string]interface{} + for _, entry := range gcron.Entries() { + taskList = append(taskList, map[string]interface{}{ + "name": entry.Name, + "status": entry.Status(), + "time": entry.Time, + }) + } + res = &v1.GetTaskListRes{ + TaskList: taskList, + } + return res, nil +} diff --git a/internal/controller/data_core/data_core_v1_get_uptime_data_sse.go b/internal/controller/data_core/data_core_v1_get_uptime_data_sse.go new file mode 100644 index 0000000..3a7d838 --- /dev/null +++ b/internal/controller/data_core/data_core_v1_get_uptime_data_sse.go @@ -0,0 +1,39 @@ +package data_core + +import ( + "context" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcache" + "home-network-watcher/api/data_core/v1" + "home-network-watcher/manifest" + "time" +) + +func (c *ControllerV1) GetUptimeDataSSE(ctx context.Context, _ *v1.GetUptimeDataSSEReq) (res *v1.GetUptimeDataSSERes, err error) { + request := g.RequestFromCtx(ctx) + request.Response.Header().Set("Content-Type", "text/event-stream") + request.Response.Header().Set("Cache-Control", "no-cache") + request.Response.Header().Set("Connection", "keep-alive") + request.Response.Header().Set("Access-Control-Allow-Origin", "*") + request.Response.Header().Set("X-Accel-Buffering", "no") + + for { + // 从缓存中获取数据 + uptimeData, err := gcache.Get(ctx, manifest.UptimeCacheKey) + if err != nil { + return nil, err + } + res = &v1.GetUptimeDataSSERes{UptimeData: uptimeData} + // 发送数据 + request.Response.Writefln("data: " + gjson.New(res).MustToJsonString() + "\n") + request.Response.Flush() + + // 等待10秒或者上下文取消 + select { + case <-time.After(10 * time.Second): + case <-ctx.Done(): + return nil, ctx.Err() + } + } +} diff --git a/internal/controller/data_core/data_core_v1_get_xui_data_sse.go b/internal/controller/data_core/data_core_v1_get_xui_data_sse.go new file mode 100644 index 0000000..e956011 --- /dev/null +++ b/internal/controller/data_core/data_core_v1_get_xui_data_sse.go @@ -0,0 +1,35 @@ +package data_core + +import ( + "context" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcache" + "home-network-watcher/manifest" + "time" + + "home-network-watcher/api/data_core/v1" +) + +func (c *ControllerV1) GetXuiDataSSE(ctx context.Context, _ *v1.GetXuiDataSSEReq) (res *v1.GetXuiDataSSERes, err error) { + request := g.RequestFromCtx(ctx) + request.Response.Header().Set("Content-Type", "text/event-stream") + request.Response.Header().Set("Cache-Control", "no-cache") + request.Response.Header().Set("Connection", "keep-alive") + request.Response.Header().Set("Access-Control-Allow-Origin", "*") + request.Response.Header().Set("X-Accel-Buffering", "no") + + for { + // 从缓存中获取数据 + xuiData, err := gcache.Get(context.Background(), manifest.XUIUserListCacheKey) + if err != nil { + return nil, err + } + res = &v1.GetXuiDataSSERes{XuiData: xuiData} + // 发送数据 + request.Response.Writefln("data: " + gjson.New(res).MustToJsonString() + "\n") + request.Response.Flush() + time.Sleep(5 * time.Second) + } + +} diff --git a/internal/controller/data_core/data_core_v1_recover_task.go b/internal/controller/data_core/data_core_v1_recover_task.go new file mode 100644 index 0000000..76c8ef4 --- /dev/null +++ b/internal/controller/data_core/data_core_v1_recover_task.go @@ -0,0 +1,25 @@ +package data_core + +import ( + "context" + "github.com/gogf/gf/v2/os/gcron" + "home-network-watcher/internal/global/g_functions" + + "home-network-watcher/api/data_core/v1" +) + +// RecoverTask 恢复任务 +func (c *ControllerV1) RecoverTask(_ context.Context, req *v1.RecoverTaskReq) (res *v1.RecoverTaskRes, err error) { + var taskMap = make(map[string]int) + for _, entry := range gcron.Entries() { + taskMap[entry.Name] = entry.Status() + } + if _, ok := taskMap[req.Name]; !ok { + return nil, g_functions.ResErr(400, "任务不存在") + } + if taskMap[req.Name] == 1 || taskMap[req.Name] == 0 { + return nil, g_functions.ResErr(400, "任务已启动") + } + gcron.Start(req.Name) + return +} diff --git a/internal/controller/data_core/data_core_v1_stop_task.go b/internal/controller/data_core/data_core_v1_stop_task.go new file mode 100644 index 0000000..ca8e874 --- /dev/null +++ b/internal/controller/data_core/data_core_v1_stop_task.go @@ -0,0 +1,22 @@ +package data_core + +import ( + "context" + "github.com/gogf/gf/v2/os/gcron" + "home-network-watcher/internal/global/g_functions" + + "home-network-watcher/api/data_core/v1" +) + +// StopTask 停止任务 +func (c *ControllerV1) StopTask(_ context.Context, req *v1.StopTaskReq) (res *v1.StopTaskRes, err error) { + var taskMap = make(map[string]string) + for _, entry := range gcron.Entries() { + taskMap[entry.Name] = entry.Name + } + if _, ok := taskMap[req.Name]; !ok { + return nil, g_functions.ResErr(400, "任务不存在") + } + gcron.Stop(req.Name) + return +} diff --git a/internal/global/g_functions/functions.go b/internal/global/g_functions/functions.go new file mode 100644 index 0000000..69e9e11 --- /dev/null +++ b/internal/global/g_functions/functions.go @@ -0,0 +1,42 @@ +package g_functions + +import ( + "context" + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/util/gconv" +) + +func ResErr(code int, errMsg ...interface{}) error { + resMsg := "" + errDetail := "" + switch len(errMsg) { + case 0: + resMsg = "Unknown error reason" + case 1: + resMsg = gconv.String(errMsg[0]) + default: + for _, v := range gconv.SliceStr(errMsg[:len(errMsg)-1]) { + resMsg += v + "," + } + resMsg = resMsg[:len(resMsg)-1] + errDetail = gconv.String(errMsg[len(errMsg)-1]) + } + return gerror.NewCode(gcode.New(code, resMsg, nil), errDetail) +} + +// SetDefaultHandler 替代默认的日志handler +func SetDefaultHandler() { + glog.SetDefaultHandler(func(ctx context.Context, in *glog.HandlerInput) { + m := map[string]interface{}{ + "stdout": true, + "writerColorEnable": false, + "file": "inside-{Y-m-d}.log", + "path": g.Config().MustGet(ctx, "server.logger.path", "log/").String(), // 此处必须重新设置,才可以实现db的log写入文件 + } + _ = in.Logger.SetConfigWithMap(m) + in.Next(ctx) + }) +} diff --git a/internal/global/g_index/index.go b/internal/global/g_index/index.go new file mode 100644 index 0000000..37504e1 --- /dev/null +++ b/internal/global/g_index/index.go @@ -0,0 +1,33 @@ +package g_index + +const IndexHTML = ` + + + + 仓鼠咖啡馆 + + + +
仓鼠咖啡馆
+ + +` diff --git a/internal/global/g_middleware/middleware.go b/internal/global/g_middleware/middleware.go new file mode 100644 index 0000000..274c7eb --- /dev/null +++ b/internal/global/g_middleware/middleware.go @@ -0,0 +1,191 @@ +package g_middleware + +import ( + "context" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/glog" + "github.com/golang-jwt/jwt/v5" + "home-network-watcher/manifest" + "sync" + "time" +) + +type sMiddleware struct{} // 创建结构体 +var ( + once = &sync.Once{} // 创建互锁 + s *sMiddleware // 创建指针 + SMiddlewares = newMiddleware() // 对外暴露 +) + +// defaultHandlerResponse 返回结构体 +type defaultHandlerResponse struct { + Status int `json:"status" dc:"Error code"` // 可业务需要可以更改json字段 + Message string `json:"msg" dc:"Error message"` + Data interface{} `json:"data" dc:"Result data for certain request according API definition"` +} + +// newMiddleware 单例中间件 +func newMiddleware() *sMiddleware { + once.Do(func() { + s = &sMiddleware{} + }) + return s +} + +func (s *sMiddleware) MiddlewareCORS(r *ghttp.Request) { + r.Response.CORSDefault() + r.Middleware.Next() +} + +// resWriteJson 返回json输出 +func (s *sMiddleware) resWriteJson(r *ghttp.Request, in defaultHandlerResponse) { + r.Response.ClearBuffer() + r.Response.WriteJson(defaultHandlerResponse{ + Status: in.Status, + Message: in.Message, + Data: in.Data, + }) +} + +// ErrorsStatus 服务器错误码处理 +func (s *sMiddleware) ErrorsStatus(server *ghttp.Server) { + server.BindStatusHandlerByMap(map[int]ghttp.HandlerFunc{ + 500: func(r *ghttp.Request) { + s.resWriteJson(r, defaultHandlerResponse{ + Status: 500, + Message: "Error 500,Internal Server Error", + Data: "", + }) + }, + 404: func(r *ghttp.Request) { + s.resWriteJson(r, defaultHandlerResponse{ + Status: 404, + Message: "Error 404,Not Found", + Data: "", + }) + }, + }) +} + +// JWTAuth 鉴权中间件 +func (s *sMiddleware) JWTAuth(r *ghttp.Request) { + JWTString := r.GetHeader("Authorization") + if JWTString == "" { + s.resWriteJson(r, defaultHandlerResponse{ + Status: 401, + Message: "JWT为空", + Data: nil, + }) + return + } + + // 验证JWT + token, err := jwt.Parse(JWTString, func(token *jwt.Token) (interface{}, error) { + return manifest.JWTKey, nil + }) + if err != nil { + s.resWriteJson(r, defaultHandlerResponse{ + Status: 401, + Message: "JWT验证失败", + Data: nil, + }) + return + } + + if !token.Valid { + s.resWriteJson(r, defaultHandlerResponse{ + Status: 401, + Message: "JWT验证失败", + Data: nil, + }) + return + } + // 验证是否过期 + expirationTime, err := token.Claims.GetExpirationTime() + if err != nil { + s.resWriteJson(r, defaultHandlerResponse{ + Status: 401, + Message: "JWT验证失败", + Data: nil, + }) + return + } + if expirationTime.Before(time.Now()) { + s.resWriteJson(r, defaultHandlerResponse{ + Status: 401, + Message: "JWT已过期", + Data: nil, + }) + return + } + + glog.Info(context.TODO(), "JWT验证通过") + // 验证通过,设置用户信息 + audience, err := token.Claims.GetAudience() + if err != nil { + s.resWriteJson(r, defaultHandlerResponse{ + Status: 401, + Message: "JWT验证失败", + Data: nil, + }) + return + } + + r.SetCtxVar("user_id", audience[0]) + r.Middleware.Next() +} + +// ResponseHandler is the middleware handling handler response object and its error. 中间件处理处理程序响应对象及其错误 +func (s *sMiddleware) ResponseHandler(r *ghttp.Request) { + //defer action_log.NewActionLog().LogAdd(r) // 日志钩子 + r.Middleware.Next() + + // There's custom buffer content, it then exits current handler. + if r.Response.BufferLength() > 0 { + return + } + + // 定义参数和类型.方便调用 + var ( + code int + msg string + resData = r.GetHandlerResponse() // 检索并返回处理程序响应对象及其错误 + err = r.GetError() // 返回请求过程中发生的错误。如果没有错误,则返回 nil + errCode = gerror.Code(err) // 将错误信息通过接口解析处理 + ) + code = 200 // 默认返回状态码信息码 + msg = "success" // 默认成功返回响应体文本信息 + if err != nil { + code = 400 // 默认返回状态码信息码 + switch errCode.Code() { + case -1: + msg = "Nonstandard error return" + resData = "I won't expose it to you. Go back and change it!" + case 500: + r.Response.Writer.Status = 500 + return + default: + msg = errCode.Message() + resData = err.Error() + r.Response.Writer.Status = 400 + if msg == "" || msg == "Unknown error reason" { + msg = "Nonstandard error return" + resData = "I won't expose it to you. Go back and change it!" + } + if resData == msg { + resData = "" + } + } + } + // 如果是空res返回,则resData返回空字符串 + if g.IsNil(resData) { + resData = "" + } + s.resWriteJson(r, defaultHandlerResponse{ + Status: code, + Message: msg, + Data: resData, + }) +} diff --git a/internal/logic/.gitkeep b/internal/logic/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/internal/logic/logic.go b/internal/logic/logic.go new file mode 100644 index 0000000..c13fa2f --- /dev/null +++ b/internal/logic/logic.go @@ -0,0 +1,7 @@ +// ========================================================================== +// Code generated by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package logic + +import () diff --git a/internal/packed/packed.go b/internal/packed/packed.go new file mode 100644 index 0000000..e20ab1e --- /dev/null +++ b/internal/packed/packed.go @@ -0,0 +1 @@ +package packed diff --git a/internal/router/r_hamster_router/router.go b/internal/router/r_hamster_router/router.go new file mode 100644 index 0000000..d38df4d --- /dev/null +++ b/internal/router/r_hamster_router/router.go @@ -0,0 +1,19 @@ +package r_hamster_router + +import ( + "github.com/gogf/gf/v2/net/ghttp" + "home-network-watcher/internal/controller/data_core" +) + +func BindController(group *ghttp.RouterGroup) { + group.Group("/", func(group *ghttp.RouterGroup) { + BindDataCore(group) + }) +} + +// BindDataCore 注册核心数据路由 +func BindDataCore(group *ghttp.RouterGroup) { + group.Group("/", func(group *ghttp.RouterGroup) { + group.Bind(data_core.NewV1()) + }) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..6e7a7b9 --- /dev/null +++ b/main.go @@ -0,0 +1,28 @@ +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + "github.com/gogf/gf/v2/os/gctx" + "home-network-watcher/internal/cmd" + _ "home-network-watcher/internal/logic" + _ "home-network-watcher/internal/packed" + binInfo "home-network-watcher/utility/bin_utils" +) + +var ( + GitTag = "unknown" + GitCommitLog = "unknown" + GitStatus = "cleanly" + BuildTime = "unknown" + BuildGoVersion = "unknown" +) + +func main() { + // 注入编译时的信息 + binInfo.GitTag = GitTag + binInfo.GitCommitLog = GitCommitLog + binInfo.GitStatus = GitStatus + binInfo.BuildTime = BuildTime + binInfo.BuildGoVersion = BuildGoVersion + cmd.Main.Run(gctx.New()) +} diff --git a/manifest/config/config.yaml b/manifest/config/config.yaml new file mode 100644 index 0000000..d73ed1f --- /dev/null +++ b/manifest/config/config.yaml @@ -0,0 +1,28 @@ +# 服务配置:https://goframe.org/pages/viewpage.action?pageId=44449486 +server: + # 基本配置 + address: ":10401" # 本地监听地址 + serverAgent: "Hamster Server" # 服务端Agent信息。默认为"GoFrame HTTP Server" + nameToUriType: 1 # 路由注册中使用对象注册时的路由生成规则—-驼峰 + clientMaxBodySize: 810241024 # 客户端最大Body上传限制大小,影响文件上传大小(Byte)。默认为8*1024*1024=8MB + formParsingMemory: 1048576 # 解析表单时的缓冲区大小(Byte),一般不需要配置。默认为1024*1024=1MB + + # 接口文档 + openapiPath: "/api.json" # OpenAPI接口文档地址 + swaggerPath: "/swagger" # 内置SwaggerUI展示地址 + + # 服务日志配置(访问日志,错误日志) + logPath: "./LOG/server_log" # 日志文件目录 + logStdout: true # 是否输出到终端 + errorStack: false # 是否记录错误堆栈信息 + errorLogEnabled: true # 是否记录错误日志 + errorLogPattern: "error.{Ymd}.log" # 错误日志文件 + # 日志配置:https://goframe.org/pages/viewpage.action?pageId=1114388 + logger: + level: "all" # 日志级别 + stdout: true # 是否输出到终端 + path: "./LOG/" # 日志输出目录 + file: "log-{Y-m-d}.log" # 输出日志命名格式。 + writerColorEnable: true # 日志文件带有颜色 + + diff --git a/manifest/consts.go b/manifest/consts.go new file mode 100644 index 0000000..1d7b15e --- /dev/null +++ b/manifest/consts.go @@ -0,0 +1,79 @@ +package manifest + +import "github.com/gogf/gf/v2/frame/g" + +var ( + JWTKey = []byte("hamster-home") +) + +// home_network.go配置文件 +const ( + HomeNetworkRouterIP = "" + HomeNetworkRouterAddress = "" + HomeNetworkRouterPassword = "" +) + +// node_utils.go配置文件 +const ( + XrayBaseUrl = "ray.sample.top:580" // PS.不加http:// + XrayLoginDataMap = `{"username":"","password":""}` +) + +// proxy_network.go配置文件 +var ( + XuiBaseUrl = "http://" + XuiLoginDataMap = g.Map{ + "username": "", + "password": "", + } +) + +// nezha 配置文件 +const ( + NezhaApiUrl = "http://ip:port/api/v1/server/details?id=" + NezhaApiKey = "token" +) + +// uptime_kuma.配置文件 + +const ( + UptimeKumaApiUrl = "http://ip:port/api/status-page/heartbeat/hamster" +) + +// coffee.go配置文件 +var ( + ProxyProviderBaseUrl = "https://*****/api/v1/user/getSubscribe" + ProxyProviderLoginUrl = "https://*****/api/v1/passport/auth/login" + ProxyProviderAuthData = g.Map{ + "email": "", + "password": "", + } +) + +// docker部分配置文件 +var ( + DockerApiUrl = "http://ip:port/api/endpoints" + DockerAuthMap = map[string]string{"x-api-key": ""} +) + +// home_assistant配置文件 +var ( + HomeAssistantBaseUrl = "http://ip:port/api/states/" + HomeAssistantAuthMap = map[string]string{ + "Authorization": "Bearer ", + } +) + +// cache key +const ( + HomeNetworkCacheKey = "homeNetwork" + ProxyNetworkCacheKey = "proxyNetwork" + ProxySessionCacheKey = "proxySession" + ProxyNodeCacheKey = "proxyNode" + ProxySubscribeCacheKey = "proxySubscribe" + ServerDataCacheKey = "serverDataList" + DockerMonitorCacheKey = "dockerMonitor" + UptimeCacheKey = "uptime" + HaEntitiesCacheKey = "haEntities" + XUIUserListCacheKey = "xuiUserList" +) diff --git a/tag.sh b/tag.sh new file mode 100644 index 0000000..8e58bc1 --- /dev/null +++ b/tag.sh @@ -0,0 +1,2 @@ +# shellcheck disable=SC2046 +git describe --tags $(git rev-list --tags --max-count=1) \ No newline at end of file diff --git a/utility/.gitkeep b/utility/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/utility/bin_utils/bin_info.go b/utility/bin_utils/bin_info.go new file mode 100644 index 0000000..15107b4 --- /dev/null +++ b/utility/bin_utils/bin_info.go @@ -0,0 +1,19 @@ +package binInfo + +// 初始化为 unknown,如果编译时没有传入这些值,则为 unknown +var ( + GitTag = "unknown" + GitCommitLog = "unknown" + GitStatus = "cleanly" + BuildTime = "unknown" + BuildGoVersion = "unknown" +) + +// VersionString 返回版本信息 +func VersionString() string { + return "GitTag:" + GitTag + "\n" + + "GitCommitLog:" + GitCommitLog + "\n" + + "GitStatus:" + GitStatus + "\n" + + "BuildTime:" + BuildTime + "\n" + + "BuildGoVersion:" + BuildGoVersion + "\n" +} diff --git a/utility/docker_utils/docker.go b/utility/docker_utils/docker.go new file mode 100644 index 0000000..852ed4d --- /dev/null +++ b/utility/docker_utils/docker.go @@ -0,0 +1,57 @@ +package docker_utils + +import ( + "context" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gclient" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/util/gconv" + "home-network-watcher/manifest" +) + +type uDockerUtils struct{} + +var DockerUtils = &uDockerUtils{} + +type dockerStatus struct { + ServerCount int + ErrorServer int + DockerCount int + ErrorDocker int +} + +// GetDockerStatus +// +// @dc: 获取docker状态 +// @author: hamster @date:2023/9/20 15:33:19 +func (u *uDockerUtils) GetDockerStatus(ctx context.Context) (status *dockerStatus, err error) { + status = &dockerStatus{} + response, err := g.Client().SetHeaderMap(manifest.DockerAuthMap).Get(context.Background(), manifest.DockerApiUrl) + if err != nil { + return + } + defer func(response *gclient.Response) { + err = response.Close() + if err != nil { + glog.Warning(ctx, "关闭DockerStatus", err) + } + }(response) + endpointList := gconv.SliceAny(response.ReadAllString()) + status.ServerCount = len(endpointList) + for _, endpoint := range endpointList { + endpointJson := gjson.New(endpoint) + // 获取全部容器数量 + serverRunningDockerCount := endpointJson.Get("Snapshots.0.RunningContainerCount").Int() + serverStoppedDockerCount := endpointJson.Get("Snapshots.0.StoppedContainerCount").Int() + status.DockerCount += serverRunningDockerCount + serverStoppedDockerCount + // 判断服务器状态 + if endpointJson.Get("Status").Int() != 1 { + status.ErrorServer++ + status.ErrorDocker += serverRunningDockerCount + serverStoppedDockerCount + } else { + status.ErrorDocker += serverStoppedDockerCount + } + } + return status, nil +} diff --git a/utility/ha_utils/ha_entity.go b/utility/ha_utils/ha_entity.go new file mode 100644 index 0000000..32f98bd --- /dev/null +++ b/utility/ha_utils/ha_entity.go @@ -0,0 +1,119 @@ +package ha_utils + +import ( + "context" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcache" + "home-network-watcher/manifest" + "time" +) + +type uHaUtils struct{} + +var HaUtils = &uHaUtils{} + +// 卧室空调 +const ( + AirConditionerEntityPattern = "climate.lumi_mcn02_1f58_air_conditioner" + AirConditionerStatePattern = "attributes.air_conditioner.on" + AirConditionerTempPattern = "attributes.temperature" +) + +// 加湿器 +const ( + HumidifierEntityPattern = "humidifier.deerma_jsq2g_d916_humidifier" + HumidifierStatePattern = "attributes.humidifier.on" + HumidifierHumidityPattern = "attributes.environment.relative_humidity" +) + +// 空气净化器 +const ( + AirPurifierEntityPattern = "fan.zhimi_mp4a_20cb_air_purifier" + AirPurifierStatePattern = "attributes.air_purifier.on" + AirPurifierPM25Pattern = "attributes.environment.pm2_5_density" +) + +// 卧室床头灯 +const ( + LightEntityPattern = "light.yeelink_bslamp2_0b02_light" + LightStatePattern = "attributes.light.on" + LightBrightnessPattern = "attributes.light.brightness" +) + +type HaEntitiesInfo struct { + AirConditioner g.Map + Humidifier g.Map + AirPurifier g.Map + Light g.Map +} + +// GetEntitiesInfo +// +// @dc: 获取实体信息 +// @author: hamster @date:2023/9/22 17:54:25 +func (u *uHaUtils) GetEntitiesInfo(ctx context.Context) (err error) { + haClient := g.Client().SetHeaderMap(manifest.HomeAssistantAuthMap) + baseUrl := manifest.HomeAssistantBaseUrl + // 获取空调状态 + airConditionerMap := g.Map{} + response, err := haClient.Get(ctx, baseUrl+AirConditionerEntityPattern) + if err != nil { + return err + } + defer response.Close() + responseJson := gjson.New(response.ReadAllString()) + responseJson.SetViolenceCheck(true) + airConditionerMap["state"] = responseJson.Get(AirConditionerStatePattern) + airConditionerMap["temp"] = responseJson.Get(AirConditionerTempPattern) + + // 获取加湿器状态 + humidifierMap := g.Map{} + response, err = haClient.Get(ctx, baseUrl+HumidifierEntityPattern) + if err != nil { + return err + } + defer response.Close() + responseJson = gjson.New(response.ReadAllString()) + responseJson.SetViolenceCheck(true) + humidifierMap["state"] = responseJson.Get(HumidifierStatePattern) + humidifierMap["humidity"] = responseJson.Get(HumidifierHumidityPattern) + + // 获取空气净化器状态 + airPurifierMap := g.Map{} + response, err = haClient.Get(ctx, baseUrl+AirPurifierEntityPattern) + if err != nil { + return err + } + defer response.Close() + responseJson = gjson.New(response.ReadAllString()) + responseJson.SetViolenceCheck(true) + airPurifierMap["state"] = responseJson.Get(AirPurifierStatePattern) + airPurifierMap["pm25"] = responseJson.Get(AirPurifierPM25Pattern) + + // 获取床头灯状态 + lightMap := g.Map{} + response, err = haClient.Get(ctx, baseUrl+LightEntityPattern) + if err != nil { + return err + } + defer response.Close() + responseJson = gjson.New(response.ReadAllString()) + responseJson.SetViolenceCheck(true) + lightMap["state"] = responseJson.Get(LightStatePattern) + lightMap["brightness"] = responseJson.Get(LightBrightnessPattern) + + // 汇总信息 + HaEntitiesInfo := &HaEntitiesInfo{ + AirConditioner: airConditionerMap, + Humidifier: humidifierMap, + AirPurifier: airPurifierMap, + Light: lightMap, + } + err = gcache.Set(ctx, manifest.HaEntitiesCacheKey, HaEntitiesInfo, 1*time.Minute) + if err != nil { + return err + } + + return +} diff --git a/utility/network_utils/coffee.go b/utility/network_utils/coffee.go new file mode 100644 index 0000000..1114d7c --- /dev/null +++ b/utility/network_utils/coffee.go @@ -0,0 +1,68 @@ +package network_utils + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gclient" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/util/gconv" + "home-network-watcher/manifest" +) + +type uProxyProvider struct{} + +var ProxyProvider = &uProxyProvider{} + +// GetSubscribeInfo +// +// @dc: 获取订阅信息 +// @author: laixin @date:2023/6/6 04:15:01 +func (u *uProxyProvider) GetSubscribeInfo() (err error) { + err, authData := getAuthData() + if err != nil || authData == "" { + return + } + proxyClient := gclient.New() + proxyClient.SetHeaderMap(map[string]string{ + "Authorization": authData, + }) + response, err := proxyClient.Get(context.TODO(), manifest.ProxyProviderBaseUrl, nil) + if err != nil { + return + } + infoData := gconv.Map(gconv.Map(response.ReadAllString())["data"]) + if infoData["d"] == nil || infoData["transfer_enable"] == nil { + return + } + usedBound := gconv.Float64(infoData["d"]) / 1024 / 1024 / 1010 + planBound := gconv.Float64(infoData["transfer_enable"]) / 1024 / 1024 / 1024 + remainBound := planBound - usedBound + // 保留两位小数 + usedBoundStr := fmt.Sprintf("%.2f", usedBound) + planBoundStr := fmt.Sprintf("%.2f", planBound) + remainBoundStr := fmt.Sprintf("%.2f", remainBound) + proxyCache := g.Map{ + "usedBound": usedBoundStr, + "planBound": planBoundStr, + "remainBound": remainBoundStr, + } + err = gcache.Set(context.TODO(), manifest.ProxySubscribeCacheKey, proxyCache, 0) + if err != nil { + return err + } + return +} + +// getAuthData +// +// @dc: 获取代理提供商AuthData +func getAuthData() (err error, authData string) { + url := manifest.ProxyProviderLoginUrl + response, err := gclient.New().Post(context.TODO(), url, manifest.ProxyProviderAuthData) + if err != nil { + return + } + return nil, gjson.New(response.ReadAllString()).Get("data.auth_data").String() +} diff --git a/utility/network_utils/home_network.go b/utility/network_utils/home_network.go new file mode 100644 index 0000000..96bedd3 --- /dev/null +++ b/utility/network_utils/home_network.go @@ -0,0 +1,94 @@ +package network_utils + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv" + "github.com/hamster1963/360-router-data-retriever/rconfig" + "github.com/hamster1963/360-router-data-retriever/rutils" + "home-network-watcher/manifest" + "time" +) + +type uNetworkUtils struct { +} + +var NetworkUtils = &uNetworkUtils{} + +var ( + routerConfig = &rconfig.RouterConfig{ + RouterIP: manifest.HomeNetworkRouterIP, + RouterAddress: manifest.HomeNetworkRouterAddress, + RouterPassword: manifest.HomeNetworkRouterPassword, + } + routerMain rutils.SRouterController = rutils.NewRouter().InitRouter(routerConfig) +) + +// GetHomeNetwork +// +// @dc: 获取家庭路由器网速 +// @author: laixin @date:2023/4/2 19:43:13 +func (u *uNetworkUtils) GetHomeNetwork() (err error) { + var ( + homeNetwork = g.Map{ + "time": "", + "rxSpeedKbps": 0, + "txSpeedKbps": 0, + "rxSpeedMbps": 0, + "txSpeedMbps": 0, + } + ) + + // 检测登陆状态 + if login, err := routerMain.CheckLogin(); err != nil || login == false { + err := routerMain.GetRandomString() + if err != nil { + return err + } + err = routerMain.GenerateAesString() + if err != nil { + return err + } + err = routerMain.Login() + if err != nil { + return err + } + } + routerSpeedInfo, err := routerMain.GetRouterSpeed() + if err != nil { + return err + } + + jsonData := gjson.New(routerSpeedInfo) + rxSpeed := jsonData.Get("data.down_speed") // 下载速度 + txSpeed := jsonData.Get("data.up_speed") // 上传速度 + + // 速度单位转换 + rxSpeedKbps := gconv.Float64(fmt.Sprintf("%.2f", gconv.Float64(rxSpeed)/1024)) + txSpeedKbps := gconv.Float64(fmt.Sprintf("%.2f", gconv.Float64(txSpeed)/1024)) + homeNetwork["rxSpeedKbps"] = rxSpeedKbps + homeNetwork["txSpeedKbps"] = txSpeedKbps + + // 转换成MB + rxSpeedMbps := gconv.Float64(fmt.Sprintf("%.2f", gconv.Float64(rxSpeed)/1024/1024)) + txSpeedMbps := gconv.Float64(fmt.Sprintf("%.2f", gconv.Float64(txSpeed)/1024/1024)) + homeNetwork["rxSpeedMbps"] = rxSpeedMbps + homeNetwork["txSpeedMbps"] = txSpeedMbps + + homeNetwork["time"] = gtime.Now().String() + deviceData, err := routerMain.GetDeviceList() + if err != nil { + return err + } + homeNetwork["deviceCount"] = len(gjson.New(deviceData).Get("client_node").Array()) + + err = gcache.Set(context.Background(), manifest.HomeNetworkCacheKey, homeNetwork, 10*time.Second) + if err != nil { + return err + } + return nil +} diff --git a/utility/network_utils/node_utils.go b/utility/network_utils/node_utils.go new file mode 100644 index 0000000..e586095 --- /dev/null +++ b/utility/network_utils/node_utils.go @@ -0,0 +1,129 @@ +package network_utils + +import ( + "context" + "crypto/tls" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gclient" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv" + "home-network-watcher/manifest" + "net/http" + "time" +) + +type uNodeUtils struct{} + +var NodeUtils = &uNodeUtils{} + +// GetNodeInfo +// +// @dc: 获取节点信息 +// @author: laixin @date:2023/4/2 20:08:50 +func (u *uNodeUtils) GetNodeInfo() (err error) { + var ( + connectedNode string + lastChangeTime string + nodeInfo = g.Map{ + "nodeName": "", + "lastChangeTime": "", + "updateTime": "", + } + ) + + // 获取token + token, err := u.GetToken() + if err != nil { + return err + } + wsUrl := "ws://" + manifest.XrayBaseUrl + "/api/message?Authorization=" + token + + // websocket获取节点列表 + client := gclient.NewWebSocket() + client.HandshakeTimeout = time.Second // 设置超时时间 + client.Proxy = http.ProxyFromEnvironment // 设置代理 + client.TLSClientConfig = &tls.Config{} // 设置 tls 配置 + conn, _, err := client.Dial(wsUrl, nil) + if err != nil { + return err + } + _, data, err := conn.ReadMessage() + if err != nil { + return err + } + // 打印消息类型和消息内容 + nodeList := gjson.New(string(data)).Get("body.outboundStatus").Array() + for _, v := range nodeList { + if !gjson.New(gconv.String(v)).Get("alive").Bool() { + continue + } + // 根据"delay"获得延迟最小的节点 + if connectedNode == "" { + connectedNode = gconv.String(v) + } else { + if gjson.New(connectedNode).Get("delay").Int() > gjson.New(gconv.String(v)).Get("delay").Int() { + connectedNode = gconv.String(v) + } + } + } + // 比对缓存中的节点信息 + cacheNodeInfo, _ := gcache.Get(context.Background(), manifest.ProxyNodeCacheKey) + if cacheNodeInfo != nil { + if gjson.New(cacheNodeInfo.String()).Get("nodeName").String() == gjson.New(connectedNode).Get("outbound_tag").String() { + lastChangeTime = gjson.New(cacheNodeInfo).Get("lastChangeTime").String() + } else { + lastChangeTime = gtime.Now().String() + } + } else { + lastChangeTime = gtime.Now().String() + } + // 获取节点信息 + nodeInfo["nodeName"] = gjson.New(connectedNode).Get("outbound_tag").String() + nodeInfo["lastChangeTime"] = lastChangeTime + nodeInfo["updateTime"] = gtime.Now().String() + // 关闭连接 + _ = conn.Close() + // 存入缓存 + err = gcache.Set(context.Background(), manifest.ProxyNodeCacheKey, nodeInfo, 0) + if err != nil { + return err + } + return nil +} + +// GetToken +// +// @dc: 获取v2raya面板token +// @author: laixin @date:2023/4/2 20:13:24 +func (u *uNodeUtils) GetToken() (token string, err error) { + var ( + url = "http://" + manifest.XrayBaseUrl + "/api/login" + ) + + // 登陆获取token + response, err := g.Client().Post(context.Background(), url, manifest.XrayLoginDataMap) + defer func(response *gclient.Response) { + err := response.Close() + if err != nil { + glog.Warning(context.Background(), err) + } + }(response) + if err != nil { + return "", err + } + resData := gjson.New(response.ReadAllString()) + if resData.Get("code").String() != "SUCCESS" { + err = gerror.New("登陆失败") + return + } + token = resData.Get("data.token").String() + if token == "" { + err = gerror.New("token获取失败") + return + } + return +} diff --git a/utility/network_utils/proxy_network.go b/utility/network_utils/proxy_network.go new file mode 100644 index 0000000..5e222c8 --- /dev/null +++ b/utility/network_utils/proxy_network.go @@ -0,0 +1,204 @@ +package network_utils + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gclient" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv" + "home-network-watcher/manifest" + "time" +) + +type uProxyNetwork struct{} + +var ProxyNetwork = &uProxyNetwork{} + +// GetXuiUserList +// +// @dc: 获取xui用户列表 +// @author: hamster @date:2023/9/19 09:29:33 +func (u *uProxyNetwork) GetXuiUserList() (err error) { + var ( + url = manifest.XuiBaseUrl + "/xui/inbound/list" + session map[string]string + ) + // 尝试获取缓存中的session + sessionData, err := gcache.Get(context.Background(), manifest.ProxySessionCacheKey) + if err != nil { + return err + } + + if sessionData.IsNil() { + // 重新获取session + session, err = u.GetXuiSession() + if err != nil { + return err + } + } else { + session = sessionData.MapStrStr() + } + if err != nil { + return err + } + post, err := g.Client().SetCookieMap(session).Post(context.Background(), url) + defer func(post *gclient.Response) { + err := post.Close() + if err != nil { + glog.Warning(context.Background(), err) + } + }(post) + if err != nil { + return err + } + if post.StatusCode != 200 { + glog.Warning(context.Background(), "获取xui用户列表失败") + return err + } + jsonData := gjson.New(post.ReadAllString()) + userList := jsonData.Get("obj").Array() + // 删除敏感信息,计算总上传下载流量 + userCacheList := make([]g.Map, 0) + upTotal := 0.00 + downTotal := 0.00 + for _, value := range userList { + userJson := gjson.New(value) + userCacheJson := g.Map{ + "id": userJson.Get("id").Int(), + "enable": userJson.Get("enable").Bool(), + "protocol": userJson.Get("protocol").String(), + "remark": userJson.Get("remark").String(), + // 转换为 GB + "up": userJson.Get("up").Float64() / 1024 / 1024 / 1024, + "down": userJson.Get("down").Float64() / 1024 / 1024 / 1024, + } + userCacheList = append(userCacheList, userCacheJson) + upTotal += userJson.Get("up").Float64() + downTotal += userJson.Get("down").Float64() + } + // 按照下载流量排序 + for i := 0; i < len(userCacheList); i++ { + for j := i + 1; j < len(userCacheList); j++ { + if userCacheList[i]["down"].(float64) < userCacheList[j]["down"].(float64) { + userCacheList[i], userCacheList[j] = userCacheList[j], userCacheList[i] + } + } + } + cacheXuiData := g.Map{ + "user_list": userCacheList, + "user_count": len(userCacheList), + "up_total": upTotal / 1024 / 1024 / 1024, + "down_total": downTotal / 1024 / 1024 / 1024, + } + err = gcache.Set(context.Background(), manifest.XUIUserListCacheKey, cacheXuiData, 1*time.Minute) + return +} + +// GetProxyNetwork +// +// @dc: 获取代理服务器的网速 +// @author: laixin @date:2023/4/2 20:06:21 +func (u *uProxyNetwork) GetProxyNetwork() (err error) { + var ( + proxyNetwork = g.Map{ + "time": "", + "rxSpeedKbps": 0, + "txSpeedKbps": 0, + "rxSpeedMbps": 0, + "txSpeedMbps": 0, + } + session map[string]string + url = manifest.XuiBaseUrl + "/server/status" + ) + + // 尝试获取缓存中的session + sessionData, err := gcache.Get(context.Background(), manifest.ProxySessionCacheKey) + if err != nil { + return err + } + + if sessionData.IsNil() { + // 重新获取session + session, err = u.GetXuiSession() + if err != nil { + return err + } + } else { + session = sessionData.MapStrStr() + } + + // 通过xui进行网速的获取 + post, err := g.Client().SetCookieMap(session).Post(context.Background(), url) + defer func(post *gclient.Response) { + err := post.Close() + if err != nil { + glog.Warning(context.Background(), err) + } + }(post) + if err != nil { + return err + } + if post.StatusCode != 200 { + glog.Warning(context.Background(), "获取网速失败") + return err + } + jsonData := gjson.New(post.ReadAllString()) + rxSpeed := jsonData.Get("obj.netIO.down") // 下载速度 + txSpeed := jsonData.Get("obj.netIO.up") // 上传速度 + + // 速度单位转换 + rxSpeedKbps := gconv.Float64(fmt.Sprintf("%.2f", gconv.Float64(rxSpeed)/1024)) + txSpeedKbps := gconv.Float64(fmt.Sprintf("%.2f", gconv.Float64(txSpeed)/1024)) + proxyNetwork["rxSpeedKbps"] = rxSpeedKbps + proxyNetwork["txSpeedKbps"] = txSpeedKbps + + // 转换成MB + rxSpeedMbps := gconv.Float64(fmt.Sprintf("%.2f", gconv.Float64(rxSpeed)/1024/1024)) + txSpeedMbps := gconv.Float64(fmt.Sprintf("%.2f", gconv.Float64(txSpeed)/1024/1024)) + proxyNetwork["rxSpeedMbps"] = rxSpeedMbps + proxyNetwork["txSpeedMbps"] = txSpeedMbps + + proxyNetwork["time"] = gtime.Now().String() + err = gcache.Set(context.Background(), manifest.ProxyNetworkCacheKey, proxyNetwork, 10*time.Second) + if err != nil { + return err + } + + return err +} + +// GetXuiSession +// +// @dc: 获取Xui登陆session +// @author: laixin @date:2023/4/2 20:06:21 +func (u *uProxyNetwork) GetXuiSession() (sessionMap map[string]string, err error) { + var ( + url = manifest.XuiBaseUrl + "/login" + ) + post, err := g.Client().Post(context.Background(), url, manifest.XuiLoginDataMap) + defer func(post *gclient.Response) { + err := post.Close() + if err != nil { + glog.Warning(context.Background(), err) + } + }(post) + if err != nil { + return nil, err + } + if post.StatusCode != 200 { + return nil, fmt.Errorf("登录失败") + } + if post.Header.Get("Set-Cookie") == "" { + return nil, fmt.Errorf("获取Cookie失败") + } + // 将session存入缓存 + err = gcache.Set(context.Background(), manifest.ProxySessionCacheKey, post.GetCookieMap(), 15*time.Minute) + if err != nil { + return nil, err + } + return post.GetCookieMap(), nil +} diff --git a/utility/redis_test.go b/utility/redis_test.go new file mode 100644 index 0000000..677086c --- /dev/null +++ b/utility/redis_test.go @@ -0,0 +1,143 @@ +package utility + +import ( + "context" + "errors" + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + "github.com/gogf/gf/v2/database/gredis" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" + "github.com/google/uuid" + "testing" + "time" +) + +var ( + // ErrLockFailed 加锁失败 + ErrLockFailed = errors.New("尝试加锁失败") + // ErrTimeout 加锁超时 + ErrTimeout = errors.New("timeout") +) + +var ( + config = gredis.Config{ + Address: "120.24.211.49:6379", + Db: 1, + Pass: "deny1963", + } + group = "cache" + ctx = gctx.New() +) + +type Locker struct { + redisClient *gredis.Redis + ttl int64 + tryInterval int +} + +func NewLocker(redisClient *gredis.Redis, ttl int64, tryInterval int) *Locker { + return &Locker{redisClient: redisClient, ttl: ttl, tryInterval: tryInterval} +} + +type Lock struct { + redisClient *gredis.Redis + resource string + uuid string + ttl int64 + tryInterval int +} + +func (l *Locker) GetLock(resource string) *Lock { + return &Lock{ + redisClient: l.redisClient, + resource: resource, + uuid: uuid.NewString(), + ttl: l.ttl, + tryInterval: l.tryInterval, + } + +} + +func (l *Lock) TryLock(ctx context.Context) error { + lockStatus, err := l.redisClient.SetNX(ctx, l.resource, l.uuid) + if err != nil { + return err + } + if lockStatus { + err := l.redisClient.SetEX(ctx, l.resource, l.uuid, l.ttl) + if err != nil { + return err + } + } else { + return ErrLockFailed + } + return nil +} + +func (l *Lock) UnLock(ctx context.Context) error { + lockStatus, err := l.redisClient.Get(ctx, l.resource) + if err != nil { + return err + } + if lockStatus.String() != l.uuid { + return ErrLockFailed + } + _, err = l.redisClient.Del(ctx, l.resource) + if err != nil { + return err + } + return nil +} + +func (l *Lock) Lock(ctx context.Context) error { + err := l.TryLock(ctx) + if err == nil { + return nil + } + if err != ErrLockFailed { + return err + } + glog.Info(ctx, "尝试加锁失败,开始重试") + // 尝试加锁失败,开始重试 + ticker := time.NewTicker(time.Duration(l.tryInterval) * time.Millisecond) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return ErrTimeout + case <-ticker.C: + if err := l.TryLock(ctx); err != nil { + continue + } + return nil + } + } +} + +// 测试方法注释 +func Test12_57_15(t *testing.T) { + gredis.SetConfig(&config, group) + redisClient := g.Redis(group) + locker := NewLocker(redisClient, 6, 200) + lock := locker.GetLock("hamster") + + err := lock.TryLock(ctx) + if err != nil { + t.Error(err) + return + } + + err = lock.Lock(ctx) + if err != nil { + t.Error(err) + return + } + t.Log("加锁成功") + err = lock.UnLock(ctx) + if err != nil { + t.Error(err) + return + } + t.Log("解锁成功") +} diff --git a/utility/server_utils/nezha.go b/utility/server_utils/nezha.go new file mode 100644 index 0000000..abf9ca2 --- /dev/null +++ b/utility/server_utils/nezha.go @@ -0,0 +1,73 @@ +package server_utils + +import ( + "context" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv" + "home-network-watcher/manifest" + "math" +) + +type uNezha struct{} + +var Nezha = &uNezha{} + +// GetAllServerInfo +// +// @dc: 获取所有服务器信息 +// @author: hamster @date:2023/9/18 14:37:42 +func (u *uNezha) GetAllServerInfo(ctx context.Context) (err error) { + // 1. 获取所有服务器信息 + //TODO (hamster) 2023/10/3: 优化获取服务器列表序号的方式 + serverList := []int{1, 2, 3, 4} + nezhaClient := g.Client().SetHeader("Authorization", manifest.NezhaApiKey) + serverDataList := make([]g.Map, 0) + for _, value := range serverList { + nezhaApi := manifest.NezhaApiUrl + gconv.String(value) + response, err := nezhaClient.Get(ctx, nezhaApi) + if err != nil { + return err + } + // 整理数据 + cacheJson := g.Map{} + resJsonData := gjson.New(response.ReadAllString()).Get("result.0").Map() + serverJsonData := gjson.New(resJsonData) + cacheJson["id"] = serverJsonData.Get("id").Int() + cacheJson["name"] = serverJsonData.Get("name").String() + // CPU 占用率保留两位小数 + cpuValue := serverJsonData.Get("status.CPU").Float64() + roundedValue := math.Round(cpuValue*100) / 100 + cacheJson["cpu"] = roundedValue + // 内存占用率保留两位小数 + memoryValue := serverJsonData.Get("host.MemTotal").Float64() + memoryUsedValue := serverJsonData.Get("status.MemUsed").Float64() + memoryUsedPercent := math.Round(memoryUsedValue/memoryValue*10000) / 100 + cacheJson["memory"] = memoryUsedPercent + // 磁盘占用率保留两位小数 + diskValue := serverJsonData.Get("host.DiskTotal").Float64() + diskUsedValue := serverJsonData.Get("status.DiskUsed").Float64() + diskUsedPercent := math.Round(diskUsedValue/diskValue*10000) / 100 + cacheJson["disk"] = diskUsedPercent + // 在线时长,转换为天 + uptimeValue := serverJsonData.Get("status.Uptime").Float64() + uptimeDay := math.Round(uptimeValue/86400*100) / 100 + cacheJson["uptime"] = uptimeDay + serverDataList = append(serverDataList, cacheJson) + _ = response.Close() + // 在线状态,通过last_active判断 + lastActiveValue := serverJsonData.Get("last_active").Int64() + if gtime.Now().Unix()-lastActiveValue > 300 { + cacheJson["status"] = "offline" + } else { + cacheJson["status"] = "online" + } + } + err = gcache.Set(ctx, manifest.ServerDataCacheKey, serverDataList, 0) + if err != nil { + return err + } + return +} diff --git a/utility/server_utils/server_cron.go b/utility/server_utils/server_cron.go new file mode 100644 index 0000000..e6d12c2 --- /dev/null +++ b/utility/server_utils/server_cron.go @@ -0,0 +1,34 @@ +package server_utils + +import ( + "context" + "github.com/gogf/gf/v2/os/gcache" + "home-network-watcher/manifest" + "home-network-watcher/utility/docker_utils" + "home-network-watcher/utility/uptime_utils" + "time" +) + +type uServerCron struct{} + +var ServerCron = &uServerCron{} + +// CronGetDockerAndMonitor +// +// @dc: 获取 Docker 状态与监控信息 +// @author: hamster @date:2023/9/20 16:20:11 +func (u *uServerCron) CronGetDockerAndMonitor(ctx context.Context) (err error) { + dockerStatus, _ := docker_utils.DockerUtils.GetDockerStatus(ctx) + serverCount, errorCount, _ := uptime_utils.Kuma.GetMonitorStatus(ctx) + // 缓存数据 + dockerMonitor := map[string]interface{}{ + "dockerStatus": dockerStatus, + "serverCount": serverCount, + "errorCount": errorCount, + } + err = gcache.Set(ctx, manifest.DockerMonitorCacheKey, dockerMonitor, 1*time.Hour) + if err != nil { + return err + } + return +} diff --git a/utility/uptime_utils/kuma.go b/utility/uptime_utils/kuma.go new file mode 100644 index 0000000..ee30f74 --- /dev/null +++ b/utility/uptime_utils/kuma.go @@ -0,0 +1,114 @@ +package uptime_utils + +import ( + "context" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gclient" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/util/gconv" + "home-network-watcher/manifest" + "time" +) + +type uKuma struct{} + +var Kuma = &uKuma{} + +type UptimeInfo struct { + Status bool `json:"status"` + Ping int `json:"ping"` + Uptime string `json:"uptime"` +} + +// GetMonitorStatus +// +// @dc: 获取监控状态 +// @author: hamster @date:2023/9/20 14:43:18 +func (u *uKuma) GetMonitorStatus(ctx context.Context) (serverCount int, errorServer int, err error) { + response, err := g.Client().Get(ctx, manifest.UptimeKumaApiUrl) + defer func(response *gclient.Response) { + err := response.Close() + if err != nil { + return + } + }(response) + if err != nil { + return 0, 0, err + } + // 获取返还数据 + heartBeatJson := gjson.New(response.ReadAllString()).Get("heartbeatList").Map() + serverCount = len(heartBeatJson) + for _, value := range heartBeatJson { + pingList := gconv.SliceAny(value) + if gjson.New(pingList[len(pingList)-1]).Get("status").Int() != 1 { + errorServer++ + } + } + return serverCount, errorServer, nil +} + +// GetUptimeData +// +// @dc: 获取核心服务监控数据 +// @author: hamster @date:2023/9/21 11:21:13 +func (u *uKuma) GetUptimeData(ctx context.Context) (err error) { + serviceMap := g.Map{ + "xui": 2, + "v2raya": 9, + "proxy": 8, + "nginx": 5, + "home": 7, + "netflix": 15} + response, err := g.Client().Get(ctx, manifest.UptimeKumaApiUrl) + if err != nil { + return err + } + defer func(response *gclient.Response) { + err := response.Close() + if err != nil { + return + } + }(response) + // 获取返还数据,获取最新状态与 24 小时状态 + heartBeatJson := gjson.New(response.ReadAllString()) + for key, value := range serviceMap { + serviceUptime := &UptimeInfo{} + statusList := heartBeatJson.Get("heartbeatList" + "." + gconv.String(value)).Array() + if len(statusList) == 0 { + serviceUptime.Status = false + serviceUptime.Ping = 0 + serviceUptime.Uptime = "0.00" + serviceMap[key] = gconv.Map(serviceUptime) + continue + } + // 获取最后一条数据 + lastHeartBeat := statusList[len(statusList)-1] + if gconv.Int(gconv.Map(lastHeartBeat)["status"]) == 1 { + serviceUptime.Status = true + } else { + serviceUptime.Status = false + } + serviceUptime.Ping = gconv.Int(gconv.Map(lastHeartBeat)["ping"]) + // 获取24小时可用率 + uptimeList := heartBeatJson.Get("uptimeList" + "." + gconv.String(value) + "_24").Float64() + switch uptimeList { + case 0: + serviceUptime.Uptime = "0.00" + case 1: + serviceUptime.Uptime = "100.0" + default: + // 转换为2位小数百分比 + serviceUptime.Uptime = gconv.String(uptimeList * 100) + if len(serviceUptime.Uptime) > 4 { + serviceUptime.Uptime = serviceUptime.Uptime[:4] + } + } + serviceMap[key] = gconv.Map(serviceUptime) + } + err = gcache.Set(ctx, manifest.UptimeCacheKey, serviceMap, 1*time.Hour) + if err != nil { + return err + } + return +}