diff --git a/cron/cron.go b/cron/cron.go index 0cb6242..b477e63 100644 --- a/cron/cron.go +++ b/cron/cron.go @@ -1 +1,20 @@ package cron + +import ( + "gitee.ltd/lxh/logger/log" + "github.com/go-co-op/gocron/v2" + "time" + "wireguard-ui/cron/task" +) + +func Task() { + sch, err := gocron.NewScheduler(gocron.WithLocation(time.Local)) + if err != nil { + log.Errorf("初始化定时任务失败") + return + } + + _, _ = sch.NewJob(gocron.DurationJob(3*time.Minute), gocron.NewTask(task.NetworkClient().ClientOfflineNotify())) // 每三分钟检测离线的客户端并且发送消息 + + sch.Start() +} diff --git a/cron/task/client.go b/cron/task/client.go new file mode 100644 index 0000000..f745b6c --- /dev/null +++ b/cron/task/client.go @@ -0,0 +1,95 @@ +package task + +import ( + "fmt" + "gitee.ltd/lxh/logger/log" + jsoniter "github.com/json-iterator/go" + "golang.org/x/exp/slices" + "strings" + "time" + "wireguard-ui/component" + "wireguard-ui/global/client" + "wireguard-ui/model" + "wireguard-ui/service" + "wireguard-ui/utils" +) + +type NetworkClientImpl interface { + ClientOfflineNotify() error // 客户端离线通知 +} + +type networkClient struct{} + +func NetworkClient() NetworkClientImpl { + return networkClient{} +} + +// ClientOfflineNotify +// @description: 客户端离线通知 +// @receiver c +// @return error +func (c networkClient) ClientOfflineNotify() error { + // 查询出所有配置了离线通知的客户端 + var clients []model.Client + if err := client.DB.Where("offline_monitoring = ?", 1).Find(&clients).Error; err != nil { + return err + } + + if len(clients) <= 0 { + return nil + } + + // 开始扫描已经链接过的客户端 + connectedPeers, err := component.Wireguard().GetClients() + if err != nil { + log.Errorf("获取已连接客户端失败: %v", err.Error()) + return err + } + + // 查询一下通知配置 + code, err := service.Setting().GetByCode("WECHAT_NOTIFY") + if err != nil { + return err + } + + for _, peer := range connectedPeers { + var clientName string + if !slices.ContainsFunc(clients, func(cli model.Client) bool { + isExist := peer.PublicKey.String() == jsoniter.Get([]byte(cli.Keys), "PublicKey").ToString() + if isExist { + clientName = cli.Name + } + return isExist + }) { + continue + } + + // 如果存在,判断离线时间 + if time.Since(peer.LastHandshakeTime) < 3 { + var ipAllocation string + for _, iaip := range peer.AllowedIPs { + ipAllocation += iaip.String() + "," + } + // 去除一下最右边的逗号 + if len(ipAllocation) > 0 { + ipAllocation = strings.TrimRight(ipAllocation, ",") + } + + // 已经离线,发送通知 + msg := fmt.Sprintf(` + [离线通知] + 客户端名称 : %v + 客户端IP : %v + 最后在线时间 : %v + `, clientName, ipAllocation, peer.LastHandshakeTime.Format("2006-01-02 15:04:05")) + err := utils.WechatNotify(code).SendTextMessage(msg) + if err != nil { + log.Errorf("微信消息[%v]通知失败: %v", msg, err.Error()) + continue + } + } + + } + + return nil +} diff --git a/go.mod b/go.mod index 42870fd..f52ccfe 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,7 @@ require ( github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/go-co-op/gocron/v2 v2.12.4 // indirect github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect @@ -61,6 +62,7 @@ require ( github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/jonboulle/clockwork v0.4.0 // indirect github.com/josharian/native v1.1.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect @@ -85,6 +87,7 @@ require ( github.com/prometheus/procfs v0.8.0 // indirect github.com/prometheus/prometheus v1.8.2-0.20201028100903-3245b3267b24 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -97,7 +100,7 @@ require ( go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.23.0 // indirect golang.org/x/arch v0.8.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/image v0.18.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect diff --git a/go.sum b/go.sum index 58ad763..da53ec0 100644 --- a/go.sum +++ b/go.sum @@ -224,6 +224,8 @@ github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-co-op/gocron/v2 v2.12.4 h1:h1HWApo3T+61UrZqEY2qG1LUpDnB7tkYITxf6YIK354= +github.com/go-co-op/gocron/v2 v2.12.4/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -508,6 +510,8 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= +github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= @@ -751,6 +755,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qq github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -924,6 +930,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= diff --git a/main.go b/main.go index cc10dd4..c40d4d3 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "os" "time" "wireguard-ui/config" + "wireguard-ui/cron" "wireguard-ui/http/router" "wireguard-ui/initialize" "wireguard-ui/script" @@ -20,6 +21,9 @@ func init() { if err := script.New().Do(); err != nil { log.Errorf("执行脚本失败: %v", err.Error()) } + + // 启动定时任务 + go cron.Task() } func main() { diff --git a/utils/wechat_notify.go b/utils/wechat_notify.go new file mode 100644 index 0000000..e333afc --- /dev/null +++ b/utils/wechat_notify.go @@ -0,0 +1,57 @@ +package utils + +import ( + "encoding/json" + "errors" + "fmt" + "gitee.ltd/lxh/logger/log" + jsoniter "github.com/json-iterator/go" + "wireguard-ui/global/client" + "wireguard-ui/model" +) + +// wxid_472vas3av5ug22 /api/sendTextMsg +type wechatNotify struct { + Addr string + Path string + Method string + toUserId string +} + +func WechatNotify(setting *model.Setting) wechatNotify { + var sm = make(map[string]string) + _ = json.Unmarshal([]byte(setting.Data), &sm) + + return wechatNotify{ + Addr: sm["addr"], + Path: sm["path"], + Method: sm["method"], + toUserId: sm["toUserId"], + } +} + +// SendTextMessage +// @description: 发送文字通知 +// @receiver website +// @param msg +// @return error +func (w wechatNotify) SendTextMessage(msg string) error { + req := client.HttpClient.R() + + req.SetHeader("Content-Type", "application/json") + req.SetBody(map[string]string{ + "wxid": w.toUserId, + "msg": msg, + }) + result, err := req.Post(fmt.Sprintf("%s:%s", w.Addr, w.Path)) + if err != nil { + return err + } + + if jsoniter.Get(result.Body(), "code").ToInt() != 1 { + log.Errorf("发送通知到微信失败: %v", jsoniter.Get(result.Body(), "msg").ToString()) + return errors.New("发送通知到微信失败") + } + + return nil +}