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