mirror of
https://github.com/hamster1963/HomeDash-Backend.git
synced 2025-06-16 04:01:22 +08:00
update: 初始化代码提交
This commit is contained in:
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
* linguist-language=GO
|
16
.github/dependabot.yml
vendored
Normal file
16
.github/dependabot.yml
vendored
Normal file
@ -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"
|
52
.github/workflows/GoCompileTest.yml
vendored
Normal file
52
.github/workflows/GoCompileTest.yml
vendored
Normal file
@ -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
|
||||
|
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
.buildpath
|
||||
.hgignore.swp
|
||||
.project
|
||||
.orig
|
||||
.swp
|
||||
.idea/
|
||||
.settings/
|
||||
.vscode/
|
||||
bin/
|
||||
**/.DS_Store
|
||||
main
|
||||
output/
|
||||
manifest/output/
|
||||
temp/
|
55
Dockerfile
Normal file
55
Dockerfile
Normal file
@ -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 <liaolaixin@gmail.com>"
|
||||
|
||||
###############################################################################
|
||||
# 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"]
|
||||
|
||||
|
||||
|
||||
|
8
README.md
Normal file
8
README.md
Normal file
@ -0,0 +1,8 @@
|
||||
# 家庭网络信息采集后端
|
||||
监控家中与网络有关的一切
|
||||
## 已接入
|
||||
- [x] 家庭路由器
|
||||
- [x] 家庭NAS
|
||||
- [x] 家庭服务器
|
||||
- [x] 推送中心
|
||||
- [x] 代理节点信息
|
22
api/data_core/data_core.go
Normal file
22
api/data_core/data_core.go
Normal file
@ -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)
|
||||
}
|
89
api/data_core/v1/api_data_core.go
Normal file
89
api/data_core/v1/api_data_core.go
Normal file
@ -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 {
|
||||
}
|
41
go.mod
Normal file
41
go.mod
Normal file
@ -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
|
||||
)
|
95
go.sum
Normal file
95
go.sum
Normal file
@ -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=
|
10
hack/config.yaml
Normal file
10
hack/config.yaml
Normal file
@ -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
|
170
internal/boot/boot.go
Normal file
170
internal/boot/boot.go
Normal file
@ -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
|
||||
}
|
65
internal/cmd/cmd.go
Normal file
65
internal/cmd/cmd.go
Normal file
@ -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
|
||||
},
|
||||
}
|
||||
)
|
5
internal/controller/data_core/data_core.go
Normal file
5
internal/controller/data_core/data_core.go
Normal file
@ -0,0 +1,5 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package data_core
|
15
internal/controller/data_core/data_core_new.go
Normal file
15
internal/controller/data_core/data_core_new.go
Normal file
@ -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{}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
24
internal/controller/data_core/data_core_v1_get_task_list.go
Normal file
24
internal/controller/data_core/data_core_v1_get_task_list.go
Normal file
@ -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
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
25
internal/controller/data_core/data_core_v1_recover_task.go
Normal file
25
internal/controller/data_core/data_core_v1_recover_task.go
Normal file
@ -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
|
||||
}
|
22
internal/controller/data_core/data_core_v1_stop_task.go
Normal file
22
internal/controller/data_core/data_core_v1_stop_task.go
Normal file
@ -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
|
||||
}
|
42
internal/global/g_functions/functions.go
Normal file
42
internal/global/g_functions/functions.go
Normal file
@ -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)
|
||||
})
|
||||
}
|
33
internal/global/g_index/index.go
Normal file
33
internal/global/g_index/index.go
Normal file
@ -0,0 +1,33 @@
|
||||
package g_index
|
||||
|
||||
const IndexHTML = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<title>仓鼠咖啡馆</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
background-image: url('https://s2.loli.net/2023/05/16/Nyaxz3QrsSKmvHE.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
.text {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
padding: 10px 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="text">仓鼠咖啡馆</div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
191
internal/global/g_middleware/middleware.go
Normal file
191
internal/global/g_middleware/middleware.go
Normal file
@ -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,
|
||||
})
|
||||
}
|
0
internal/logic/.gitkeep
Normal file
0
internal/logic/.gitkeep
Normal file
7
internal/logic/logic.go
Normal file
7
internal/logic/logic.go
Normal file
@ -0,0 +1,7 @@
|
||||
// ==========================================================================
|
||||
// Code generated by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package logic
|
||||
|
||||
import ()
|
1
internal/packed/packed.go
Normal file
1
internal/packed/packed.go
Normal file
@ -0,0 +1 @@
|
||||
package packed
|
19
internal/router/r_hamster_router/router.go
Normal file
19
internal/router/r_hamster_router/router.go
Normal file
@ -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())
|
||||
})
|
||||
}
|
28
main.go
Normal file
28
main.go
Normal file
@ -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())
|
||||
}
|
28
manifest/config/config.yaml
Normal file
28
manifest/config/config.yaml
Normal file
@ -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 # 日志文件带有颜色
|
||||
|
||||
|
79
manifest/consts.go
Normal file
79
manifest/consts.go
Normal file
@ -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"
|
||||
)
|
2
tag.sh
Normal file
2
tag.sh
Normal file
@ -0,0 +1,2 @@
|
||||
# shellcheck disable=SC2046
|
||||
git describe --tags $(git rev-list --tags --max-count=1)
|
0
utility/.gitkeep
Normal file
0
utility/.gitkeep
Normal file
19
utility/bin_utils/bin_info.go
Normal file
19
utility/bin_utils/bin_info.go
Normal file
@ -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"
|
||||
}
|
57
utility/docker_utils/docker.go
Normal file
57
utility/docker_utils/docker.go
Normal file
@ -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
|
||||
}
|
119
utility/ha_utils/ha_entity.go
Normal file
119
utility/ha_utils/ha_entity.go
Normal file
@ -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
|
||||
}
|
68
utility/network_utils/coffee.go
Normal file
68
utility/network_utils/coffee.go
Normal file
@ -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()
|
||||
}
|
94
utility/network_utils/home_network.go
Normal file
94
utility/network_utils/home_network.go
Normal file
@ -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
|
||||
}
|
129
utility/network_utils/node_utils.go
Normal file
129
utility/network_utils/node_utils.go
Normal file
@ -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
|
||||
}
|
204
utility/network_utils/proxy_network.go
Normal file
204
utility/network_utils/proxy_network.go
Normal file
@ -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
|
||||
}
|
143
utility/redis_test.go
Normal file
143
utility/redis_test.go
Normal file
@ -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("解锁成功")
|
||||
}
|
73
utility/server_utils/nezha.go
Normal file
73
utility/server_utils/nezha.go
Normal file
@ -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
|
||||
}
|
34
utility/server_utils/server_cron.go
Normal file
34
utility/server_utils/server_cron.go
Normal file
@ -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
|
||||
}
|
114
utility/uptime_utils/kuma.go
Normal file
114
utility/uptime_utils/kuma.go
Normal file
@ -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
|
||||
}
|
Reference in New Issue
Block a user