├── 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 | ![image](/.github/backend1.png) -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------