1. 二进制版本 v1.7.2 发布

2. 源码更新至1.5.1
This commit is contained in:
古大羊 2022-12-09 15:33:44 +08:00
parent b7538fb4ef
commit 6b6799f3ca
92 changed files with 11038 additions and 784 deletions

View File

@ -10,7 +10,9 @@
- [后台界面](#后台界面)
- [开发编译](#开发编译)
- [更新日志](#更新日志)
- [使用注意与常见问题](#使用注意与常见问题)
<!-- /TOC -->
@ -39,7 +41,7 @@
- 其它细节功能自己慢慢发现...
- 没有文档,后台各处的提示信息已经足够多.
- 支持的DNS服务商和DDNS-GO一样,有Alidns(阿里云),百度云,Cloudflare,Dnspod(腾讯云),华为云.自定义(Callback)内置有每步,No-IP,Dynv6,Dynu模版,一键填充,仅需修改相应用户密码或者token即可快速接入.
- 3.http/https反向代理
- 3.Web服务
- 特点
- 设置简单
- 支持HttpBasic认证
@ -48,6 +50,7 @@
- 日志记录最近访问情况
- 一键开关子规则
- 前端域名与后端地址 支持一对一,一对多(均衡负载),多对多(下一级反向代理)
- 支持307重定向和跳转
- 4.网络唤醒
- 特点
- 支持远程控制唤醒和关机操作
@ -57,7 +60,10 @@
- 点灯科技支持 小爱同学 小度 天猫精灵
- 巴法云支持小爱同学 小度 天猫精灵 google语音 AmazonAlexa
- 具备但一般用不上的功能:支持一个设备设置多组网卡mac和多个广播地址,实现批量控制设备.
- 5.STUN IPv4内网穿透
- 特点
- 无需额外服务器,将局域网服务端口暴露于IPv4公网(端口随机)
- 适合于国内运营商级NAT1宽带网络.
- 将要实现的功能
- 有建议可联系作者.
@ -201,6 +207,10 @@
1.加入后台登录日志
2.适配新版本luci-app-lucky
3.此版本开始以后会同时发布多平台openwrt ipk安装包
2022-12-09 v1.7.2
1.新增STUN IPv4 内网穿透模块
2.反向代理模块改名为Web服务
3.Web服务增加跳转和重定向支持

View File

@ -6,11 +6,19 @@ import (
"fmt"
"log"
"net"
"os"
"runtime"
"strings"
"sync"
"github.com/gdy666/lucky/socketproxy"
ddnsconf "github.com/gdy666/lucky/module/ddns/conf"
portforwardconf "github.com/gdy666/lucky/module/portforward/conf"
"github.com/gdy666/lucky/module/portforward/socketproxy"
reverseproxyconf "github.com/gdy666/lucky/module/reverseproxy/conf"
safeconf "github.com/gdy666/lucky/module/safe/conf"
sslconf "github.com/gdy666/lucky/module/sslcertficate/conf"
wolconf "github.com/gdy666/lucky/module/wol/conf"
"github.com/gdy666/lucky/thirdlib/gdylib/fileutils"
"github.com/gdy666/lucky/thirdlib/gdylib/stringsp"
)
@ -54,23 +62,27 @@ type BaseConfigure struct {
AllowInternetaccess bool `json:"AllowInternetaccess"` //允许外网访问
//GlobalMaxConnections int64 `json:"GlobalMaxConnections"` //全局最大连接数
LogMaxSize int `json:"LogMaxSize"` //日志记录最大条数
HttpClientSecureVerify bool `json:"HttpClientSecureVerify"`
HttpClientTimeout int `json:"HttpClientTimeout"`
}
type ProgramConfigure struct {
BaseConfigure BaseConfigure `json:"BaseConfigure"`
WhiteListConfigure WhiteListConfigure `json:"WhiteListConfigure"`
BlackListConfigure BlackListConfigure `json:"BlackListConfigure"`
DDNSConfigure DDNSConfigure `json:"DDNSConfigure"` //DDNS 参数设置
DDNSTaskList []DDNSTask `json:"DDNSTaskList"` //DDNS任务列表
ReverseProxyRuleList []ReverseProxyRule `json:"ReverseProxyRuleList"` //反向代理规则列表
SSLCertficateList []SSLCertficate `json:"SSLCertficateList"` //SSL证书列表
PortForwardsRuleList []PortForwardsRule `json:"PortForwardsRuleList"` //端口转发规则列表
PortForwardsConfigure PortForwardsConfigure `json:"PortForwardsConfigure"` //端口转发设置
WOLDeviceList []WOLDevice `json:"WOLDeviceList"` //网络唤醒设备列表
BaseConfigure BaseConfigure `json:"BaseConfigure"`
WhiteListConfigure safeconf.WhiteListConfigure `json:"WhiteListConfigure"`
BlackListConfigure safeconf.BlackListConfigure `json:"BlackListConfigure"`
DDNSConfigure ddnsconf.DDNSConfigure `json:"DDNSConfigure"` //DDNS 参数设置
DDNSTaskList []ddnsconf.DDNSTask `json:"DDNSTaskList"` //DDNS任务列表
ReverseProxyRuleList []reverseproxyconf.ReverseProxyRule `json:"ReverseProxyRuleList"` //反向代理规则列表
SSLCertficateList []sslconf.SSLCertficate `json:"SSLCertficateList"` //SSL证书列表
PortForwardsRuleList []portforwardconf.PortForwardsRule `json:"PortForwardsRuleList"` //端口转发规则列表
PortForwardsConfigure portforwardconf.PortForwardsConfigure `json:"PortForwardsConfigure"` //端口转发设置
WOLDeviceList []wolconf.WOLDevice `json:"WOLDeviceList"` //网络唤醒设备列表
WOLServiceConfigure wolconf.WOLServiceConfigure `json:"WOLServiceConfigure"` //网络唤醒客户端设置
}
var programConfigureMutex sync.RWMutex
var programConfigure *ProgramConfigure
var ConfigureMutex sync.RWMutex
var Configure *ProgramConfigure
var configurePath string
// var readConfigureFileOnce sync.Once
@ -82,10 +94,10 @@ var configureFileSign int8 = -1
// }
func GetAuthAccount() map[string]string {
programConfigureMutex.RLock()
defer programConfigureMutex.RUnlock()
ConfigureMutex.RLock()
defer ConfigureMutex.RUnlock()
accountInfo := make(map[string]string)
accountInfo[programConfigure.BaseConfigure.AdminAccount] = programConfigure.BaseConfigure.AdminPassword
accountInfo[Configure.BaseConfigure.AdminAccount] = Configure.BaseConfigure.AdminPassword
return accountInfo
}
@ -98,27 +110,27 @@ func SetRunMode(mode string) {
}
func SetConfig(p *ProgramConfigure) error {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
programConfigure = p
ConfigureMutex.Lock()
defer ConfigureMutex.Unlock()
Configure = p
return Save()
}
func GetConfig() *ProgramConfigure {
programConfigureMutex.RLock()
defer programConfigureMutex.RUnlock()
conf := *programConfigure
ConfigureMutex.RLock()
defer ConfigureMutex.RUnlock()
conf := *Configure
return &conf
}
func GetConfigureBytes() []byte {
programConfigureMutex.RLock()
defer programConfigureMutex.RUnlock()
if programConfigure == nil {
ConfigureMutex.RLock()
defer ConfigureMutex.RUnlock()
if Configure == nil {
return []byte("{}")
}
//JSON.Pars
res, err := json.MarshalIndent(*programConfigure, "", "\t")
res, err := json.MarshalIndent(*Configure, "", "\t")
if err != nil {
return []byte("{}")
}
@ -126,61 +138,17 @@ func GetConfigureBytes() []byte {
}
func GetBaseConfigure() BaseConfigure {
programConfigureMutex.RLock()
defer programConfigureMutex.RUnlock()
baseConf := programConfigure.BaseConfigure
ConfigureMutex.RLock()
defer ConfigureMutex.RUnlock()
baseConf := Configure.BaseConfigure
return baseConf
}
func GetDDNSConfigure() DDNSConfigure {
programConfigureMutex.RLock()
defer programConfigureMutex.RUnlock()
conf := programConfigure.DDNSConfigure
return conf
}
func GetPortForwardsConfigure() PortForwardsConfigure {
programConfigureMutex.RLock()
defer programConfigureMutex.RUnlock()
conf := programConfigure.PortForwardsConfigure
return conf
}
func SetPortForwardsConfigure(conf *PortForwardsConfigure) error {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
if conf.PortForwardsLimit < 0 {
conf.PortForwardsLimit = 0
} else if conf.PortForwardsLimit > 1024 {
conf.PortForwardsLimit = 1024
}
if conf.TCPPortforwardMaxConnections < 0 {
conf.TCPPortforwardMaxConnections = 0
} else if conf.TCPPortforwardMaxConnections > 4096 {
conf.TCPPortforwardMaxConnections = 4096
}
if conf.UDPReadTargetDataMaxgoroutineCount < 0 {
conf.UDPReadTargetDataMaxgoroutineCount = 0
} else if conf.UDPReadTargetDataMaxgoroutineCount > 4096 {
conf.UDPReadTargetDataMaxgoroutineCount = 4096
}
programConfigure.PortForwardsConfigure = *conf
socketproxy.SetGlobalMaxPortForwardsCountLimit(conf.PortForwardsLimit)
socketproxy.SetGlobalTCPPortforwardMaxConnections(conf.TCPPortforwardMaxConnections)
socketproxy.SetGlobalUDPReadTargetDataMaxgoroutineCountLimit(conf.UDPReadTargetDataMaxgoroutineCount)
return Save()
}
// 保存基础配置
func SetBaseConfigure(conf *BaseConfigure) error {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
programConfigure.BaseConfigure = *conf
ConfigureMutex.Lock()
defer ConfigureMutex.Unlock()
Configure.BaseConfigure = *conf
//socketproxy.SetGlobalMaxConnections(conf.GlobalMaxConnections)
//socketproxy.SetGlobalMaxPortForwardsCount(conf.ProxyCountLimit)
@ -191,30 +159,12 @@ func SetBaseConfigure(conf *BaseConfigure) error {
conf.LogMaxSize = maxLogSize
}
return Save()
}
func SetDDNSConfigure(conf *DDNSConfigure) error {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
if conf.Intervals < 30 {
conf.Intervals = 30
if conf.HttpClientTimeout <= 0 {
conf.HttpClientTimeout = 1
} else if conf.HttpClientTimeout > 60 {
conf.HttpClientTimeout = 60
}
if conf.Intervals > 3600 {
conf.Intervals = 3600
}
if conf.FirstCheckDelay < 0 {
conf.FirstCheckDelay = 0
}
if conf.FirstCheckDelay > 3600 {
conf.FirstCheckDelay = 3600
}
programConfigure.DDNSConfigure = *conf
return Save()
}
@ -230,6 +180,13 @@ func Read(filePath string) (err error) {
return err
}
checkConfigue(pc)
Configure = pc
return nil
}
func checkConfigue(pc *ProgramConfigure) {
if pc.PortForwardsConfigure.PortForwardsLimit <= 0 {
pc.PortForwardsConfigure.PortForwardsLimit = socketproxy.DEFAULT_MAX_PORTFORWARDS_LIMIT
}
@ -259,13 +216,43 @@ func Read(filePath string) (err error) {
pc.BaseConfigure.LogMaxSize = maxLogSize
}
programConfigure = pc
if pc.BaseConfigure.HttpClientTimeout <= 0 {
pc.BaseConfigure.HttpClientTimeout = 20
} else if pc.BaseConfigure.HttpClientTimeout > 60 {
pc.BaseConfigure.HttpClientTimeout = 60
}
return nil
if pc.WOLServiceConfigure.Client.Port <= 0 {
pc.WOLServiceConfigure.Client.Port = 9
}
if pc.WOLServiceConfigure.Client.Repeat <= 0 {
pc.WOLServiceConfigure.Client.Repeat = 5
}
if pc.WOLServiceConfigure.Client.DeviceName == "" {
hostname, _ := os.Hostname()
pc.WOLServiceConfigure.Client.DeviceName = hostname
}
if pc.WOLServiceConfigure.Client.PowerOffCMD == "" {
switch runtime.GOOS {
case "linux":
pc.WOLServiceConfigure.Client.PowerOffCMD = "poweroff"
case "windows":
pc.WOLServiceConfigure.Client.PowerOffCMD = "Shutdown /s /t 0"
default:
pc.WOLServiceConfigure.Client.PowerOffCMD = ""
}
}
if pc.WOLServiceConfigure.Server.Token == "" {
pc.WOLServiceConfigure.Server.Token = "666666"
}
}
func LoadDefault(adminWebListenPort int) {
programConfigure = loadDefaultConfigure(adminWebListenPort)
Configure = loadDefaultConfigure(adminWebListenPort)
}
func Save() (err error) {
@ -284,7 +271,7 @@ func Save() (err error) {
}()
err = saveProgramConfig(programConfigure, configurePath)
err = saveProgramConfig(Configure, configurePath)
return
}
@ -327,39 +314,41 @@ func loadDefaultConfigure(
AllowInternetaccess: false,
LogMaxSize: defaultLogSize}
whiteListConfigure := WhiteListConfigure{BaseConfigure: WhiteListBaseConfigure{ActivelifeDuration: 36, BasicAccount: defaultAdminAccount, BasicPassword: defaultAdminPassword}}
whiteListConfigure := safeconf.WhiteListConfigure{BaseConfigure: safeconf.WhiteListBaseConfigure{ActivelifeDuration: 36, BasicAccount: defaultAdminAccount, BasicPassword: defaultAdminPassword}}
var pc ProgramConfigure
pc.BaseConfigure = baseConfigure
pc.WhiteListConfigure = whiteListConfigure
if pc.PortForwardsConfigure.PortForwardsLimit <= 0 {
pc.PortForwardsConfigure.PortForwardsLimit = socketproxy.DEFAULT_MAX_PORTFORWARDS_LIMIT
}
socketproxy.SetGlobalMaxPortForwardsCountLimit(pc.PortForwardsConfigure.PortForwardsLimit)
checkConfigue(&pc)
if pc.PortForwardsConfigure.TCPPortforwardMaxConnections <= 0 {
pc.PortForwardsConfigure.TCPPortforwardMaxConnections = socketproxy.TCPUDP_DEFAULT_SINGLE_PROXY_MAX_CONNECTIONS
}
socketproxy.SetGlobalTCPPortforwardMaxConnections(pc.PortForwardsConfigure.TCPPortforwardMaxConnections)
// if pc.PortForwardsConfigure.PortForwardsLimit <= 0 {
// pc.PortForwardsConfigure.PortForwardsLimit = socketproxy.DEFAULT_MAX_PORTFORWARDS_LIMIT
// }
// socketproxy.SetGlobalMaxPortForwardsCountLimit(pc.PortForwardsConfigure.PortForwardsLimit)
if pc.PortForwardsConfigure.UDPReadTargetDataMaxgoroutineCount <= 0 {
pc.PortForwardsConfigure.UDPReadTargetDataMaxgoroutineCount = socketproxy.DEFAULT_GLOBAL_UDPReadTargetDataMaxgoroutineCount
}
// if pc.PortForwardsConfigure.TCPPortforwardMaxConnections <= 0 {
// pc.PortForwardsConfigure.TCPPortforwardMaxConnections = socketproxy.TCPUDP_DEFAULT_SINGLE_PROXY_MAX_CONNECTIONS
// }
// socketproxy.SetGlobalTCPPortforwardMaxConnections(pc.PortForwardsConfigure.TCPPortforwardMaxConnections)
socketproxy.SetGlobalUDPReadTargetDataMaxgoroutineCountLimit(pc.PortForwardsConfigure.UDPReadTargetDataMaxgoroutineCount)
// if pc.PortForwardsConfigure.UDPReadTargetDataMaxgoroutineCount <= 0 {
// pc.PortForwardsConfigure.UDPReadTargetDataMaxgoroutineCount = socketproxy.DEFAULT_GLOBAL_UDPReadTargetDataMaxgoroutineCount
// }
if pc.BaseConfigure.AdminWebListenPort <= 0 {
pc.BaseConfigure.AdminWebListenPort = defaultAdminListenPort
}
// socketproxy.SetGlobalUDPReadTargetDataMaxgoroutineCountLimit(pc.PortForwardsConfigure.UDPReadTargetDataMaxgoroutineCount)
if pc.DDNSConfigure.Intervals < 30 {
pc.DDNSConfigure.Intervals = 30
}
// if pc.BaseConfigure.AdminWebListenPort <= 0 {
// pc.BaseConfigure.AdminWebListenPort = defaultAdminListenPort
// }
if pc.DDNSConfigure.FirstCheckDelay <= 0 {
pc.DDNSConfigure.FirstCheckDelay = 0
}
// if pc.DDNSConfigure.Intervals < 30 {
// pc.DDNSConfigure.Intervals = 30
// }
// if pc.DDNSConfigure.FirstCheckDelay <= 0 {
// pc.DDNSConfigure.FirstCheckDelay = 0
// }
return &pc
}

19
go.mod
View File

@ -4,16 +4,18 @@ go 1.18
require (
github.com/buger/jsonparser v1.1.1
github.com/eclipse/paho.mqtt.golang v1.4.1
github.com/eclipse/paho.mqtt.golang v1.4.2
github.com/fatedier/golib v0.2.0
github.com/gin-contrib/gzip v0.0.6
github.com/gin-gonic/gin v1.8.1
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/gorilla/websocket v1.5.0
github.com/guonaihong/gout v0.3.1
github.com/kardianos/service v1.2.2
github.com/miekg/dns v1.1.50
github.com/shirou/gopsutil/v3 v3.22.9
github.com/sirupsen/logrus v1.9.0
golang.org/x/net v0.0.0-20221004154528-8021a29435af
golang.org/x/net v0.1.0
)
require (
@ -23,7 +25,6 @@ require (
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/goccy/go-json v0.9.11 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c // indirect
@ -37,12 +38,12 @@ require (
github.com/tklauser/numcpus v0.5.0 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
golang.org/x/sys v0.0.0-20221010170243-090e33056c14 // indirect
golang.org/x/text v0.3.8 // indirect
golang.org/x/tools v0.1.12 // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/mod v0.6.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/tools v0.2.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

38
go.sum
View File

@ -4,8 +4,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eclipse/paho.mqtt.golang v1.4.1 h1:tUSpviiL5G3P9SZZJPC4ZULZJsxQKXxfENpMvdbAXAI=
github.com/eclipse/paho.mqtt.golang v1.4.1/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA=
github.com/eclipse/paho.mqtt.golang v1.4.2 h1:66wOzfUHSSI1zamx7jR6yMEI5EuHnT1G6rNA5PM12m4=
github.com/eclipse/paho.mqtt.golang v1.4.2/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA=
github.com/fatedier/golib v0.2.0 h1:8BxiUcjleBlXBYlTNUllD8KZZHaFU/NP/vP0Yu1Fkpg=
github.com/fatedier/golib v0.2.0/go.mod h1:e2NPpBGUFsHDjXrfP1B5aK3S0+yUeVxgqfc3go3KNj0=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
@ -38,12 +38,15 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/guonaihong/gout v0.3.1 h1:pj/44Jw0TTmcHF2RjMaCWhKPwCH98YuQejbN15Hts/o=
github.com/guonaihong/gout v0.3.1/go.mod h1:lhje0jRkh/gcIogrG22ENPITo9tylQa3kwD9eVxcDrk=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
@ -109,11 +112,11 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2 h1:x8vtB3zMecnlqZIwJNUUpwYKYSqCz5jXbiyv0ZJJZeI=
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -122,16 +125,17 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4=
golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -145,20 +149,20 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14 h1:k5II8e6QD8mITdi+okbbmR/cIyEbeXLBhy5Ha4nevyc=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

129
main.go
View File

@ -6,19 +6,27 @@ import (
"flag"
"log"
"os"
"os/signal"
"syscall"
"sync"
"time"
"github.com/gdy666/lucky/config"
"github.com/gdy666/lucky/ddns"
"github.com/gdy666/lucky/reverseproxy"
"github.com/gdy666/lucky/socketproxy"
"github.com/gdy666/lucky/module/ddns"
"github.com/gdy666/lucky/module/ddns/ddnsgo"
"github.com/gdy666/lucky/module/portforward"
"github.com/gdy666/lucky/module/portforward/socketproxy"
"github.com/gdy666/lucky/module/reverseproxy"
"github.com/gdy666/lucky/module/safe"
"github.com/gdy666/lucky/module/service"
ssl "github.com/gdy666/lucky/module/sslcertficate"
"github.com/gdy666/lucky/module/wol"
"github.com/gdy666/lucky/web"
kservice "github.com/kardianos/service"
)
var (
listenPort = flag.Int("p", 16601, "http Admin Web listen port ")
configureFileURL = flag.String("c", "", "configure file url")
disableService = flag.Bool("ds", false, "disable service mode ")
)
var (
@ -33,10 +41,49 @@ var runTime time.Time
func init() {
var cstZone = time.FixedZone("CST", 8*3600) // 东八
time.Local = cstZone
service.RegisterStartFunc(run)
}
func main() {
flag.Parse()
service.SetListenPort(*listenPort)
service.SetConfigureFile(*configureFileURL)
s, _ := service.GetService()
if s != nil && !*disableService {
status, _ := s.Status()
//fmt.Printf("status:%d\n", status)
if status != kservice.StatusUnknown {
log.Printf("以服务形式运行\n")
if status == kservice.StatusStopped {
log.Printf("调用启动lucky windows服务")
service.Start()
log.Printf("本窗口5秒后退出,lucky将以windows后台服务方式启动.")
<-time.After(time.Second * 5)
os.Exit(0)
}
s.Run()
os.Exit(0)
}
}
run()
var w sync.WaitGroup
w.Add(1)
w.Wait()
// err := service.UninstallService()
// if err != nil {
// fmt.Printf("%s\n", err.Error())
// }
//
}
func run() {
config.InitAppInfo(version, date)
err := config.Read(*configureFileURL)
@ -54,15 +101,13 @@ func main() {
gcf := config.GetConfig()
config.BlackListInit()
config.WhiteListInit()
config.SSLCertficateListInit()
safe.Init()
ssl.Init()
//fmt.Printf("*gcf:%v\n", *gcf)
wol.Init(web.GetLogger())
socketproxy.SetSafeCheck(safe.SafeCheck)
socketproxy.SetSafeCheck(config.SafeCheck)
//socketproxy.SetGlobalMaxConnections(gcf.BaseConfigure.GlobalMaxConnections)
//socketproxy.SetGlobalMaxProxyCount(gcf.BaseConfigure.ProxyCountLimit)
config.SetRunMode(runMode)
config.SetVersion(version)
log.Printf("RunMode:%s\n", runMode)
@ -72,60 +117,24 @@ func main() {
runTime = time.Now()
//LoadRuleFromConfigFile(gcf)
portforward.Init()
config.PortForwardsRuleListInit()
//config.DDNSTaskListTaskDetailsInit()
config.DDNSTaskListConfigureCheck()
ddnsConf := config.GetDDNSConfigure()
ddnsgo.DDNSTaskListConfigureCheck()
ddnsConf := ddnsgo.GetDDNSConfigure()
if ddnsConf.Enable {
go ddns.Run(time.Duration(ddnsConf.FirstCheckDelay)*time.Second, time.Duration(ddnsConf.Intervals)*time.Second)
}
reverseproxy.InitReverseProxyServer()
//ddns.RunTimer(time.Second, time.Second*30)
//initProxyList()
//*****************
// time.Sleep(time.Microsecond * 50)
// cruuentPath, _ := fileutils.GetCurrentDirectory()
// panicFile := fmt.Sprintf("%s/relayport_panic.log", cruuentPath)
// fileutils.PanicRedirect(panicFile)
//*****************
//main goroutine wait
sigs := make(chan os.Signal, 1)
exit := make(chan bool, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
exit <- true
}()
<-exit
// sigs := make(chan os.Signal, 1)
// exit := make(chan bool, 1)
// signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
// go func() {
// <-sigs
// exit <- true
// }()
// <-exit
}
// func LoadRuleFromConfigFile(pc *config.ProgramConfigure) {
// if pc == nil {
// return
// }
// for i := range pc.RelayRuleList {
// relayRule, err := rule.CreateRuleByConfigureAndOptions(
// pc.RelayRuleList[i].Name,
// pc.RelayRuleList[i].Configurestr,
// pc.RelayRuleList[i].Options)
// if err != nil {
// continue
// }
// relayRule.From = "configureFile" //规则来源
// relayRule.IsEnable = pc.RelayRuleList[i].Enable
// _, e := rule.AddRuleToGlobalRuleList(false, *relayRule)
// if e != nil {
// log.Printf("%s\n", e)
// }
// }
// }

280
module/ddns/conf/ddns.go Normal file
View File

@ -0,0 +1,280 @@
package ddnsconf
import (
"fmt"
"io"
"log"
"regexp"
"strings"
"sync"
"time"
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
"github.com/gdy666/lucky/thirdlib/gdylib/netinterfaces"
)
var getDDNSConfigureFunc func() DDNSConfigure
func SetGetDDNSConfigureFunc(f func() DDNSConfigure) {
getDDNSConfigureFunc = f
}
type DDNSConfigure struct {
Enable bool `json:"Enable"`
HttpClientSecureVerify bool `json:"HttpClientSecureVerify"`
FirstCheckDelay int `json:"FirstCheckDelay"` //首次检查延迟时间
Intervals int `json:"Intervals"`
}
type DDNSTask struct {
TaskName string `json:"TaskName"`
TaskKey string `json:"TaskKey"` //添加任务时随机生成,方便管理任务(修改删除)
//规则类型 IPv4/IPv6
TaskType string `json:"TaskType"`
Enable bool
// 获取IP类型 url/netInterface
GetType string `json:"GetType"` //IP获取方式
URL []string `json:"URL"`
NetInterface string `json:"NetInterface"`
IPReg string `json:"IPReg"`
Domains []string `json:"Domains"`
DNS DNSConfig `json:"DNS"`
Webhook
TTL string `json:"TTL"`
HttpClientTimeout int `json:"HttpClientTimeout"`
ModifyTime int64 `json:"ModifyTime"`
}
type Webhook struct {
WebhookEnable bool `json:"WebhookEnable"` //Webhook开关
WebhookCallOnGetIPfail bool `json:"WebhookCallOnGetIPfail"` //获取IP失败时触发Webhook 开关
WebhookURL string `json:"WebhookURL"`
WebhookMethod string `json:"WebhookMethod"`
WebhookHeaders []string `json:"WebhookHeaders"`
WebhookRequestBody string `json:"WebhookRequestBody"`
WebhookDisableCallbackSuccessContentCheck bool `json:"WebhookDisableCallbackSuccessContentCheck"` //禁用成功调用返回检测
WebhookSuccessContent []string `json:"WebhookSuccessContent"` //接口调用成功包含的内容
WebhookProxy string `json:"WebhookProxy"` //使用DNS代理设置 ""表示禁用,"dns"表示使用dns的代理设置
WebhookProxyAddr string `json:"WebhookProxyAddr"` //代理服务器IP
WebhookProxyUser string `json:"WebhookProxyUser"` //代理用户
WebhookProxyPassword string `json:"WebhookProxyPassword"` //代理密码
}
// DNSConfig DNS配置
type DNSConfig struct {
// 名称。如alidns,webhook
Name string `json:"Name"`
ID string `json:"ID"`
Secret string `json:"Secret"`
ForceInterval int `json:"ForceInterval"` //(秒)即使IP没有变化,到一定时间后依然强制更新或先DNS解析比较IP再更新
ResolverDoaminCheck bool `json:"ResolverDoaminCheck"` //调用callback同步前先解析一次域名,如果IP相同就不同步
DNSServerList []string `json:"DNSServerList"` //DNS服务器列表
CallAPINetwork string `json:"CallAPINetwork"` //空代理tcp, tcp4,tcp6
Callback DNSCallback `json:"Callback"`
HttpClientProxyType string `json:"HttpClientProxyType"` //http client代理服务器设置
HttpClientProxyAddr string `json:"HttpClientProxyAddr"` //代理服务器IP
HttpClientProxyUser string `json:"HttpClientProxyUser"` //代理用户
HttpClientProxyPassword string `json:"HttpClientProxyPassword"` //代理密码
}
func (d *DNSConfig) GetCallAPINetwork() string {
switch d.CallAPINetwork {
case "tcp4", "tcp6":
return d.CallAPINetwork
default:
return "tcp"
}
}
type DNSCallback struct {
URL string `json:"URL"` //请求地址
Method string `json:"Method"` //请求方法
Headers []string `json:"Headers"`
RequestBody string `json:"RequestBody"`
Server string `json:"Server"` //预设服务商
DisableCallbackSuccessContentCheck bool `json:"DisableCallbackSuccessContentCheck"` //禁用成功调用返回检测
CallbackSuccessContent []string `json:"CallbackSuccessContent"` //接口调用成功包含内容
}
// Ipv4Reg IPv4正则
const Ipv4Reg = `((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])`
// Ipv6Reg IPv6正则
const Ipv6Reg = `((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))`
var ipUrlAddrMap sync.Map
func (d *DDNSTask) GetIpAddr() (result string) {
if d.TaskType == "IPv6" {
return d.getIpv6Addr()
}
return d.getIpv4Addr()
}
// getIpv4Addr 获取IPv4地址
func (d *DDNSTask) getIpv4Addr() (result string) {
// 判断从哪里获取IP
if d.GetType == "netInterface" {
result = netinterfaces.GetIPFromNetInterface("IPv4", d.NetInterface, d.IPReg)
return
}
ddnsGlobalConf := getDDNSConfigureFunc()
client, err := httputils.CreateHttpClient(
"tcp4",
"",
ddnsGlobalConf.HttpClientSecureVerify,
"",
"",
"",
"",
time.Duration(d.HttpClientTimeout)*time.Second)
if err != nil {
log.Printf("%s", err.Error())
return
}
for _, url := range d.URL {
url = strings.TrimSpace(url)
mapIp, ok := ipUrlAddrMap.Load(url)
if ok {
//log.Printf("URL[%s]已缓存IP[%s]", url, mapIp)
result = mapIp.(string)
return
}
resp, err := client.Get(url)
if err != nil {
//log.Printf("连接失败!%s查看接口能否返回IPv4地址</a>,", url)
continue
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Printf("读取IPv4结果失败! 接口:%s", url)
continue
}
comp := regexp.MustCompile(Ipv4Reg)
result = comp.FindString(string(body))
if result != "" {
ipUrlAddrMap.Store(url, result)
return
}
// else {
// log.Printf("获取IPv4结果失败! 接口: %s ,返回值: %s\n", url, result)
// }
}
log.Printf("所有查询公网IPv4的接口均获取IPv4结果失败,请检查接口或当前网络情况")
return
}
// getIpv6Addr 获得IPv6地址
func (d *DDNSTask) getIpv6Addr() (result string) {
// 判断从哪里获取IP
if d.GetType == "netInterface" {
result = netinterfaces.GetIPFromNetInterface("IPv6", d.NetInterface, d.IPReg)
return
}
ddnsGlobalConf := getDDNSConfigureFunc()
client, err := httputils.CreateHttpClient(
"tcp6",
"",
!ddnsGlobalConf.HttpClientSecureVerify,
"",
"",
"",
"",
time.Duration(d.HttpClientTimeout)*time.Second)
if err != nil {
log.Printf("%s", err.Error())
return
}
for _, url := range d.URL {
url = strings.TrimSpace(url)
mapIp, ok := ipUrlAddrMap.Load(url)
if ok {
//log.Printf("URL[%s]已缓存IP[%s]", url, mapIp)
result = mapIp.(string)
return
}
resp, err := client.Get(url)
if err != nil {
//log.Printf("连接失败! %s查看接口能否返回IPv6地址 ", url)
continue
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Println("读取IPv6结果失败! 接口: ", url)
continue
}
comp := regexp.MustCompile(Ipv6Reg)
result = comp.FindString(string(body))
if result != "" {
ipUrlAddrMap.Store(url, result)
return
}
}
log.Printf("所有查询公网IPv6的接口均获取IPv6结果失败,请检查接口或当前网络情况")
return
}
func CleanIPUrlAddrMap() {
keys := []string{}
ipUrlAddrMap.Range(func(key, value any) bool {
keys = append(keys, key.(string))
return true
})
for _, k := range keys {
ipUrlAddrMap.Delete(k)
}
}
var checkIPv4URLList = []string{"https://4.ipw.cn", "http://v4.ip.zxinc.org/getip", "https://myip4.ipip.net", "https://www.taobao.com/help/getip.php", "https://ddns.oray.com/checkip", "https://ip.3322.net", "https://v4.myip.la"}
var checkIPv6URLList = []string{"https://6.ipw.cn", "https://ipv6.ddnspod.com", "http://v6.ip.zxinc.org/getip", "https://speed.neu6.edu.cn/getIP.php", "https://v6.ident.me", "https://v6.myip.la"}
func CheckDDNSTaskAvalid(task *DDNSTask) error {
if len(task.URL) == 0 {
if task.TaskType == "IPv6" {
task.URL = checkIPv6URLList
} else {
task.URL = checkIPv4URLList
}
}
switch task.DNS.Name {
case "cloudflare":
if task.DNS.Secret == "" {
return fmt.Errorf("cloudflare token不能为空")
}
case "callback":
if task.DNS.Callback.URL == "" {
return fmt.Errorf("callback URL不能为空")
}
if task.DNS.Callback.Method == "" {
return fmt.Errorf("请选择callback method")
}
default:
if task.DNS.ID == "" || task.DNS.Secret == "" {
return fmt.Errorf("dns服务商相关参数不能为空")
}
}
if len(task.Domains) <= 0 {
return fmt.Errorf("域名列表不能为空")
}
return nil
}

View File

@ -0,0 +1,178 @@
package ddnscore
import (
"fmt"
"sync"
"sync/atomic"
"time"
ddnsconf "github.com/gdy666/lucky/module/ddns/conf"
"github.com/gdy666/lucky/module/ddns/ddnsgo"
)
var taskInfoMap sync.Map
var taskInfoMapMutex sync.RWMutex
var webLastAccessDDNSTaskListLastTime int64
// 记录最后的前端请求DDNS任务列表时间
func FLushWebLastAccessDDNSTaskListLastTime() {
atomic.StoreInt64(&webLastAccessDDNSTaskListLastTime, time.Now().Unix())
}
// webAccessAvalid 判断前端访问是否处于活跃时间内
func webAccessAvalid() bool {
lastTime := atomic.LoadInt64(&webLastAccessDDNSTaskListLastTime)
return time.Now().Unix()-lastTime <= 5
}
func EnableDDNSTaskByKey(key string, enable bool) error {
taskInfoMapMutex.Lock()
defer taskInfoMapMutex.Unlock()
taskInfo, ok := taskInfoMap.Load(key)
if !ok {
return fmt.Errorf("DDNSTaskInfoMap key[%s] no found", key)
}
if enable {
taskInfo.(*DDNSTaskState).SetDomainUpdateStatus(UpdateWaiting, "")
} else {
taskInfo.(*DDNSTaskState).SetDomainUpdateStatus(UpdateStop, "")
}
return ddnsgo.EnableDDNSTaskByKey(key, enable)
}
func DDNSTaskInfoMapUpdate(task *DDNSTaskInfo) bool {
taskInfoMapMutex.Lock()
defer taskInfoMapMutex.Unlock()
t := ddnsgo.GetDDNSTaskByKey(task.TaskKey)
if t == nil {
return false
}
if t.ModifyTime > task.ModifyTime {
//fmt.Printf("失效,不刷新\n")
return false
}
task.TaskState.LastWorkTime = time.Now()
taskInfoMap.Store(task.TaskKey, &task.TaskState)
// preInfo, ok := taskInfoMap.Load(task.TaskKey)
// if ok {
// var checkDomains []Domain
// //防止有域名被删除
// for i, new := range task.TaskState.Domains {
// for _, pre := range preInfo.(*DDNSTaskState).Domains {
// if strings.Compare(new.String(), pre.String()) == 0 {
// checkDomains = append(checkDomains, task.TaskState.Domains[i])
// break
// }
// }
// }
// task.TaskState.Domains = checkDomains
// if len(preInfo.(*DDNSTaskState).Domains) > 0 && preInfo.(*DDNSTaskState).Domains[0].UpdateStatus == UpdateStop {
// task.TaskState.SetDomainUpdateStatus(UpdateStop, "")
// }
// task.TaskState.LastWorkTime = time.Now()
// taskInfoMap.Store(task.TaskKey, &task.TaskState)
// return true
// }
return false
}
// 即时更新IP相关数据信息
func DDNSTaskInfoMapUpdateIPInfo(task *DDNSTaskInfo) {
if !webAccessAvalid() {
//log.Printf("前端没有访问,不即时更新")
return
}
//log.Printf("前端没有访问,不即时更新")
taskInfoMapMutex.Lock()
defer taskInfoMapMutex.Unlock()
state, ok := taskInfoMap.Load(task.TaskKey)
if !ok {
return
}
state.(*DDNSTaskState).IpAddr = task.TaskState.IpAddr
state.(*DDNSTaskState).IPAddrHistory = task.TaskState.IPAddrHistory
}
func DDNSTaskInfoMapUpdateDomainInfo(task *DDNSTaskInfo) {
if !webAccessAvalid() {
//log.Printf("前端没有访问,不即时更新")
return
}
//log.Printf("前端有访问,即时更新")
taskInfoMapMutex.Lock()
defer taskInfoMapMutex.Unlock()
state, ok := taskInfoMap.Load(task.TaskKey)
if !ok {
return
}
state.(*DDNSTaskState).Domains = task.TaskState.Domains
}
func DDNSTaskInfoMapDelete(key string) {
taskInfoMapMutex.Lock()
defer taskInfoMapMutex.Unlock()
taskInfoMap.Delete(key)
}
func UpdateDomainsStateByTaskKey(key, status, message string) {
taskInfoMapMutex.Lock()
defer taskInfoMapMutex.Unlock()
preInfo, ok := taskInfoMap.Load(key)
if !ok {
return
}
preInfo.(*DDNSTaskState).SetDomainUpdateStatus(status, message)
}
func GetDDNSTaskInfoList() []*DDNSTaskInfo {
taskInfoMapMutex.RLock()
defer taskInfoMapMutex.RUnlock()
ddnsTaskList := ddnsgo.GetDDNSTaskConfigureList()
var res []*DDNSTaskInfo
for i := range ddnsTaskList {
ti := GetDDNSTaskState(ddnsTaskList[i])
//ti.syncDomains()
res = append(res, ti)
}
return res
}
func GetDDNSTaskInfoByKey(key string) *DDNSTaskInfo {
taskInfoMapMutex.RLock()
defer taskInfoMapMutex.RUnlock()
ddnsConf := ddnsgo.GetDDNSTaskByKey(key)
if ddnsConf == nil {
return nil
}
info := GetDDNSTaskState(ddnsConf)
return info
}
func GetDDNSTaskState(task *ddnsconf.DDNSTask) *DDNSTaskInfo {
var d DDNSTaskInfo
d.DDNSTask = *task
info, ok := taskInfoMap.Load(task.TaskKey)
if ok {
d.TaskState = *info.(*DDNSTaskState)
} else {
var state DDNSTaskState
state.Init(d.Domains, d.ModifyTime)
if task.Enable {
state.SetDomainUpdateStatus(UpdateWaiting, "")
} else {
state.SetDomainUpdateStatus(UpdateStop, "")
}
d.TaskState = state
taskInfoMap.Store(task.TaskKey, &state)
}
return &d
}

View File

@ -0,0 +1,167 @@
package ddnscore
import (
"log"
"net/url"
"strings"
"time"
)
const (
// UpdatedNothing 未改变
UpdatedNothing string = "域名IP和公网IP一致"
// UpdatedFailed 更新失败
UpdatedFailed = "失败"
// UpdatedSuccess 更新成功
UpdatedSuccess = "成功"
// UpdateStop 暂停
UpdateStop = "停止同步"
//UpdatePause 暂停 获取IP失败时暂停
UpdatePause = "暂停同步"
// UpdateWaiting
UpdateWaiting = "等待更新"
)
// Domain 域名实体
type Domain struct {
RawStr string
DomainName string
SubDomain string
CustomParams string
UpdateStatus string // 更新状态
LastUpdateStatusTime string //最后更新状态的时间
Message string
UpdateHistroy []any
}
type UpdateHistroyItem struct {
UpdateStatus string
UpdateTime string
}
func (d *Domain) String() string {
if d.SubDomain != "" {
return d.SubDomain + "." + d.DomainName
}
return d.DomainName
}
// GetFullDomain 返回完整子域名
func (d *Domain) GetFullDomain() string {
if d.SubDomain != "" {
return d.SubDomain + "." + d.DomainName
}
return "@" + "." + d.DomainName
}
// GetCustomParams not be nil
func (d *Domain) GetCustomParams() url.Values {
if d.CustomParams != "" {
q, err := url.ParseQuery(d.CustomParams)
if err == nil {
return q
}
}
return url.Values{}
}
// GetSubDomain 获得子域名,为空返回@
// 阿里云dnspod需要
func (d *Domain) GetSubDomain() string {
if d.SubDomain != "" {
return d.SubDomain
}
return "@"
}
func (d *Domain) SetDomainUpdateStatus(status string, message string) {
if status != UpdateWaiting {
if status != UpdateStop || d.UpdateStatus != UpdateStop {
d.LastUpdateStatusTime = time.Now().Format("2006-01-02 15:04:05")
// 状态更新历史记录
hi := UpdateHistroyItem{UpdateStatus: string(status), UpdateTime: d.LastUpdateStatusTime}
d.UpdateHistroy = append(d.UpdateHistroy, hi)
if len(d.UpdateHistroy) > 10 {
d.UpdateHistroy = DeleteAnyListlice(d.UpdateHistroy, 0)
}
}
}
d.UpdateStatus = status
d.Message = message
}
func checkParseDomains(domainArr []string) (domains []Domain, domainsRawStrList []string) {
for _, domainStr := range domainArr {
domainStr = strings.TrimSpace(domainStr)
if domainStr == "" {
continue
}
domain := Domain{}
domain.RawStr = domainStr
dp := strings.Split(domainStr, ":")
dplen := len(dp)
switch dplen {
case 1:
{
sp := strings.Split(domainStr, ".")
length := len(sp)
if length <= 1 {
log.Println(domainStr, "域名不正确")
continue
}
// 处理域名
domain.DomainName = sp[length-2] + "." + sp[length-1]
// 如包含在org.cn等顶级域名下后三个才为用户主域名
for _, staticMainDomain := range staticMainDomains {
if staticMainDomain == domain.DomainName {
domain.DomainName = sp[length-3] + "." + domain.DomainName
break
}
}
domainLen := len(domainStr) - len(domain.DomainName)
if domainLen > 0 {
domain.SubDomain = domainStr[:domainLen-1]
} else {
domain.SubDomain = domainStr[:domainLen]
}
}
case 2:
{
sp := strings.Split(dp[1], ".")
length := len(sp)
if length <= 1 {
log.Println(domainStr, "域名不正确")
continue
}
domain.DomainName = dp[1]
domain.SubDomain = dp[0]
}
default:
{
log.Println(domainStr, "域名不正确")
continue
}
}
if strings.Contains(domain.DomainName, "?") {
u, err := url.Parse("http://" + domain.DomainName)
if err != nil {
log.Println(domainStr, "域名解析失败")
continue
}
domain.DomainName = u.Host
domain.CustomParams = u.Query().Encode()
}
domains = append(domains, domain)
domainsRawStrList = append(domainsRawStrList, domainStr)
}
return
}

View File

@ -0,0 +1,52 @@
package ddnscore
import (
ddnsconf "github.com/gdy666/lucky/module/ddns/conf"
)
type DDNSTaskInfo struct {
ddnsconf.DDNSTask
TaskState DDNSTaskState `json:"TaskState"`
}
// CheckIPChange 检测公网IP是否改变
func (d *DDNSTaskInfo) CheckIPChange() (ipAddr string, change bool) {
ipAddr = d.GetIpAddr()
checkIPChange := d.TaskState.IPChanged(ipAddr)
if checkIPChange {
return ipAddr, true
}
//IP没变化
return ipAddr, false
}
func (d *DDNSTaskInfo) SyncDomains() {
if d.ModifyTime == d.TaskState.ModifyTime {
//fmt.Printf("不需要syncDomains\n")
return
}
//fmt.Printf("需要syncDomains\n")
domains, _ := checkParseDomains(d.Domains)
for i := range domains {
index := getDomainIndex(d.TaskState.Domains, &domains[i])
if index < 0 {
continue
}
domains[i] = d.TaskState.Domains[index]
}
d.TaskState.Domains = domains
d.TaskState.ModifyTime = d.ModifyTime
taskInfoMap.Store(d.TaskKey, &d.TaskState)
}
func getDomainIndex(domains []Domain, domain *Domain) (index int) {
index = -1
for i := range domains {
if domains[i].RawStr == domain.RawStr {
index = i
return
}
}
return
}

View File

@ -0,0 +1,105 @@
package ddnscore
import (
"time"
)
// 固定的主域名
var staticMainDomains = []string{"com.cn", "org.cn", "net.cn", "ac.cn", "eu.org"}
// 获取ip失败的次数
// Domains Ipv4/Ipv6 DDNSTaskState
type DDNSTaskState struct {
IpAddr string
Domains []Domain
domainsRawStrList []string
WebhookCallTime string `json:"WebhookCallTime"` //最后触发时间
WebhookCallResult bool `json:"WebhookCallResult"` //触发结果
WebhookCallErrorMsg string `json:"WebhookCallErrorMsg"` //触发错误信息
LastSyncTime time.Time `json:"-"` //记录最新一次同步操作时间
LastWorkTime time.Time `json:"-"`
IPAddrHistory []any `json:"IPAddrHistory"`
WebhookCallHistroy []any `json:"WebhookCallHistroy"`
ModifyTime int64
}
type IPAddrHistoryItem struct {
IPaddr string
RecordTime string
}
type WebhookCallHistroyItem struct {
CallTime string
CallResult string
}
func (d *DDNSTaskState) SetIPAddr(ipaddr string) {
if d.IpAddr == ipaddr {
return
}
d.IpAddr = ipaddr
hi := IPAddrHistoryItem{IPaddr: ipaddr, RecordTime: time.Now().Local().Format("2006-01-02 15:04:05")}
d.IPAddrHistory = append(d.IPAddrHistory, hi)
if len(d.IPAddrHistory) > 10 {
d.IPAddrHistory = DeleteAnyListlice(d.IPAddrHistory, 0)
}
}
func DeleteAnyListlice(a []any, deleteIndex int) []any {
j := 0
for i := range a {
if i != deleteIndex {
a[j] = a[i]
j++
}
}
return a[:j]
}
func (d *DDNSTaskState) SetDomainUpdateStatus(status string, message string) {
for i := range d.Domains {
d.Domains[i].SetDomainUpdateStatus(status, message)
}
}
func (d *DDNSTaskState) SetWebhookResult(result bool, errMsg string) {
d.WebhookCallResult = result
d.WebhookCallErrorMsg = errMsg
d.WebhookCallTime = time.Now().Format("2006-01-02 15:04:05")
cr := "成功"
if !result {
cr = "出错"
}
hi := WebhookCallHistroyItem{CallResult: cr, CallTime: time.Now().Local().Format("2006-01-02 15:04:05")}
d.WebhookCallHistroy = append(d.WebhookCallHistroy, hi)
if len(d.WebhookCallHistroy) > 10 {
d.WebhookCallHistroy = DeleteAnyListlice(d.WebhookCallHistroy, 0)
}
}
func (d *DDNSTaskState) Init(domains []string, mt int64) {
d.Domains, d.domainsRawStrList = checkParseDomains(domains)
d.ModifyTime = mt
}
// Check 检测IP是否有改变
func (d *DDNSTaskState) IPChanged(newAddr string) bool {
if newAddr == "" {
return true
}
// 地址改变
if d.IpAddr != newAddr {
//log.Printf("公网地址改变:[%s]===>[%s]", d.DomainsInfo.IpAddr, newAddr)
//domains.IpAddr = newAddr
return true
}
return false
}

View File

@ -0,0 +1,275 @@
package ddnscore
import (
"fmt"
"net/http"
"strings"
"time"
"github.com/gdy666/lucky/module/ddns/ddnsgo"
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
)
// ExecWebhook 添加或更新IPv4/IPv6记录
func (d *DDNSTaskInfo) ExecWebhook(state *DDNSTaskState) {
if !d.WebhookEnable {
return
}
if state.IpAddr == "" && !d.WebhookCallOnGetIPfail {
return
}
hasUpdate := hasDomainTryToUpdate(&state.Domains)
if d.WebhookURL != "" && (hasUpdate || (state.IpAddr == "" && d.WebhookCallOnGetIPfail)) {
//log.Printf("DDNS任务【%s】触发Webhook", d.TaskName)
nowTime := time.Now().Format("2006-01-02 15:04:05")
url := d.replaceWebhookPara(nowTime, d.WebhookURL)
requestBody := d.replaceWebhookPara(nowTime, d.WebhookRequestBody)
//headersStr := cb.task.DNS.Callback.Headers
var headerStrList []string
for i := range d.WebhookHeaders {
header := d.replaceWebhookPara(nowTime, d.WebhookHeaders[i])
headerStrList = append(headerStrList, header)
}
headers := httputils.CreateHeadersMap(headerStrList)
succcssCotentList := []string{}
for i := range d.WebhookSuccessContent {
content := d.replaceWebhookPara(nowTime, d.WebhookSuccessContent[i])
succcssCotentList = append(succcssCotentList, content)
}
callErr := d.webhookHttpClientDo(d.WebhookMethod, url, requestBody, headers, succcssCotentList)
if callErr != nil {
state.SetWebhookResult(false, callErr.Error())
return
}
state.SetWebhookResult(true, "")
}
}
func WebhookTest(d *DDNSTaskInfo, url, method, WebhookRequestBody, proxy, addr, user, passwd string, headerList, successContentListraw []string) (string, error) {
nowTime := time.Now().Format("2006-01-02 15:04:05")
url = replaceWebhookTestPara(url, nowTime)
requestBody := replaceWebhookTestPara(WebhookRequestBody, nowTime)
var headerStrList []string
for i := range headerList {
header := replaceWebhookTestPara(headerList[i], nowTime)
headerStrList = append(headerStrList, header)
}
headers := httputils.CreateHeadersMap(headerStrList)
succcssCotentList := []string{}
for i := range successContentListraw {
content := replaceWebhookTestPara(successContentListraw[i], nowTime)
succcssCotentList = append(succcssCotentList, content)
}
globalDDNSConf := ddnsgo.GetDDNSConfigure()
proxyType := ""
proxyAddr := ""
proxyUser := ""
proxyPasswd := ""
switch proxy {
case "dns":
{
if d.DNS.HttpClientProxyType != "" && d.DNS.HttpClientProxyAddr != "" {
proxyType = d.DNS.HttpClientProxyType
proxyAddr = d.DNS.HttpClientProxyAddr
proxyUser = d.DNS.HttpClientProxyUser
proxyPasswd = d.DNS.HttpClientProxyPassword
}
}
case "http", "https", "socks5":
{
proxyType = proxy
proxyAddr = addr
proxyUser = user
proxyPasswd = passwd
}
default:
}
//fmt.Printf("proxyType:%s\taddr:%s\t,user[%s]passwd[%s]\n", proxyType, proxyAddr, proxyUser, proxyPasswd)
//dnsConf := cb.task.DNS
_, respStr, err := httputils.GetStringGoutDoHttpRequest(
"tcp",
"",
method,
url,
requestBody,
proxyType,
proxyAddr,
proxyUser,
proxyPasswd,
headers,
!globalDDNSConf.HttpClientSecureVerify,
time.Second*20)
if err != nil {
return "", fmt.Errorf("webhookTest 调用接口[%s]出错:%s", url, err.Error())
}
for _, successContent := range succcssCotentList {
if strings.Contains(respStr, successContent) {
return respStr, nil
}
}
return respStr, fmt.Errorf("接口调用出错,未匹配预设成功返回的字符串")
}
func (d *DDNSTaskInfo) webhookHttpClientDo(method, url, requestBody string, headers map[string]string, callbackSuccessContent []string) error {
globalDDNSConf := ddnsgo.GetDDNSConfigure()
proxyType := ""
proxyAddr := ""
proxyUser := ""
proxyPasswd := ""
switch d.WebhookProxy {
case "dns":
{
if d.DNS.HttpClientProxyType != "" && d.DNS.HttpClientProxyAddr != "" {
proxyType = d.DNS.HttpClientProxyType
proxyAddr = d.DNS.HttpClientProxyAddr
proxyUser = d.DNS.HttpClientProxyUser
proxyPasswd = d.DNS.HttpClientProxyPassword
}
}
case "http", "https", "socks5":
{
proxyType = d.WebhookProxy
proxyAddr = d.WebhookProxyAddr
proxyUser = d.WebhookProxyUser
proxyPasswd = d.WebhookProxyPassword
}
default:
}
//dnsConf := cb.task.DNS
statusCode, respStr, err := httputils.GetStringGoutDoHttpRequest(
"tcp",
"",
method,
url,
requestBody,
proxyType,
proxyAddr,
proxyUser,
proxyPasswd,
headers,
!globalDDNSConf.HttpClientSecureVerify,
time.Second*20)
if err != nil {
return fmt.Errorf("webhook 调用接口[%s]出错:%s", url, err.Error())
}
if d.WebhookDisableCallbackSuccessContentCheck {
if statusCode == http.StatusOK {
return nil
}
return fmt.Errorf("webhook调用接口失败:\n statusCode:%d\n%s", statusCode, respStr)
}
for _, successContent := range callbackSuccessContent {
if strings.Contains(respStr, successContent) {
return nil
}
}
return fmt.Errorf("webhook 调用接口失败:\n%s", respStr)
}
// DomainsIsChange
func hasDomainTryToUpdate(domains *[]Domain) bool {
for _, v46 := range *domains {
switch v46.UpdateStatus {
case UpdatedFailed:
return true
case UpdatedSuccess:
return true
default:
}
}
return false
}
// replaceWebhookTestPara WebhookTest替换参数 #{successDomains},#{failedDomains}
func replaceWebhookTestPara(orgPara, nowTime string) (newPara string) {
orgPara = strings.ReplaceAll(orgPara, "#{ipAddr}", "66.66.66.66")
successDomains := "www1.google.com,www2.google.com,www3.google.com,www4.google.com"
failedDomains := "www1.github.com,www2.github.com,www3.github.com,www4.github.com"
successDomainsLine := strings.Replace(successDomains, ",", `\n`, -1)
failedDomainsLine := strings.Replace(failedDomains, ",", `\n`, -1)
orgPara = strings.ReplaceAll(orgPara, "#{successDomains}", successDomains)
orgPara = strings.ReplaceAll(orgPara, "#{failedDomains}", failedDomains)
orgPara = strings.ReplaceAll(orgPara, "#{successDomainsLine}", successDomainsLine)
orgPara = strings.ReplaceAll(orgPara, "#{failedDomainsLine}", failedDomainsLine)
orgPara = strings.ReplaceAll(orgPara, "#{time}", nowTime)
return orgPara
}
// replacePara 替换参数 #{successDomains},#{failedDomains}
func (d *DDNSTaskInfo) replaceWebhookPara(nowTime, orgPara string) (newPara string) {
ipAddrText := d.TaskState.IpAddr
successDomains, failedDomains := d.getDomainsStr(&d.TaskState.Domains)
if ipAddrText == "" {
ipAddrText = "获取IP失败"
successDomains = ""
failedDomains = ""
}
successDomainsLine := strings.Replace(successDomains, ",", `\n`, -1)
failedDomainsLine := strings.Replace(failedDomains, ",", `\n`, -1)
orgPara = strings.ReplaceAll(orgPara, "#{ipAddr}", ipAddrText)
orgPara = strings.ReplaceAll(orgPara, "#{successDomains}", successDomains)
orgPara = strings.ReplaceAll(orgPara, "#{failedDomains}", failedDomains)
orgPara = strings.ReplaceAll(orgPara, "#{successDomainsLine}", successDomainsLine)
orgPara = strings.ReplaceAll(orgPara, "#{failedDomainsLine}", failedDomainsLine)
orgPara = strings.ReplaceAll(orgPara, "#{time}", nowTime)
return orgPara
}
// getDomainsStr 用逗号分割域名,分类域名返回,成功和失败的
func (d *DDNSTaskInfo) getDomainsStr(domains *[]Domain) (string, string) {
var successDomainBuf strings.Builder
var failedDomainsBuf strings.Builder
for _, v46 := range *domains {
if v46.UpdateStatus == UpdatedFailed || (d.Webhook.WebhookCallOnGetIPfail && v46.UpdateStatus == UpdatePause) {
if failedDomainsBuf.Len() > 0 {
failedDomainsBuf.WriteString(",")
}
failedDomainsBuf.WriteString(v46.String())
continue
}
//if v46.UpdateStatus == UpdatedNothing || v46.UpdateStatus == UpdatedSuccess {
if v46.UpdateStatus == UpdatedSuccess {
if successDomainBuf.Len() > 0 {
successDomainBuf.WriteString(",")
}
successDomainBuf.WriteString(v46.String())
}
}
return successDomainBuf.String(), failedDomainsBuf.String()
}

209
module/ddns/ddnsgo/ddns.go Normal file
View File

@ -0,0 +1,209 @@
package ddnsgo
import (
"fmt"
"log"
"time"
"github.com/gdy666/lucky/config"
ddnsconf "github.com/gdy666/lucky/module/ddns/conf"
"github.com/gdy666/lucky/thirdlib/gdylib/stringsp"
)
func init() {
ddnsconf.SetGetDDNSConfigureFunc(GetDDNSConfigure)
}
func DDNSTaskListConfigureCheck() {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
for i := range config.Configure.DDNSTaskList {
if config.Configure.DDNSTaskList[i].DNS.ForceInterval < 60 {
config.Configure.DDNSTaskList[i].DNS.ForceInterval = 60
} else if config.Configure.DDNSTaskList[i].DNS.ForceInterval > 360000 {
config.Configure.DDNSTaskList[i].DNS.ForceInterval = 360000
}
if config.Configure.DDNSTaskList[i].HttpClientTimeout < 3 {
config.Configure.DDNSTaskList[i].HttpClientTimeout = 3
} else if config.Configure.DDNSTaskList[i].HttpClientTimeout > 60 {
config.Configure.DDNSTaskList[i].HttpClientTimeout = 60
}
}
}
func DDNSTaskSetWebhookCallResult(taskKey string, result bool, message string) {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
taskIndex := -1
for i := range config.Configure.DDNSTaskList {
if config.Configure.DDNSTaskList[i].TaskKey == taskKey {
taskIndex = i
break
}
}
if taskIndex == -1 {
return
}
log.Printf("DDNSTaskSetWebhookCallResult %s", taskKey)
}
func GetDDNSTaskConfigureList() []*ddnsconf.DDNSTask {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
var resList []*ddnsconf.DDNSTask
for i := range config.Configure.DDNSTaskList {
task := config.Configure.DDNSTaskList[i]
resList = append(resList, &task)
}
return resList
}
func GetDDNSTaskByKey(taskKey string) *ddnsconf.DDNSTask {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
taskIndex := -1
for i := range config.Configure.DDNSTaskList {
if config.Configure.DDNSTaskList[i].TaskKey == taskKey {
taskIndex = i
break
}
}
if taskIndex == -1 {
return nil
}
res := config.Configure.DDNSTaskList[taskIndex]
return &res
}
func DDNSTaskListAdd(task *ddnsconf.DDNSTask) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
task.TaskKey = stringsp.GetRandomString(16)
task.ModifyTime = time.Now().Unix()
config.Configure.DDNSTaskList = append(config.Configure.DDNSTaskList, *task)
return config.Save()
}
func DDNSTaskListDelete(taskKey string) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
taskIndex := -1
for i := range config.Configure.DDNSTaskList {
if config.Configure.DDNSTaskList[i].TaskKey == taskKey {
taskIndex = i
break
}
}
if taskIndex == -1 {
return fmt.Errorf("找不到需要删除的DDNS任务")
}
config.Configure.DDNSTaskList = DeleteDDNSTaskListlice(config.Configure.DDNSTaskList, taskIndex)
return config.Save()
}
func EnableDDNSTaskByKey(taskKey string, enable bool) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
taskIndex := -1
for i := range config.Configure.DDNSTaskList {
if config.Configure.DDNSTaskList[i].TaskKey == taskKey {
taskIndex = i
break
}
}
if taskIndex == -1 {
return fmt.Errorf("开关DDNS任务失败,TaskKey不存在")
}
config.Configure.DDNSTaskList[taskIndex].Enable = enable
return config.Save()
}
func UpdateTaskToDDNSTaskList(taskKey string, task ddnsconf.DDNSTask) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
taskIndex := -1
for i := range config.Configure.DDNSTaskList {
if config.Configure.DDNSTaskList[i].TaskKey == taskKey {
taskIndex = i
break
}
}
if taskIndex == -1 {
return fmt.Errorf("找不到需要更新的DDNS任务")
}
config.Configure.DDNSTaskList[taskIndex].TaskName = task.TaskName
config.Configure.DDNSTaskList[taskIndex].TaskType = task.TaskType
config.Configure.DDNSTaskList[taskIndex].Enable = task.Enable
config.Configure.DDNSTaskList[taskIndex].GetType = task.GetType
config.Configure.DDNSTaskList[taskIndex].URL = task.URL
config.Configure.DDNSTaskList[taskIndex].NetInterface = task.NetInterface
config.Configure.DDNSTaskList[taskIndex].IPReg = task.IPReg
config.Configure.DDNSTaskList[taskIndex].Domains = task.Domains
config.Configure.DDNSTaskList[taskIndex].DNS = task.DNS
config.Configure.DDNSTaskList[taskIndex].Webhook = task.Webhook
config.Configure.DDNSTaskList[taskIndex].TTL = task.TTL
config.Configure.DDNSTaskList[taskIndex].HttpClientTimeout = task.HttpClientTimeout
config.Configure.DDNSTaskList[taskIndex].ModifyTime = time.Now().Unix()
return config.Save()
}
func DeleteDDNSTaskListlice(a []ddnsconf.DDNSTask, deleteIndex int) []ddnsconf.DDNSTask {
j := 0
for i := range a {
if i != deleteIndex {
a[j] = a[i]
j++
}
}
return a[:j]
}
func GetDDNSConfigure() ddnsconf.DDNSConfigure {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
conf := config.Configure.DDNSConfigure
return conf
}
func SetDDNSConfigure(conf *ddnsconf.DDNSConfigure) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
if conf.Intervals < 30 {
conf.Intervals = 30
}
if conf.Intervals > 3600 {
conf.Intervals = 3600
}
if conf.FirstCheckDelay < 0 {
conf.FirstCheckDelay = 0
}
if conf.FirstCheckDelay > 3600 {
conf.FirstCheckDelay = 3600
}
config.Configure.DDNSConfigure = *conf
return config.Save()
}

View File

@ -0,0 +1,180 @@
package providers
import (
"bytes"
"fmt"
"log"
"net/http"
"net/url"
"github.com/gdy666/lucky/module/ddns/ddnscore.go"
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
"github.com/gdy666/lucky/thirdlib/jeessy2/ddns-go/util"
)
const (
alidnsEndpoint string = "https://alidns.aliyuncs.com/"
)
// https://help.aliyun.com/document_detail/29776.html?spm=a2c4g.11186623.6.672.715a45caji9dMA
// Alidns Alidns
type Alidns struct {
ProviderCommon
TTL string
}
// AlidnsRecord record
type AlidnsRecord struct {
DomainName string
RecordID string
Value string
}
// AlidnsSubDomainRecords 记录
type AlidnsSubDomainRecords struct {
TotalCount int
DomainRecords struct {
Record []AlidnsRecord
}
}
// AlidnsResp 修改/添加返回结果
type AlidnsResp struct {
RecordID string
RequestID string
}
// Init 初始化
func (ali *Alidns) Init(task *ddnscore.DDNSTaskInfo) {
ali.ProviderCommon.Init(task)
if task.TTL == "" {
// 默认600s
ali.TTL = "600"
} else {
ali.TTL = task.TTL
}
ali.SetCreateUpdateDomainFunc(ali.createUpdateDomain)
}
func (ali *Alidns) createUpdateDomain(recordType, ipAddr string, domain *ddnscore.Domain) {
var records AlidnsSubDomainRecords
// 获取当前域名信息
params := domain.GetCustomParams()
params.Set("Action", "DescribeSubDomainRecords")
params.Set("DomainName", domain.DomainName)
params.Set("SubDomain", domain.GetFullDomain())
params.Set("Type", recordType)
err := ali.request(params, &records)
if err != nil {
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, err.Error())
return
}
if records.TotalCount > 0 {
// 默认第一个
recordSelected := records.DomainRecords.Record[0]
if params.Has("RecordId") {
for i := 0; i < len(records.DomainRecords.Record); i++ {
if records.DomainRecords.Record[i].RecordID == params.Get("RecordId") {
recordSelected = records.DomainRecords.Record[i]
}
}
}
// 存在,更新
ali.modify(recordSelected, domain, recordType, ipAddr)
} else {
// 不存在,创建
ali.create(domain, recordType, ipAddr)
}
}
// 创建
func (ali *Alidns) create(domain *ddnscore.Domain, recordType string, ipAddr string) {
params := domain.GetCustomParams()
params.Set("Action", "AddDomainRecord")
params.Set("DomainName", domain.DomainName)
params.Set("RR", domain.GetSubDomain())
params.Set("Type", recordType)
params.Set("Value", ipAddr)
params.Set("TTL", ali.TTL)
var result AlidnsResp
err := ali.request(params, &result)
if err == nil && result.RecordID != "" {
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
errMsg := fmt.Sprintf("创建域名失败:\n%v\n", result)
if err != nil {
errMsg += err.Error()
}
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, errMsg)
}
}
// 修改
func (ali *Alidns) modify(recordSelected AlidnsRecord, domain *ddnscore.Domain, recordType string, ipAddr string) {
// 相同不修改
if recordSelected.Value == ipAddr {
if domain.UpdateStatus == ddnscore.UpdatedFailed {
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
domain.SetDomainUpdateStatus(ddnscore.UpdatedNothing, "")
}
return
}
params := domain.GetCustomParams()
params.Set("Action", "UpdateDomainRecord")
params.Set("RR", domain.GetSubDomain())
params.Set("RecordId", recordSelected.RecordID)
params.Set("Type", recordType)
params.Set("Value", ipAddr)
params.Set("TTL", ali.TTL)
var result AlidnsResp
err := ali.request(params, &result)
if err == nil && result.RecordID != "" {
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
errMsg := fmt.Sprintf("更新域名解析失败:%v\n", result)
if err != nil {
errMsg += err.Error()
}
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, errMsg)
}
}
// request 统一请求接口
func (ali *Alidns) request(params url.Values, result interface{}) (err error) {
util.AliyunSigner(ali.task.DNS.ID, ali.task.DNS.Secret, &params)
req, err := http.NewRequest(
"GET",
alidnsEndpoint,
bytes.NewBuffer(nil),
)
req.URL.RawQuery = params.Encode()
if err != nil {
log.Println("http.NewRequest失败. Error: ", err)
return
}
client, err := ali.CreateHTTPClient()
if err != nil {
return err
}
resp, err := client.Do(req)
if err != nil {
return err
}
return httputils.GetAndParseJSONResponseFromHttpResponse(resp, result)
}

View File

@ -0,0 +1,204 @@
package providers
import (
"bytes"
"encoding/json"
"log"
"net/http"
"strconv"
"github.com/gdy666/lucky/module/ddns/ddnscore.go"
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
"github.com/gdy666/lucky/thirdlib/jeessy2/ddns-go/util"
)
// https://cloud.baidu.com/doc/BCD/s/4jwvymhs7
const (
baiduEndpoint = "https://bcd.baidubce.com"
)
type BaiduCloud struct {
ProviderCommon
TTL int
}
// BaiduRecord 单条解析记录
type BaiduRecord struct {
RecordId uint `json:"recordId"`
Domain string `json:"domain"`
View string `json:"view"`
Rdtype string `json:"rdtype"`
TTL int `json:"ttl"`
Rdata string `json:"rdata"`
ZoneName string `json:"zoneName"`
Status string `json:"status"`
}
// BaiduRecordsResp 获取解析列表拿到的结果
type BaiduRecordsResp struct {
TotalCount int `json:"totalCount"`
Result []BaiduRecord `json:"result"`
}
// BaiduListRequest 获取解析列表请求的body json
type BaiduListRequest struct {
Domain string `json:"domain"`
PageNum int `json:"pageNum"`
PageSize int `json:"pageSize"`
}
// BaiduModifyRequest 修改解析请求的body json
type BaiduModifyRequest struct {
RecordId uint `json:"recordId"`
Domain string `json:"domain"`
View string `json:"view"`
RdType string `json:"rdType"`
TTL int `json:"ttl"`
Rdata string `json:"rdata"`
ZoneName string `json:"zoneName"`
}
// BaiduCreateRequest 创建新解析请求的body json
type BaiduCreateRequest struct {
Domain string `json:"domain"`
RdType string `json:"rdType"`
TTL int `json:"ttl"`
Rdata string `json:"rdata"`
ZoneName string `json:"zoneName"`
}
func (baidu *BaiduCloud) Init(task *ddnscore.DDNSTaskInfo) {
baidu.ProviderCommon.Init(task)
if task.TTL == "" {
// 默认300s
baidu.TTL = 300
} else {
ttl, err := strconv.Atoi(task.TTL)
if err != nil {
baidu.TTL = 300
} else {
baidu.TTL = ttl
}
}
baidu.SetCreateUpdateDomainFunc(baidu.createUpdateDomain)
}
func (baidu *BaiduCloud) createUpdateDomain(recordType, ipAddr string, domain *ddnscore.Domain) {
var records BaiduRecordsResp
requestBody := BaiduListRequest{
Domain: domain.DomainName,
PageNum: 1,
PageSize: 1000,
}
err := baidu.request("POST", baiduEndpoint+"/v1/domain/resolve/list", requestBody, &records)
if err != nil {
errMsg := "更新失败[001]:\n"
errMsg += err.Error()
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, errMsg)
return
}
find := false
for _, record := range records.Result {
if record.Domain == domain.GetSubDomain() {
//存在就去更新
baidu.modify(record, domain, recordType, ipAddr)
find = true
break
}
}
if !find {
//没找到,去创建
baidu.create(domain, recordType, ipAddr)
}
}
// create 创建新的解析
func (baidu *BaiduCloud) create(domain *ddnscore.Domain, recordType string, ipAddr string) {
var baiduCreateRequest = BaiduCreateRequest{
Domain: domain.GetSubDomain(), //处理一下@
RdType: recordType,
TTL: baidu.TTL,
Rdata: ipAddr,
ZoneName: domain.DomainName,
}
var result BaiduRecordsResp
err := baidu.request("POST", baiduEndpoint+"/v1/domain/resolve/add", baiduCreateRequest, &result)
if err == nil {
//log.Printf("新增域名解析 %s 成功IP: %s", domain, ipAddr)
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
//log.Printf("新增域名解析 %s 失败!", domain)
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, err.Error())
}
}
// modify 更新解析
func (baidu *BaiduCloud) modify(record BaiduRecord, domain *ddnscore.Domain, rdType string, ipAddr string) {
//没有变化直接跳过
if record.Rdata == ipAddr {
if domain.UpdateStatus == ddnscore.UpdatedFailed {
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
domain.SetDomainUpdateStatus(ddnscore.UpdatedNothing, "")
}
return
}
var baiduModifyRequest = BaiduModifyRequest{
RecordId: record.RecordId,
Domain: record.Domain,
View: record.View,
RdType: rdType,
TTL: record.TTL,
Rdata: ipAddr,
ZoneName: record.ZoneName,
}
var result BaiduRecordsResp
err := baidu.request("POST", baiduEndpoint+"/v1/domain/resolve/edit", baiduModifyRequest, &result)
if err == nil {
//log.Printf("更新域名解析 %s 成功IP: %s", domain, ipAddr)
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
//log.Printf("更新域名解析 %s 失败!", domain)
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, err.Error())
}
}
// request 统一请求接口
func (baidu *BaiduCloud) request(method string, url string, data interface{}, result interface{}) (err error) {
jsonStr := make([]byte, 0)
if data != nil {
jsonStr, _ = json.Marshal(data)
}
req, err := http.NewRequest(
method,
url,
bytes.NewBuffer(jsonStr),
)
if err != nil {
log.Println("http.NewRequest失败. Error: ", err)
return
}
util.BaiduSigner(baidu.task.DNS.ID, baidu.task.DNS.Secret, req)
client, err := baidu.CreateHTTPClient()
if err != nil {
return err
}
resp, err := client.Do(req)
if err != nil {
return err
}
return httputils.GetAndParseJSONResponseFromHttpResponse(resp, result)
}

View File

@ -0,0 +1,126 @@
package providers
import (
"fmt"
"net/http"
"strings"
"time"
"github.com/gdy666/lucky/module/ddns/ddnscore.go"
"github.com/gdy666/lucky/module/ddns/ddnsgo"
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
)
type Callback struct {
ProviderCommon
TTL string
}
// Init 初始化
func (cb *Callback) Init(task *ddnscore.DDNSTaskInfo) {
cb.ProviderCommon.Init(task)
if task.TTL == "" {
// 默认600
cb.TTL = "600"
} else {
cb.TTL = task.TTL
}
cb.SetCreateUpdateDomainFunc(cb.createUpdateDomain)
}
func CopyHeadersMap(sm map[string]string) map[string]string {
dm := make(map[string]string)
for k, v := range sm {
dm[k] = v
}
return dm
}
func (cb *Callback) createUpdateDomain(recordType, ipAddr string, domain *ddnscore.Domain) {
url := replacePara(cb.task.DNS.Callback.URL, ipAddr, domain, recordType, cb.TTL)
requestBody := replacePara(cb.task.DNS.Callback.RequestBody, ipAddr, domain, recordType, cb.TTL)
//headersStr := cb.task.DNS.Callback.Headers
var headerStrList []string
for i := range cb.task.DNS.Callback.Headers {
header := replacePara(cb.task.DNS.Callback.Headers[i], ipAddr, domain, recordType, cb.TTL)
headerStrList = append(headerStrList, header)
}
headers := httputils.CreateHeadersMap(headerStrList)
succcssCotentList := []string{}
for i := range cb.task.DNS.Callback.CallbackSuccessContent {
content := replacePara(cb.task.DNS.Callback.CallbackSuccessContent[i], ipAddr, domain, recordType, cb.TTL)
succcssCotentList = append(succcssCotentList, content)
}
callErr := cb.CallbackHttpClientDo(cb.task.DNS.Callback.Method, url, requestBody, headers, succcssCotentList)
if callErr != nil {
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, callErr.Error())
return
}
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
}
// replacePara 替换参数
func replacePara(orgPara, ipAddr string, domain *ddnscore.Domain, recordType string, ttl string) (newPara string) {
orgPara = strings.ReplaceAll(orgPara, "#{ip}", ipAddr)
orgPara = strings.ReplaceAll(orgPara, "#{domain}", domain.String())
orgPara = strings.ReplaceAll(orgPara, "#{recordType}", recordType)
orgPara = strings.ReplaceAll(orgPara, "#{ttl}", ttl)
for k, v := range domain.GetCustomParams() {
if len(v) == 1 {
orgPara = strings.ReplaceAll(orgPara, "#{"+k+"}", v[0])
}
}
return orgPara
}
func (cb *Callback) CallbackHttpClientDo(method, url, requestBody string, headers map[string]string, callbackSuccessContent []string) error {
globalDDNSConf := ddnsgo.GetDDNSConfigure()
dnsConf := cb.task.DNS
statusCode, respStr, err := httputils.GetStringGoutDoHttpRequest(
"tcp",
"",
method,
url,
requestBody,
dnsConf.HttpClientProxyType,
dnsConf.HttpClientProxyAddr,
dnsConf.HttpClientProxyUser,
dnsConf.HttpClientProxyPassword,
headers,
!globalDDNSConf.HttpClientSecureVerify,
time.Duration(cb.task.HttpClientTimeout)*time.Second)
if err != nil {
return fmt.Errorf("Callback 调用接口[%s]出错:%s", url, err.Error())
}
if cb.task.DNS.Callback.DisableCallbackSuccessContentCheck {
if statusCode == http.StatusOK {
return nil
}
return fmt.Errorf("调用接口失败:\n statusCode:%d\n%s", statusCode, respStr)
}
//log.Printf("接口[%s]调用响应:%s\n", url, respStr)
//fmt.Printf("statusCode:%d\n", statusCode)
for _, successContent := range callbackSuccessContent {
if strings.Contains(respStr, successContent) {
return nil
}
}
return fmt.Errorf("调用接口失败:\n%s", respStr)
}

View File

@ -0,0 +1,226 @@
package providers
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"github.com/gdy666/lucky/module/ddns/ddnscore.go"
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
)
const (
zonesAPI string = "https://api.cloudflare.com/client/v4/zones"
)
// Cloudflare Cloudflare实现
type Cloudflare struct {
ProviderCommon
TTL int
}
// CloudflareZonesResp cloudflare zones返回结果
type CloudflareZonesResp struct {
CloudflareStatus
Result []struct {
ID string
Name string
Status string
Paused bool
}
}
// CloudflareRecordsResp records
type CloudflareRecordsResp struct {
CloudflareStatus
Result []CloudflareRecord
}
// CloudflareRecord 记录实体
type CloudflareRecord struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Content string `json:"content"`
Proxied bool `json:"proxied"`
TTL int `json:"ttl"`
}
// CloudflareStatus 公共状态
type CloudflareStatus struct {
Success bool
Messages []string
}
// Init 初始化
func (cf *Cloudflare) Init(task *ddnscore.DDNSTaskInfo) {
cf.ProviderCommon.Init(task)
if task.TTL == "" {
// 默认1 auto ttl
cf.TTL = 1
} else {
ttl, err := strconv.Atoi(task.TTL)
if err != nil {
cf.TTL = 1
} else {
cf.TTL = ttl
}
}
cf.SetCreateUpdateDomainFunc(cf.createUpdateDomain)
}
func (cf *Cloudflare) createUpdateDomain(recordType, ipAddr string, domain *ddnscore.Domain) {
result, err := cf.getZones(domain)
if err != nil || len(result.Result) != 1 {
errMsg := "更新失败[001]:\n"
if err != nil {
errMsg += err.Error()
} else {
errMsg += fmt.Sprintf("%v", result)
}
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, errMsg)
return
}
zoneID := result.Result[0].ID
var records CloudflareRecordsResp
// getDomains 最多更新前50条
err = cf.request(
"GET",
fmt.Sprintf(zonesAPI+"/%s/dns_records?type=%s&name=%s&per_page=50", zoneID, recordType, domain),
nil,
&records,
)
if err != nil || !records.Success {
errMsg := "更新失败[002]:\n"
if err != nil {
errMsg += err.Error()
}
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, errMsg)
return
}
if len(records.Result) > 0 {
// 更新
cf.modify(records, zoneID, domain, recordType, ipAddr)
} else {
// 新增
cf.create(zoneID, domain, recordType, ipAddr)
}
}
// 创建
func (cf *Cloudflare) create(zoneID string, domain *ddnscore.Domain, recordType string, ipAddr string) {
record := &CloudflareRecord{
Type: recordType,
Name: domain.String(),
Content: ipAddr,
Proxied: false,
TTL: cf.TTL,
}
var status CloudflareStatus
err := cf.request(
"POST",
fmt.Sprintf(zonesAPI+"/%s/dns_records", zoneID),
record,
&status,
)
if err == nil && status.Success {
//log.Printf("新增域名解析 %s 成功IP: %s", domain, ipAddr)
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
errMsg := fmt.Sprintf("创建域名失败:\n%v\n", status)
if err != nil {
errMsg += fmt.Sprintf(":%s", err.Error())
}
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, errMsg)
}
}
// 修改
func (cf *Cloudflare) modify(result CloudflareRecordsResp, zoneID string, domain *ddnscore.Domain, recordType string, ipAddr string) {
for _, record := range result.Result {
// 相同不修改
if record.Content == ipAddr {
if domain.UpdateStatus == ddnscore.UpdatedFailed {
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
domain.SetDomainUpdateStatus(ddnscore.UpdatedNothing, "")
}
continue
}
var status CloudflareStatus
record.Content = ipAddr
record.TTL = cf.TTL
err := cf.request(
"PUT",
fmt.Sprintf(zonesAPI+"/%s/dns_records/%s", zoneID, record.ID),
record,
&status,
)
if err == nil && status.Success {
//log.Printf("更新域名解析 %s 成功IP: %s", domain, ipAddr)
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
//log.Printf("更新域名解析 %s 失败Messages: %s", domain, status.Messages)
errMsg := fmt.Sprintf("更新域名解析失败:%v\n", status)
if err != nil {
errMsg += err.Error()
}
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, errMsg)
}
}
}
// 获得域名记录列表
func (cf *Cloudflare) getZones(domain *ddnscore.Domain) (result CloudflareZonesResp, err error) {
err = cf.request(
"GET",
fmt.Sprintf(zonesAPI+"?name=%s&status=%s&per_page=%s", domain.DomainName, "active", "50"),
nil,
&result,
)
return
}
// request 统一请求接口
func (cf *Cloudflare) request(method string, url string, data interface{}, result interface{}) (err error) {
jsonStr := make([]byte, 0)
if data != nil {
jsonStr, _ = json.Marshal(data)
}
req, err := http.NewRequest(
method,
url,
bytes.NewBuffer(jsonStr),
)
if err != nil {
log.Println("http.NewRequest失败. Error: ", err)
return
}
req.Header.Set("Authorization", "Bearer "+cf.task.DNS.Secret)
req.Header.Set("Content-Type", "application/json")
client, err := cf.CreateHTTPClient()
if err != nil {
return err
}
resp, err := client.Do(req)
if err != nil {
return err
}
return httputils.GetAndParseJSONResponseFromHttpResponse(resp, result)
}

View File

@ -0,0 +1,195 @@
package providers
import (
"fmt"
"net/url"
"github.com/gdy666/lucky/module/ddns/ddnscore.go"
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
)
const (
recordListAPI string = "https://dnsapi.cn/Record.List"
recordModifyURL string = "https://dnsapi.cn/Record.Modify"
recordCreateAPI string = "https://dnsapi.cn/Record.Create"
)
// https://cloud.tencent.com/document/api/302/8516
// Dnspod 腾讯云dns实现
type Dnspod struct {
ProviderCommon
TTL string
}
// DnspodRecordListResp recordListAPI结果
type DnspodRecordListResp struct {
DnspodStatus
Records []struct {
ID string
Name string
Type string
Value string
Enabled string
}
}
// DnspodStatus DnspodStatus
type DnspodStatus struct {
Status struct {
Code string
Message string
}
}
// Init 初始化
func (dnspod *Dnspod) Init(task *ddnscore.DDNSTaskInfo) {
dnspod.ProviderCommon.Init(task)
if task.TTL == "" {
// 默认600s
dnspod.TTL = "600"
} else {
dnspod.TTL = task.TTL
}
dnspod.SetCreateUpdateDomainFunc(dnspod.createUpdateDomain)
}
func (dnspod *Dnspod) createUpdateDomain(recordType, ipAddr string, domain *ddnscore.Domain) {
result, err := dnspod.getRecordList(domain, recordType)
if err != nil {
errMsg := "更新失败[001]:\n"
errMsg += err.Error()
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, errMsg)
return
}
if len(result.Records) > 0 {
// 更新
dnspod.modify(result, domain, recordType, ipAddr)
} else {
// 新增
dnspod.create(result, domain, recordType, ipAddr)
}
}
// 创建
func (dnspod *Dnspod) create(result DnspodRecordListResp, domain *ddnscore.Domain, recordType string, ipAddr string) {
params := domain.GetCustomParams()
params.Add("login_token", dnspod.task.DNS.ID+","+dnspod.task.DNS.Secret)
params.Add("domain", domain.DomainName)
params.Add("sub_domain", domain.GetSubDomain())
params.Add("record_type", recordType)
params.Add("value", ipAddr)
params.Add("ttl", dnspod.TTL)
params.Add("format", "json")
if !params.Has("record_line") {
params.Add("record_line", "默认")
}
status, err := dnspod.commonRequest(recordCreateAPI, params, domain)
if err == nil && status.Status.Code == "1" {
//log.Printf("新增域名解析 %s 成功IP: %s", domain, ipAddr)
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
errMsg := fmt.Sprintf("创建域名失败:%v\n", status)
if err != nil {
errMsg += err.Error()
}
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, errMsg)
}
}
// 修改
func (dnspod *Dnspod) modify(result DnspodRecordListResp, domain *ddnscore.Domain, recordType string, ipAddr string) {
for _, record := range result.Records {
// 相同不修改
if record.Value == ipAddr {
if domain.UpdateStatus == ddnscore.UpdatedFailed {
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
domain.SetDomainUpdateStatus(ddnscore.UpdatedNothing, "")
}
continue
}
params := domain.GetCustomParams()
params.Add("login_token", dnspod.task.DNS.ID+","+dnspod.task.DNS.Secret)
params.Add("domain", domain.DomainName)
params.Add("sub_domain", domain.GetSubDomain())
params.Add("record_type", recordType)
params.Add("value", ipAddr)
params.Add("ttl", dnspod.TTL)
params.Add("format", "json")
params.Add("record_id", record.ID)
if !params.Has("record_line") {
params.Add("record_line", "默认")
}
status, err := dnspod.commonRequest(recordModifyURL, params, domain)
if err == nil && status.Status.Code == "1" {
//log.Printf("更新域名解析 %s 成功IP: %s", domain, ipAddr)
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
errMsg := fmt.Sprintf("更新域名解析失败:%v\n", status)
if err != nil {
errMsg += err.Error()
}
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, errMsg)
}
}
}
// 公共
func (dnspod *Dnspod) commonRequest(apiAddr string, values url.Values, domain *ddnscore.Domain) (status DnspodStatus, err error) {
client, e := dnspod.CreateHTTPClient()
if e != nil {
err = e
return
}
resp, e := client.PostForm(
apiAddr,
values,
)
if e != nil {
err = e
return
}
err = httputils.GetAndParseJSONResponseFromHttpResponse(resp, &status)
return
}
// 获得域名记录列表
func (dnspod *Dnspod) getRecordList(domain *ddnscore.Domain, typ string) (result DnspodRecordListResp, err error) {
params := domain.GetCustomParams()
params.Add("login_token", dnspod.task.DNS.ID+","+dnspod.task.DNS.Secret)
params.Add("domain", domain.DomainName)
params.Add("record_type", typ)
params.Add("sub_domain", domain.GetSubDomain())
params.Add("format", "json")
if !params.Has("record_line") {
params.Add("record_line", "默认")
}
client, e := dnspod.CreateHTTPClient()
if e != nil {
err = e
return
}
resp, err := client.PostForm(
recordListAPI,
params,
)
if err != nil {
err = e
return
}
err = httputils.GetAndParseJSONResponseFromHttpResponse(resp, &result)
return
}

View File

@ -0,0 +1,100 @@
package providers
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/gdy666/lucky/module/ddns/ddnscore.go"
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
)
type godaddyRecord struct {
Data string `json:"data"`
Name string `json:"name"`
TTL int `json:"ttl"`
Type string `json:"type"`
}
type godaddyRecords []godaddyRecord
type GoDaddy struct {
ProviderCommon
TTL int
header http.Header
client *http.Client
}
// Init 初始化
func (gd *GoDaddy) Init(task *ddnscore.DDNSTaskInfo) {
gd.ProviderCommon.Init(task)
// if task.TTL == "" {
// // 默认600s
// gd.TTL = 600
// } else {
// gd.TTL = task.TTL
// }
if task.TTL == "" {
// 默认300s
gd.TTL = 600
} else {
ttl, err := strconv.Atoi(task.TTL)
if err != nil {
gd.TTL = 600
} else {
gd.TTL = ttl
}
}
gd.header = map[string][]string{
"Authorization": {fmt.Sprintf("sso-key %s:%s", task.DNS.ID, task.DNS.Secret)},
"Content-Type": {"application/json"},
}
//g.throttle, _ = util.GetThrottle(55)
gd.client, _ = gd.CreateHTTPClient()
gd.SetCreateUpdateDomainFunc(gd.createUpdateDomain)
}
func (gd *GoDaddy) createUpdateDomain(recordType, ipAddr string, domain *ddnscore.Domain) {
_, err := gd.sendReq(http.MethodPut, recordType, domain, &godaddyRecords{godaddyRecord{
Data: ipAddr,
Name: domain.SubDomain,
TTL: gd.TTL,
Type: recordType,
}})
if err != nil {
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, err.Error())
}
}
func (gd *GoDaddy) sendReq(method string, rType string, domain *ddnscore.Domain, data any) (*godaddyRecords, error) {
var body *bytes.Buffer
if data != nil {
if buffer, err := json.Marshal(data); err != nil {
return nil, err
} else {
body = bytes.NewBuffer(buffer)
}
}
path := fmt.Sprintf("https://api.godaddy.com/v1/domains/%s/records/%s/%s",
domain.DomainName, rType, domain.SubDomain)
req, err := http.NewRequest(method, path, body)
if err != nil {
return nil, err
}
req.Header = gd.header
resp, err := gd.client.Do(req)
if err != nil {
return nil, err
}
result := &godaddyRecords{}
httputils.GetAndParseJSONResponseFromHttpResponse(resp, result)
return result, nil
}

View File

@ -0,0 +1,234 @@
package providers
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"github.com/gdy666/lucky/module/ddns/ddnscore.go"
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
"github.com/gdy666/lucky/thirdlib/jeessy2/ddns-go/util"
)
const (
huaweicloudEndpoint string = "https://dns.myhuaweicloud.com"
)
// https://support.huaweicloud.com/api-dns/dns_api_64001.html
// Huaweicloud Huaweicloud
type Huaweicloud struct {
ProviderCommon
TTL int
}
// HuaweicloudZonesResp zones response
type HuaweicloudZonesResp struct {
Zones []struct {
ID string
Name string
Recordsets []HuaweicloudRecordsets
}
}
// HuaweicloudRecordsResp 记录返回结果
type HuaweicloudRecordsResp struct {
Recordsets []HuaweicloudRecordsets
}
// HuaweicloudRecordsets 记录
type HuaweicloudRecordsets struct {
ID string
Name string `json:"name"`
ZoneID string `json:"zone_id"`
Status string
Type string `json:"type"`
TTL int `json:"ttl"`
Records []string `json:"records"`
}
// Init 初始化
func (hw *Huaweicloud) Init(task *ddnscore.DDNSTaskInfo) {
hw.ProviderCommon.Init(task)
if task.TTL == "" {
// 默认300s
hw.TTL = 300
} else {
ttl, err := strconv.Atoi(task.TTL)
if err != nil {
hw.TTL = 300
} else {
hw.TTL = ttl
}
}
hw.SetCreateUpdateDomainFunc(hw.createUpdateDomain)
}
func (hw *Huaweicloud) createUpdateDomain(recordType, ipAddr string, domain *ddnscore.Domain) {
var records HuaweicloudRecordsResp
err := hw.request(
"GET",
fmt.Sprintf(huaweicloudEndpoint+"/v2/recordsets?type=%s&name=%s", recordType, domain),
nil,
&records,
)
if err != nil {
errMsg := "更新失败[001]:\n"
errMsg += err.Error()
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, errMsg)
return
}
find := false
for _, record := range records.Recordsets {
// 名称相同才更新。华为云默认是模糊搜索
if record.Name == domain.String()+"." {
// 更新
hw.modify(record, domain, recordType, ipAddr)
find = true
break
}
}
if !find {
// 新增
hw.create(domain, recordType, ipAddr)
}
}
// 创建
func (hw *Huaweicloud) create(domain *ddnscore.Domain, recordType string, ipAddr string) {
zone, err := hw.getZones(domain)
if err != nil {
return
}
if len(zone.Zones) == 0 {
log.Println("未能找到公网域名, 请检查域名是否添加")
return
}
zoneID := zone.Zones[0].ID
for _, z := range zone.Zones {
if z.Name == domain.DomainName+"." {
zoneID = z.ID
break
}
}
record := &HuaweicloudRecordsets{
Type: recordType,
Name: domain.String() + ".",
Records: []string{ipAddr},
TTL: hw.TTL,
}
var result HuaweicloudRecordsets
err = hw.request(
"POST",
fmt.Sprintf(huaweicloudEndpoint+"/v2/zones/%s/recordsets", zoneID),
record,
&result,
)
if err == nil && (len(result.Records) > 0 && result.Records[0] == ipAddr) {
//log.Printf("新增域名解析 %s 成功IP: %s", domain, ipAddr)
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
errMsg := fmt.Sprintf("新增域名失败:%v\n", result)
if err != nil {
errMsg += err.Error()
}
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, errMsg)
}
}
// 修改
func (hw *Huaweicloud) modify(record HuaweicloudRecordsets, domain *ddnscore.Domain, recordType string, ipAddr string) {
// 相同不修改
if len(record.Records) > 0 && record.Records[0] == ipAddr {
if domain.UpdateStatus == ddnscore.UpdatedFailed {
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
domain.SetDomainUpdateStatus(ddnscore.UpdatedNothing, "")
}
return
}
var request map[string]interface{} = make(map[string]interface{})
request["records"] = []string{ipAddr}
request["ttl"] = hw.TTL
var result HuaweicloudRecordsets
err := hw.request(
"PUT",
fmt.Sprintf(huaweicloudEndpoint+"/v2/zones/%s/recordsets/%s", record.ZoneID, record.ID),
&request,
&result,
)
if err == nil && (len(result.Records) > 0 && result.Records[0] == ipAddr) {
//log.Printf("更新域名解析 %s 成功IP: %s, 状态: %s", domain, ipAddr, result.Status)
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
errMsg := fmt.Sprintf("更新域名解析:%v\n", result)
if err != nil {
errMsg += err.Error()
}
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, errMsg)
}
}
// 获得域名记录列表
func (hw *Huaweicloud) getZones(domain *ddnscore.Domain) (result HuaweicloudZonesResp, err error) {
err = hw.request(
"GET",
fmt.Sprintf(huaweicloudEndpoint+"/v2/zones?name=%s", domain.DomainName),
nil,
&result,
)
return
}
// request 统一请求接口
func (hw *Huaweicloud) request(method string, url string, data interface{}, result interface{}) (err error) {
jsonStr := make([]byte, 0)
if data != nil {
jsonStr, _ = json.Marshal(data)
}
req, err := http.NewRequest(
method,
url,
bytes.NewBuffer(jsonStr),
)
if err != nil {
log.Println("http.NewRequest失败. Error: ", err)
return
}
s := util.Signer{
Key: hw.task.DNS.ID,
Secret: hw.task.DNS.Secret,
}
s.Sign(req)
req.Header.Add("content-type", "application/json")
client, err := hw.CreateHTTPClient()
if err != nil {
return err
}
resp, err := client.Do(req)
if err != nil {
return err
}
return httputils.GetAndParseJSONResponseFromHttpResponse(resp, result)
}

View File

@ -0,0 +1,192 @@
package providers
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/gdy666/lucky/module/ddns/ddnscore.go"
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
)
const (
porkbunEndpoint string = "https://porkbun.com/api/json/v3/dns"
)
type Porkbun struct {
ProviderCommon
TTL string
}
type PorkbunDomainRecord struct {
Name string `json:"name"` // subdomain
Type string `json:"type"` // record type, e.g. A AAAA CNAME
Content string `json:"content"` // value
Ttl string `json:"ttl"` // default 300
}
type PorkbunResponse struct {
Status string `json:"status"`
}
type PorkbunDomainQueryResponse struct {
*PorkbunResponse
Records []PorkbunDomainRecord `json:"records"`
}
type PorkbunApiKey struct {
AccessKey string `json:"apikey"`
SecretKey string `json:"secretapikey"`
}
type PorkbunDomainCreateOrUpdateVO struct {
*PorkbunApiKey
*PorkbunDomainRecord
}
// Init 初始化
func (pb *Porkbun) Init(task *ddnscore.DDNSTaskInfo) {
pb.ProviderCommon.Init(task)
if task.TTL == "" {
// 默认600s
pb.TTL = "600"
} else {
pb.TTL = task.TTL
}
pb.SetCreateUpdateDomainFunc(pb.createUpdateDomain)
}
func (pb *Porkbun) createUpdateDomain(recordType, ipAddr string, domain *ddnscore.Domain) {
var record PorkbunDomainQueryResponse
// 获取当前域名信息
err := pb.request(
porkbunEndpoint+fmt.Sprintf("/retrieveByNameType/%s/%s/%s", domain.DomainName, recordType, domain.SubDomain),
&PorkbunApiKey{
AccessKey: pb.task.DNS.ID,
SecretKey: pb.task.DNS.Secret,
},
&record,
)
if err != nil {
return
}
if record.Status == "SUCCESS" {
if len(record.Records) > 0 {
// 存在,更新
pb.modify(&record, domain, recordType, ipAddr)
} else {
// 不存在,创建
pb.create(domain, recordType, ipAddr)
}
} else {
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, "查询现有域名记录失败")
}
}
// 创建
func (pb *Porkbun) create(domain *ddnscore.Domain, recordType string, ipAddr string) {
var response PorkbunResponse
err := pb.request(
porkbunEndpoint+fmt.Sprintf("/create/%s", domain.DomainName),
&PorkbunDomainCreateOrUpdateVO{
PorkbunApiKey: &PorkbunApiKey{
AccessKey: pb.task.DNS.ID,
SecretKey: pb.task.DNS.Secret,
},
PorkbunDomainRecord: &PorkbunDomainRecord{
Name: domain.SubDomain,
Type: recordType,
Content: ipAddr,
Ttl: pb.TTL,
},
},
&response,
)
if err == nil && response.Status == "SUCCESS" {
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
errMsg := fmt.Sprintf("新增域名失败:%v\n", response)
if err != nil {
errMsg += err.Error()
}
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, errMsg)
}
}
// 修改
func (pb *Porkbun) modify(record *PorkbunDomainQueryResponse, domain *ddnscore.Domain, recordType string, ipAddr string) {
// 相同不修改
if len(record.Records) > 0 && record.Records[0].Content == ipAddr {
if domain.UpdateStatus == ddnscore.UpdatedFailed {
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
domain.SetDomainUpdateStatus(ddnscore.UpdatedNothing, "")
}
return
}
var response PorkbunResponse
err := pb.request(
porkbunEndpoint+fmt.Sprintf("/editByNameType/%s/%s/%s", domain.DomainName, recordType, domain.SubDomain),
&PorkbunDomainCreateOrUpdateVO{
PorkbunApiKey: &PorkbunApiKey{
AccessKey: pb.task.DNS.ID,
SecretKey: pb.task.DNS.Secret,
},
PorkbunDomainRecord: &PorkbunDomainRecord{
Content: ipAddr,
Ttl: pb.TTL,
},
},
&response,
)
if err == nil && response.Status == "SUCCESS" {
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
errMsg := fmt.Sprintf("更新域名解析失败:%v\n", response)
if err != nil {
errMsg += err.Error()
}
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, errMsg)
}
}
// request 统一请求接口
func (pb *Porkbun) request(url string, data interface{}, result interface{}) (err error) {
jsonStr := make([]byte, 0)
if data != nil {
jsonStr, _ = json.Marshal(data)
}
req, err := http.NewRequest(
"POST",
url,
bytes.NewBuffer(jsonStr),
)
if err != nil {
log.Println("http.NewRequest失败. Error: ", err)
return
}
req.Header.Set("Content-Type", "application/json")
client, e := pb.CreateHTTPClient()
if e != nil {
err = e
return
}
resp, err := client.Do(req)
if err != nil {
return err
}
return httputils.GetAndParseJSONResponseFromHttpResponse(resp, result)
}

View File

@ -0,0 +1,10 @@
package providers
import "github.com/gdy666/lucky/module/ddns/ddnscore.go"
// Provider interface
type Provider interface {
Init(task *ddnscore.DDNSTaskInfo)
// 添加或更新IPv4/IPv6记录
AddUpdateDomainRecords() string
}

View File

@ -0,0 +1,167 @@
package providers
import (
"log"
"net/http"
"time"
"github.com/gdy666/lucky/module/ddns/ddnscore.go"
"github.com/gdy666/lucky/module/ddns/ddnsgo"
"github.com/gdy666/lucky/thirdlib/gdylib/dnsutils"
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
)
type ProviderCommon struct {
createUpdateDomainFunc func(recordType, ipaddr string, domain *ddnscore.Domain)
task *ddnscore.DDNSTaskInfo
taskKey string
}
func (d *ProviderCommon) SetCreateUpdateDomainFunc(f func(recordType, ipaddr string, domain *ddnscore.Domain)) {
d.createUpdateDomainFunc = f
}
func (d *ProviderCommon) Init(task *ddnscore.DDNSTaskInfo) {
d.task = task
d.taskKey = task.TaskKey
}
// 添加或更新IPv4/IPv6记录
func (d *ProviderCommon) AddUpdateDomainRecords() string {
if d.task.TaskType == "IPv6" {
return d.addUpdateDomainRecords("AAAA")
}
return d.addUpdateDomainRecords("A")
}
func (d *ProviderCommon) addUpdateDomainRecords(recordType string) string {
ipAddr, change := d.task.CheckIPChange()
defer ddnscore.DDNSTaskInfoMapUpdateDomainInfo(d.task)
d.task.TaskState.SetIPAddr(ipAddr)
//及时刷新IP地址显示
ddnscore.DDNSTaskInfoMapUpdateIPInfo(d.task)
if ipAddr == "" {
d.task.TaskState.SetDomainUpdateStatus(ddnscore.UpdatePause, "获取公网IP失败")
return ipAddr
}
checkDoamins := d.task.TaskState.Domains
if time.Since(d.task.TaskState.LastSyncTime) > time.Second*time.Duration(d.task.DNS.ForceInterval-1) {
//log.Printf("DDNS任务[%s]强制更新", d.task.TaskName)
change = true
goto sync
}
//设置原先状态成功的为继续成功
//不成功的就更新
if !change { //公网IP没有改变
checkDoamins = []ddnscore.Domain{}
for i := range d.task.TaskState.Domains { //如果原先状态成功或不改变就刷新时间
if d.task.TaskState.Domains[i].UpdateStatus == ddnscore.UpdatedNothing ||
d.task.TaskState.Domains[i].UpdateStatus == ddnscore.UpdatedSuccess {
d.task.TaskState.Domains[i].SetDomainUpdateStatus(ddnscore.UpdatedNothing, "")
ddnscore.DDNSTaskInfoMapUpdateDomainInfo(d.task)
continue
}
checkDoamins = append(checkDoamins, d.task.TaskState.Domains[i])
}
if len(checkDoamins) == 0 {
return ipAddr
}
}
sync:
if change {
syncTime := time.Now()
defer func() {
//记录最近一次同步操作时间
d.task.TaskState.LastSyncTime = syncTime
}()
}
for i := range checkDoamins {
if d.createUpdateDomainFunc == nil {
log.Printf("ddns createUpdateDomainFunc undefine")
break
}
domain := getDomainItem(checkDoamins[i].String(), &d.task.TaskState.Domains)
if domain == nil {
log.Printf("getDomainItem nil")
continue
}
if d.task.DNS.ResolverDoaminCheck {
//<-time.After(time.Second)
domainResolverIPaddr, _ := dnsutils.ResolveDomainAtServerList(recordType, domain.String(), d.task.DNS.DNSServerList)
//log.Printf("domain:%s domainResolverIPaddr:%s ,ipaddr:%s", domain.String(), domainResolverIPaddr, ipAddr)
if domainResolverIPaddr == ipAddr {
if domain.UpdateStatus == ddnscore.UpdatedFailed {
domain.SetDomainUpdateStatus(ddnscore.UpdatedSuccess, "")
} else {
domain.SetDomainUpdateStatus(ddnscore.UpdatedNothing, "")
}
ddnscore.DDNSTaskInfoMapUpdateDomainInfo(d.task)
continue
}
}
//*********
// params := domain.GetCustomParams()
// if params.Has("recordType") {
// recordType = params.Get("recordType")
// }
// if params.Has("recordContent") {
// //ipAddr = params.Get("recordContent")
// recordContent := params.Get("recordContent")
// recordContent = strings.Replace(recordContent, "#{ip}", ipAddr, -1)
// ipAddr = recordContent
// log.Printf("recordType[%s]recordContent[%s]", recordType, recordContent)
// }
//*********
d.createUpdateDomainFunc(recordType, ipAddr, domain)
ddnscore.DDNSTaskInfoMapUpdateDomainInfo(d.task)
}
return ipAddr
}
func getDomainItem(fullDomain string, domains *[]ddnscore.Domain) *ddnscore.Domain {
if domains == nil {
return nil
}
for i, domain := range *domains {
if domain.String() == fullDomain {
return &(*domains)[i]
}
}
return nil
}
//--------------------------------------------------------------------------------------------------
func (d *ProviderCommon) CreateHTTPClient() (*http.Client, error) {
ddnsGlobalConf := ddnsgo.GetDDNSConfigure()
return httputils.CreateHttpClient(
d.task.DNS.GetCallAPINetwork(),
"",
!ddnsGlobalConf.HttpClientSecureVerify,
d.task.DNS.HttpClientProxyType,
d.task.DNS.HttpClientProxyAddr,
d.task.DNS.HttpClientProxyUser,
d.task.DNS.HttpClientProxyPassword,
time.Duration(d.task.HttpClientTimeout)*time.Second)
}

142
module/ddns/worker.go Normal file
View File

@ -0,0 +1,142 @@
package ddns
import (
"log"
"runtime/debug"
"sync"
"time"
ddnsconf "github.com/gdy666/lucky/module/ddns/conf"
"github.com/gdy666/lucky/module/ddns/ddnscore.go"
"github.com/gdy666/lucky/module/ddns/ddnsgo"
"github.com/gdy666/lucky/module/ddns/providers"
"github.com/gdy666/lucky/thirdlib/gdylib/service"
)
var DDNSService *service.Service
func init() {
DDNSService, _ = service.NewService("ddns")
DDNSService.SetTimerFunc(syncAllDomainsOnce)
DDNSService.SetEventFunc(syncTaskDomainsOnce)
}
// Run 定时运行
func Run(firstDelay time.Duration, delay time.Duration) {
log.Printf("DDNS 第一次运行将等待 %d 秒后运行 (等待网络)", int(firstDelay.Seconds()))
<-time.After(firstDelay)
DDNSService.Start()
}
var wg sync.WaitGroup
// RunOnce RunOnce
func syncAllDomainsOnce(params ...any) {
ddnsTaskList := ddnscore.GetDDNSTaskInfoList()
ddnsconf.CleanIPUrlAddrMap()
ddnsConf := ddnsgo.GetDDNSConfigure()
//log.Printf("批量执行DDNS任务")
taskBeginTime := time.Now()
//fmt.Printf("ddnsTaskList:%v\n", ddnsTaskList)
for index := range ddnsTaskList {
task := ddnsTaskList[index]
if !task.Enable {
continue
}
if time.Since(task.TaskState.LastWorkTime) < time.Second*15 {
//log.Printf("[%s]太接近,忽略", task.TaskName)
continue
}
//log.Printf("task[%s] enable\n", task.TaskName)
wg.Add(1)
go func() {
defer func() {
wg.Done()
recoverErr := recover()
if recoverErr == nil {
return
}
log.Printf("syncDDNSTask[%s]panic:\n%v", task.TaskName, recoverErr)
log.Printf("%s", debug.Stack())
}()
syncDDNSTask(task)
}()
<-time.After(time.Millisecond * 600)
}
wg.Wait()
taskEndTime := time.Now()
usedTime := taskEndTime.Sub(taskBeginTime)
nextTaskTimer := time.Second*time.Duration(ddnsConf.Intervals) - usedTime
//debug.FreeOSMemory()
//log.Printf("syncAllDomainsOnce 任务完成")
DDNSService.Timer = time.NewTimer(nextTaskTimer)
}
func syncTaskDomainsOnce(params ...any) {
serverMsg := (params[1]).(service.ServiceMsg)
taskKey := serverMsg.Params[0].(string)
switch serverMsg.Type {
case "syncDDNSTask":
{
//log.Printf("syncTaskDomainsOnce 单DDNS任务更新%s", taskKey)
ddnsconf.CleanIPUrlAddrMap()
task := ddnscore.GetDDNSTaskInfoByKey(taskKey)
syncDDNSTask(task)
}
default:
return
}
}
func syncDDNSTask(task *ddnscore.DDNSTaskInfo) {
if task == nil {
return
}
var dnsSelected providers.Provider
switch task.DNS.Name {
case "alidns":
dnsSelected = &providers.Alidns{}
case "dnspod":
dnsSelected = &providers.Dnspod{}
case "cloudflare":
dnsSelected = &providers.Cloudflare{}
case "huaweicloud":
dnsSelected = &providers.Huaweicloud{}
case "callback":
dnsSelected = &providers.Callback{}
case "baiducloud":
dnsSelected = &providers.BaiduCloud{}
case "porkbun":
dnsSelected = &providers.Porkbun{}
default:
return
}
dnsSelected.Init(task)
dnsSelected.AddUpdateDomainRecords()
task.ExecWebhook(&task.TaskState)
// log.Printf("假装耗时10秒\n")
// <-time.After(time.Second * 10)
// log.Printf("耗时完成\n")
ddnscore.DDNSTaskInfoMapUpdate(task)
//task.TaskState.LastWorkTime = time.Now() //记录最近一次检测时间,防止批量检测和单个检测时间间隔过于接近
//
}

View File

@ -0,0 +1,226 @@
package portforwardconf
import (
"fmt"
"log"
"strconv"
"strings"
"github.com/gdy666/lucky/module/portforward/socketproxy"
"github.com/gdy666/lucky/module/weblog"
"github.com/gdy666/lucky/thirdlib/gdylib/logsbuffer"
"github.com/sirupsen/logrus"
)
type PortForwardsConfigure struct {
PortForwardsLimit int64 `json:"PortForwardsLimit"` //全局端口转发数量限制
TCPPortforwardMaxConnections int64 `json:"TCPPortforwardMaxConnections"` //端口转发全局TCP并发链接数限制
UDPReadTargetDataMaxgoroutineCount int64 `json:"UDPReadTargetDataMaxgoroutineCount"` //端口转发全局UDP读取目标地址数据协程数限制
}
type PortForwardsRule struct {
Name string `json:"Name"`
Key string `json:"Key"`
Enable bool `json:"Enable"`
ForwardTypes []string `json:"ForwardTypes"`
ListenAddress string `json:"ListenAddress"`
ListenPorts string `json:"ListenPorts"`
TargetAddressList []string `json:"TargetAddressList"`
TargetPorts string `json:"TargetPorts"`
Options socketproxy.RelayRuleOptions `json:"Options"`
ReverseProxyList *[]socketproxy.Proxy `json:"-"`
logsBuffer *logsbuffer.LogsBuffer
logrus *logrus.Logger
LogLevel int `json:"LogLevel"` //日志输出级别
LogOutputToConsole bool `json:"LogOutputToConsole"` //日志输出到终端
AccessLogMaxNum int `json:"AccessLogMaxNum"`
WebListShowLastLogMaxCount int `json:"WebListShowLastLogMaxCount"` //前端列表显示最新日志最大条数
}
func (r *PortForwardsRule) ProxyCount() int {
if r.ReverseProxyList == nil {
return 0
}
return len(*r.ReverseProxyList)
}
func (r *PortForwardsRule) StartAllProxys() {
if r.ReverseProxyList == nil {
return
}
for i := range *r.ReverseProxyList {
(*r.ReverseProxyList)[i].StartProxy()
}
}
func (r *PortForwardsRule) GetLastLogs(maxCount int) []any {
return r.GetLogsBuffer().GetLastLogs(weblog.WebLogConvert, maxCount)
}
func (r *PortForwardsRule) Fire(entry *logrus.Entry) error {
if !r.LogOutputToConsole {
return nil
}
s, _ := entry.String()
log.Print(s)
return nil
}
func (r *PortForwardsRule) Levels() []logrus.Level {
return logrus.AllLevels
}
func (r *PortForwardsRule) GetLogrus() *logrus.Logger {
if r.logrus == nil {
r.logrus = logrus.New()
r.logrus.SetLevel(logrus.Level(r.LogLevel))
r.logrus.SetOutput(r.GetLogsBuffer())
r.logrus.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
DisableTimestamp: true,
DisableHTMLEscape: true,
DataKey: "ExtInfo",
})
r.logrus.AddHook(r)
}
return r.logrus
}
func (r *PortForwardsRule) GetLogsBuffer() *logsbuffer.LogsBuffer {
if r.logsBuffer == nil {
r.logsBuffer = logsbuffer.CreateLogbuffer("portforward:"+r.Key, r.AccessLogMaxNum)
}
return r.logsBuffer
}
func (r *PortForwardsRule) StopAllProxys() {
if r.ReverseProxyList == nil {
return
}
for i := range *r.ReverseProxyList {
(*r.ReverseProxyList)[i].StopProxy()
}
}
func (r *PortForwardsRule) InitProxyList() error {
listenPorts, err := PortsStrToIList(r.ListenPorts)
if err != nil {
return err
}
targetPorts, err := PortsStrToIList(r.TargetPorts)
if err != nil {
return err
}
if len(listenPorts) != len(targetPorts) {
return fmt.Errorf("端口个数不一致")
}
var proxyList []socketproxy.Proxy
for i := range r.ForwardTypes {
for j := range listenPorts {
p, err := socketproxy.CreateProxy(r.GetLogrus(), r.ForwardTypes[i],
r.ListenAddress,
r.TargetAddressList,
listenPorts[j],
targetPorts[j],
&r.Options)
if err != nil {
return err
}
proxyList = append(proxyList, p)
}
}
r.ReverseProxyList = &proxyList
return nil
}
// portsStrToIList
func PortsStrToIList(portsStr string) (ports []int, err error) {
if portsStr == "" {
return
}
if strings.Contains(portsStr, ",") {
tmpStrList := strings.Split(portsStr, ",")
for i := range tmpStrList {
tps, e := PortsStrToIList(tmpStrList[i])
if e != nil {
err = fmt.Errorf("端口字符串处理出错:%s", e.Error())
return
}
ports = append(ports, tps...)
}
return
}
portsStrList := strings.Split(portsStr, "-")
if len(portsStrList) > 2 {
err = fmt.Errorf("端口%s格式有误", portsStr)
return
}
if len(portsStrList) == 1 { //single listen port
listenPort, e := portStrToi(portsStrList[0])
if e != nil {
err = fmt.Errorf("端口格式有误!%s", e.Error())
return
}
ports = append(ports, listenPort)
}
if len(portsStrList) == 2 {
minListenPort, e := portStrToi(portsStrList[0])
if e != nil {
err = fmt.Errorf("端口格式有误!%s", portsStrList[0])
return
}
maxListenPort, e := portStrToi(portsStrList[1])
if e != nil {
err = fmt.Errorf("端口格式有误!%s", portsStrList[1])
return
}
if maxListenPort <= minListenPort {
err = fmt.Errorf("前一个端口[%d]要小于后一个端口[%d]", minListenPort, maxListenPort)
return
}
i := minListenPort
for {
if i > maxListenPort {
break
}
ports = append(ports, i)
i++
}
}
return
}
func portStrToi(portStr string) (int, error) {
port, err := strconv.Atoi(portStr)
if err != nil {
return 0, fmt.Errorf("端口格式有误:%s", err.Error())
}
if port < 1 || port > 65535 {
return 0, fmt.Errorf("端口[%d]超出范围", port)
}
return port, nil
}
func PortsCheck(ports1Str, ports2Str string) (bool, error) {
ports1, err := PortsStrToIList(ports1Str)
if err != nil {
return false, err
}
ports2, err := PortsStrToIList(ports2Str)
if err != nil {
return false, err
}
if len(ports1) != len(ports2) {
return false, fmt.Errorf("端口个数不一致")
}
return true, nil
}

View File

@ -0,0 +1,280 @@
package portforward
import (
"fmt"
"log"
"strings"
"github.com/gdy666/lucky/config"
portforwardconf "github.com/gdy666/lucky/module/portforward/conf"
"github.com/gdy666/lucky/module/portforward/socketproxy"
"github.com/gdy666/lucky/thirdlib/gdylib/logsbuffer"
)
func Init() {
PortForwardsRuleListInit()
}
// TidyReverseProxyCache 整理端口转发日志缓存
func TidyPortforwardLogsCache() {
ruleList := GetPortForwardsRuleList()
var keyListBuffer strings.Builder
for _, rule := range ruleList {
keyListBuffer.WriteString(rule.Key)
keyListBuffer.WriteString(",")
}
keyListStr := keyListBuffer.String()
logsbuffer.LogsBufferStoreMu.Lock()
defer logsbuffer.LogsBufferStoreMu.Unlock()
var needDeleteKeys []string
for k := range logsbuffer.LogsBufferStore {
if !strings.HasPrefix(k, "portforward:") {
continue
}
if len(k) <= 13 {
continue
}
if !strings.Contains(keyListStr, k[12:]) {
needDeleteKeys = append(needDeleteKeys, k)
}
}
for i := range needDeleteKeys {
delete(logsbuffer.LogsBufferStore, needDeleteKeys[i])
}
}
func PortForwardsRuleListInit() {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
var err error
for i := range config.Configure.PortForwardsRuleList {
err = config.Configure.PortForwardsRuleList[i].InitProxyList()
if err != nil {
log.Printf("InitProxyList error:%s\n", err.Error())
}
if config.Configure.PortForwardsRuleList[i].Enable {
config.Configure.PortForwardsRuleList[i].StartAllProxys()
}
}
}
func GetPortForwardsRuleList() []portforwardconf.PortForwardsRule {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
var resList []portforwardconf.PortForwardsRule
for i := range config.Configure.PortForwardsRuleList {
r := config.Configure.PortForwardsRuleList[i]
resList = append(resList, r)
}
return resList
}
func GetPortForwardsGlobalProxyCount() int {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
count := 0
for i := range config.Configure.PortForwardsRuleList {
count += config.Configure.PortForwardsRuleList[i].ProxyCount()
}
return count
}
func GetPortForwardsGlobalProxyCountExcept(key string) int {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
count := 0
for i := range config.Configure.PortForwardsRuleList {
if key == config.Configure.PortForwardsRuleList[i].Key {
continue
}
count += config.Configure.PortForwardsRuleList[i].ProxyCount()
}
return count
}
func GetPortForwardsRuleByKey(key string) *portforwardconf.PortForwardsRule {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
index := -1
for i := range config.Configure.PortForwardsRuleList {
if config.Configure.PortForwardsRuleList[i].Key == key {
index = i
break
}
}
if index == -1 {
return nil
}
res := config.Configure.PortForwardsRuleList[index]
return &res
}
func StopAllSocketProxysByRuleKey(key string) error {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
index := -1
for i := range config.Configure.PortForwardsRuleList {
if config.Configure.PortForwardsRuleList[i].Key == key {
index = i
break
}
}
if index == -1 {
return fmt.Errorf("找不到key:%s对应的规则", key)
}
config.Configure.PortForwardsRuleList[index].StopAllProxys()
return nil
}
func StartAllSocketProxysByRuleKey(key string) error {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
index := -1
for i := range config.Configure.PortForwardsRuleList {
if config.Configure.PortForwardsRuleList[i].Key == key {
index = i
break
}
}
if index == -1 {
return fmt.Errorf("找不到key:%s对应的规则", key)
}
config.Configure.PortForwardsRuleList[index].StartAllProxys()
return nil
}
func PortForwardsRuleListAdd(r *portforwardconf.PortForwardsRule) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
r.Enable = true
config.Configure.PortForwardsRuleList = append(config.Configure.PortForwardsRuleList, *r)
return config.Save()
}
func PortForwardsRuleListDelete(key string) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
index := -1
for i := range config.Configure.PortForwardsRuleList {
if config.Configure.PortForwardsRuleList[i].Key == key {
index = i
break
}
}
if index == -1 {
return fmt.Errorf("找不到需要删除的端口转发规则")
}
config.Configure.PortForwardsRuleList[index].StopAllProxys()
config.Configure.PortForwardsRuleList = DeletePortForwardsRuleListSlice(config.Configure.PortForwardsRuleList, index)
return config.Save()
}
func EnablePortForwardsRuleByKey(key string, enable bool) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
index := -1
for i := range config.Configure.DDNSTaskList {
if config.Configure.PortForwardsRuleList[i].Key == key {
index = i
break
}
}
if index == -1 {
return fmt.Errorf("开关端口转发规则失败,key查找失败")
}
if enable {
config.Configure.PortForwardsRuleList[index].StartAllProxys()
} else {
config.Configure.PortForwardsRuleList[index].StopAllProxys()
}
config.Configure.PortForwardsRuleList[index].Enable = enable
return config.Save()
}
func UpdatePortForwardsRuleToPortForwardsRuleList(key string, r *portforwardconf.PortForwardsRule) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
index := -1
for i := range config.Configure.PortForwardsRuleList {
if config.Configure.PortForwardsRuleList[i].Key == key {
index = i
break
}
}
if index == -1 {
return fmt.Errorf("找不到需要更新的端口转发规则")
}
config.Configure.PortForwardsRuleList[index] = *r
return config.Save()
}
func DeletePortForwardsRuleListSlice(a []portforwardconf.PortForwardsRule, deleteIndex int) []portforwardconf.PortForwardsRule {
j := 0
for i := range a {
if i != deleteIndex {
a[j] = a[i]
j++
}
}
return a[:j]
}
func GetPortForwardsConfigure() portforwardconf.PortForwardsConfigure {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
conf := config.Configure.PortForwardsConfigure
return conf
}
func SetPortForwardsConfigure(conf *portforwardconf.PortForwardsConfigure) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
if conf.PortForwardsLimit < 0 {
conf.PortForwardsLimit = 0
} else if conf.PortForwardsLimit > 1024 {
conf.PortForwardsLimit = 1024
}
if conf.TCPPortforwardMaxConnections < 0 {
conf.TCPPortforwardMaxConnections = 0
} else if conf.TCPPortforwardMaxConnections > 4096 {
conf.TCPPortforwardMaxConnections = 4096
}
if conf.UDPReadTargetDataMaxgoroutineCount < 0 {
conf.UDPReadTargetDataMaxgoroutineCount = 0
} else if conf.UDPReadTargetDataMaxgoroutineCount > 4096 {
conf.UDPReadTargetDataMaxgoroutineCount = 4096
}
config.Configure.PortForwardsConfigure = *conf
socketproxy.SetGlobalMaxPortForwardsCountLimit(conf.PortForwardsLimit)
socketproxy.SetGlobalTCPPortforwardMaxConnections(conf.TCPPortforwardMaxConnections)
socketproxy.SetGlobalUDPReadTargetDataMaxgoroutineCountLimit(conf.UDPReadTargetDataMaxgoroutineCount)
return config.Save()
}

View File

@ -0,0 +1,38 @@
// Copyright 2022 gdy, 272288813@qq.com
package socketproxy
import (
"sync/atomic"
)
type BaseProxyConf struct {
TrafficIn int64
TrafficOut int64
key string
ProxyType string // tcp tcp4 tcp6 udp udp4 udp6
}
func (p *BaseProxyConf) GetProxyType() string {
return p.ProxyType
}
func (p *BaseProxyConf) GetStatus() string {
return p.ProxyType
}
func (p *BaseProxyConf) ReceiveDataCallback(nw int64) {
atomic.AddInt64(&p.TrafficIn, nw)
}
func (p *BaseProxyConf) SendDataCallback(nw int64) {
atomic.AddInt64(&p.TrafficOut, nw)
}
func (p *BaseProxyConf) GetTrafficIn() int64 {
return atomic.LoadInt64(&p.TrafficIn)
}
func (p *BaseProxyConf) GetTrafficOut() int64 {
return atomic.LoadInt64(&p.TrafficOut)
}

View File

@ -0,0 +1,180 @@
// Copyright 2022 gdy, 272288813@qq.com
package socketproxy
import (
"errors"
"fmt"
"io"
"strings"
"sync"
"github.com/gdy666/lucky/thirdlib/gdylib/pool"
"github.com/sirupsen/logrus"
)
type Proxy interface {
StartProxy()
StopProxy()
ReceiveDataCallback(int64)
SendDataCallback(int64)
GetProxyType() string
GetStatus() string
GetListenIP() string
GetListenPort() int
GetKey() string
GetCurrentConnections() int64
String() string
GetTrafficIn() int64
GetTrafficOut() int64
SafeCheck(ip string) bool
}
type RelayRuleOptions struct {
UDPPackageSize int `json:"UDPPackageSize,omitempty"`
SingleProxyMaxTCPConnections int64 `json:"SingleProxyMaxTCPConnections,omitempty"`
SingleProxyMaxUDPReadTargetDatagoroutineCount int64 `json:"SingleProxyMaxUDPReadTargetDatagoroutineCount"`
UDPProxyPerformanceMode bool `json:"UDPProxyPerformanceMode,omitempty"`
UDPShortMode bool `json:"UDPShortMode,omitempty"`
SafeMode string `json:"SafeMode,omitempty"`
}
// Join two io.ReadWriteCloser and do some operations.
func (p *BaseProxyConf) relayData(targetServer io.ReadWriteCloser, client io.ReadWriteCloser) {
var wait sync.WaitGroup
pipe := func(to io.ReadWriteCloser, from io.ReadWriteCloser, writedataCallback func(int64)) {
defer to.Close()
defer from.Close()
defer wait.Done()
nw, _ := p.copyBuffer(to, from, nil, nil)
if writedataCallback != nil {
writedataCallback(nw)
}
// if p.TrafficMonitor {
// buf := pool.GetBuf(8 * 1024 * 1024)
// p.CopyBuffer(to, from, buf, writedataCallback)
// pool.PutBuf(buf)
// } else {
// nw, _ := p.copyBuffer(to, from, nil, nil)
// if writedataCallback != nil {
// writedataCallback(nw)
// }
// }
}
wait.Add(2)
go pipe(targetServer, client, p.ReceiveDataCallback)
go pipe(client, targetServer, p.SendDataCallback)
wait.Wait()
}
func (p *BaseProxyConf) CopyBuffer(dst io.Writer, src io.Reader, buf []byte, writedataCallback func(int64)) (written int64, err error) {
if buf != nil && len(buf) == 0 {
panic("empty buffer in CopyBuffer")
}
return p.copyBuffer(dst, src, buf, writedataCallback)
}
// copyBuffer is the actual implementation of Copy and CopyBuffer.
// if buf is nil, one is allocated.
func (p *BaseProxyConf) copyBuffer(dst io.Writer, src io.Reader, buf []byte, writedataCallback func(int64)) (written int64, err error) {
if buf == nil {
if wt, ok := src.(io.WriterTo); ok {
return wt.WriteTo(dst)
}
if rt, ok := dst.(io.ReaderFrom); ok {
return rt.ReadFrom(src)
}
size := 32 * 1024
if l, ok := src.(*io.LimitedReader); ok && int64(size) > l.N {
if l.N < 1 {
size = 1
} else {
size = int(l.N)
}
}
buf = pool.GetBuf(8 * size)
defer pool.PutBuf(buf)
}
for {
nr, er := src.Read(buf)
if nr > 0 {
nw, ew := dst.Write(buf[0:nr])
if nw < 0 || nr < nw {
nw = 0
if ew == nil {
ew = errors.New("invalid write result")
}
}
written += int64(nw)
if writedataCallback != nil {
writedataCallback(int64(nw))
}
if ew != nil {
err = ew
break
}
if nr != nw {
err = io.ErrShortWrite
break
}
}
if er != nil {
if er != io.EOF {
err = er
}
break
}
}
return written, err
}
func formatFileSize(fileSize int64) (size string) {
switch {
case fileSize < 1024:
return fmt.Sprintf("%.2fB", float64(fileSize)/float64(1))
case fileSize < (1024 * 1024):
return fmt.Sprintf("%.2fKB", float64(fileSize)/float64(1024))
case fileSize < (1024 * 1024 * 1024):
return fmt.Sprintf("%.2fMB", float64(fileSize)/float64(1024*1024))
case fileSize < (1024 * 1024 * 1024 * 1024):
return fmt.Sprintf("%.2fGB", float64(fileSize)/float64(1024*1024*1024))
case fileSize < (1024 * 1024 * 1024 * 1024 * 1024):
return fmt.Sprintf("%.2fTB", float64(fileSize)/float64(1024*1024*1024*1024))
default:
return fmt.Sprintf("%.2fEB", float64(fileSize)/float64(1024*1024*1024*1024*1024))
}
}
func CreateProxy(log *logrus.Logger, proxyType, listenIP string, targetAddressList []string, listenPort, targetPort int, options *RelayRuleOptions) (p Proxy, err error) {
//key := GetProxyKey(proxyType, listenIP, listenPort)
switch {
case strings.HasPrefix(proxyType, "tcp"):
{
return CreateTCPProxy(log, proxyType, listenIP, targetAddressList, listenPort, targetPort, options), nil
}
case strings.HasPrefix(proxyType, "udp"):
{
return CreateUDPProxy(log, proxyType, listenIP, targetAddressList, listenPort, targetPort, options), nil
}
default:
return nil, fmt.Errorf("未支持的类型:%s", proxyType)
}
}
func GetProxyKey(proxyType, listenIP string, listenPort int) string {
return fmt.Sprintf("%s@%s:%d", proxyType, listenIP, listenPort)
}

View File

@ -0,0 +1 @@
package socketproxy

View File

@ -0,0 +1,186 @@
// Copyright 2022 gdy, 272288813@qq.com
package socketproxy
import (
"fmt"
"log"
"net"
"strings"
"sync"
"github.com/sirupsen/logrus"
)
type TCPProxy struct {
TCPUDPProxyCommonConf
//TcpSingleProxyMaxConns int64
// tcpCurrentConns int64
listenConn net.Listener
listenConnMutex sync.Mutex
connMap map[string]net.Conn
connMapMutex sync.Mutex
}
func CreateTCPProxy(log *logrus.Logger, proxyType, listenIP string, targetAddressList []string, listenPort, targetPort int, options *RelayRuleOptions) *TCPProxy {
p := &TCPProxy{}
p.ProxyType = proxyType
p.listenIP = listenIP
p.listenPort = listenPort
p.targetAddressList = targetAddressList
p.targetPort = targetPort
p.log = log
p.safeMode = options.SafeMode
p.SetMaxConnections(options.SingleProxyMaxTCPConnections)
return p
}
func (p *TCPProxy) GetStatus() string {
return fmt.Sprintf("%s\nactivity connections:[%d]", p.String(), p.GetCurrentConnections())
}
func (p *TCPProxy) CheckConnectionsLimit() error {
if GetGlobalTCPPortForwardConnections() >= GetGlobalTCPPortforwardMaxConnections() {
return fmt.Errorf("超出TCP最大总连接数[%d]限制", GetGlobalTCPPortforwardMaxConnections())
}
if p.GetCurrentConnections() >= p.SingleProxyMaxConnections {
return fmt.Errorf("超出单端口TCP最大连接数[%d]限制", p.SingleProxyMaxConnections)
}
//全局,单端口限制
return nil
}
func (p *TCPProxy) StartProxy() {
p.listenConnMutex.Lock()
defer p.listenConnMutex.Unlock()
if p.listenConn != nil {
//log.Printf("proxy %s is started", p.String())
p.log.Warnf("proxy %s is started", p.String())
return
}
if p.connMap == nil {
p.connMap = make(map[string]net.Conn)
}
ln, err := net.Listen(p.ProxyType, p.GetListentAddress())
if err != nil {
if strings.Contains(err.Error(), "Only one usage of each socket address") {
p.log.Errorf("监听IP端口[%s]已被占用,proxy[%s]启动失败", p.GetListentAddress(), p.String())
} else {
p.log.Errorf("Cannot start proxy[%s]:%s", p.String(), err)
}
return
}
p.listenConn = ln
p.log.Infof("[端口转发][开启][%s]", p.String())
go func() {
for {
newConn, err := ln.Accept()
if err != nil {
if strings.Contains(err.Error(), "use of closed network connection") {
break
}
p.log.Errorf(" Cannot accept connection due to error %s", err.Error())
continue
}
err = p.CheckConnectionsLimit()
if err != nil {
//p.PrintConnectionsInfo()
p.log.Warnf("[%s]超出最大连接数限制,不再接受新连接:%s", p.GetKey(), err.Error())
newConn.Close()
continue
}
newConnAddr := newConn.RemoteAddr().String()
if !p.SafeCheck(newConnAddr) {
p.log.Warnf("[%s]新连接 [%s]安全检查未通过", p.GetKey(), newConnAddr)
newConn.Close()
continue
}
p.log.Infof("[%s]新连接[%s]安全检查通过", p.GetKey(), newConnAddr)
p.connMapMutex.Lock()
p.connMap[newConn.RemoteAddr().String()] = newConn
p.connMapMutex.Unlock()
p.AddCurrentConnections(1)
go p.handle(newConn)
}
}()
//
//p.test()
//go p.test()
//go p.tcptest()
}
func (p *TCPProxy) StopProxy() {
p.listenConnMutex.Lock()
defer p.listenConnMutex.Unlock()
defer func() {
p.log.Infof("[端口转发][关闭][%s]", p.String())
}()
if p.listenConn == nil {
return
}
p.listenConn.Close()
p.listenConn = nil
p.connMapMutex.Lock()
for _, conn := range p.connMap {
conn.Close()
}
p.connMap = make(map[string]net.Conn)
p.connMapMutex.Unlock()
}
func (p *TCPProxy) handle(conn net.Conn) {
//dialer := net.Dialer{Timeout: 10 * time.Second}
//targetConn, err := dialer.Dial("tcp", p.TargetAddress)
targetConn, err := net.Dial("tcp", p.GetTargetAddress())
defer func() {
if targetConn != nil {
targetConn.Close()
}
defer conn.Close()
p.AddCurrentConnections(-1)
p.connMapMutex.Lock()
delete(p.connMap, conn.RemoteAddr().String())
p.log.Infof("[%s]%s 断开连接", p.GetKey(), conn.RemoteAddr().String())
p.connMapMutex.Unlock()
}()
if err != nil {
log.Printf("%s error:%s", p.String(), err.Error())
return
}
//targetConn.SetDeadline(time.Now().Add(time.Second * 3))
// targetTcpConn, ok := targetConn.(*net.TCPConn)
// if ok {
// targetTcpConn.SetReadBuffer(p.BufferSize * 1024 * 256 * 1024)
// targetTcpConn.SetWriteBuffer(p.BufferSize * 1024 * 256 * 1024)
// }
p.relayData(targetConn, conn)
}

View File

@ -0,0 +1,169 @@
// Copyright 2022 gdy, 272288813@qq.com
package socketproxy
import (
"fmt"
"net"
"strings"
"sync"
"sync/atomic"
"github.com/sirupsen/logrus"
)
const TCP_DEFAULT_STREAM_BUFFERSIZE = 128
const DEFAULT_GLOBAL_MAX_CONNECTIONS = int64(1024)
const DEFAULT_GLOBAL_UDPReadTargetDataMaxgoroutineCount = int64(1024)
const TCPUDP_DEFAULT_SINGLE_PROXY_MAX_CONNECTIONS = int64(256)
const DEFAULT_MAX_PORTFORWARDS_LIMIT = int64(128)
var globalTCPPortforwardMaxConnectionsLimit = DEFAULT_GLOBAL_MAX_CONNECTIONS
var globalUDPReadTargetDataMaxgoroutineCountLimit = DEFAULT_GLOBAL_UDPReadTargetDataMaxgoroutineCount
var globalTCPPortForwardCurrentConnections int64 = 0
var globalUDPPortForwardCurrentGroutineCount int64 = 0
var gloMaxPortForwardsCountLimit int64 = DEFAULT_MAX_PORTFORWARDS_LIMIT
var safeCheckFunc func(mode, ip string) bool
func SetSafeCheck(f func(mode, ip string) bool) {
safeCheckFunc = f
}
func SetGlobalUDPReadTargetDataMaxgoroutineCountLimit(max int64) {
atomic.StoreInt64(&globalUDPReadTargetDataMaxgoroutineCountLimit, max)
}
func GetGlobalUDPReadTargetDataMaxgoroutineCountLimit() int64 {
return atomic.LoadInt64(&globalUDPReadTargetDataMaxgoroutineCountLimit)
}
func SetGlobalMaxPortForwardsCountLimit(max int64) {
atomic.StoreInt64(&gloMaxPortForwardsCountLimit, max)
}
func GetGlobalMaxPortForwardsCountLimit() int64 {
return atomic.LoadInt64(&gloMaxPortForwardsCountLimit)
}
func SetGlobalTCPPortforwardMaxConnections(max int64) {
atomic.StoreInt64(&globalTCPPortforwardMaxConnectionsLimit, max)
}
func GetGlobalTCPPortforwardMaxConnections() int64 {
return atomic.LoadInt64(&globalTCPPortforwardMaxConnectionsLimit)
}
func GetGlobalTCPPortForwardConnections() int64 {
return atomic.LoadInt64(&globalTCPPortForwardCurrentConnections)
}
func GloBalTCPPortForwardConnectionsAdd(add int64) int64 {
return atomic.AddInt64(&globalTCPPortForwardCurrentConnections, add)
}
func GetGlobalUDPPortForwardGroutineCount() int64 {
return atomic.LoadInt64(&globalUDPPortForwardCurrentGroutineCount)
}
func GloBalUDPPortForwardGroutineCountAdd(add int64) int64 {
return atomic.AddInt64(&globalUDPPortForwardCurrentGroutineCount, add)
}
type TCPUDPProxyCommonConf struct {
CurrentConnectionsCount int64
SingleProxyMaxConnections int64
BaseProxyConf
listentAddress string
listenIP string
listenPort int
//targetIP string
targetAddressList []string
targetAddressCount int
targetAddressIndex uint64
targetAddressLock sync.Mutex
targetPort int
safeMode string
log *logrus.Logger
}
// func (p *TCPUDPProxyCommonConf) PrintConnectionsInfo() {
// p.log.Infof("[%s]当前连接数:[%d],当前端口最大TCP连接数限制[%d],全局最大TCP连接数限制[%d]", p.GetKey(), p.GetCurrentConnections(), p.SingleProxyMaxConnections, GetGlobalTCPPortforwardMaxConnections())
// }
func (p *TCPUDPProxyCommonConf) SetMaxConnections(max int64) {
if max <= 0 {
p.SingleProxyMaxConnections = TCPUDP_DEFAULT_SINGLE_PROXY_MAX_CONNECTIONS
} else {
p.SingleProxyMaxConnections = max
}
}
func (p *TCPUDPProxyCommonConf) AddCurrentConnections(a int64) {
atomic.AddInt64(&p.CurrentConnectionsCount, a)
if strings.HasPrefix(p.ProxyType, "tcp") {
GloBalTCPPortForwardConnectionsAdd(a)
return
}
if strings.HasPrefix(p.ProxyType, "udp") {
GloBalUDPPortForwardGroutineCountAdd(a)
return
}
}
func (p *TCPUDPProxyCommonConf) GetCurrentConnections() int64 {
return atomic.LoadInt64(&p.CurrentConnectionsCount)
}
func (p *TCPUDPProxyCommonConf) GetListentAddress() string {
if p.listentAddress == "" {
if strings.Contains(p.listenIP, ":") {
p.listentAddress = fmt.Sprintf("[%s]:%d", p.listenIP, p.listenPort)
} else {
p.listentAddress = fmt.Sprintf("%s:%d", p.listenIP, p.listenPort)
}
}
return p.listentAddress
}
func (p *TCPUDPProxyCommonConf) GetKey() string {
if p.key == "" {
p.key = GetProxyKey(p.ProxyType, p.listenIP, p.listenPort)
}
return p.key
}
func (p *TCPUDPProxyCommonConf) GetListenIP() string {
return p.listenIP
}
func (p *TCPUDPProxyCommonConf) GetListenPort() int {
return p.listenPort
}
func (p *TCPUDPProxyCommonConf) GetTargetAddress() string {
p.targetAddressLock.Lock()
defer p.targetAddressLock.Unlock()
if p.targetAddressCount <= 0 {
p.targetAddressCount = len(p.targetAddressList)
p.targetAddressIndex = 0
}
address := fmt.Sprintf("%s:%d", p.targetAddressList[p.targetAddressIndex%uint64(p.targetAddressCount)], p.targetPort)
p.targetAddressIndex++
return address
}
func (p *TCPUDPProxyCommonConf) String() string {
return fmt.Sprintf("%s@%v ===> %v:%d", p.ProxyType, p.GetListentAddress(), p.targetAddressList, p.targetPort)
}
func (p *TCPUDPProxyCommonConf) SafeCheck(remodeAddr string) bool {
host, _, _ := net.SplitHostPort(remodeAddr)
return safeCheckFunc(p.safeMode, host)
}

View File

@ -0,0 +1,423 @@
// Copyright 2022 gdy, 272288813@qq.com
package socketproxy
import (
"fmt"
"net"
"runtime"
"strings"
"sync"
"time"
"github.com/fatedier/golib/errors"
"github.com/gdy666/lucky/thirdlib/gdylib/pool"
"github.com/sirupsen/logrus"
)
const UDP_DEFAULT_PACKAGE_SIZE = 1500
//测试
type UDPProxy struct {
//BaseProxyConf
TCPUDPProxyCommonConf
// targetAddr *net.UDPAddr
listenConn *net.UDPConn
listenConnMutex sync.Mutex
relayChs []chan *udpPackge
replyCh chan *udpPackge
udpPackageSize int
//targetudpConnItemMap map[string]*udpMapItem
//targetudpConnItemMapMutex sync.RWMutex
targetConnectSessions sync.Map
Upm bool //性能模式
ShortMode bool
isStop bool
SingleProxyMaxUDPReadTargetDatagoroutineCount int64
}
type udpPackge struct {
dataSize int
data *[]byte
remoteAddr *net.UDPAddr
}
type udpTagetConSession struct {
targetConn *net.UDPConn
lastTime time.Time
}
func CreateUDPProxy(log *logrus.Logger, proxyType, listenIP string, targetAddressList []string, listenPort, targetPort int, options *RelayRuleOptions) *UDPProxy {
p := &UDPProxy{}
//p.Key = key
p.ProxyType = proxyType
p.listenIP = listenIP
p.listenPort = listenPort
p.targetAddressList = targetAddressList
p.targetPort = targetPort
p.Upm = options.UDPProxyPerformanceMode
p.ShortMode = options.UDPShortMode
p.safeMode = options.SafeMode
p.log = log
p.SingleProxyMaxUDPReadTargetDatagoroutineCount = options.SingleProxyMaxUDPReadTargetDatagoroutineCount
p.SetUDPPacketSize(options.UDPPackageSize)
return p
}
func (p *UDPProxy) getHandlegoroutineNum() int {
if p.Upm {
return runtime.NumCPU()
}
return 1
}
func (p *UDPProxy) SetUDPPacketSize(size int) {
if size <= 0 {
p.udpPackageSize = UDP_DEFAULT_PACKAGE_SIZE
return
}
if size > 65507 {
p.udpPackageSize = 65507
return
}
p.udpPackageSize = size
}
func (p *UDPProxy) GetUDPPacketSize() int {
return p.udpPackageSize
}
func (p *UDPProxy) StartProxy() {
//p.init()
p.listenConnMutex.Lock()
defer p.listenConnMutex.Unlock()
if p.listenConn != nil {
return
}
bindAddr, err := net.ResolveUDPAddr(p.ProxyType, p.GetListentAddress())
if err != nil {
p.log.Errorf("Cannot start proxy[%s]:%s", p.GetKey(), err)
return
}
ln, err := net.ListenUDP(p.ProxyType, bindAddr)
if err != nil {
if strings.Contains(err.Error(), " bind: Only one usage of each socket address") {
p.log.Errorf("监听IP端口[%s]已被占用,proxy[%s]启动失败", p.GetListentAddress(), p.String())
} else {
p.log.Errorf("Cannot start proxy[%s]:%s", p.String(), err)
}
return
}
ln.SetReadBuffer(p.getHandlegoroutineNum() * 4 * 1024 * 1024)
ln.SetWriteBuffer(p.getHandlegoroutineNum() * 4 * 1024 * 1024)
p.listenConn = ln
p.log.Infof("[端口转发][开启][%s]", p.String())
p.relayChs = make([]chan *udpPackge, p.getHandlegoroutineNum())
for i := range p.relayChs {
p.relayChs[i] = make(chan *udpPackge, 1024)
}
p.replyCh = make(chan *udpPackge, 1024)
// if p.targetudpConnItemMap == nil {
// p.targetudpConnItemMap = make(map[string]*udpMapItem)
// }
for i := range p.relayChs {
go p.Forwarder(i, p.relayChs[i])
}
go p.replyDataToRemotAddress()
go p.CheckTargetUDPConnectSessions()
for i := 0; i < p.getHandlegoroutineNum(); i++ {
go p.ListenHandler(ln)
}
}
func (p *UDPProxy) StopProxy() {
p.listenConnMutex.Lock()
defer p.listenConnMutex.Unlock()
defer func() {
p.targetConnectSessions.Range(func(key any, value any) bool {
session := value.(*udpTagetConSession)
session.targetConn.Close()
p.targetConnectSessions.Delete(key)
return true
})
p.log.Infof("[端口转发][关闭][%s]", p.String())
}()
if p.listenConn == nil {
return
}
p.listenConn.Close()
p.listenConn = nil
p.isStop = true
close(p.replyCh)
for i := range p.relayChs {
close(p.relayChs[i])
}
}
// ReadFromTargetOnce one clientAddr only read once,short mode eg: udp dns
func (p *UDPProxy) ReadFromTargetOnce() bool {
if p.targetPort == 53 || p.ShortMode {
return true
}
return false
}
// func (p *UDPProxy) GetStatus() string {
// return fmt.Sprintf("%s max packet size[%d]", p.String(), p.GetUDPPacketSize())
// }
func (p *UDPProxy) ListenHandler(ln *net.UDPConn) {
inDatabuf := pool.GetBuf(p.GetUDPPacketSize())
defer pool.PutBuf(inDatabuf)
i := uint64(0)
for {
if p.listenConn == nil {
break
}
inDatabufSize, remoteAddr, err := ln.ReadFromUDP(inDatabuf)
if err != nil {
if strings.Contains(err.Error(), `smaller than the datagram`) {
p.log.Errorf("[%s] UDP包最大长度设置过小,请重新设置", p.GetKey())
} else {
if !strings.Contains(err.Error(), "use of closed network connection") {
p.log.Errorf(" %s ReadFromUDP error:\n%s \n", p.String(), err.Error())
}
}
continue
}
remoteAddrStr := remoteAddr.String()
if !p.SafeCheck(remoteAddrStr) {
p.log.Warnf("[%s]新连接 [%s]安全检查未通过", p.GetKey(), remoteAddrStr)
continue
}
_, ok := p.targetConnectSessions.Load(remoteAddrStr)
if !ok {
p.log.Infof("[%s]新连接 [%s]安全检查通过", p.GetKey(), remoteAddrStr)
}
data := pool.GetBuf(inDatabufSize)
copy(data, inDatabuf[:inDatabufSize])
inUdpPack := udpPackge{dataSize: inDatabufSize, data: &data, remoteAddr: remoteAddr}
p.relayChs[i%uint64(p.getHandlegoroutineNum())] <- &inUdpPack
i++
}
}
func (p *UDPProxy) handlerDataFromTargetAddress(raddr *net.UDPAddr, tgConn *net.UDPConn) {
readBuffer := pool.GetBuf(p.GetUDPPacketSize())
var session *udpTagetConSession
sessionKey := raddr.String()
defer func() {
pool.PutBuf(readBuffer)
if p.ReadFromTargetOnce() {
tgConn.Close()
} else {
p.targetConnectSessions.Delete(sessionKey)
}
p.AddCurrentConnections(-1)
p.log.Infof("[%s]目标地址[%s]关闭连接[%s]", p.GetKey(), tgConn.RemoteAddr().String(), tgConn.LocalAddr().String())
}()
var targetConn *net.UDPConn
p.AddCurrentConnections(1)
for {
targetConn = nil
session = nil
timeout := 1200 * time.Millisecond
if p.ReadFromTargetOnce() {
timeout = 300 * time.Millisecond
}
if p.ReadFromTargetOnce() {
targetConn = tgConn
} else {
se, ok := p.targetConnectSessions.Load(sessionKey)
if !ok {
return
}
session = se.(*udpTagetConSession)
targetConn = session.targetConn
}
targetConn.SetReadDeadline(time.Now().Add(timeout))
n, _, err := targetConn.ReadFromUDP(readBuffer)
if err != nil {
errStr := err.Error()
if strings.Contains(errStr, `i/o timeout`) && !p.ReadFromTargetOnce() {
continue
}
if !strings.Contains(errStr, `use of closed network connection`) {
p.log.Errorf("[%s]targetConn ReadFromUDP error:%s", p.GetKey(), err.Error())
}
return
}
data := pool.GetBuf(n)
copy(data, readBuffer[:n])
udpMsg := udpPackge{dataSize: n, data: &data, remoteAddr: raddr}
if err = errors.PanicToError(func() {
select {
case p.replyCh <- &udpMsg: //转发数据到远程地址
default:
}
}); err != nil {
return
}
if p.ReadFromTargetOnce() { //一次性
return
}
//非一次性,刷新时间或者退出
_, ok := p.targetConnectSessions.Load(sessionKey)
if !ok {
return
}
}
}
func (p *UDPProxy) Forwarder(kk int, replych chan *udpPackge) {
// read from targetAddr and write clientAddr
var err error
// read from readCh
for udpMsg := range replych {
err = nil
se, ok := p.targetConnectSessions.Load(udpMsg.remoteAddr.String())
if !ok {
err := p.CheckReadTargetDataGoroutineLimit()
if err != nil {
p.log.Warnf("[%s]转发中止:%s", p.GetKey(), err.Error())
continue
}
}
var session *udpTagetConSession
if ok {
session = se.(*udpTagetConSession)
} else {
session = &udpTagetConSession{}
}
if !ok {
addr := p.GetTargetAddress()
tgAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
p.log.Errorf("[%s]UDP端口转发目标地址[%s]解析出错:%s", p.GetKey(), addr, err.Error())
pool.PutBuf(*udpMsg.data)
continue
}
targetConn, err := net.DialUDP("udp", nil, tgAddr)
if err != nil {
p.log.Errorf("[%s]UDP端口转发目标地址[%s]连接出错:%s", p.GetKey(), addr, err.Error())
pool.PutBuf(*udpMsg.data)
continue
}
targetConn.SetWriteBuffer(4 * 1024 * 1024)
targetConn.SetReadBuffer(4 * 1024 * 1024)
session.targetConn = targetConn
}
session.lastTime = time.Now()
if !p.ReadFromTargetOnce() { //只存储非一次性
p.targetConnectSessions.Store(udpMsg.remoteAddr.String(), session)
}
p.ReceiveDataCallback(int64(udpMsg.dataSize)) //接收流量记录
_, err = session.targetConn.Write(*udpMsg.data)
if err != nil {
p.log.Errorf("[%s]转发数据到目标端口出错:%s", p.GetKey(), err.Error())
session.targetConn.Close()
continue
}
pool.PutBuf(*udpMsg.data)
if !ok {
go p.handlerDataFromTargetAddress(udpMsg.remoteAddr, session.targetConn)
}
}
}
func (p *UDPProxy) replyDataToRemotAddress() {
for msg := range p.replyCh {
_, err := p.listenConn.WriteToUDP(*(msg.data), msg.remoteAddr)
pool.PutBuf(*msg.data)
if err != nil {
p.log.Errorf("[%s]转发目标端口数据到远程端口出错:%s", p.GetKey(), err.Error())
continue
}
p.SendDataCallback(int64(msg.dataSize)) //发送流量记录
}
}
func (p *UDPProxy) CheckReadTargetDataGoroutineLimit() error {
if GetGlobalUDPPortForwardGroutineCount() >= GetGlobalUDPReadTargetDataMaxgoroutineCountLimit() {
return fmt.Errorf("超出端口转发全局UDP读取目标地址数据协程数限制[%d]", GetGlobalUDPReadTargetDataMaxgoroutineCountLimit())
}
if p.GetCurrentConnections() >= p.SingleProxyMaxUDPReadTargetDatagoroutineCount {
return fmt.Errorf("超出单端口UDP读取目标地址数据协程数限制[%d]", p.SingleProxyMaxUDPReadTargetDatagoroutineCount)
}
return nil
}
func (p *UDPProxy) CheckTargetUDPConnectSessions() {
for {
<-time.After(time.Second * 1)
if p.isStop {
return
}
if p.GetCurrentConnections() <= 0 {
continue
}
p.targetConnectSessions.Range(func(key any, value any) bool {
session := value.(*udpTagetConSession)
if time.Since(session.lastTime) >= 30*time.Second {
session.targetConn.Close()
p.targetConnectSessions.Delete(key)
}
return true
})
}
}

View File

@ -0,0 +1,521 @@
package reverseproxyconf
import (
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
"strings"
"sync"
"time"
//"github.com/gdy666/lucky/module/safe"
//ssl "github.com/gdy666/lucky/module/sslcertficate"
"github.com/gdy666/lucky/module/weblog"
"github.com/gdy666/lucky/thirdlib/gdylib/ginutils"
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
"github.com/gdy666/lucky/thirdlib/gdylib/logsbuffer"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
var ReverseProxyServerStore sync.Map
var ReverseProxyServerStoreMu sync.Mutex
var GetValidSSLCertficateList func() []tls.Certificate
var SafeCheck func(string, string) bool
type SubReverProxyRule struct {
Key string `json:"Key"`
initOnce sync.Once
Locations []string `json:"Locations"` //长度大于1时均衡负载
locationMutex *sync.Mutex `json:"-"`
locationsCount int `json:"-"`
locationIndex uint64 `json:"-"`
EnableAccessLog bool `json:"EnableAccessLog"` //开启日志
LogLevel int `json:"LogLevel"` //日志输出级别
LogOutputToConsole bool `json:"LogOutputToConsole"` //日志输出到终端
AccessLogMaxNum int `json:"AccessLogMaxNum"` //最大条数
WebListShowLastLogMaxCount int `json:"WebListShowLastLogMaxCount"` //前端列表显示最新日志最大条数
ForwardedByClientIP bool `json:"ForwardedByClientIP"`
TrustedCIDRsStrList []string `json:"TrustedCIDRsStrList"`
RemoteIPHeaders []string `json:"RemoteIPHeaders"` //识别客户端原始IP的Http请求头
TrustedProxyCIDRs []*net.IPNet `json:"-"`
AddRemoteIPToHeader bool `json:"AddRemoteIPToHeader"` //追加客户端连接IP到指定Header
AddRemoteIPHeaderKey string `json:"AddRemoteIPHeaderKey"`
EnableBasicAuth bool `json:"EnableBasicAuth"` //启用BasicAuth认证
BasicAuthUser string `json:"BasicAuthUser"` //如果配置此参数,暴露出去的 HTTP 服务需要采用 Basic Auth 的鉴权才能访问
BasicAuthPasswd string `json:"BasicAuthPasswd"` //结合 BasicAuthUser 使用
SafeIPMode string `json:"SafeIPMode"` //IP过滤模式 黑白名单
SafeUserAgentMode string `json:"SafeUserAgentMode"` //UserAgent 过滤模式 黑白名单
UserAgentfilter []string `json:"UserAgentfilter"` //UserAgent 过滤内容
CustomRobotTxt bool `json:"CustomRobotTxt"`
RobotTxt string `json:"RobotTxt"`
//------------------
logsBuffer *logsbuffer.LogsBuffer
logrus *logrus.Logger
logger *log.Logger
}
type ReverseProxyRule struct {
RuleName string `json:"RuleName"`
RuleKey string `json:"RuleKey"`
Enable bool `json:"Enable"`
ListenIP string `json:"ListenIP"`
ListenPort int `json:"ListenPort"`
EnableTLS bool `json:"EnableTLS"`
Network string `json:"Network"`
DefaultProxy struct {
SubReverProxyRule
} `json:"DefaultProxy"`
ProxyList []ReverseProxy `json:"ProxyList"`
domainsMap *sync.Map
initOnec sync.Once
}
func (r *ReverseProxyRule) Init() {
r.initOnec.Do(func() {
r.initDomainsMap()
})
}
func (r *SubReverProxyRule) Logf(level logrus.Level, c *gin.Context, format string, v ...any) {
clientIP := r.ClientIP(c)
remoteIP := c.RemoteIP()
method := c.Request.Method
host := c.Request.Host
//hostname, hostport := httputils.SplitHostPort(c.Request.Host)
url := c.Request.URL.String()
//path := c.Request.URL.Path
r.GetLogrus().WithFields(logrus.Fields{
"ClientIP": clientIP,
"RemoteIP": remoteIP,
"Method": method,
"Host": host,
// "Hostname": hostname,
// "Hostport": hostport,
"URL": url,
//"path": path,
"UserAgent": c.Request.UserAgent(),
}).Logf(level, format, v...)
}
func (r *SubReverProxyRule) HandlerReverseProxy(remote *url.URL, host, path string, c *gin.Context) {
proxy := httputil.NewSingleHostReverseProxy(remote)
proxy.Director = func(req *http.Request) {
req.Header = c.Request.Header
req.Host = host //remote.Host
//req.Host = remote.Host
req.URL.Scheme = remote.Scheme
req.URL.Host = remote.Host
req.URL.Path = path
if r.AddRemoteIPToHeader && r.AddRemoteIPHeaderKey != "" {
cip := r.ClientIP(c)
req.Header.Add(r.AddRemoteIPHeaderKey, cip)
}
}
proxy.ErrorLog = r.GetLogger()
proxy.ServeHTTP(c.Writer, c.Request)
}
func (r *SubReverProxyRule) Fire(entry *logrus.Entry) error {
if !r.LogOutputToConsole {
return nil
}
s, _ := entry.String()
log.Print(s)
return nil
}
func (r *SubReverProxyRule) Levels() []logrus.Level {
return logrus.AllLevels
}
func (r *SubReverProxyRule) GetLogrus() *logrus.Logger {
if r.logrus == nil {
r.logrus = logrus.New()
r.logrus.SetLevel(logrus.Level(r.LogLevel))
r.logrus.SetOutput(r.GetLogsBuffer())
r.logrus.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
DisableTimestamp: true,
DisableHTMLEscape: true,
DataKey: "ExtInfo",
})
r.logrus.AddHook(r)
}
return r.logrus
}
func (r *SubReverProxyRule) GetLogger() *log.Logger {
if r.logger == nil {
r.logger = log.New(r.GetLogsBuffer(), "", log.LstdFlags)
}
return r.logger
}
func (r *SubReverProxyRule) GetLogsBuffer() *logsbuffer.LogsBuffer {
if r.logsBuffer == nil {
r.logsBuffer = logsbuffer.CreateLogbuffer("reverseproxy:"+r.Key, r.AccessLogMaxNum)
}
return r.logsBuffer
}
func (r *SubReverProxyRule) checkupClientIP(ip string) bool {
return SafeCheck(r.SafeIPMode, ip)
}
func (r *SubReverProxyRule) checkupUserAgent(ua string) bool {
isContains := false
for _, c := range r.UserAgentfilter {
if strings.Contains(ua, c) {
isContains = true
break
}
}
switch r.SafeUserAgentMode {
case "whitelist":
return isContains
case "blacklist":
return !isContains
default:
return false
}
}
func (r *ReverseProxyRule) ReverseProxyHandler(c *gin.Context) {
path := c.Param("proxyPath")
hostName, _ := httputils.SplitHostPort(c.Request.Host)
rule, ok := r.GetSubRuleByDomain(hostName)
var subRule *SubReverProxyRule = nil
if ok && rule.Enable {
subRule = &rule.SubReverProxyRule
} else {
subRule = &r.DefaultProxy.SubReverProxyRule
}
if !subRule.checkupClientIP(subRule.ClientIP(c)) { //IP检查
subRule.Logf(logrus.WarnLevel, c, "IP[%s]禁止访问,当前Ip检查模式[%s]", subRule.ClientIP(c), subRule.SafeIPMode)
c.Abort()
return
}
if !subRule.checkupUserAgent(c.Request.UserAgent()) {
subRule.Logf(logrus.WarnLevel, c, "IP[%s]UA[%s]禁止访问,当前UA检查模式[%s]", subRule.ClientIP(c), c.Request.UserAgent(), subRule.SafeUserAgentMode)
c.Abort()
return
}
if !subRule.BasicAuthHandler(c) {
subRule.Logf(logrus.WarnLevel, c, "BasicAuth认证不通过")
c.Abort()
return
}
if subRule.CustomRobotTxt && c.Request.RequestURI == "/robots.txt" {
if c.Request.Method != "GET" && c.Request.Method != "HEAD" {
status := http.StatusOK
if c.Request.Method != "OPTIONS" {
status = http.StatusMethodNotAllowed
}
c.Header("Allow", "GET,HEAD,OPTIONS")
c.AbortWithStatus(status)
return
}
c.Data(http.StatusOK, "text/plain", []byte(subRule.RobotTxt))
subRule.Logf(logrus.InfoLevel, c, "触发自定义robots.txt")
return
}
location := subRule.GetLocation()
if location == "" && subRule.Key == r.RuleKey {
subRule.Logf(logrus.InfoLevel, c, "域名[%s]没有对应后端地址,默认后端地址没有设置", hostName)
c.Abort()
return
}
if subRule.Key == r.RuleKey {
subRule.Logf(logrus.InfoLevel, c, "[%s] 指向默认后端地址[%s%s]", hostName, location, c.Request.URL.String())
} else {
subRule.Logf(logrus.InfoLevel, c, "[%s] 指向后端地址[%s%s]", hostName, location, c.Request.URL.String())
}
remote, err := url.Parse(location)
if err != nil {
subRule.Logf(logrus.ErrorLevel, c, "后端地址转换出错:%s", err.Error())
c.JSON(http.StatusBadGateway, gin.H{"ret": 1, "msg": fmt.Sprintf("后端地址[%s] 转换出错:%s", location, err.Error())})
return
}
subRule.HandlerReverseProxy(remote, hostName, path, c)
}
func (r *ReverseProxyRule) GetSubRuleByDomain(domain string) (*ReverseProxy, bool) {
val, ok := r.domainsMap.Load(domain)
if !ok {
return nil, false
}
return val.(*ReverseProxy), true
}
type ReverseProxy struct {
SubReverProxyRule
Enable bool `json:"Enable"`
Remark string `json:"Remark"`
Domains []string `json:"Domains"` //自定义域名
}
func (r *ReverseProxyRule) GetServer() *http.Server {
s, loaded := ReverseProxyServerStore.Load(r.RuleKey)
if !loaded {
return nil
}
return s.(*http.Server)
}
func (r *ReverseProxyRule) SetServer(s *http.Server) {
if s == nil {
ReverseProxyServerStore.Delete(r.RuleKey)
return
}
ReverseProxyServerStore.Store(r.RuleKey, s)
}
func (r *ReverseProxyRule) ServerStart() error {
// r.smu.Lock()
// defer r.smu.Unlock()
ReverseProxyServerStoreMu.Lock()
defer ReverseProxyServerStoreMu.Unlock()
server := r.GetServer()
if server != nil {
return fmt.Errorf("RuleServer[%s]已经启动,请勿重复启动", r.Addr())
}
ginR := gin.New()
ginR.Any("/*proxyPath", r.ReverseProxyHandler)
server = &http.Server{
Addr: r.Addr(),
Handler: ginR,
ErrorLog: r.DefaultProxy.GetLogger(),
}
//***************************
var err error
server.TLSConfig = &tls.Config{}
if r.EnableTLS {
certList := GetValidSSLCertficateList() //ssl.GetValidSSLCertficateList()
server.TLSConfig.Certificates = certList
}
//server.TLSConfig.Certificates = make([]tls.Certificate, 3)
//****************************
ln, err := net.Listen(r.Network, r.Addr())
if err != nil {
return err
}
var serveResult error
go func() {
if !r.EnableTLS {
serveResult = server.Serve(ln)
return
}
if len(server.TLSConfig.Certificates) <= 0 {
log.Printf("可用证书列表为空,%s 未能启用TLS", r.Addr())
serveResult = server.Serve(ln)
return
}
log.Printf("%s 已启用TLS", r.Addr())
serveResult = server.ServeTLS(ln, "", "")
}()
<-time.After(time.Millisecond * 300)
defer func() {
if serveResult == nil {
r.SetServer(server)
}
}()
return serveResult
}
func (r *ReverseProxyRule) ServerStop() {
ReverseProxyServerStoreMu.Lock()
defer ReverseProxyServerStoreMu.Unlock()
server := r.GetServer()
if server == nil {
return
}
server.Close()
r.SetServer(nil)
}
func (r *ReverseProxyRule) initDomainsMap() error {
r.domainsMap = &sync.Map{}
for i := range r.ProxyList {
for j := range r.ProxyList[i].Domains {
_, loaded := r.domainsMap.LoadOrStore(r.ProxyList[i].Domains[j], &r.ProxyList[i])
if loaded {
return fmt.Errorf("前端域名[%s]冲突", r.ProxyList[i].Domains[j])
}
}
}
return nil
}
func (r *SubReverProxyRule) initOnceExec() {
r.initOnce.Do(func() {
r.locationsCount = len(r.Locations)
r.InitTrustedProxyCIDRs()
r.locationMutex = &sync.Mutex{}
})
}
func (r *SubReverProxyRule) GetLocation() string {
r.initOnceExec()
r.locationMutex.Lock()
defer func() {
r.locationIndex++
r.locationMutex.Unlock()
}()
if r.locationsCount == 0 {
return ""
}
return r.Locations[r.locationIndex%uint64(r.locationsCount)]
}
func (r *SubReverProxyRule) BasicAuthHandler(c *gin.Context) bool {
if !r.EnableBasicAuth || r.BasicAuthUser == "" {
return true
}
realm := "Basic realm=" + strconv.Quote("Authorization Required")
pairs := ginutils.ProcessAccounts(gin.Accounts{r.BasicAuthUser: r.BasicAuthPasswd})
user, found := pairs.SearchCredential(c.GetHeader("Authorization"))
if !found {
// Credentials doesn't match, we return 401 and abort handlers chain.
c.Header("WWW-Authenticate", realm)
c.AbortWithStatus(http.StatusUnauthorized)
return false
}
c.Set("user", user)
return true
}
func (r *SubReverProxyRule) InitTrustedProxyCIDRs() error {
var res []*net.IPNet
for i := range r.TrustedCIDRsStrList {
if strings.TrimSpace(r.TrustedCIDRsStrList[i]) == "" {
continue
}
_, cidr, err := net.ParseCIDR(r.TrustedCIDRsStrList[i])
if err != nil {
return fmt.Errorf("[%s]网段格式有误", r.TrustedCIDRsStrList[i])
}
res = append(res, cidr)
}
r.TrustedProxyCIDRs = res
return nil
}
func (r *SubReverProxyRule) ClientIP(c *gin.Context) string {
remoteIP := net.ParseIP(c.RemoteIP())
if remoteIP == nil {
return ""
}
trusted := r.isTrustedProxy(remoteIP)
if trusted && r.ForwardedByClientIP && r.RemoteIPHeaders != nil {
for _, headerName := range r.RemoteIPHeaders {
ip, valid := r.validateHeader(c.Request.Header.Get(headerName))
if valid {
return ip
}
}
}
return remoteIP.String()
}
func (r *SubReverProxyRule) validateHeader(header string) (clientIP string, valid bool) {
if header == "" {
return "", false
}
items := strings.Split(header, ",")
for i := len(items) - 1; i >= 0; i-- {
ipStr := strings.TrimSpace(items[i])
ip := net.ParseIP(ipStr)
if ip == nil {
break
}
if (i == 0) || (!r.isTrustedProxy(ip)) {
return ipStr, true
}
}
return "", false
}
func (r *SubReverProxyRule) isTrustedProxy(ip net.IP) bool {
r.initOnceExec()
if r.TrustedProxyCIDRs == nil {
return false
}
for _, cidr := range r.TrustedProxyCIDRs {
if cidr.Contains(ip) {
return true
}
}
return false
}
func (r *ReverseProxyRule) Addr() string {
return fmt.Sprintf("%s:%d", r.ListenIP, r.ListenPort)
}
func (r *ReverseProxyRule) GetLastLogs() map[string][]any {
res := make(map[string][]any)
res["default"] = r.DefaultProxy.GetLogsBuffer().GetLastLogs(weblog.WebLogConvert, r.DefaultProxy.WebListShowLastLogMaxCount)
for i := range r.ProxyList {
res[r.ProxyList[i].Key] = r.ProxyList[i].GetLogsBuffer().GetLastLogs(
weblog.WebLogConvert, r.ProxyList[i].WebListShowLastLogMaxCount)
}
return res
}

View File

@ -0,0 +1,77 @@
package reverseproxy
import (
"fmt"
"log"
reverseproxyconf "github.com/gdy666/lucky/module/reverseproxy/conf"
"github.com/gdy666/lucky/module/weblog"
)
func InitReverseProxyServer() {
ruleList := GetReverseProxyRuleList()
for ruleIndex := range ruleList {
if ruleList[ruleIndex].Enable {
startRes := ruleList[ruleIndex].ServerStart()
if startRes == nil {
log.Printf("启动反向代理服务[%s]成功", ruleList[ruleIndex].Addr())
} else {
log.Printf("启动反向代理服务[%s]失败:%s", ruleList[ruleIndex].Addr(), startRes.Error())
}
}
}
}
func EnableRuleByKey(key string, enable bool) error {
rule := GetReverseProxyRuleByKey(key)
if rule == nil {
return fmt.Errorf("GetReverseProxyRuleByKey not found:%s", key)
}
if enable {
err := rule.ServerStart()
if err != nil {
log.Printf("启用反向代理规则[%s]出错:%s", rule.Addr(), err.Error())
EnableReverseProxyRuleByKey(key, false)
return fmt.Errorf("启用反向代理规则[%s]出错:%s", rule.Addr(), err.Error())
} else {
log.Printf("启用反向代理规则[%s]成功", rule.Addr())
}
} else {
rule.ServerStop()
log.Printf("停用反向代理规则[%s]成功", rule.Addr())
}
return EnableReverseProxyRuleByKey(key, enable)
}
type RuleInfo struct {
reverseproxyconf.ReverseProxyRule
AccessLogs map[string][]any
}
func GetProxyRuleListInfo() *[]RuleInfo {
ruleList := GetReverseProxyRuleList()
var res []RuleInfo
for i := range ruleList {
//ti := createProxyRuleInfo(nil, ruleList[i])
var ri RuleInfo
ri.ReverseProxyRule = *ruleList[i]
ri.AccessLogs = ruleList[i].GetLastLogs()
res = append(res, ri)
}
return &res
}
func GetAccessLogs(ruleKey, proxyKey string, pageSize, page int) (int, []any) {
var res []any
total := 0
subRule := GetSubRuleByKey(ruleKey, proxyKey)
if subRule == nil {
return 0, res
}
total, res = subRule.GetLogsBuffer().GetLogsByLimit(weblog.WebLogConvert, pageSize, page)
return total, res
}

View File

@ -0,0 +1,229 @@
package reverseproxy
import (
"fmt"
"strings"
"github.com/gdy666/lucky/config"
reverseproxyconf "github.com/gdy666/lucky/module/reverseproxy/conf"
"github.com/gdy666/lucky/module/safe"
ssl "github.com/gdy666/lucky/module/sslcertficate"
"github.com/gdy666/lucky/thirdlib/gdylib/logsbuffer"
)
func init() {
reverseproxyconf.GetValidSSLCertficateList = ssl.GetValidSSLCertficateList
reverseproxyconf.SafeCheck = safe.SafeCheck
}
// TidyReverseProxyCache 整理反向代理日志缓存
func TidyReverseProxyCache() {
ruleList := GetReverseProxyRuleList()
var keyListBuffer strings.Builder
for _, rule := range ruleList {
keyListBuffer.WriteString(rule.DefaultProxy.Key)
keyListBuffer.WriteString(",")
for _, sr := range rule.ProxyList {
keyListBuffer.WriteString(sr.Key)
keyListBuffer.WriteString(",")
}
}
keyListStr := keyListBuffer.String()
logsbuffer.LogsBufferStoreMu.Lock()
defer logsbuffer.LogsBufferStoreMu.Unlock()
var needDeleteKeys []string
for k := range logsbuffer.LogsBufferStore {
if !strings.HasPrefix(k, "reverseproxy:") {
continue
}
if len(k) <= 13 {
continue
}
if !strings.Contains(keyListStr, k[13:]) {
needDeleteKeys = append(needDeleteKeys, k)
}
}
for i := range needDeleteKeys {
delete(logsbuffer.LogsBufferStore, needDeleteKeys[i])
reverseproxyconf.ReverseProxyServerStore.Delete(needDeleteKeys[i])
}
}
//------------------------------------------------------------
func GetReverseProxyRuleList() []*reverseproxyconf.ReverseProxyRule {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
var resList []*reverseproxyconf.ReverseProxyRule
for i := range config.Configure.ReverseProxyRuleList {
config.Configure.ReverseProxyRuleList[i].Init()
rule := config.Configure.ReverseProxyRuleList[i]
resList = append(resList, &rule)
}
return resList
}
func GetReverseProxyRuleByKey(ruleKey string) *reverseproxyconf.ReverseProxyRule {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
ruleIndex := -1
for i := range config.Configure.ReverseProxyRuleList {
if config.Configure.ReverseProxyRuleList[i].RuleKey == ruleKey {
ruleIndex = i
break
}
}
if ruleIndex == -1 {
return nil
}
res := config.Configure.ReverseProxyRuleList[ruleIndex]
return &res
}
func ReverseProxyRuleListAdd(rule *reverseproxyconf.ReverseProxyRule) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
config.Configure.ReverseProxyRuleList = append(config.Configure.ReverseProxyRuleList, *rule)
return config.Save()
}
func ReverseProxyRuleListDelete(ruleKey string) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
ruleIndex := -1
for i := range config.Configure.ReverseProxyRuleList {
if config.Configure.ReverseProxyRuleList[i].RuleKey == ruleKey {
ruleIndex = i
break
}
}
if ruleIndex == -1 {
return fmt.Errorf("找不到需要删除的反向代理任务")
}
config.Configure.ReverseProxyRuleList = DeleteReverseProxyRuleListlice(config.Configure.ReverseProxyRuleList, ruleIndex)
return config.Save()
}
func EnableReverseProxyRuleByKey(ruleKey string, enable bool) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
ruleIndex := -1
for i := range config.Configure.ReverseProxyRuleList {
if config.Configure.ReverseProxyRuleList[i].RuleKey == ruleKey {
ruleIndex = i
break
}
}
if ruleIndex == -1 {
return fmt.Errorf("开关反向代理规则失败,ruleKey %s 未找到", ruleKey)
}
config.Configure.ReverseProxyRuleList[ruleIndex].Enable = enable
return config.Save()
}
func EnableReverseProxySubRule(ruleKey, proxyKey string, enable bool) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
ruleIndex := -1
for i := range config.Configure.ReverseProxyRuleList {
if config.Configure.ReverseProxyRuleList[i].RuleKey == ruleKey {
ruleIndex = i
break
}
}
if ruleIndex == -1 {
return fmt.Errorf("开关反向代理子规则失败,ruleKey %s 未找到", ruleKey)
}
proxyIndex := -1
for i := range config.Configure.ReverseProxyRuleList[ruleIndex].ProxyList {
if config.Configure.ReverseProxyRuleList[ruleIndex].ProxyList[i].Key == proxyKey {
proxyIndex = i
break
}
}
if proxyIndex == -1 {
return fmt.Errorf("开关反向代理子规则失败,proxyKey %s 未找到", proxyKey)
}
config.Configure.ReverseProxyRuleList[ruleIndex].ProxyList[proxyIndex].Enable = enable
return config.Save()
}
func UpdateReverseProxyRulet(rule reverseproxyconf.ReverseProxyRule) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
ruleIndex := -1
for i := range config.Configure.ReverseProxyRuleList {
if config.Configure.ReverseProxyRuleList[i].RuleKey == rule.RuleKey {
ruleIndex = i
break
}
}
if ruleIndex == -1 {
return fmt.Errorf("找不到需要更新的反向代理规则")
}
// rule.RuleKey = programConfigure.ReverseProxyRuleList[ruleIndex].RuleKey
config.Configure.ReverseProxyRuleList[ruleIndex] = rule
return config.Save()
}
func DeleteReverseProxyRuleListlice(a []reverseproxyconf.ReverseProxyRule, deleteIndex int) []reverseproxyconf.ReverseProxyRule {
j := 0
for i := range a {
if i != deleteIndex {
a[j] = a[i]
j++
}
}
return a[:j]
}
func GetSubRuleByKey(ruleKey, proxyKey string) *reverseproxyconf.SubReverProxyRule {
//rule := getSubRuleByKey()
rule := GetReverseProxyRuleByKey(ruleKey)
if rule == nil {
return nil
}
//fmt.Printf("FFF ruleKey:%s proxyKey:%s\n", ruleKey, proxyKey)
if proxyKey == "default" {
return &rule.DefaultProxy.SubReverProxyRule
}
for i := range rule.ProxyList {
if rule.ProxyList[i].Key == proxyKey {
return &rule.ProxyList[i].SubReverProxyRule
}
}
return nil
}

157
module/safe/blacklist.go Normal file
View File

@ -0,0 +1,157 @@
// Copyright 2022 gdy, 272288813@qq.com
package safe
import (
"fmt"
"net"
"strings"
"time"
"github.com/gdy666/lucky/config"
safeconf "github.com/gdy666/lucky/module/safe/conf"
)
func GetBlackList() []safeconf.BlackListItem {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
BlackListFlush(false)
var resList []safeconf.BlackListItem
if config.Configure == nil {
return resList
}
for i := range config.Configure.BlackListConfigure.BlackList {
resList = append(resList, config.Configure.BlackListConfigure.BlackList[i])
}
return resList
}
func BlackListInit() {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
var netIP net.IP
var cidr *net.IPNet
for i := range config.Configure.BlackListConfigure.BlackList {
netIP = nil
cidr = nil
if strings.Contains(config.Configure.BlackListConfigure.BlackList[i].IP, "/") {
_, cidr, _ = net.ParseCIDR(config.Configure.BlackListConfigure.BlackList[i].IP)
} else {
netIP = net.ParseIP(config.Configure.BlackListConfigure.BlackList[i].IP)
}
config.Configure.BlackListConfigure.BlackList[i].Cidr = cidr
config.Configure.BlackListConfigure.BlackList[i].NetIP = netIP
}
}
func BlackListAdd(ip string, activelifeDuration int32) (string, error) {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
var err error
var netIP net.IP = nil
var cidr *net.IPNet = nil
if strings.Contains(ip, "/") {
_, cidr, err = net.ParseCIDR(ip)
if err != nil {
return "", fmt.Errorf("网段格式有误,转换出错:%s", err.Error())
}
} else {
netIP = net.ParseIP(ip)
if netIP == nil {
return "", fmt.Errorf("IP格式有误")
}
}
if activelifeDuration <= 0 {
activelifeDuration = 666666
}
EffectiveTimeStr := time.Now().Add(time.Hour * time.Duration(activelifeDuration)).Format("2006-01-02 15:04:05")
for i, ipr := range config.Configure.BlackListConfigure.BlackList {
if ipr.IP == ip {
config.Configure.BlackListConfigure.BlackList[i].EffectiveTime = EffectiveTimeStr
return EffectiveTimeStr, config.Save()
}
}
item := safeconf.BlackListItem{IP: ip, EffectiveTime: EffectiveTimeStr, NetIP: netIP, Cidr: cidr}
config.Configure.BlackListConfigure.BlackList = append(config.Configure.BlackListConfigure.BlackList, item)
return EffectiveTimeStr, config.Save()
}
func BlackListDelete(ip string) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
removeCount := 0
CONTINUECHECK:
removeIndex := -1
for i, ipr := range config.Configure.BlackListConfigure.BlackList {
if ipr.IP == ip {
removeIndex = i
break
}
}
if removeIndex >= 0 {
removeCount++
config.Configure.BlackListConfigure.BlackList = DeleteBlackListlice(config.Configure.BlackListConfigure.BlackList, removeIndex)
goto CONTINUECHECK
}
if removeCount == 0 {
return nil
}
return config.Save()
}
func BlackListFlush(lock bool) error {
if lock {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
}
removeCount := 0
CONTINUECHECK:
removeIndex := -1
for i, ipr := range config.Configure.BlackListConfigure.BlackList {
ipat, err := time.ParseInLocation("2006-01-02 15:04:05", ipr.EffectiveTime, time.Local)
if err != nil { //有效时间格式有误,当失效处理
removeIndex = i
break
}
if time.Since(ipat) > 0 {
removeIndex = i
break
}
}
if removeIndex >= 0 {
removeCount++
config.Configure.BlackListConfigure.BlackList = DeleteBlackListlice(config.Configure.BlackListConfigure.BlackList, removeIndex)
goto CONTINUECHECK
}
if removeCount == 0 {
return nil
}
return config.Save()
}
func DeleteBlackListlice(a []safeconf.BlackListItem, deleteIndex int) []safeconf.BlackListItem {
j := 0
for i := range a {
if i != deleteIndex {
a[j] = a[i]
j++
}
}
return a[:j]
}

24
module/safe/conf/black.go Normal file
View File

@ -0,0 +1,24 @@
package safeconf
import "net"
type BlackListItem WhiteListItem
type BlackListConfigure struct {
BlackList []BlackListItem `json:"BlackList"` //黑名单列表
}
func (w *BlackListItem) Contains(ip string) bool {
netIP := net.ParseIP(ip)
if netIP == nil {
return false
}
if w.NetIP != nil {
return w.NetIP.Equal(netIP)
}
if w.Cidr != nil {
return w.Cidr.Contains(netIP)
}
return false
}

37
module/safe/conf/white.go Normal file
View File

@ -0,0 +1,37 @@
package safeconf
import "net"
type WhiteListBaseConfigure struct {
URL string `json:"URL"`
ActivelifeDuration int32 `json:"ActivelifeDuration"` //有效期限,小时
BasicAccount string `json:"BasicAccount"`
BasicPassword string `json:"BasicPassword"`
}
type WhiteListConfigure struct {
BaseConfigure WhiteListBaseConfigure `json:"BaseConfigure"`
WhiteList []WhiteListItem `json:"WhiteList"` //白名单列表
}
type WhiteListItem struct {
IP string `json:"IP"`
EffectiveTime string `json:"Effectivetime"` //有效时间
NetIP net.IP `json:"-"`
Cidr *net.IPNet `json:"-"`
}
func (w *WhiteListItem) Contains(ip string) bool {
netIP := net.ParseIP(ip)
if netIP == nil {
return false
}
if w.NetIP != nil {
return w.NetIP.Equal(netIP)
}
if w.Cidr != nil {
return w.Cidr.Contains(netIP)
}
return false
}

6
module/safe/safe.go Normal file
View File

@ -0,0 +1,6 @@
package safe
func Init() {
BlackListInit()
WhiteListInit()
}

75
module/safe/safecheck.go Normal file
View File

@ -0,0 +1,75 @@
// Copyright 2022 gdy, 272288813@qq.com
package safe
import (
"time"
"github.com/gdy666/lucky/config"
)
func SafeCheck(mode, ip string) bool {
switch mode {
case "whitelist":
//log.Printf("whitelist")
return whiteListCheck(ip)
case "blacklist":
//log.Printf("blacklist")
return blackListCheck(ip)
default:
return false
}
}
func whiteListCheck(ip string) bool {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
if config.Configure == nil {
return false
}
for _, item := range config.Configure.WhiteListConfigure.WhiteList {
if !item.Contains(ip) {
continue
}
itemEffectiveTime, err := time.ParseInLocation("2006-01-02 15:04:05", item.EffectiveTime, time.Local)
if err != nil {
return false
}
if time.Since(itemEffectiveTime) < 0 {
//log.Printf("CCC")
return true
}
return false
}
//log.Printf("DDDD")
return false
}
func blackListCheck(ip string) bool {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
if config.Configure == nil {
return true
}
for _, item := range config.Configure.BlackListConfigure.BlackList {
if !item.Contains(ip) {
continue
}
itemEffectiveTime, err := time.ParseInLocation("2006-01-02 15:04:05", item.EffectiveTime, time.Local)
if err != nil {
return true
}
if time.Since(itemEffectiveTime) < 0 {
return false
}
return true
}
return true
}

173
module/safe/whitelist.go Normal file
View File

@ -0,0 +1,173 @@
// Copyright 2022 gdy, 272288813@qq.com
package safe
import (
"fmt"
"net"
"strings"
"time"
"github.com/gdy666/lucky/config"
safeconf "github.com/gdy666/lucky/module/safe/conf"
)
func GetWhiteListBaseConfigure() safeconf.WhiteListBaseConfigure {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
return config.Configure.WhiteListConfigure.BaseConfigure
}
func SetWhiteListBaseConfigure(activelifeDuration int32, url, account, password string) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
config.Configure.WhiteListConfigure.BaseConfigure.URL = url
config.Configure.WhiteListConfigure.BaseConfigure.ActivelifeDuration = activelifeDuration
config.Configure.WhiteListConfigure.BaseConfigure.BasicAccount = account
config.Configure.WhiteListConfigure.BaseConfigure.BasicPassword = password
return config.Save()
}
func GetWhiteList() []safeconf.WhiteListItem {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
WhiteListFlush(false)
var resList []safeconf.WhiteListItem
if config.Configure == nil {
return resList
}
for i := range config.Configure.WhiteListConfigure.WhiteList {
resList = append(resList, config.Configure.WhiteListConfigure.WhiteList[i])
}
return resList
}
func WhiteListInit() {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
var netIP net.IP
var cidr *net.IPNet
for i := range config.Configure.WhiteListConfigure.WhiteList {
netIP = nil
cidr = nil
if strings.Contains(config.Configure.WhiteListConfigure.WhiteList[i].IP, "/") {
_, cidr, _ = net.ParseCIDR(config.Configure.WhiteListConfigure.WhiteList[i].IP)
} else {
netIP = net.ParseIP(config.Configure.WhiteListConfigure.WhiteList[i].IP)
}
config.Configure.WhiteListConfigure.WhiteList[i].Cidr = cidr
config.Configure.WhiteListConfigure.WhiteList[i].NetIP = netIP
}
}
func WhiteListAdd(ip string, activelifeDuration int32) (string, error) {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
var err error
var netIP net.IP = nil
var cidr *net.IPNet = nil
if strings.Contains(ip, "/") {
_, cidr, err = net.ParseCIDR(ip)
if err != nil {
return "", fmt.Errorf("网段格式有误,转换出错:%s", err.Error())
}
} else {
netIP = net.ParseIP(ip)
if netIP == nil {
return "", fmt.Errorf("IP格式有误")
}
}
if activelifeDuration <= 0 {
activelifeDuration = config.Configure.WhiteListConfigure.BaseConfigure.ActivelifeDuration
}
EffectiveTimeStr := time.Now().Add(time.Hour * time.Duration(activelifeDuration)).Format("2006-01-02 15:04:05")
for i, ipr := range config.Configure.WhiteListConfigure.WhiteList {
if ipr.IP == ip {
config.Configure.WhiteListConfigure.WhiteList[i].EffectiveTime = EffectiveTimeStr
return EffectiveTimeStr, config.Save()
}
}
item := safeconf.WhiteListItem{IP: ip, EffectiveTime: EffectiveTimeStr, NetIP: netIP, Cidr: cidr}
config.Configure.WhiteListConfigure.WhiteList = append(config.Configure.WhiteListConfigure.WhiteList, item)
return EffectiveTimeStr, config.Save()
}
func WhiteListDelete(ip string) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
removeCount := 0
CONTINUECHECK:
removeIndex := -1
for i, ipr := range config.Configure.WhiteListConfigure.WhiteList {
if ipr.IP == ip {
removeIndex = i
break
}
}
if removeIndex >= 0 {
removeCount++
config.Configure.WhiteListConfigure.WhiteList = DeleteWhiteListlice(config.Configure.WhiteListConfigure.WhiteList, removeIndex)
goto CONTINUECHECK
}
if removeCount == 0 {
return nil
}
return config.Save()
}
func WhiteListFlush(lock bool) error {
if lock {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
}
removeCount := 0
CONTINUECHECK:
removeIndex := -1
for i, ipr := range config.Configure.WhiteListConfigure.WhiteList {
ipat, err := time.ParseInLocation("2006-01-02 15:04:05", ipr.EffectiveTime, time.Local)
if err != nil { //有效时间格式有误,当失效处理
removeIndex = i
break
}
if time.Since(ipat) > 0 {
removeIndex = i
break
}
}
if removeIndex >= 0 {
removeCount++
config.Configure.WhiteListConfigure.WhiteList = DeleteWhiteListlice(config.Configure.WhiteListConfigure.WhiteList, removeIndex)
goto CONTINUECHECK
}
if removeCount == 0 {
return nil
}
return config.Save()
}
func DeleteWhiteListlice(a []safeconf.WhiteListItem, deleteIndex int) []safeconf.WhiteListItem {
j := 0
for i := range a {
if i != deleteIndex {
a[j] = a[i]
j++
}
}
return a[:j]
}

136
module/service/service.go Normal file
View File

@ -0,0 +1,136 @@
package service
import (
"fmt"
"runtime"
"time"
kservice "github.com/kardianos/service"
)
// var globalService Service
var startFunc func()
var listenPort string
var configureFileURL string
func RegisterStartFunc(f func()) {
startFunc = f
}
func SetListenPort(port int) {
listenPort = fmt.Sprintf("%d", port)
}
func SetConfigureFile(f string) {
configureFileURL = f
}
type Service struct {
}
func (p *Service) Start(s kservice.Service) error {
go p.run()
return nil
}
func (p *Service) run() {
go func() {
<-time.After(time.Second * 2)
startFunc()
}()
}
func (p *Service) Stop(s kservice.Service) error {
return nil
}
func GetServiceState() int {
serviceStatus := -1
s, err := GetService()
if err == nil {
status, _ := s.Status()
serviceStatus = int(status)
}
return serviceStatus
}
func GetService() (kservice.Service, error) {
options := make(kservice.KeyValue)
// if kservice.ChosenSystem().String() == "unix-systemv" {
// options["SysvScript"] = sysvScript
// }
if runtime.GOOS != "windows" {
return nil, fmt.Errorf("仅支持安装卸载windows服务")
}
svcConfig := &kservice.Config{
Name: "lucky",
DisplayName: "lucky",
Description: "ipv6端口转发,反向代理,DDNS,网络唤醒...",
Arguments: []string{"-p", listenPort, "-c", configureFileURL},
Option: options,
}
prg := &Service{}
s, err := kservice.New(prg, svcConfig)
if err != nil {
return nil, err
}
return s, nil
}
// 卸载服务
func UninstallService() error {
s, err := GetService()
if err != nil {
return err
}
return s.Uninstall()
}
// 安装服务
func InstallService() error {
s, err := GetService()
if err != nil {
return err
}
status, err := s.Status()
if err != nil && status == kservice.StatusUnknown {
// 服务未知,创建服务
if err = s.Install(); err == nil {
//s.Start()
//log.Println("安装 lucky 服务成功!")
return nil
}
return fmt.Errorf("安装 lucky 服务失败:%s", err.Error())
}
return fmt.Errorf("lucky服务已安装,无需再次安装:下一次系统启动lucky会以服务形式启动.")
}
func Stop() error {
s, err := GetService()
if err != nil {
return err
}
return s.Stop()
}
func Start() error {
s, err := GetService()
if err != nil {
return err
}
return s.Start()
}
func Restart() error {
s, err := GetService()
if err != nil {
return err
}
return s.Restart()
}

View File

@ -0,0 +1,110 @@
package sslconf
import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"fmt"
"strings"
)
type SSLCertficate struct {
Key string `json:"Key"`
Enable bool `json:"Enable"`
Remark string `json:"Remark"` //备注
CertBase64 string `json:"CertBase64"`
KeyBase64 string `json:"KeyBase64"`
AddTime string `json:"AddTime"` //添加时间
CertsInfo *[]CertInfo `json:"-"`
//---------------------
Certificate *tls.Certificate `json:"-"`
}
type CertInfo struct {
Domains []string
NotBeforeTime string `json:"NotBeforeTime"` //time.Time
NotAfterTime string `json:"NotAfterTime"` //time.Time
}
func (s *SSLCertficate) Init() error {
tc, err := CreateX509KeyPairByBase64Str(s.CertBase64, s.KeyBase64)
if err != nil {
return fmt.Errorf("CreateX509KeyPairByBase64Str error:%s", err.Error())
}
domainsInfo, err := GetCertDomainInfo(tc)
if err != nil {
return fmt.Errorf("GetCertDomainInfo error:%s", err.Error())
}
s.Certificate = tc
s.CertsInfo = domainsInfo
return nil
}
// GetOnlyDomain 返回证书第一条域名
func (s *SSLCertficate) GetFirstDomain() string {
if s.CertsInfo == nil {
return ""
}
if len(*s.CertsInfo) <= 0 {
return ""
}
if len((*s.CertsInfo)[0].Domains) <= 0 {
return ""
}
return (*s.CertsInfo)[0].Domains[0]
}
func CreateX509KeyPairByBase64Str(certBase64, keyBase64 string) (*tls.Certificate, error) {
crtBytes, err := base64.StdEncoding.DecodeString(certBase64)
if err != nil {
return nil, fmt.Errorf("certBase64 decode error:%s", err.Error())
}
keyBytes, err := base64.StdEncoding.DecodeString(keyBase64)
if err != nil {
return nil, fmt.Errorf("keyBase64 decode error:%s", err.Error())
}
cert, err := tls.X509KeyPair(crtBytes, keyBytes)
if err != nil {
return nil, fmt.Errorf("create X509KeyPair error:%s", err.Error())
}
return &cert, nil
}
func GetCertDomainInfo(cert *tls.Certificate) (*[]CertInfo, error) {
if cert == nil {
return nil, fmt.Errorf("cert == nil")
}
var res []CertInfo
for i := range cert.Certificate {
xx, err := x509.ParseCertificate(cert.Certificate[i])
if err != nil {
continue
}
ds := GetDomainsTrimSpace(xx.DNSNames)
if len(ds) == 0 {
continue
}
info := CertInfo{Domains: ds, NotBeforeTime: xx.NotBefore.Format("2006-01-02 15:04:05"), NotAfterTime: xx.NotAfter.Format("2006-01-02 15:04:05")}
res = append(res, info)
}
return &res, nil
}
// 除去空域名
func GetDomainsTrimSpace(dst []string) []string {
var res []string
for i := range dst {
if strings.TrimSpace(dst[i]) == "" {
continue
}
res = append(res, strings.TrimSpace(dst[i]))
}
return res
}

View File

@ -0,0 +1,5 @@
package ssl
func Init() {
SSLCertficateListInit()
}

View File

@ -0,0 +1,189 @@
package ssl
import (
"crypto/tls"
"crypto/x509"
"fmt"
"log"
"strings"
"time"
"github.com/gdy666/lucky/config"
sslconf "github.com/gdy666/lucky/module/sslcertficate/conf"
"github.com/gdy666/lucky/thirdlib/gdylib/stringsp"
)
func GetCertDomains(cert *tls.Certificate) []string {
var res []string
if cert == nil {
return res
}
for i := range cert.Certificate {
xx, err := x509.ParseCertificate(cert.Certificate[i])
if err != nil {
continue
}
for j := range xx.DNSNames {
d := strings.TrimSpace(xx.DNSNames[j])
if d == "" {
continue
}
res = append(res, d)
}
}
return res
}
func GetDomainsStrByDomains(dst []string) string {
var res strings.Builder
for i := range dst {
d := strings.TrimSpace(dst[i])
if d == "" {
continue
}
if res.Len() > 0 {
res.WriteString(",")
}
res.WriteString(d)
}
return res.String()
}
//---------------------------------
func SSLCertficateListInit() {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
var err error
for i := range config.Configure.SSLCertficateList {
err = config.Configure.SSLCertficateList[i].Init()
if err != nil {
log.Printf("SSLCertficateListInit [%s]err:%s", config.Configure.SSLCertficateList[i].Key, err.Error())
}
}
}
func GetSSLCertficateList() []sslconf.SSLCertficate {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
var res []sslconf.SSLCertficate
if config.Configure == nil {
return res
}
for i := range config.Configure.SSLCertficateList {
res = append(res, config.Configure.SSLCertficateList[i])
}
return res
}
func SSLCertficateListAdd(s *sslconf.SSLCertficate) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
//************
//重复检测
for i := range config.Configure.SSLCertficateList {
if config.Configure.SSLCertficateList[i].CertBase64 == s.CertBase64 {
return fmt.Errorf("绑定域名[%s]的相同证书已存在,请勿重复添加", (*s.CertsInfo)[0].Domains[0])
}
if config.Configure.SSLCertficateList[i].GetFirstDomain() != "" &&
config.Configure.SSLCertficateList[i].GetFirstDomain() == s.GetFirstDomain() {
return fmt.Errorf("绑定域名[%s]的证书已存在,如果要添加新证书请先手动删除旧证书", (*s.CertsInfo)[0].Domains[0])
}
}
//************
if s.Key == "" {
s.Key = stringsp.GetRandomString(8)
}
s.AddTime = time.Now().Format("2006-01-02 15:04:05")
s.Enable = true
config.Configure.SSLCertficateList = append(config.Configure.SSLCertficateList, *s)
return config.Save()
}
func SSLCertficateListDelete(key string) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
deleteIndex := -1
for i := range config.Configure.SSLCertficateList {
if config.Configure.SSLCertficateList[i].Key == key {
deleteIndex = i
break
}
}
if deleteIndex < 0 {
return fmt.Errorf("key:%s 不存在", key)
}
config.Configure.SSLCertficateList = DeleteSSLCertficateListslice(config.Configure.SSLCertficateList, deleteIndex)
return config.Save()
}
func SSLCertficateEnable(key string, enable bool) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
index := -1
for i := range config.Configure.SSLCertficateList {
if config.Configure.SSLCertficateList[i].Key == key {
index = i
break
}
}
if index < 0 {
return fmt.Errorf("key:%s 不存在", key)
}
config.Configure.SSLCertficateList[index].Enable = enable
return config.Save()
}
func SSLCertficateAlterRemark(key, remark string) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
index := -1
for i := range config.Configure.SSLCertficateList {
if config.Configure.SSLCertficateList[i].Key == key {
index = i
break
}
}
if index < 0 {
return fmt.Errorf("key:%s 不存在", key)
}
config.Configure.SSLCertficateList[index].Remark = remark
return config.Save()
}
func DeleteSSLCertficateListslice(a []sslconf.SSLCertficate, deleteIndex int) []sslconf.SSLCertficate {
j := 0
for i := range a {
if i != deleteIndex {
a[j] = a[i]
j++
}
}
return a[:j]
}
func GetValidSSLCertficateList() []tls.Certificate {
var res []tls.Certificate
var gdnRes []tls.Certificate
sslListCache := GetSSLCertficateList()
for _, s := range sslListCache {
if !s.Enable {
continue
}
if strings.HasPrefix(s.GetFirstDomain(), "*.") {
gdnRes = append(gdnRes, *s.Certificate)
continue
}
res = append(res, *s.Certificate)
}
res = append(res, gdnRes...)
return res
}

22
module/weblog/weblog.go Normal file
View File

@ -0,0 +1,22 @@
package weblog
import (
"time"
"github.com/gdy666/lucky/thirdlib/gdylib/logsbuffer"
)
type LogItem struct {
ProxyKey string
ClientIP string
LogContent string
LogTime string
}
// 2006-01-02 15:04:05
func WebLogConvert(lg *logsbuffer.LogItem) any {
l := LogItem{
LogContent: lg.Content,
LogTime: time.Unix(lg.Timestamp/int64(time.Second), 0).Format("2006-01-02 15:04:05")}
return l
}

148
module/wol/client.go Normal file
View File

@ -0,0 +1,148 @@
package wol
import (
"fmt"
"os/exec"
"strings"
"sync"
"time"
wolconf "github.com/gdy666/lucky/module/wol/conf"
websocketcontroller "github.com/gdy666/lucky/thirdlib/gdylib/websocketController"
"github.com/sirupsen/logrus"
)
var wolClient *websocketcontroller.Controller
var wolClientMu sync.Mutex
var clientState string
var clientStateMsg string
func init() {
wolconf.SetClientInitFunc(WOLClientInit)
wolconf.SetClientDisconnectFunc(ClientDisconnect)
}
func GetClientState() string {
return clientState
}
func GetClientStateMsg() string {
return clientStateMsg
}
func ClientDisconnect() {
wolClientMu.Lock()
defer wolClientMu.Unlock()
if wolClient == nil {
return
}
go wolClient.Disconnect()
}
func WOLClientInit(logger *logrus.Logger, c *wolconf.WOLClientConfigure) {
if !c.Enable || c.ServerURL == "" {
return
}
client := websocketcontroller.Controller{}
client.ReceiveMessageCallback = receiveMessageCallback
client.ClientDisconnectedCallback = clientStop
client.ClientReadyCallback = clientReady
client.Logs = logger
client.SetConnectRetry(true)
client.SetConnectRetryInterval(time.Second * 3)
client.SetReadDeadline(time.Second * 5)
client.SetServerURL(fmt.Sprintf("%s/api/wol/service", c.ServerURL))
client.ScureSkipVerify(!wolconf.GetHttpClientSecureVerify())
client.SetSendMessageEncryptionFunc(SendMessageEncryptionFunc)
client.SetReceiveMessageDecryptionFunc(ReceiveMessageDecryptionFunc)
wolClientMu.Lock()
defer wolClientMu.Unlock()
wolClient = &client
go wolClient.Connect()
}
func receiveMessageCallback(c *websocketcontroller.Controller, msgBytes []byte) {
rawMsg, err := UnPack(msgBytes)
if err != nil {
return
}
switch m := rawMsg.(type) {
case *LoginResp:
go handlerLoginResp(m, c)
case *SyncClientConfigure:
go synsClientConfigureToClient(m)
case *ReplyWakeUp:
//fmt.Printf("中继唤醒包:%v\n", m)
c.Logs.Infof("中继唤醒包:%v\n", m)
go wolconf.WakeOnLan(false, m.MacList, m.BroadcastIPs, m.Port, m.Repeat, nil)
case *ShutDown:
go func() {
conf := GetWOLServiceConfigure()
c.Logs.Infof("执行关机指令:%s\n", conf.Client.PowerOffCMD)
//<-time.After(time.Second * 1)
cmd := strings.Split(conf.Client.PowerOffCMD, " ")
var output []byte
var err error
//fileutils.OpenProgramOrFile(cmd)
if len(cmd) == 1 {
output, err = exec.Command(cmd[0], []string{}...).Output()
} else {
output, err = exec.Command(cmd[0], cmd[1:]...).Output()
}
if err != nil {
c.Logs.Errorf("执行关机指令[%s]出错:%s:%s", conf.Client.PowerOffCMD, string(output), err.Error())
}
}()
default:
}
}
func clientStop(cc *websocketcontroller.Controller) {
//fmt.Printf("客户端已断开服务器连接\n")
//clientState = "服务端已断开连接"
//clientStateMsg = ""
}
func clientReady(cc *websocketcontroller.Controller) {
cc.Logs.Info("WOL 客户端已连接上服务端,发送登录消息...")
logMsg := &Login{}
logMsg.WOLClientConfigure = wolconf.GetWOLServiceConfigure().Client
logMsg.ClientTimeStamp = time.Now().Unix()
SendMessage(cc, logMsg)
}
func handlerLoginResp(m *LoginResp, c *websocketcontroller.Controller) {
if m.Ret != 0 {
c.Disconnect()
c.Logs.Error("WOl 服务端登录失败:%s", m.Msg)
clientState = "服务端登录失败"
clientStateMsg = m.Msg
return
}
c.Logs.Info("WOL服务端登录成功")
clientState = "服务端登录成功"
clientStateMsg = ""
}
func synsClientConfigureToClient(m *SyncClientConfigure) {
//fmt.Printf("处理来自服务端同步配置\n")
conf := GetWOLServiceConfigure()
conf.Client.Relay = m.Relay
conf.Client.DeviceName = m.DeviceName
conf.Client.Mac = m.Mac
conf.Client.BroadcastIP = m.BroadcastIP
conf.Client.Port = m.Port
conf.Client.Repeat = m.Repeat
conf.Client.UpdateTime = m.UpdateTime
AlterWOLClientConfigure(&conf, logger, false)
}

223
module/wol/conf/device.go Normal file
View File

@ -0,0 +1,223 @@
package wolconf
import (
"fmt"
"log"
"github.com/gdy666/lucky/thirdlib/gdylib/bemfa"
"github.com/gdy666/lucky/thirdlib/gdylib/blinker"
"github.com/gdy666/lucky/thirdlib/gdylib/netinterfaces"
"github.com/gdy666/lucky/thirdlib/gdylib/stringsp"
gowol "github.com/gdy666/lucky/thirdlib/go-wol"
)
var httpClientSecureVerify bool
var httpClientTimeout int
func GetHttpClientSecureVerify() bool {
return httpClientSecureVerify
}
func SetHttpClientSecureVerify(b bool) {
httpClientSecureVerify = b
}
func SetHttpClientTimeout(t int) {
httpClientTimeout = t
}
type WOLDevice struct {
Key string
DeviceName string
MacList []string
BroadcastIPs []string
Port int
Relay bool //交给中继设备发送
Repeat int //重复发送次数
//PowerOffCMD string //关机指令
UpdateTime int64 //配置更新时间
IOT_DianDeng_Enable bool //点灯科技开关
IOT_DianDeng_AUTHKEY string
dianDengClient *blinker.Device
dianDengClientMsg string
IOT_Bemfa_Enable bool //巴法平台开关
IOT_Bemfa_SecretKey string
IOT_Bemfa_Topic string
bemfaClient *bemfa.Device
bemfaClientMsg string
shutDownFunc func(d *WOLDevice) int
}
func (d *WOLDevice) SetShutDownFunc(f func(d *WOLDevice) int) {
d.shutDownFunc = f
}
func (d *WOLDevice) GetDianDengClientState() string {
if d.dianDengClient == nil {
if d.dianDengClientMsg != "" {
return d.dianDengClientMsg
}
return "未设置"
}
if d.dianDengClient.OnLine() {
return "已连接"
}
return "未连接"
}
func (d *WOLDevice) GetBemfaClientState() string {
if d.bemfaClient == nil {
if d.bemfaClientMsg != "" {
return d.bemfaClientMsg
}
return "未设置"
}
if d.bemfaClient.OnLine() {
return "已连接"
}
return "未连接"
}
func (d *WOLDevice) BemfaClientStart() {
if !d.IOT_Bemfa_Enable || d.IOT_Bemfa_SecretKey == "" || d.IOT_Bemfa_Topic == "" {
return
}
bemfaClient, err := bemfa.GetBemfaDevice(d.IOT_Bemfa_SecretKey, httpClientSecureVerify, httpClientTimeout)
if err != nil {
//fmt.Printf("FUCK:%s\n", err.Error())
d.SetBemfaClientMsg(err.Error())
}
if bemfaClient != nil {
d.SetBemfaClientMsg("")
bemfaClient.ResigsterPowerChangeCallbackFunc(d.IOT_Bemfa_Topic, d.GetIdentKey(), d.powerChange)
d.bemfaClient = bemfaClient
}
}
func (d *WOLDevice) BemfaClientStop() {
if d.bemfaClient == nil {
return
}
bemfa.UnRegisterPowerChangeCallback(d.bemfaClient, d.IOT_Bemfa_Topic, d.GetIdentKey())
d.bemfaClient = nil
}
func (d *WOLDevice) SetBemfaClientMsg(msg string) {
d.bemfaClientMsg = msg
}
func (d *WOLDevice) GetBemfaClient() *bemfa.Device {
return d.bemfaClient
}
func (d *WOLDevice) SetBemfaClient(dd *bemfa.Device) {
d.bemfaClient = dd
}
//--------------------------
func (d *WOLDevice) DianDengClientStart() {
if !d.IOT_DianDeng_Enable || d.IOT_DianDeng_AUTHKEY == "" {
return
}
blinkerClient, err := blinker.GetBlinkerDevice(d.IOT_DianDeng_AUTHKEY, httpClientSecureVerify, httpClientTimeout)
if err != nil {
//fmt.Printf("FUCK:%s\n", err.Error())
d.SetDianDengClientMsg(err.Error())
}
if blinkerClient != nil {
d.SetDianDengClientMsg("")
blinkerClient.RegisterPowerChangeCallbackFunc(d.GetIdentKey(), d.powerChange)
d.dianDengClient = blinkerClient
}
}
func (d *WOLDevice) DianDengClientStop() {
if d.dianDengClient == nil {
return
}
blinker.UnRegisterPowerChangeCallback(d.dianDengClient, d.GetIdentKey())
d.dianDengClient = nil
}
func (d *WOLDevice) powerChange(state string) {
log.Printf("WOLDevice 语音助手控制设备[%s]状态:%s\n", d.DeviceName, state)
if state == "on" || state == "true" {
d.WakeUp(nil)
} else {
if d.shutDownFunc != nil {
d.shutDownFunc(d)
}
}
}
func (d *WOLDevice) SetDianDengClientMsg(msg string) {
d.dianDengClientMsg = msg
}
func (d *WOLDevice) GetDianDengClient() *blinker.Device {
return d.dianDengClient
}
func (d *WOLDevice) SetDianDengClient(dd *blinker.Device) {
d.dianDengClient = dd
}
func (d *WOLDevice) GetIdentKey() string {
return fmt.Sprintf("WOL:%s", d.Key)
}
func (d *WOLDevice) WakeUp(finishedCallback func(relay bool, macList []string, broadcastIps []string, port, repeat int)) error {
return WakeOnLan(d.Relay, d.MacList, d.BroadcastIPs, d.Port, d.Repeat, finishedCallback)
}
func WakeOnLan(relay bool, macList []string, broadcastIps []string, port, repeat int,
finishedCallback func(relay bool, macList []string, broadcastIps []string, port, repeat int),
) (err error) {
defer func() {
if finishedCallback != nil {
finishedCallback(relay, macList, broadcastIps, port, repeat)
}
}()
globalBroadcastList := netinterfaces.GetGlobalIPv4BroadcastList()
matchCount := 0
defer func() {
if matchCount <= 0 {
err = fmt.Errorf("找不到匹配的局域网广播IP,未能发送唤醒指令")
}
}()
if len(broadcastIps) > 0 {
for _, bcst := range broadcastIps {
bcstOk := stringsp.StrIsInList(bcst, globalBroadcastList)
if !bcstOk {
continue
}
matchCount++
for _, mac := range macList {
gowol.WakeUpRepeat(mac, bcst, "", port, repeat)
}
}
return
}
for _, bcst := range globalBroadcastList {
matchCount++
for _, mac := range macList {
gowol.WakeUpRepeat(mac, bcst, "", port, repeat)
}
}
return
}

View File

@ -0,0 +1,71 @@
package wolconf
import (
"sync"
"github.com/sirupsen/logrus"
)
type WOLServerConfigure struct {
Enable bool
Token string
}
type WOLClientConfigure struct {
Enable bool //开关
ServerURL string //服务器地址
Token string //验证token
Relay bool //中继唤醒包
Key string
DeviceName string //设备名称
Mac string //网卡物理地址
BroadcastIP string //广播地址
Port int //端口
Repeat int //重复次数
PowerOffCMD string //关机指令
UpdateTime int64 //配置更新时间
}
type WOLServiceConfigure struct {
Server WOLServerConfigure
Client WOLClientConfigure
}
var serviceConfigure *WOLServiceConfigure
var serviceConfigureMu sync.RWMutex
var clientInitFunc func(logger *logrus.Logger, c *WOLClientConfigure)
var clientDisconnectFunc func()
func SetClientInitFunc(f func(logger *logrus.Logger, c *WOLClientConfigure)) {
clientInitFunc = f
}
func SetClientDisconnectFunc(f func()) {
clientDisconnectFunc = f
}
func GetWOLServiceConfigure() WOLServiceConfigure {
serviceConfigureMu.RLock()
defer serviceConfigureMu.RUnlock()
conf := *serviceConfigure
return conf
}
func StoreWOLServiceConfigure(con *WOLServiceConfigure) {
serviceConfigureMu.Lock()
defer serviceConfigureMu.Unlock()
serviceConfigure = con
}
func (c *WOLClientConfigure) Init(logger *logrus.Logger) {
if clientInitFunc != nil {
clientInitFunc(logger, c)
}
}
func (c *WOLClientConfigure) ClientDisconnect() {
if clientDisconnectFunc != nil {
clientDisconnectFunc()
}
}

39
module/wol/ctl.go Normal file
View File

@ -0,0 +1,39 @@
package wol
import (
"encoding/base64"
jsonMsg "github.com/gdy666/lucky/thirdlib/fatedier/golib/json"
"github.com/gdy666/lucky/thirdlib/gdylib/stringsp"
)
type Message = jsonMsg.Message
var (
msgCtl *jsonMsg.MsgCtl
)
var msgkeyBytes = []byte("lucky666")
func init() {
msgCtl = jsonMsg.NewMsgCtl()
for typeByte, msg := range msgTypeMap {
msgCtl.RegisterMsg(typeByte, msg)
}
}
func SendMessageEncryptionFunc(messageBytesPtr []byte) ([]byte, error) {
outs, _ := stringsp.DesEncrypt(messageBytesPtr, msgkeyBytes) //加密
buf := make([]byte, base64.StdEncoding.EncodedLen(len(outs)))
base64.StdEncoding.Encode(buf, outs)
return buf, nil
}
// receiveMessageDecryptionFunc 自定义接收消息解密函数
func ReceiveMessageDecryptionFunc(messageBytes []byte) ([]byte, error) {
rawEncryptMsgBytes, err := base64.StdEncoding.DecodeString(string(messageBytes))
if err != nil {
return nil, err
}
rawMsgBytes, err := stringsp.DesDecrypt(rawEncryptMsgBytes, msgkeyBytes)
return rawMsgBytes, err
}

343
module/wol/device.go Normal file
View File

@ -0,0 +1,343 @@
package wol
import (
"fmt"
"net/url"
"strings"
"time"
"github.com/gdy666/lucky/config"
wolconf "github.com/gdy666/lucky/module/wol/conf"
"github.com/gdy666/lucky/thirdlib/gdylib/stringsp"
websocketcontroller "github.com/gdy666/lucky/thirdlib/gdylib/websocketController"
"github.com/sirupsen/logrus"
)
var logger *logrus.Logger
func WOLClientConfigureInit(l *logrus.Logger) {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
wolconf.StoreWOLServiceConfigure(&config.Configure.WOLServiceConfigure)
wolconf.SetHttpClientSecureVerify(config.Configure.BaseConfigure.HttpClientSecureVerify)
wolconf.SetHttpClientTimeout(config.Configure.BaseConfigure.HttpClientTimeout)
config.Configure.WOLServiceConfigure.Client.Init(l)
logger = l
}
//----------------------------------------
func GetWOLServiceConfigure() wolconf.WOLServiceConfigure {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
conf := config.Configure.WOLServiceConfigure
return conf
}
func AlterWOLClientConfigure(conf *wolconf.WOLServiceConfigure, logger *logrus.Logger, updateTimeOpt bool) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
if conf.Client.Key == "" {
conf.Client.Key = fmt.Sprintf("Client_%s", stringsp.GetRandomString(16))
}
// if programConfigure.WOLClientConfigure.Client != nil {
// go programConfigure.WOLClientConfigure.Client.Disconnect()
// }
if updateTimeOpt {
config.Configure.WOLServiceConfigure.Client.ClientDisconnect()
}
conf.Client.ServerURL = strings.TrimSpace(conf.Client.ServerURL)
conf.Client.ServerURL = handlerWOLServerURL(conf.Client.ServerURL)
conf.Client.Token = strings.TrimSpace(conf.Client.Token)
conf.Client.DeviceName = strings.TrimSpace(conf.Client.DeviceName)
conf.Client.Mac = strings.TrimSpace(conf.Client.Mac)
conf.Client.BroadcastIP = strings.TrimSpace(conf.Client.BroadcastIP)
conf.Client.PowerOffCMD = strings.TrimSpace(conf.Client.PowerOffCMD)
if err := CheckWOLServiceConfigure(conf); err != nil {
return err
}
if updateTimeOpt {
conf.Client.UpdateTime = time.Now().Unix()
}
config.Configure.WOLServiceConfigure = *conf
if updateTimeOpt {
config.Configure.WOLServiceConfigure.Client.Init(logger)
}
wolconf.StoreWOLServiceConfigure(&config.Configure.WOLServiceConfigure)
return config.Save()
}
func handlerWOLServerURL(l string) string {
if !strings.HasPrefix(l, "http") && !strings.HasPrefix(l, "ws") {
l = "ws://" + l
}
u, e := url.Parse(l)
if e != nil {
return ""
}
scheme := ""
switch u.Scheme {
case "http":
scheme = "ws"
case "https":
scheme = "wss"
case "ws":
scheme = "ws"
case "wss":
scheme = "wss"
default:
scheme = "未知协议"
}
l = fmt.Sprintf("%s://%s", scheme, u.Host)
return l
}
func CheckWOLServiceConfigure(conf *wolconf.WOLServiceConfigure) error {
if conf.Client.Enable && strings.TrimSpace(conf.Client.ServerURL) == "" {
return fmt.Errorf("客户端设置 服务端地址不能为空")
}
if conf.Client.Enable && strings.TrimSpace(conf.Client.Token) == "" {
return fmt.Errorf("客户端设置 Token不能为空")
}
if conf.Client.Enable && strings.TrimSpace(conf.Client.PowerOffCMD) == "" {
return fmt.Errorf("客户端设置 关机指令不能为空")
}
//广播地址,设备名称,关机指令可以为空
// if strings.TrimSpace(conf.DeviceName) == "" {
// return fmt.Errorf("设备名称不能为空")
// }
if conf.Client.Enable && strings.TrimSpace(conf.Client.Mac) == "" {
return fmt.Errorf("客户端设置 物理网卡地址不能为空")
}
if conf.Server.Enable && strings.TrimSpace(conf.Server.Token) == "" {
return fmt.Errorf("服务端设置 Token不能为空")
}
return nil
}
func GetWOLDeviceByKey(key string) *wolconf.WOLDevice {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
index := -1
for i := range config.Configure.WOLDeviceList {
if config.Configure.WOLDeviceList[i].Key == key {
index = i
break
}
}
if index < 0 {
return nil
}
device := config.Configure.WOLDeviceList[index]
return &device
}
func GetWOLDeviceByMac(mac string) *wolconf.WOLDevice {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
index := -1
for i := range config.Configure.WOLDeviceList {
if config.Configure.WOLDeviceList[i].MacList[0] == mac && len(config.Configure.WOLDeviceList[i].MacList) == 1 {
index = i
break
}
}
if index < 0 {
return nil
}
device := config.Configure.WOLDeviceList[index]
return &device
}
func GetWOLDeviceList() []wolconf.WOLDevice {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
var res []wolconf.WOLDevice
if config.Configure == nil {
return res
}
for i := range config.Configure.WOLDeviceList {
res = append(res, config.Configure.WOLDeviceList[i])
}
return res
}
func GetDeviceStateDetail(d *wolconf.WOLDevice) (state string, onlineMacList []string) {
// clientControllerMap.Range(func(key any, val any) bool {
// fmt.Printf("%v %v\n", key, val)
// return true
// })
if strings.HasPrefix(d.Key, "Client_") {
c, ok := clientControllerMap.Load(d.Key)
if ok {
deviceName := "未设置主机名"
dn, ok := c.(*websocketcontroller.Controller).GetExtData("deviceName")
if ok {
deviceName = dn.(string)
}
remoteAddr := strings.Split(c.(*websocketcontroller.Controller).GetRemoteAddr(), ":")[0]
onlineMacList = append(onlineMacList, fmt.Sprintf("主机名:%s [ %s ] 网卡物理地址:%s", deviceName, remoteAddr, d.MacList[0]))
state = "在线"
} else {
state = "离线"
}
return
}
for i := range d.MacList {
c, ok := clientControllerMap.Load(d.MacList[i])
if ok {
deviceName := "未设置主机名"
dn, ok := c.(*websocketcontroller.Controller).GetExtData("deviceName")
if ok {
deviceName = dn.(string)
}
remoteAddr := strings.Split(c.(*websocketcontroller.Controller).GetRemoteAddr(), ":")[0]
onlineMacList = append(onlineMacList, fmt.Sprintf("主机名:%s [ %s ] 网卡物理地址:%s", deviceName, remoteAddr, d.MacList[i]))
}
}
if len(onlineMacList) == 0 { //离线
// if len(d.MacList) == 1 {
// state = "离线"
// } else {
// state = "全部离线"
// }
state = "未知"
return
}
if len(onlineMacList) == len(d.MacList) {
if len(d.MacList) == 1 {
state = "在线"
} else {
state = "全部在线"
}
return
}
state = "部分在线"
return
}
func WOLDeviceListAdd(d *wolconf.WOLDevice) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
if d.Key == "" {
d.Key = stringsp.GetRandomString(8)
}
if d.UpdateTime == 0 {
d.UpdateTime = time.Now().Unix()
}
d.SetShutDownFunc(ExecShutDown)
config.Configure.WOLDeviceList = append(config.Configure.WOLDeviceList, *d)
listLength := len(config.Configure.WOLDeviceList)
go config.Configure.WOLDeviceList[listLength-1].DianDengClientStart()
go config.Configure.WOLDeviceList[listLength-1].BemfaClientStart()
return config.Save()
}
func WOLDeviceListAlter(d *wolconf.WOLDevice) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
index := -1
for i := range config.Configure.WOLDeviceList {
if config.Configure.WOLDeviceList[i].Key == d.Key {
index = i
break
}
}
if index < 0 {
return fmt.Errorf("key:%s 不存在", d.Key)
}
d.UpdateTime = time.Now().Unix()
config.Configure.WOLDeviceList[index].DianDengClientStop()
config.Configure.WOLDeviceList[index].BemfaClientStop()
//go d.DianDengClientStart()
d.SetShutDownFunc(ExecShutDown)
config.Configure.WOLDeviceList[index] = *d
go config.Configure.WOLDeviceList[index].DianDengClientStart()
go config.Configure.WOLDeviceList[index].BemfaClientStart()
return config.Save()
}
func WOLDeviceListReplace(key string, d *wolconf.WOLDevice) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
index := -1
for i := range config.Configure.WOLDeviceList {
if config.Configure.WOLDeviceList[i].Key == key {
index = i
break
}
}
if index < 0 {
return fmt.Errorf("key:%s 不存在", d.Key)
}
config.Configure.WOLDeviceList[index] = *d
return config.Save()
}
func WOLDeviceListDelete(key string) error {
config.ConfigureMutex.Lock()
defer config.ConfigureMutex.Unlock()
deleteIndex := -1
for i := range config.Configure.WOLDeviceList {
if config.Configure.WOLDeviceList[i].Key == key {
deleteIndex = i
break
}
}
if deleteIndex < 0 {
return fmt.Errorf("key:%s 不存在", key)
}
config.Configure.WOLDeviceList[deleteIndex].DianDengClientStop()
config.Configure.WOLDeviceList[deleteIndex].BemfaClientStop()
config.Configure.WOLDeviceList = DeleteWOLDeviceListslice(config.Configure.WOLDeviceList, deleteIndex)
return config.Save()
}
func DeleteWOLDeviceListslice(a []wolconf.WOLDevice, deleteIndex int) []wolconf.WOLDevice {
j := 0
for i := range a {
if i != deleteIndex {
a[j] = a[i]
j++
}
}
return a[:j]
}

269
module/wol/httpapi/api.go Normal file
View File

@ -0,0 +1,269 @@
package wolhttpapi
import (
"fmt"
"log"
"net/http"
"strings"
"github.com/gdy666/lucky/module/service"
"github.com/gdy666/lucky/module/wol"
wolconf "github.com/gdy666/lucky/module/wol/conf"
"github.com/gdy666/lucky/thirdlib/gdylib/netinterfaces"
websocketcontroller "github.com/gdy666/lucky/thirdlib/gdylib/websocketController"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/sirupsen/logrus"
)
var logger *logrus.Logger
func SetLogger(l *logrus.Logger) {
logger = l
}
// RegisterAPI 注册相关API
func RegisterAPI(r *gin.Engine, authorized *gin.RouterGroup) {
authorized.POST("/api/wol/device", AddWOLDevice)
authorized.GET("/api/wol/device/wakeup", WOLDeviceWakeUp)
authorized.GET("/api/wol/device/shutdown", WOLDeviceShutDown)
authorized.GET("/api/wol/devices", GetWOLDeviceList)
authorized.PUT("/api/wol/device", AlterWOLDevice)
authorized.DELETE("/api/wol/device", DeleteWOLDevice)
authorized.GET("/api/wol/service/configure", WOLServiceConfigure)
authorized.PUT("/api/wol/service/configure", WOLAlterServiceConfigure)
authorized.GET("api/wol/service/getipv4interface", GetIPv4Interface)
r.GET("/api/wol/service", WOLWebsocketHandler)
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 4096 * 1,
WriteBufferSize: 4096 * 1,
CheckOrigin: func(r *http.Request) bool { //允许跨域
return true
},
}
func AddWOLDevice(c *gin.Context) {
var requestObj wolconf.WOLDevice
err := c.BindJSON(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "请求解析出错"})
return
}
err = checkWolDevice(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": fmt.Sprintf("添加网络唤醒设备出错:%s", err.Error())})
return
}
err = wol.WOLDeviceListAdd(&requestObj)
if err != nil {
log.Printf("config.WOLDeviceListAdd error:%s", err.Error())
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": fmt.Sprintf("添加网络唤醒设备出错!:%s", err.Error())})
return
}
c.JSON(http.StatusOK, gin.H{"ret": 0})
}
type DeviceInfo struct {
wolconf.WOLDevice
State string
OnlineMacList []string
DianDengClientState string
BemfaClientState string
}
func GetWOLDeviceList(c *gin.Context) {
rawlist := wol.GetWOLDeviceList()
var list []DeviceInfo
for i := range rawlist {
state, onlineMacList := wol.GetDeviceStateDetail(&rawlist[i])
d := DeviceInfo{}
d.WOLDevice = rawlist[i]
d.State = state
d.OnlineMacList = onlineMacList
d.DianDengClientState = rawlist[i].GetDianDengClientState()
d.BemfaClientState = rawlist[i].GetBemfaClientState()
list = append(list, d)
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "list": list})
}
func AlterWOLDevice(c *gin.Context) {
var requestObj wolconf.WOLDevice
err := c.BindJSON(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "请求解析出错"})
return
}
err = checkWolDevice(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": fmt.Sprintf("修改网络唤醒设备出错:%s", err.Error())})
return
}
err = wol.WOLDeviceListAlter(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("修改网络唤醒设备配置失败:%s", err.Error())})
return
}
wol.SyncClientConfigureToClient(&requestObj)
c.JSON(http.StatusOK, gin.H{"ret": 0})
}
func DeleteWOLDevice(c *gin.Context) {
key := c.Query("key")
err := wol.WOLDeviceListDelete(key)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("删除网络唤醒设备失败:%s", err.Error())})
return
}
c.JSON(http.StatusOK, gin.H{"ret": 0})
}
func WOLDeviceWakeUp(c *gin.Context) {
key := c.Query("key")
device := wol.GetWOLDeviceByKey(key)
if device == nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "找不到Key对应的设备,唤醒失败"})
return
}
err := device.WakeUp(wol.WakeUpFinishedCallback)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": "唤醒失败"})
return
}
c.JSON(http.StatusOK, gin.H{"ret": 0})
}
func WOLDeviceShutDown(c *gin.Context) {
key := c.Query("key")
device := wol.GetWOLDeviceByKey(key)
if device == nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "找不到Key对应的设备,发送执行关机指令失败"})
return
}
count := wol.ExecShutDown(device)
if count == 0 {
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": "没有设备在线,未能发送关机指令"})
return
}
c.SecureJSON(http.StatusOK, gin.H{"ret": 0, "successCount": count})
}
func checkWolDevice(d *wolconf.WOLDevice) error {
if len(d.MacList) <= 0 {
return fmt.Errorf("网卡MAC不能为空")
}
if strings.HasPrefix(d.Key, "Client_") {
if len(d.MacList) > 1 {
return fmt.Errorf("与客户端关联的设备(由客户端连接同步)只能填写一个mac")
}
if len(d.BroadcastIPs) > 1 {
return fmt.Errorf("与客户端关联的设备(由客户端连接同步)只能填写一个广播地址")
}
}
if d.IOT_Bemfa_Enable {
if strings.TrimSpace(d.IOT_Bemfa_SecretKey) == "" {
return fmt.Errorf("巴法云私钥不能为空")
}
if strings.TrimSpace(d.IOT_Bemfa_Topic) == "" {
return fmt.Errorf("巴法云主题不能为空")
}
if !strings.HasSuffix(d.IOT_Bemfa_Topic, "001") {
return fmt.Errorf("巴法云主题需要以001结尾,表示插座类型")
}
}
if d.IOT_DianDeng_Enable {
if strings.TrimSpace(d.IOT_DianDeng_AUTHKEY) == "" {
return fmt.Errorf("点灯科技设备密钥不能为空")
}
}
if d.Port <= 0 || d.Port > 065535 {
d.Port = 9
}
if d.Repeat <= 0 || d.Repeat > 10 {
d.Repeat = 5
}
return nil
}
func WOLServiceConfigure(c *gin.Context) {
conf := wol.GetWOLServiceConfigure()
c.JSON(http.StatusOK, gin.H{
"ret": 0,
"configure": conf,
"ClientState": wol.GetClientState(),
"ClientStateMsg": wol.GetClientStateMsg(),
"serviceStatus": service.GetServiceState()})
}
func GetIPv4Interface(c *gin.Context) {
interfacceList, err := netinterfaces.GetIPv4NetInterfaceInfoList()
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("获取IPv4网络信息列表出错:%s", err.Error())})
return
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "list": interfacceList})
}
func WOLAlterServiceConfigure(c *gin.Context) {
var conf wolconf.WOLServiceConfigure
err := c.BindJSON(&conf)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "请求解析出错"})
return
}
err = wol.AlterWOLClientConfigure(&conf, logger, true)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": fmt.Sprintf("保存配置出错:%s", err.Error())})
return
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "configure": conf})
}
func WOLWebsocketHandler(c *gin.Context) {
if !wol.GetWOLServiceConfigure().Server.Enable {
c.Abort()
return
}
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
logger.Errorf("WOLWebsocketHandler upgrader.Upgrade :error:%s", err.Error())
c.Abort()
return
}
wsc := new(websocketcontroller.Controller)
wsc.Logs = logger
wsc.ReceiveMessageCallback = wol.ReceiveMsgFromWOLClient
wsc.ClientDisconnectedCallback = wol.WOLClientDisconnect
wsc.ClientReadyCallback = wol.WOLClientConnected
wsc.SetSendMessageEncryptionFunc(wol.SendMessageEncryptionFunc)
wsc.SetReceiveMessageDecryptionFunc(wol.ReceiveMessageDecryptionFunc)
wsc.ConnectReady(conn)
}

23
module/wol/module.go Normal file
View File

@ -0,0 +1,23 @@
package wol
import (
"github.com/gdy666/lucky/config"
"github.com/sirupsen/logrus"
)
func Init(log *logrus.Logger) {
WOLClientConfigureInit(log)
deviceInit(log)
}
// deviceInit 暂时用于第三方物联网平台部分初始化
func deviceInit(log *logrus.Logger) {
config.ConfigureMutex.RLock()
defer config.ConfigureMutex.RUnlock()
for i := range config.Configure.WOLDeviceList {
config.Configure.WOLDeviceList[i].SetShutDownFunc(ExecShutDown)
go config.Configure.WOLDeviceList[i].DianDengClientStart()
go config.Configure.WOLDeviceList[i].BemfaClientStart()
}
}

48
module/wol/msg.go Normal file
View File

@ -0,0 +1,48 @@
package wol
import (
wolconf "github.com/gdy666/lucky/module/wol/conf"
)
const (
TypeLogin = '0'
TypeLoginResp = '1'
TypeSyncClientConfigure = '2'
TypeReplyWakeUp = '3'
TypeShutDown = '4'
)
var (
msgTypeMap = map[byte]interface{}{
TypeLogin: Login{},
TypeLoginResp: LoginResp{},
TypeSyncClientConfigure: SyncClientConfigure{},
TypeReplyWakeUp: ReplyWakeUp{},
TypeShutDown: ShutDown{},
}
)
type Login struct {
wolconf.WOLClientConfigure
ClientTimeStamp int64
}
// 服务器发送给客户端,登录认证反馈 ,ret 为0成功,其它失败
type LoginResp struct {
Ret int //
Msg string
}
type SyncClientConfigure struct {
wolconf.WOLClientConfigure
}
type ReplyWakeUp struct {
MacList []string
BroadcastIPs []string
Port int
Repeat int
}
type ShutDown struct {
}

214
module/wol/service.go Normal file
View File

@ -0,0 +1,214 @@
package wol
import (
"fmt"
"sync"
"time"
wolconf "github.com/gdy666/lucky/module/wol/conf"
websocketcontroller "github.com/gdy666/lucky/thirdlib/gdylib/websocketController"
)
var clientControllerMap sync.Map
func ReceiveMsgFromWOLClient(c *websocketcontroller.Controller, msgBytes []byte) {
rawMsg, err := UnPack(msgBytes)
if err != nil {
return
}
switch m := rawMsg.(type) {
case *Login:
go hanlderWOLDeviceClientLogin(m, c)
default:
fmt.Printf("未知消息类型\n")
}
}
func SyncClientConfigureToClient(d *wolconf.WOLDevice) {
wc, wcOk := clientControllerMap.Load(d.Key)
if !wcOk {
return
}
syncMsg := &SyncClientConfigure{}
syncMsg.DeviceName = d.DeviceName
syncMsg.BroadcastIP = d.BroadcastIPs[0]
syncMsg.Mac = d.MacList[0]
syncMsg.Port = d.Port
syncMsg.Relay = d.Relay
syncMsg.Repeat = d.Repeat
syncMsg.UpdateTime = d.UpdateTime
SendMessage(wc.(*websocketcontroller.Controller), syncMsg)
}
func WOLClientDisconnect(c *websocketcontroller.Controller) {
mac, macOk := c.GetExtData("mac")
if macOk {
clientControllerMap.Delete(mac)
}
key, keyOk := c.GetExtData("key")
if keyOk {
clientControllerMap.Delete(key)
}
}
func WOLClientConnected(c *websocketcontroller.Controller) {
//fmt.Printf("WOLClientConnected \n")
}
func WakeUpFinishedCallback(reply bool, macList []string, broadcastIps []string, port, repeat int) {
if !reply {
return
}
msg := &ReplyWakeUp{MacList: macList, BroadcastIPs: broadcastIps, Port: port, Repeat: repeat}
devicesList := GetWOLDeviceList()
for _, d := range devicesList {
if !d.Relay {
continue
}
wc, wcOk := clientControllerMap.Load(d.Key)
if !wcOk {
continue
}
SendMessage(wc.(*websocketcontroller.Controller), msg)
}
}
func hanlderWOLDeviceClientLogin(m *Login, c *websocketcontroller.Controller) {
msg := &LoginResp{}
defer func() {
if msg.Ret != 0 {
return
}
c.StoreExtData("mac", m.Mac)
c.StoreExtData("key", m.Key)
c.StoreExtData("deviceName", m.DeviceName)
clientControllerMap.Store(m.Key, c)
clientControllerMap.Store(m.Mac, c)
}()
nowTimeStamp := time.Now().Unix()
if nowTimeStamp-m.ClientTimeStamp > 30 || m.ClientTimeStamp-nowTimeStamp > 30 {
msg.Msg = "客户端和服务器端时间相差大于30秒,请先校对时间"
msg.Ret = 1
SendMessage(c, msg)
return
}
if m.Token != GetWOLServiceConfigure().Server.Token {
msg.Msg = "Token不匹配"
msg.Ret = 1
SendMessage(c, msg)
return
}
d := GetWOLDeviceByKey(m.Key)
if d == nil { //根据Key找不到记录
d = GetWOLDeviceByMac(m.Mac)
dev := &wolconf.WOLDevice{
Key: m.Key,
DeviceName: m.DeviceName,
MacList: []string{m.Mac},
BroadcastIPs: []string{m.BroadcastIP},
Port: m.Port,
Relay: m.Relay,
Repeat: m.Repeat,
UpdateTime: m.UpdateTime,
}
if d == nil { //创建记录
err := WOLDeviceListAdd(dev)
if err == nil {
msg.Ret = 0
SendMessage(c, msg)
} else {
msg.Msg = fmt.Sprintf("添加唤醒设备记录出错:%s", err.Error())
msg.Ret = 2
SendMessage(c, msg)
}
} else { //修改相同Mac记录的设备未
fmt.Printf("修改相同Mac记录的设备\n")
err := WOLDeviceListReplace(d.Key, dev)
if err == nil {
msg.Ret = 0
SendMessage(c, msg)
} else {
msg.Msg = fmt.Sprintf("替换唤醒设备记录出错:%s", err.Error())
msg.Ret = 3
SendMessage(c, msg)
}
}
return
}
if d.UpdateTime == m.UpdateTime {
//fmt.Printf("两端规则更新时间一致,不用同步\n")
msg.Ret = 0
SendMessage(c, msg)
return
}
//fmt.Printf("d.UpdateTime:%d m.UpdateTime:%d\n", d.UpdateTime, m.UpdateTime)
if d.UpdateTime > m.UpdateTime {
msg.Ret = 0
SendMessage(c, msg)
//fmt.Printf("服务端配置较新,同步至客户端\n")
syncMsg := &SyncClientConfigure{m.WOLClientConfigure}
syncMsg.DeviceName = d.DeviceName
syncMsg.BroadcastIP = d.BroadcastIPs[0]
syncMsg.Mac = d.MacList[0]
syncMsg.Port = d.Port
syncMsg.Relay = d.Relay
syncMsg.Repeat = d.Repeat
syncMsg.UpdateTime = d.UpdateTime
SendMessage(c, syncMsg)
} else {
//fmt.Printf("客户端配置较新,同步至服务器端")
dev := &wolconf.WOLDevice{
Key: m.Key,
DeviceName: m.DeviceName,
MacList: []string{m.Mac},
BroadcastIPs: []string{m.BroadcastIP},
Port: m.Port,
Relay: m.Relay,
Repeat: m.Repeat,
UpdateTime: m.UpdateTime,
IOT_DianDeng_Enable: d.IOT_Bemfa_Enable,
IOT_DianDeng_AUTHKEY: d.IOT_DianDeng_AUTHKEY,
IOT_Bemfa_Enable: d.IOT_Bemfa_Enable,
IOT_Bemfa_SecretKey: d.IOT_Bemfa_SecretKey,
IOT_Bemfa_Topic: d.IOT_Bemfa_Topic,
}
err := WOLDeviceListAlter(dev)
if err == nil {
msg.Ret = 0
SendMessage(c, msg)
} else {
msg.Ret = 3
msg.Msg = fmt.Sprintf("同步客户端配置到服务端出错:%s", err.Error())
}
}
}
func ExecShutDown(d *wolconf.WOLDevice) int {
successCount := 0
msg := &ShutDown{}
for _, mac := range d.MacList {
cc, ccOk := clientControllerMap.Load(mac)
if !ccOk {
continue
}
SendMessage(cc.(*websocketcontroller.Controller), msg)
successCount++
}
return successCount
}

View File

@ -0,0 +1,29 @@
package wol
import (
"fmt"
websocketcontroller "github.com/gdy666/lucky/thirdlib/gdylib/websocketController"
)
func SendMessage(c *websocketcontroller.Controller, msg any) error {
msgBytes, err := Pack(msg)
if err != nil {
fmt.Printf("pack FUck:%s\n", err.Error())
return err
}
c.SendMessage(msgBytes)
return nil
}
func Pack(msg interface{}) ([]byte, error) {
return msgCtl.Pack(msg)
}
func UnPack(bytes []byte) (msg Message, err error) {
if len(bytes) <= 9 {
err = fmt.Errorf("len(bytes) <= 9")
return
}
return msgCtl.UnPack(bytes[0], bytes[9:])
}

View File

@ -0,0 +1,50 @@
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package json
import (
"reflect"
)
var (
defaultMaxMsgLength int64 = 10240
)
// Message wraps socket packages for communicating between frpc and frps.
type Message interface{}
type MsgCtl struct {
typeMap map[byte]reflect.Type
typeByteMap map[reflect.Type]byte
maxMsgLength int64
}
func NewMsgCtl() *MsgCtl {
return &MsgCtl{
typeMap: make(map[byte]reflect.Type),
typeByteMap: make(map[reflect.Type]byte),
maxMsgLength: defaultMaxMsgLength,
}
}
func (msgCtl *MsgCtl) RegisterMsg(typeByte byte, msg interface{}) {
msgCtl.typeMap[typeByte] = reflect.TypeOf(msg)
msgCtl.typeByteMap[reflect.TypeOf(msg)] = typeByte
}
func (msgCtl *MsgCtl) SetMaxMsgLength(length int64) {
msgCtl.maxMsgLength = length
}

View File

@ -0,0 +1,66 @@
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package json
import (
"bytes"
"encoding/binary"
"encoding/json"
"reflect"
)
func (msgCtl *MsgCtl) unpack(typeByte byte, buffer []byte, msgIn Message) (msg Message, err error) {
if msgIn == nil {
t, ok := msgCtl.typeMap[typeByte]
if !ok {
err = ErrMsgType
return
}
msg = reflect.New(t).Interface().(Message)
} else {
msg = msgIn
}
err = json.Unmarshal(buffer, &msg)
return
}
func (msgCtl *MsgCtl) UnPackInto(buffer []byte, msg Message) (err error) {
_, err = msgCtl.unpack(' ', buffer, msg)
return
}
func (msgCtl *MsgCtl) UnPack(typeByte byte, buffer []byte) (msg Message, err error) {
return msgCtl.unpack(typeByte, buffer, nil)
}
func (msgCtl *MsgCtl) Pack(msg Message) ([]byte, error) {
typeByte, ok := msgCtl.typeByteMap[reflect.TypeOf(msg).Elem()]
if !ok {
return nil, ErrMsgType
}
content, err := json.Marshal(msg)
if err != nil {
return nil, err
}
buffer := bytes.NewBuffer(nil)
buffer.WriteByte(typeByte)
binary.Write(buffer, binary.BigEndian, int64(len(content)))
buffer.Write(content)
return buffer.Bytes(), nil
}

View File

@ -0,0 +1,93 @@
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package json
import (
"encoding/binary"
"errors"
"io"
)
var (
ErrMsgType = errors.New("message type error")
ErrMaxMsgLength = errors.New("message length exceed the limit")
ErrMsgLength = errors.New("message length error")
ErrMsgFormat = errors.New("message format error")
)
func (msgCtl *MsgCtl) readMsg(c io.Reader) (typeByte byte, buffer []byte, err error) {
buffer = make([]byte, 1)
_, err = c.Read(buffer)
if err != nil {
return
}
typeByte = buffer[0]
if _, ok := msgCtl.typeMap[typeByte]; !ok {
err = ErrMsgType
return
}
var length int64
err = binary.Read(c, binary.BigEndian, &length)
if err != nil {
return
}
if length > msgCtl.maxMsgLength {
err = ErrMaxMsgLength
return
} else if length < 0 {
err = ErrMsgLength
return
}
buffer = make([]byte, length)
n, err := io.ReadFull(c, buffer)
if err != nil {
return
}
if int64(n) != length {
err = ErrMsgFormat
}
return
}
func (msgCtl *MsgCtl) ReadMsg(c io.Reader) (msg Message, err error) {
typeByte, buffer, err := msgCtl.readMsg(c)
if err != nil {
return
}
return msgCtl.UnPack(typeByte, buffer)
}
func (msgCtl *MsgCtl) ReadMsgInto(c io.Reader, msg Message) (err error) {
_, buffer, err := msgCtl.readMsg(c)
if err != nil {
return
}
return msgCtl.UnPackInto(buffer, msg)
}
func (msgCtl *MsgCtl) WriteMsg(c io.Writer, msg interface{}) (err error) {
buffer, err := msgCtl.Pack(msg)
if err != nil {
return
}
if _, err = c.Write(buffer); err != nil {
return
}
return nil
}

View File

@ -0,0 +1,191 @@
package bemfa
import (
"crypto/tls"
"fmt"
"log"
"sync"
"sync/atomic"
"time"
MQTT "github.com/eclipse/paho.mqtt.golang"
)
const (
bemfaHost = "bemfa.com"
//mqttPort = 9501
mqttsPort = 9503
)
const (
Disconnected uint32 = iota
Connecting
Reconnecting
Connected
)
type Device struct {
linkState uint32
secretKey string
extStroe sync.Map
secureVerify bool
httpClientTimeout int
powerChangeCallbackMap map[string]map[string]func(string)
mu *sync.Mutex
client MQTT.Client
//clientMu sync.Mutex
}
func CreateDevice(secretKey string, secureVerify bool, httpClientTimeout int) *Device {
d := &Device{secretKey: secretKey, secureVerify: secureVerify, httpClientTimeout: httpClientTimeout}
d.powerChangeCallbackMap = make(map[string]map[string]func(string))
d.mu = &sync.Mutex{}
return d
}
func (d *Device) OnLine() bool {
state := atomic.LoadUint32(&d.linkState)
return state == Connected
}
func (d *Device) IsDisconnected() bool {
state := atomic.LoadUint32(&d.linkState)
return state == Disconnected
}
func (d *Device) ResigsterPowerChangeCallbackFunc(subTopic, key string, f func(string)) error {
d.mu.Lock()
defer d.mu.Unlock()
if d.client == nil {
return fmt.Errorf("client == nil")
}
if _, ok := d.powerChangeCallbackMap[subTopic]; ok { //已订阅
d.powerChangeCallbackMap[subTopic][key] = f
} else {
d.powerChangeCallbackMap[subTopic] = make(map[string]func(string))
d.powerChangeCallbackMap[subTopic][key] = f
//fmt.Printf("订阅主题")
d.client.Subscribe(subTopic, 1, d.ReceiveMessageHandler)
}
return nil
}
func (d *Device) UnRegisterPowerChangeCallbackFunc(subTopic, key string) {
d.mu.Lock()
defer d.mu.Unlock()
if _, ok := d.powerChangeCallbackMap[subTopic]; !ok { //已订阅
return
}
delete(d.powerChangeCallbackMap[subTopic], key)
if len(d.powerChangeCallbackMap[subTopic]) <= 0 {
delete(d.powerChangeCallbackMap, subTopic)
}
}
func (d *Device) StoreExtData(key any, val any) {
d.extStroe.Store(key, val)
}
func (d *Device) GetExtData(key any) (val any, ok bool) {
val, ok = d.extStroe.Load(key)
return
}
func (d *Device) Login() error {
opts := MQTT.NewClientOptions()
brokeyURL := fmt.Sprintf("mqtts://%s:%d", bemfaHost, mqttsPort)
opts.AddBroker(brokeyURL)
opts.SetClientID(d.secretKey)
opts.SetUsername("")
opts.SetPassword("")
opts.SetConnectTimeout(time.Second * 2)
opts.SetKeepAlive(time.Second * 30)
opts.SetTLSConfig(&tls.Config{InsecureSkipVerify: !d.secureVerify})
opts.SetAutoReconnect(true)
opts.ConnectRetryInterval = time.Second * 3
opts.SetOnConnectHandler(func(c MQTT.Client) {
log.Printf("巴法云 [%s]已连接\n", d.secretKey)
d.mu.Lock()
d.client = c
d.mu.Unlock()
atomic.StoreUint32(&d.linkState, Connected)
d.SubTopicOption()
})
opts.OnConnectionLost = func(c MQTT.Client, err error) {
log.Printf("巴法云 [%s]连接丢失:%s\n", d.secretKey, err.Error())
atomic.StoreUint32(&d.linkState, Disconnected)
}
opts.SetReconnectingHandler(func(c MQTT.Client, opt *MQTT.ClientOptions) {
atomic.StoreUint32(&d.linkState, Reconnecting)
})
client := MQTT.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
return fmt.Errorf("连接出错:%s", token.Error())
}
return nil
}
func (d *Device) Stop() {
d.closeMQTTClient()
}
func (d *Device) closeMQTTClient() {
d.mu.Lock()
defer d.mu.Unlock()
if d.client == nil {
return
}
d.client.Disconnect(0)
log.Printf("巴法云 [%s]主动关闭连接", d.secretKey)
d.client = nil
}
func (d *Device) SubTopicOption() {
d.mu.Lock()
defer d.mu.Unlock()
if d.client == nil {
return
}
topicList := []string{}
for k := range d.powerChangeCallbackMap {
topicList = append(topicList, k)
}
for _, t := range topicList {
d.client.Subscribe(t, 1, d.ReceiveMessageHandler)
}
}
func (d *Device) ReceiveMessageHandler(c MQTT.Client, m MQTT.Message) {
// fmt.Printf("接收到MQTT消息:\n[【%s】\n%s\n\n", m.Topic(), m.Payload())
// switch string(m.Payload()) {
// case "on":
// c.Publish("switch001/up", 1, true, m.Payload())
// case "off":
// c.Publish("switch001/up", 1, true, m.Payload())
// default:
// }
c.Publish(fmt.Sprintf("%s/up", m.Topic()), 1, true, m.Payload())
go d.handlerReceivemessage(m.Topic(), string(m.Payload()))
}
func (d *Device) handlerReceivemessage(topic, msg string) {
d.mu.Lock()
defer d.mu.Unlock()
if _, ok := d.powerChangeCallbackMap[topic]; !ok {
return
}
for _, f := range d.powerChangeCallbackMap[topic] {
f(msg)
}
}

View File

@ -0,0 +1,93 @@
package bemfa
import (
"fmt"
"sync"
"time"
)
var bemfaStore sync.Map
var bemfaStroeMu sync.Mutex
func GetBemfaDevice(secretKey string, httpClientSecureVerify bool, httpClientTimeout int) (*Device, error) {
bemfaStroeMu.Lock()
defer bemfaStroeMu.Unlock()
device, deviceOk := bemfaStore.Load(secretKey)
if deviceOk {
d := device.(*Device)
if d.OnLine() {
return d, nil
}
if d.IsDisconnected() {
d.Stop()
err := d.Login()
if err != nil {
return nil, fmt.Errorf("bemfa Device login error:%s", err.Error())
}
i := 0
for {
<-time.After(time.Second * 200)
i++
if d.OnLine() {
break
}
if i > 26 {
break
}
}
if d.OnLine() {
return d, nil
}
return nil, fmt.Errorf("blinker drvice 连接服务器失败")
}
return device.(*Device), nil
}
d := CreateDevice(secretKey, httpClientSecureVerify, httpClientTimeout)
err := d.Login()
if err != nil {
return nil, fmt.Errorf("bemfa Device Login error:%s", err.Error())
}
i := 0
for {
<-time.After(time.Millisecond * 100)
i++
if d.OnLine() {
break
}
if i > 51 {
break
}
}
//fmt.Printf("在线\n")
if d.OnLine() {
bemfaStore.Store(secretKey, d)
return d, nil
}
return nil, fmt.Errorf("bemfa drvice 连接服务器失败")
}
func UnRegisterPowerChangeCallback(d *Device, topic, key string) {
bemfaStroeMu.Lock()
defer bemfaStroeMu.Unlock()
d.UnRegisterPowerChangeCallbackFunc(topic, key)
if len(d.powerChangeCallbackMap) != 0 {
return
}
d.Stop()
bemfaStore.Delete(d.secretKey)
}

View File

@ -14,7 +14,7 @@ const (
type VoiceAssistant struct {
DeviceType string //语言助手类型 (设备类型).
VAType string //语言助手类型 MIOT AliGenie DuerOS
Device *BlinkerDevice
Device *Device
topic string
}
@ -31,42 +31,34 @@ func (v *VoiceAssistant) GetSKey() string {
}
}
func (v *VoiceAssistant) PowerChangeReply(msgid, st string) {
state := "off"
if st == "true" {
func (v *VoiceAssistant) PowerChangeReply(msgid, state string) {
if state == "true" {
state = "on"
} else if state == "false" {
state = "off"
}
// if v.VAType == "MIOT" {
// if state == "on" {
// state = "true"
// } else {
// state = "false"
// }
// }
data := map[string]string{"pState": state}
data := map[string]string{"pState": state, "messageId": msgid}
v.Device.SendMessage("vAssistant", v.GetToDevice(), msgid, data)
}
func (v *VoiceAssistant) QueryDeviceState(msgid string) {
state := v.Device.state
// if v.VAType == "MIOT" {
// if state == "on" {
// state = "true"
// } else {
// state = "false"
// }
// }
data := map[string]string{"pState": state}
state := v.Device.GetState()
var stateStr = ""
if state {
stateStr = "on"
} else {
stateStr = "off"
}
data := map[string]string{"pState": stateStr, "messageId": msgid}
v.Device.SendMessage("vAssistant", v.GetToDevice(), msgid, data)
}
func (v *VoiceAssistant) GetToDevice() string {
// if v.Device.DetailInfo.Broker == "blinker" {
// return "ServerReceiver"
// }
if v.Device.DetailInfo.Broker == "blinker" {
return "ServerReceiver"
}
return v.topic
}

View File

@ -2,7 +2,7 @@ package blinker
import (
"compress/gzip"
"encoding/base64"
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
@ -10,12 +10,25 @@ import (
"net/http"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/buger/jsonparser"
MQTT "github.com/eclipse/paho.mqtt.golang"
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
)
const (
Disconnected uint32 = iota
Connecting
Reconnecting
Connected
)
func init() {
}
const (
HOST = "https://iot.diandeng.tech"
API_AUTH = HOST + "/api/v1/user/device/diy/auth"
@ -23,24 +36,30 @@ const (
API_VOICE_ASSISTANT = HOST + "/api/v1/user/device/voice_assistant"
)
type BlinkerDevice struct {
authKey string
type Device struct {
linkState uint32
authKey string
subTopic string
pubTopic string
exasubTopic string //aliyun特有
exapubTopic string //aliyun特有
client MQTT.Client
clientMu sync.Mutex
DetailInfo BlinkerDetailInfo
heartBeatChan chan uint8
hbmu sync.Mutex
preSendTime time.Time
sendMsgChan chan message
//
state string
//sendMsgChan chan message
voiceAssistants map[string]*VoiceAssistant
extStroe sync.Map
powerChangeCallbackMap sync.Map
state bool
queryStateFunc func() bool
voiceAssistants map[string]*VoiceAssistant
httpClientSecureVerify bool
httpClientTimeout int
httpclient *http.Client
}
type message struct {
@ -50,19 +69,54 @@ type message struct {
Msg any
}
func CreateBlinkerDevice(ak string) *BlinkerDevice {
d := &BlinkerDevice{authKey: ak}
func CreateDevice(ak string, httpClientSecureVerify bool, httpClientTimeout int) *Device {
d := &Device{authKey: ak, httpClientSecureVerify: httpClientSecureVerify, httpClientTimeout: httpClientTimeout}
d.voiceAssistants = make(map[string]*VoiceAssistant)
d.state = "on"
d.httpclient, _ = httputils.CreateHttpClient(
"tcp",
"",
!httpClientSecureVerify,
"",
"",
"",
"",
time.Duration(httpClientTimeout)*time.Second)
return d
}
func (d *BlinkerDevice) AddVoiceAssistant(v *VoiceAssistant) {
func (d *Device) GetState() bool {
if d.queryStateFunc != nil {
return d.queryStateFunc()
}
return d.state
}
func (d *Device) SetQueryStateFunc(f func() bool) {
d.queryStateFunc = f
}
func (d *Device) RegisterPowerChangeCallbackFunc(key string, cb func(string)) {
d.powerChangeCallbackMap.Store(key, cb)
}
func (d *Device) UnRegisterPowerChangeCallbackFunc(key string) {
d.powerChangeCallbackMap.Delete(key)
}
func (d *Device) AddVoiceAssistant(v *VoiceAssistant) {
v.Device = d
d.voiceAssistants[v.VAType] = v
}
func (d *BlinkerDevice) SyncAssistants() error {
func (d *Device) StoreExtData(key any, val any) {
d.extStroe.Store(key, val)
}
func (d *Device) GetExtData(key any) (val any, ok bool) {
val, ok = d.extStroe.Load(key)
return
}
func (d *Device) SyncAssistants() error {
for _, v := range d.voiceAssistants {
skey := v.GetSKey()
dataMap := make(map[string]string)
@ -71,7 +125,7 @@ func (d *BlinkerDevice) SyncAssistants() error {
dataBytes, _ := json.Marshal(dataMap)
resp, err := http.Post(API_VOICE_ASSISTANT, "application/json", strings.NewReader(string(dataBytes)))
resp, err := d.httpclient.Post(API_VOICE_ASSISTANT, "application/json", strings.NewReader(string(dataBytes)))
if err != nil {
return err
}
@ -85,46 +139,75 @@ func (d *BlinkerDevice) SyncAssistants() error {
return nil
}
func (d *BlinkerDevice) RunSenderMessageService() {
for m := range d.sendMsgChan {
t := time.Since(d.preSendTime) - time.Millisecond*1100
if t < 0 {
//log.Printf("太快,睡眠一下:%d\n", -t)
<-time.After(-t)
// func (d *Device) RunSenderMessageService() {
// for m := range d.sendMsgChan {
// t := time.Since(d.preSendTime) - time.Millisecond*1100
// if t < 0 {
// //log.Printf("太快,睡眠一下:%d\n", -t)
// <-time.After(-t)
// }
// d.sendMessage(m.TargetType, m.Device, m.MessageID, m.Msg)
// }
// }
func (d *Device) RunHeartBearTimer() {
ticker := time.NewTicker(time.Second * 599)
defer func() {
ticker.Stop()
}()
for {
select {
case _, ok := <-d.heartBeatChan:
{
if !ok {
return
}
d.heartBeat()
}
case <-ticker.C:
d.heartBeatChan <- uint8(1)
}
d.sendMessage(m.TargetType, m.Device, m.MessageID, m.Msg)
}
}
func (d *BlinkerDevice) RunHeartBearTimer() {
if !d.hbmu.TryLock() {
return
}
defer d.hbmu.Unlock()
log.Printf("开始心跳...\n")
d.heartBeatChan <- uint8(1)
for range d.heartBeatChan {
d.heartBeat()
<-time.After(time.Second * 599)
d.heartBeatChan <- uint8(1)
}
log.Printf("心跳中止...\n")
}
func (d *BlinkerDevice) Init() error {
func (d *Device) Init() error {
apiurl := fmt.Sprintf("%s?authKey=%s", API_AUTH, d.authKey)
resp, err := http.Get(apiurl)
resp, err := d.httpclient.Get(apiurl)
if err != nil {
return fmt.Errorf("device init http.Get err:%s", err.Error())
return fmt.Errorf("device init httpclient.Get err:%s", err.Error())
}
var infoRes BlinkerInfoRes
err = GetAndParseJSONResponseFromHttpResponse(resp, &infoRes)
//jsonparser.Get()
respBytes, err := GetBytesFromHttpResponse(resp)
if err != nil {
return fmt.Errorf("parse DeviceInfo resp err:%s", err.Error())
return fmt.Errorf("GetBytesFromHttpResponse error:%s", err.Error())
}
messageRet, _ := jsonparser.GetInt(respBytes, "message")
if messageRet != 1000 {
detailStr, _ := jsonparser.GetString(respBytes, "detail")
return fmt.Errorf("%s", detailStr)
}
err = json.Unmarshal(respBytes, &infoRes)
if err != nil {
return fmt.Errorf("登录过程解析登录结果出错:\n%s\n%s", string(respBytes), err.Error())
}
// err = GetAndParseJSONResponseFromHttpResponse(resp, &infoRes)
// if err != nil {
// return fmt.Errorf("parse DeviceInfo resp err:%s", err.Error())
// }
d.DetailInfo = infoRes.Detail
err = d.SyncAssistants()
@ -154,7 +237,30 @@ func (d *BlinkerDevice) Init() error {
return nil
}
func (d *BlinkerDevice) Login() error {
func (d *Device) closeMQTTClient() {
d.clientMu.Lock()
defer d.clientMu.Unlock()
if d.client == nil {
return
}
log.Printf("点灯科技 [%s]主动关闭连接", d.authKey)
d.client.Disconnect(0)
close(d.heartBeatChan)
//close(d.sendMsgChan)
d.client = nil
}
func (d *Device) OnLine() bool {
state := atomic.LoadUint32(&d.linkState)
return state == Connected
}
func (d *Device) IsDisconnected() bool {
state := atomic.LoadUint32(&d.linkState)
return state == Disconnected
}
func (d *Device) Login() error {
opts := MQTT.NewClientOptions()
brokeyURL := fmt.Sprintf("%s:%s", d.DetailInfo.Host, d.DetailInfo.Port)
@ -164,69 +270,71 @@ func (d *BlinkerDevice) Login() error {
opts.SetClientID(d.DetailInfo.DeviceName)
opts.SetUsername(d.DetailInfo.IotID)
opts.SetPassword(d.DetailInfo.IotToken)
//opts.SetKeepAlive(time.Second * 3)
//opts.WillRetained = true
//choke := make(chan [2]string)
// opts.SetDefaultPublishHandler(func(client MQTT.Client, msg MQTT.Message) {
// //choke <- [2]string{msg.Topic(), string(msg.Payload())}
// msg.Payload()
// })
opts.SetConnectTimeout(time.Second * 2)
opts.SetKeepAlive(time.Second * 30)
opts.SetTLSConfig(&tls.Config{InsecureSkipVerify: !d.httpClientSecureVerify})
opts.SetAutoReconnect(true)
opts.ConnectRetryInterval = time.Second * 3
opts.SetOnConnectHandler(func(c MQTT.Client) {
log.Printf("连接成功!")
atomic.StoreUint32(&d.linkState, Connected)
log.Printf("点灯物联 [%s]已连接\n", d.authKey)
d.clientMu.Lock()
defer d.clientMu.Unlock()
d.client = c
c.Subscribe(d.subTopic, byte(0), d.ReceiveMessageHandler)
//c.Subscribe(d.exasubTopic, byte(0), d.ReceiveMessageHandler)
d.heartBeatChan = make(chan uint8, 1)
go d.RunHeartBearTimer()
d.sendMsgChan = make(chan message, 8)
go d.RunSenderMessageService()
//d.sendMsgChan = make(chan message, 8)
//go d.RunSenderMessageService()
})
opts.OnConnectionLost = func(c MQTT.Client, err error) {
log.Printf("连接丢失:%s\n", err.Error())
close(d.heartBeatChan)
close(d.sendMsgChan)
d.client = nil
}
//d.client.Disconnect()
//opts.
opts.SetConnectionLostHandler(func(c MQTT.Client, err error) {
log.Printf("点灯物联 [%s]连接丢失:%s\n", d.authKey, err.Error())
atomic.StoreUint32(&d.linkState, Disconnected)
//d.closeMQTTClient()
//fmt.Printf("SetConnectionLostHandler\n")
})
opts.SetReconnectingHandler(func(c MQTT.Client, opt *MQTT.ClientOptions) {
atomic.StoreUint32(&d.linkState, Reconnecting)
})
client := MQTT.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
return fmt.Errorf("连接出错:%s", token.Error())
}
<-time.After(time.Second * 60000)
return nil
}
func (d *BlinkerDevice) heartBeat() error {
func (d *Device) Stop() {
d.closeMQTTClient()
}
func (d *Device) heartBeat() error {
//hr := fmt.Sprintf("%s?deviceName=%s&key=%s&heartbeat=600", SERVER+HEARTBEAT_URL, d.DetailInfo.DeviceName, d.authKey)
hr := fmt.Sprintf("%s?deviceName=%s&key=%s&heartbeat=600", API_HEARTBEAT, d.DetailInfo.DeviceName, d.authKey)
resp, err := http.Get(hr)
resp, err := d.httpclient.Get(hr)
if err != nil {
return fmt.Errorf("device init http.Get err:%s", err.Error())
return fmt.Errorf("device init httpclient.Get err:%s", err.Error())
}
respBytes, err := GetBytesFromHttpResponse(resp)
_, err = GetBytesFromHttpResponse(resp)
if err != nil {
return err
}
fmt.Printf("HearBeat:%s\n", string(respBytes))
//fmt.Printf("HearBeat:%s\n", string(respBytes))
return nil
}
func (d *BlinkerDevice) ReceiveMessageHandler(c MQTT.Client, m MQTT.Message) {
func (d *Device) ReceiveMessageHandler(c MQTT.Client, m MQTT.Message) {
log.Printf("接收到MQTT消息:【%s】%s\n", m.Topic(), m.Payload())
//log.Printf("接收到MQTT消息:\n[【%s】\n%s\n\n", m.Topic(), m.Payload())
if m.Topic() != d.subTopic {
return
@ -254,9 +362,9 @@ func (d *BlinkerDevice) ReceiveMessageHandler(c MQTT.Client, m MQTT.Message) {
}
func (d *BlinkerDevice) voiceAssistantMessageHandler(from string, msg []byte) {
func (d *Device) voiceAssistantMessageHandler(from string, msg []byte) {
fmt.Printf("from:%s msg:%s\n", from, string(msg))
//fmt.Printf("from:%s msg:%s\n", from, string(msg))
va, ok := d.voiceAssistants[from]
if !ok {
@ -285,14 +393,28 @@ func (d *BlinkerDevice) voiceAssistantMessageHandler(from string, msg []byte) {
}
func (d *BlinkerDevice) powerChange(va *VoiceAssistant, msgId, state string) {
d.state = state
func (d *Device) powerChange(va *VoiceAssistant, msgId, state string) {
if va != nil {
va.PowerChangeReply(msgId, state)
}
if state == "true" || state == "on" {
d.state = true
} else {
d.state = false
}
go func() {
d.powerChangeCallbackMap.Range(func(key any, val any) bool {
cb := val.(func(string))
cb(state)
return true
})
}()
}
func (d *BlinkerDevice) ownAppMessagehandler(msg []byte) {
func (d *Device) ownAppMessagehandler(msg []byte) {
getValue, getKeyError := jsonparser.GetString(msg, "data", "get")
if getKeyError == nil {
switch getValue {
@ -303,7 +425,7 @@ func (d *BlinkerDevice) ownAppMessagehandler(msg []byte) {
case "countdown":
d.SendMessage("OwnApp", d.DetailInfo.UUID, "", map[string]any{"countdown": "false"}) //`{ "countdown": false }`
default:
fmt.Printf(` "data", "get":Value:%s`, getValue)
//fmt.Printf(` "data", "get":Value:%s`, getValue)
}
return
@ -325,34 +447,37 @@ type mess2assistant struct {
MessageID string `json:"-"` //`json:"messageId"`
}
func (d *BlinkerDevice) formatMess2assistant(targetType, toDevice, msgid string, data any) ([]byte, error) {
func (d *Device) formatMess2assistant(targetType, toDevice, msgid string, data any) ([]byte, error) {
m := mess2assistant{DeviceType: targetType, Data: data, FromDeivce: d.DetailInfo.DeviceName, ToDevice: toDevice, MessageID: msgid}
rawBytes, err := json.Marshal(m)
if err != nil {
return []byte{}, err
}
str := base64.StdEncoding.EncodeToString(rawBytes)
log.Printf("回复语音助手:%s\n", string(rawBytes))
//str := base64.StdEncoding.EncodeToString(rawBytes)
//log.Printf("回复语音助手:%s\n", string(rawBytes))
//fmt.Printf("base64:%s\n", str)
return []byte(str), nil
//return rawBytes, nil
//return []byte(str), nil
return rawBytes, nil
}
func (d *BlinkerDevice) formatMess2Device(targetType, toDevice string, data any) ([]byte, error) {
func (d *Device) formatMess2Device(targetType, toDevice string, data any) ([]byte, error) {
m := mess2device{DeviceType: targetType, Data: data, FromDeivce: d.DetailInfo.DeviceName, ToDevice: toDevice}
return json.Marshal(m)
}
func (d *BlinkerDevice) SendMessage(targetType, todevice, msgid string, msg any) {
m := message{Device: todevice, Msg: msg, TargetType: targetType, MessageID: msgid}
d.sendMsgChan <- m
func (d *Device) SendMessage(targetType, todevice, msgid string, msg any) {
//m := message{Device: todevice, Msg: msg, TargetType: targetType, MessageID: msgid}
//d.sendMsgChan <- m
d.sendMessage(targetType, todevice, msgid, msg)
}
func (d *BlinkerDevice) sendMessage(targetType, todevice, msgid string, msg any) error {
func (d *Device) sendMessage(targetType, todevice, msgid string, msg any) error {
d.clientMu.Lock()
defer d.clientMu.Unlock()
if d.client == nil {
return fmt.Errorf("SendMessage error:client == nil")
return fmt.Errorf("d.Client == nil")
}
var pubTopic string
var payload []byte
@ -374,10 +499,10 @@ func (d *BlinkerDevice) sendMessage(targetType, todevice, msgid string, msg any)
}
}
fmt.Printf("topic:%s\n", pubTopic)
// fmt.Printf("topic:%s\n", pubTopic)
if token := d.client.Publish(pubTopic, 0, false, payload); token.Wait() && token.Error() != nil {
fmt.Printf("Publish error:%s\n", token.Error())
if token := d.client.Publish(pubTopic, 1, true, payload); token.Wait() && token.Error() != nil {
//fmt.Printf("Publish error:%s\n", token.Error())
return token.Error()
}
d.preSendTime = time.Now()
@ -428,6 +553,8 @@ func GetAndParseJSONResponseFromHttpResponse(resp *http.Response, result interfa
if err != nil {
return fmt.Errorf("GetBytesFromHttpResponse err:%s", err.Error())
}
// fmt.Printf("FUCK:\n%s\n", string(bytes))
if len(bytes) > 0 {
err = json.Unmarshal(bytes, &result)
if err != nil {

View File

@ -0,0 +1,116 @@
package blinker
import (
"fmt"
"sync"
"time"
)
var blinkerDeviceStore sync.Map
var blinkerdeviceStroeMu sync.Mutex
func GetBlinkerDevice(authKey string, httpClientSecureVerify bool, httpClientTimeout int) (*Device, error) {
blinkerdeviceStroeMu.Lock()
defer blinkerdeviceStroeMu.Unlock()
device, deviceOk := blinkerDeviceStore.Load(authKey)
if deviceOk {
d := device.(*Device)
if d.OnLine() {
return d, nil
}
if d.IsDisconnected() {
err := d.Init()
if err != nil {
return nil, fmt.Errorf("blinker Device init error:%s", err.Error())
}
d.Stop()
err = d.Login()
if err != nil {
return nil, fmt.Errorf("blinker Device login error:%s", err.Error())
}
i := 0
for {
<-time.After(time.Second * 200)
i++
if d.OnLine() {
break
}
if i > 26 {
break
}
}
if d.OnLine() {
return d, nil
}
return nil, fmt.Errorf("blinker drvice 连接服务器失败")
}
return device.(*Device), nil
}
d := CreateDevice(authKey, httpClientSecureVerify, httpClientTimeout)
d.AddVoiceAssistant(CreateVoiceAssistant(VA_TYPE_OUTLET, "MIOT"))
d.AddVoiceAssistant(CreateVoiceAssistant(VA_TYPE_OUTLET, "AliGenie"))
d.AddVoiceAssistant(CreateVoiceAssistant(VA_TYPE_OUTLET, "DuerOS"))
err := d.Init()
if err != nil {
return nil, fmt.Errorf("blinker Device init error:%s", err.Error())
}
err = d.Login()
if err != nil {
return nil, fmt.Errorf("blinker Device Login error:%s", err.Error())
}
i := 0
for {
<-time.After(time.Millisecond * 100)
i++
if d.OnLine() {
break
}
if i > 51 {
break
}
}
//fmt.Printf("在线\n")
if d.OnLine() {
blinkerDeviceStore.Store(authKey, d)
return d, nil
}
return nil, fmt.Errorf("blinker drvice 连接服务器失败")
}
// func RegisterPowerChangeCallback(authkey, key string, cb func(string)) (*Device, error) {
// d, err := GetBlinkerDevice(authkey)
// if err != nil {
// return nil, err
// }
// d.RegisterPowerChangeCallbackFunc(key, cb)
// return d, nil
// }
func UnRegisterPowerChangeCallback(d *Device, key string) {
blinkerdeviceStroeMu.Lock()
defer blinkerdeviceStroeMu.Unlock()
d.UnRegisterPowerChangeCallbackFunc(key)
isEmpty := true
d.powerChangeCallbackMap.Range(func(key any, val any) bool {
isEmpty = false
return false
})
if isEmpty {
d.Stop()
blinkerDeviceStore.Delete(d.authKey)
}
}

View File

@ -0,0 +1,124 @@
package dnsutils
import (
"fmt"
"strings"
"github.com/gdy666/lucky/thirdlib/gdylib/stringsp"
"github.com/miekg/dns"
"golang.org/x/net/idna"
)
var DefaultIPv6DNSServerList = []string{
"[2001:4860:4860::8888]:53", //谷歌
"[2001:4860:4860::8844]:53", //谷歌
"[2606:4700:4700::64]:53", //cloudflare
"[2606:4700:4700::6400]:53", //cloudflare
"[240C::6666]:53", //下一代互联网北京研究中心
"[240C::6644]:53", //下一代互联网北京研究中心
"[2402:4e00::]:53", //dnspod
//"[2400:3200::1]:53", //阿里
// "[2400:3200:baba::1]:53", //阿里
"[240e:4c:4008::1]:53", //中国电信
"[240e:4c:4808::1]:53", //中国电信
"[2408:8899::8]:53", //中国联通
"[2408:8888::8]:53", //中国联通
"[2409:8088::a]:53", //中国移动
"[2409:8088::b]:53", //中国移动
"[2001:dc7:1000::1]:53", //CNNIC
"[2400:da00::6666]:53", //百度
}
var DefaultIPv4DNSServerList = []string{
"1.1.1.1:53",
"1.2.4.8:53",
"8.8.8.8:53",
"9.9.9.9:53",
"8.8.4.4:53",
"114.114.114.114:53",
"223.5.5.5:53",
"223.6.6.6:53",
"101.226.4.6:53",
"218.30.118.6:53",
"119.28.28.28:53",
}
func ResolveDomainAtServerList(queryType, domain string, dnsServerList []string) (string, error) {
if len(dnsServerList) == 0 {
if queryType == "AAAA" {
dnsServerList = DefaultIPv6DNSServerList
} else {
dnsServerList = DefaultIPv4DNSServerList
}
}
//some name that ought to exist, does not exist (NXDOMAIN)
querytype, querytypeOk := dns.StringToType[strings.ToUpper(queryType)]
if !querytypeOk {
return "", fmt.Errorf("queryType error:%s", queryType)
}
if strings.HasPrefix(domain, "*.") {
randomStr := stringsp.GetRandomString(8)
domain = strings.Replace(domain, "*", randomStr, 1)
}
domain = dns.Fqdn(domain)
domain, err := idna.ToASCII(domain)
if err != nil {
return "", fmt.Errorf(` idna.ToASCII(domain) error:%s`, err.Error())
}
m := new(dns.Msg)
m.SetQuestion(domain, querytype)
m.MsgHdr.RecursionDesired = true
c := new(dns.Client)
noExistTimes := 0
for _, dnsServer := range dnsServerList {
c.Net = ""
ipaddr, err := resolveDomain(m, c, dnsServer)
if err != nil {
//log.Printf("[%s]===>[%s][%s] ResolveDomain error:%s", dnsServer, queryType, domain, err.Error())
if strings.Contains(err.Error(), "some name that ought to exist, does not exist (NXDOMAIN)") {
noExistTimes++
if noExistTimes >= 4 {
return "", fmt.Errorf("解析域名[%s][%s]IP失败:noExistTimes", queryType, domain)
}
}
continue
}
return ipaddr, nil
}
return "", fmt.Errorf("解析域名[%s][%s]IP失败", queryType, domain)
}
func resolveDomain(msg *dns.Msg, client *dns.Client, dnsServer string) (string, error) {
Redo:
if in, _, err := client.Exchange(msg, dnsServer); err == nil { // Second return value is RTT, not used for now
if in.MsgHdr.Truncated {
client.Net = "tcp"
goto Redo
}
switch in.MsgHdr.Rcode {
case dns.RcodeServerFailure:
return "", fmt.Errorf("the name server encountered an internal failure while processing this request (SERVFAIL)")
case dns.RcodeNameError:
return "", fmt.Errorf("some name that ought to exist, does not exist (NXDOMAIN)")
case dns.RcodeRefused:
return "", fmt.Errorf("the name server refuses to perform the specified operation for policy or security reasons (REFUSED)")
default:
//fmt.Printf("in.Answer len:%d\n", len(in.Answer))
for _, rr := range in.Answer {
//fmt.Printf("rr.String :%s\n", rr.String())
return strings.Replace(rr.String(), rr.Header().String(), "", -1), nil
}
}
}
return "", fmt.Errorf("DNS server could not be reached")
}

View File

@ -11,6 +11,7 @@ import (
// NetInterface 本机网络
type NetInterface struct {
NetInterfaceName string
HardwareAddr string
AddressList []string
}
@ -51,6 +52,7 @@ func GetNetInterface() (ipv4NetInterfaces []NetInterface, ipv6NetInterfaces []Ne
ipv4NetInterfaces,
NetInterface{
NetInterfaceName: allNetInterfaces[i].Name,
HardwareAddr: allNetInterfaces[i].HardwareAddr.String(),
AddressList: ipv4,
},
)
@ -194,3 +196,58 @@ func GetBroadcast(ip net.IP, mask net.IPMask) string {
}
return bcst.String()
}
// ----------------------------------------------------------------
// NetInterface 本机网络
type NetInterfaceInfo struct {
NetInterfaceName string
HardwareAddr string
AddressList []IPInfo
}
type IPInfo struct {
IP string
BroadcastIP string
}
// GetNetInterface 获得网卡地址
// 返回ipv4, ipv6地址
func GetIPv4NetInterfaceInfoList() (ipv4NetInterfaces []NetInterfaceInfo, err error) {
allNetInterfaces, err := net.Interfaces()
if err != nil {
return ipv4NetInterfaces, err
}
for i := 0; i < len(allNetInterfaces); i++ {
if (allNetInterfaces[i].Flags & net.FlagUp) != 0 {
addrs, _ := allNetInterfaces[i].Addrs()
ipv4 := []IPInfo{}
for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); ok && ipnet.IP.IsGlobalUnicast() {
_, bits := ipnet.Mask.Size()
// 需匹配全局单播地址
//if bits == 128 && ipv6Unicast.Contains(ipnet.IP) {
if bits == 32 {
info := IPInfo{IP: ipnet.IP.String(), BroadcastIP: GetBroadcast(ipnet.IP, ipnet.Mask)}
ipv4 = append(ipv4, info)
}
}
}
if len(ipv4) > 0 {
ipv4NetInterfaces = append(
ipv4NetInterfaces,
NetInterfaceInfo{
NetInterfaceName: allNetInterfaces[i].Name,
HardwareAddr: allNetInterfaces[i].HardwareAddr.String(),
AddressList: ipv4,
},
)
}
}
}
return ipv4NetInterfaces, nil
}

View File

@ -1,5 +1,11 @@
package stringsp
import (
"bytes"
"crypto/des"
"errors"
)
func StrIsInList(str string, strList []string) bool {
checkMap := make(map[string]uint8)
for i := range strList {
@ -10,3 +16,58 @@ func StrIsInList(str string, strList []string) bool {
}
return false
}
func zeroPadding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{0}, padding)
return append(ciphertext, padtext...)
}
func zeroUnPadding(origData []byte) []byte {
return bytes.TrimFunc(origData,
func(r rune) bool {
return r == rune(0)
})
}
// DesEncrypt Des加密
func DesEncrypt(src, key []byte) ([]byte, error) {
block, err := des.NewCipher(key)
if err != nil {
return nil, err
}
bs := block.BlockSize()
src = zeroPadding(src, bs)
if len(src)%bs != 0 {
return nil, errors.New("Need a multiple of the blocksize")
}
out := make([]byte, len(src))
dst := out
for len(src) > 0 {
block.Encrypt(dst, src[:bs])
src = src[bs:]
dst = dst[bs:]
}
return out, nil
}
// DesDecrypt Des解密
func DesDecrypt(src, key []byte) ([]byte, error) {
block, err := des.NewCipher(key)
if err != nil {
return nil, err
}
out := make([]byte, len(src))
dst := out
bs := block.BlockSize()
if len(src)%bs != 0 {
return nil, errors.New("crypto/cipher: input not full blocks")
}
for len(src) > 0 {
block.Decrypt(dst, src[:bs])
src = src[bs:]
dst = dst[bs:]
}
out = zeroUnPadding(out)
return out, nil
}

View File

@ -0,0 +1,404 @@
package websocketcontroller
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"runtime/debug"
"sync"
"sync/atomic"
"time"
"github.com/gorilla/websocket"
"github.com/sirupsen/logrus"
)
const (
disconnected uint32 = iota
connecting
reconnecting
connected
)
const (
// Time allowed to write a message to the peer.
writeWait = 5 * time.Second
// Time allowed to read the next pong message from the peer.
pongWait = 9 * time.Second
// Send pings to peer with this period. Must be less than pongWait.
pingPeriod = 1500 * time.Millisecond
// Maximum message size allowed from peer.
maxMessageSize = 4096 * 4
handshakeTimeout = 10 * time.Second
readTimeout = 9 * time.Second
)
type Controller struct {
Logs *logrus.Logger
mu sync.Mutex
wait sync.WaitGroup
initOnce sync.Once //一次
status uint32
readTimeout time.Duration
writeWait time.Duration //自定义写超时参数
pongWait time.Duration //自定义Pong超时参数
pingPeriod time.Duration //自定义发送ping时间间隔
sendChan chan []byte
ReceiveMessageCallback func(c *Controller, messageBytes []byte) //接收消息回调函数
ctx context.Context
ctxCancelFunc context.CancelFunc
sendMessageEncryptionFunc func(messageBytes []byte) ([]byte, error) //发送消息加密函数
receiveMessageDecryptionFunc func(messageBytes []byte) ([]byte, error) //接收消息解密函数
//---------- 客户端特有
serverUrl string
scureSkipVerify bool
handshakeTimeout time.Duration
disconnectSign uint32 //主动断开连接标记,不自动重连
connectRetry bool //重连
connectRetryInterval time.Duration
connectRetryCount int //0表示无限次
ClientReadyCallback func(c *Controller)
ClientDisconnectedCallback func(c *Controller)
remoteAddr string
extDataMap sync.Map
}
func (c *Controller) SetSendMessageEncryptionFunc(f func(messageBytes []byte) ([]byte, error)) {
c.sendMessageEncryptionFunc = f
}
func (c *Controller) SetReceiveMessageDecryptionFunc(f func(messageBytes []byte) ([]byte, error)) {
c.receiveMessageDecryptionFunc = f
}
func (c *Controller) StoreExtData(key any, data any) {
c.extDataMap.Store(key, data)
}
func (c *Controller) GetExtData(key any) (any, bool) {
return c.extDataMap.Load(key)
}
func (c *Controller) disconnect() {
c.mu.Lock()
defer c.mu.Unlock()
status := atomic.LoadUint32(&c.status)
if status == disconnected {
return
}
c.wait.Wait()
if c.ctxCancelFunc != nil {
c.ctxCancelFunc()
c.ctxCancelFunc = nil
}
if c.ClientDisconnectedCallback != nil {
c.ClientDisconnectedCallback(c)
}
atomic.StoreUint32(&c.status, disconnected)
disconnectSign := atomic.LoadUint32(&c.disconnectSign)
// if c.connectRetry && !c.disconnectSign {
if c.connectRetry && disconnectSign == 0 {
go func() {
<-time.After(c.connectRetryInterval)
c.Connect()
}()
}
}
func (c *Controller) SetServerURL(url string) {
c.serverUrl = url
}
func (c *Controller) ScureSkipVerify(b bool) {
c.scureSkipVerify = b
}
func (c *Controller) SetConnectRetry(b bool) {
c.connectRetry = b
}
func (c *Controller) SetConnectRetryInterval(t time.Duration) {
c.connectRetryInterval = t
}
func (c *Controller) SetConnectRetryCount(count int) {
c.connectRetryCount = count
}
func (c *Controller) SetReadDeadline(d time.Duration) {
c.readTimeout = d
}
func (c *Controller) init() {
c.mu.Lock()
defer c.mu.Unlock()
status := atomic.LoadUint32(&c.status)
if status == connected {
return
}
if c.writeWait <= 0 {
c.writeWait = writeWait
}
if c.pongWait <= 0 {
c.pongWait = pongWait
}
if c.pingPeriod <= 0 {
c.pingPeriod = pingPeriod
}
if c.handshakeTimeout <= 0 {
c.handshakeTimeout = handshakeTimeout
}
if c.readTimeout <= 0 {
c.readTimeout = readTimeout
}
//c.disconnectSign = false
atomic.StoreUint32(&c.disconnectSign, 0)
c.initOnce.Do(func() {
c.sendChan = make(chan []byte, 1024)
})
}
func (c *Controller) Disconnect() {
//c.disconnectSign = true
atomic.StoreUint32(&c.disconnectSign, 1)
c.mu.Lock()
defer c.mu.Unlock()
status := atomic.LoadUint32(&c.status)
if status != connected {
return
}
c.Logs.Infof("[%s]Disconnect", c.serverUrl)
if c.ctxCancelFunc != nil {
c.ctxCancelFunc()
}
}
func (c *Controller) SendMessage(messageBytes []byte) {
c.mu.Lock()
defer c.mu.Unlock()
status := atomic.LoadUint32(&c.status)
if status != connected {
c.Logs.Warn("websocketclient 未连接,消息[%s]发送失败", string(messageBytes))
return
}
if c.sendMessageEncryptionFunc != nil {
enMsgBytes, err := c.sendMessageEncryptionFunc(messageBytes)
if err != nil {
c.Logs.Error("WebSocket客户端 自定义发送消息加密函数加密出错:%s", err.Error())
return
}
messageBytes = enMsgBytes
}
select {
case c.sendChan <- messageBytes:
default:
}
}
func (c *Controller) writePump(ctx context.Context, conn *websocket.Conn) {
ticker := time.NewTicker(c.pingPeriod)
c.wait.Add(1)
defer func() {
conn.Close()
c.wait.Done()
ticker.Stop()
c.disconnect()
//c.Logs.Printf("writePump return\n")
recoverErr := recover()
if recoverErr == nil {
return
}
c.Logs.Errorf("webscoket controller writePump panic:\n%v", recoverErr)
c.Logs.Errorf("%s", debug.Stack())
}()
//ccontext, _ := context.WithCancel(ctx)
for {
select {
case messageBytes, ok := <-c.sendChan: //需要发送的消息
conn.SetWriteDeadline(time.Now().Add(c.writeWait))
if !ok {
conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
w, err := conn.NextWriter(websocket.TextMessage)
if err != nil {
return
}
if len(messageBytes) == 0 {
continue
}
w.Write(messageBytes)
if err := w.Close(); err != nil {
return
}
case <-ticker.C:
conn.SetWriteDeadline(time.Now().Add(c.writeWait))
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
// case <-ccontext.Done():
// {
// return
// }
case <-ctx.Done():
{
//fmt.Printf("ctx.Done():666\n")
return
}
}
}
}
func (c *Controller) readPump(cancelFunc context.CancelFunc, conn *websocket.Conn) {
defer func() {
c.wait.Done()
cancelFunc()
//c.Logs.Printf("readPump return ")
recoverErr := recover()
if recoverErr == nil {
return
}
c.Logs.Errorf("webscoket controller readPump panic:\n%v", recoverErr)
c.Logs.Errorf("%s", debug.Stack())
}()
c.wait.Add(1)
conn.SetReadLimit(maxMessageSize)
conn.SetReadDeadline(time.Now().Add(c.readTimeout))
conn.SetPongHandler(func(string) error {
conn.SetReadDeadline(time.Now().Add(c.pongWait))
return nil
})
for {
_, messageBytes, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
c.Logs.Error("websocket.IsUnexpectedCloseError : %s", err.Error())
}
break
}
messageSize := len(messageBytes)
if messageSize <= 1 { //忽略心跳消息
continue
}
if c.receiveMessageDecryptionFunc != nil {
deMsgBytes, err := c.receiveMessageDecryptionFunc(messageBytes)
if err != nil {
c.Logs.Error("Websocket controller 消息接收消息解密出错:%s", err.Error())
continue
}
messageBytes = deMsgBytes
}
if c.ReceiveMessageCallback != nil {
go c.ReceiveMessageCallback(c, messageBytes)
}
}
}
func (c *Controller) Connect() error {
c.init()
c.mu.Lock()
defer c.mu.Unlock()
dialer := &websocket.Dialer{
Proxy: http.ProxyFromEnvironment,
HandshakeTimeout: c.handshakeTimeout,
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.scureSkipVerify}}
atomic.StoreUint32(&c.status, connecting)
retryCount := 0
RETRYCONN:
connect, _, err := dialer.Dial(c.serverUrl, nil)
if err != nil {
disconnectSign := atomic.LoadUint32(&c.disconnectSign)
// if c.connectRetry && (c.connectRetryCount <= 0 || retryCount < c.connectRetryCount) && !c.disconnectSign {
if c.connectRetry && (c.connectRetryCount <= 0 || retryCount < c.connectRetryCount) && disconnectSign == 0 {
<-time.After(c.connectRetryInterval)
retryCount++
c.Logs.Errorf("[%d]Connect error:%s", disconnectSign, err.Error())
c.Logs.Infof("[%s]重新连接...%d\n", c.serverUrl, retryCount)
goto RETRYCONN
}
atomic.StoreUint32(&c.status, disconnected)
return fmt.Errorf("Connect DefaultDialer error:%s", err.Error())
}
//c.conn = connect
if c.ctxCancelFunc != nil {
c.ctxCancelFunc()
}
c.monitor(connect)
return nil
}
// ConnectReady Websocket 服务器端使用
func (c *Controller) ConnectReady(connect *websocket.Conn) {
c.init()
c.mu.Lock()
defer c.mu.Unlock()
c.remoteAddr = connect.RemoteAddr().String()
c.monitor(connect)
}
func (c *Controller) GetRemoteAddr() string {
return c.remoteAddr
}
func (c *Controller) monitor(connect *websocket.Conn) {
ctx, ctxCancelFunc := context.WithCancel(context.Background())
c.ctx = ctx
c.ctxCancelFunc = ctxCancelFunc
atomic.StoreUint32(&c.status, connected)
go c.writePump(ctx, connect)
go c.readPump(ctxCancelFunc, connect)
if c.ClientReadyCallback != nil {
go c.ClientReadyCallback(c)
}
}

View File

@ -1,4 +1,4 @@
package wol
package gowol
////////////////////////////////////////////////////////////////////////////////

View File

@ -1,4 +1,4 @@
package wol
package gowol
import (
"fmt"

View File

@ -55,6 +55,7 @@ declare module '@vue/runtime-core' {
WhiteLists: typeof import('./src/components/WhiteLists.vue')['default']
WhiteListSet: typeof import('./src/components/WhiteListSet.vue')['default']
WOL: typeof import('./src/components/tools/WOL.vue')['default']
WOLServiceSet: typeof import('./src/components/tools/WOLServiceSet.vue')['default']
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
web/adminviews/dist/diandeng_log.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -8,8 +8,8 @@
<title>Lucky</title>
<script type="module" crossorigin src="/assets/index.e5c8aec2.js"></script>
<link rel="stylesheet" href="/assets/index.f23c7bd8.css">
<script type="module" crossorigin src="/assets/index.0c84c960.js"></script>
<link rel="stylesheet" href="/assets/index.abda1f8d.css">
</head>
<body style="margin:0">
<div id="app"></div>

View File

@ -83,21 +83,21 @@
}
},
"node_modules/@babel/core": {
"version": "7.19.3",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.3.tgz",
"integrity": "sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==",
"version": "7.19.6",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.6.tgz",
"integrity": "sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==",
"dev": true,
"dependencies": {
"@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.19.3",
"@babel/generator": "^7.19.6",
"@babel/helper-compilation-targets": "^7.19.3",
"@babel/helper-module-transforms": "^7.19.0",
"@babel/helpers": "^7.19.0",
"@babel/parser": "^7.19.3",
"@babel/helper-module-transforms": "^7.19.6",
"@babel/helpers": "^7.19.4",
"@babel/parser": "^7.19.6",
"@babel/template": "^7.18.10",
"@babel/traverse": "^7.19.3",
"@babel/types": "^7.19.3",
"@babel/traverse": "^7.19.6",
"@babel/types": "^7.19.4",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@ -113,9 +113,9 @@
}
},
"node_modules/@babel/generator": {
"version": "7.19.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.5.tgz",
"integrity": "sha512-DxbNz9Lz4aMZ99qPpO1raTbcrI1ZeYh+9NR9qhfkQIbFtVEqotHojEBxHzmxhVONkGt6VyrqVQcgpefMy9pqcg==",
"version": "7.19.6",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.6.tgz",
"integrity": "sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==",
"dev": true,
"dependencies": {
"@babel/types": "^7.19.4",
@ -205,19 +205,19 @@
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz",
"integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==",
"version": "7.19.6",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz",
"integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==",
"dev": true,
"dependencies": {
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-module-imports": "^7.18.6",
"@babel/helper-simple-access": "^7.18.6",
"@babel/helper-simple-access": "^7.19.4",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/helper-validator-identifier": "^7.18.6",
"@babel/helper-validator-identifier": "^7.19.1",
"@babel/template": "^7.18.10",
"@babel/traverse": "^7.19.0",
"@babel/types": "^7.19.0"
"@babel/traverse": "^7.19.6",
"@babel/types": "^7.19.4"
},
"engines": {
"node": ">=6.9.0"
@ -303,9 +303,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.4.tgz",
"integrity": "sha512-qpVT7gtuOLjWeDTKLkJ6sryqLliBaFpAtGeqw5cs5giLldvh+Ch0plqnUMKoVAUS6ZEueQQiZV+p5pxtPitEsA==",
"version": "7.19.6",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.6.tgz",
"integrity": "sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==",
"bin": {
"parser": "bin/babel-parser.js"
},
@ -328,18 +328,18 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.4.tgz",
"integrity": "sha512-w3K1i+V5u2aJUOXBFFC5pveFLmtq1s3qcdDNC2qRI6WPBQIDaKFqXxDEqDO/h1dQ3HjsZoZMyIy6jGLq0xtw+g==",
"version": "7.19.6",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.6.tgz",
"integrity": "sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.19.4",
"@babel/generator": "^7.19.6",
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-function-name": "^7.19.0",
"@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/parser": "^7.19.4",
"@babel/parser": "^7.19.6",
"@babel/types": "^7.19.4",
"debug": "^4.1.0",
"globals": "^11.1.0"
@ -400,9 +400,9 @@
"integrity": "sha512-bO37brCPfteXQfFY0DyNDGB3+IMe4j150KFQcgJ5aBP295p9nBGeHEs/p0czrRbtlHq4Px/yoPXO/+dOCcF4uA=="
},
"node_modules/@floating-ui/dom": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.0.2.tgz",
"integrity": "sha512-5X9WSvZ8/fjy3gDu8yx9HAA4KG1lazUN2P4/VnaXLxTO9Dz53HI1oYoh1OlhqFNlHgGDiwFX5WhFCc2ljbW3yA==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.0.3.tgz",
"integrity": "sha512-6H1kwjkOZKabApNtXRiYHvMmYJToJ1DV7rQ3xc/WJpOABhQIOJJOdz2AOejj8X+gcybaFmBpisVTZxBZAM3V0w==",
"dependencies": {
"@floating-ui/core": "^1.0.1"
}
@ -484,9 +484,9 @@
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.16",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.16.tgz",
"integrity": "sha512-LCQ+NeThyJ4k1W2d+vIKdxuSt9R3pQSZ4P92m7EakaYuXcVWbHuT5bjNcqLd4Rdgi6xYWYDvBJZJLZSLanjDcA==",
"version": "0.3.17",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
"integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "3.1.0",
@ -750,9 +750,9 @@
}
},
"node_modules/@types/eslint": {
"version": "8.4.6",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz",
"integrity": "sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==",
"version": "8.4.7",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.7.tgz",
"integrity": "sha512-ehM7cCt2RSFs42mb+lcmhFT9ouIlV92PuaeRGn8N8c98oMjG4Z5pJHA9b1QiCcuqnbPSHcyfiD3mlhqMaHsQIw==",
"dev": true,
"dependencies": {
"@types/estree": "*",
@ -851,9 +851,9 @@
"peer": true
},
"node_modules/@types/node": {
"version": "18.8.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.4.tgz",
"integrity": "sha512-WdlVphvfR/GJCLEMbNA8lJ0lhFNBj4SW3O+O5/cEGw9oYrv0al9zTwuQsq+myDUXgNx2jgBynoVgZ2MMJ6pbow==",
"version": "18.11.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.2.tgz",
"integrity": "sha512-BWN3M23gLO2jVG8g/XHIRFWiiV4/GckeFIqbU/C4V3xpoBBWSMk4OZomouN0wCkfQFPqgZikyLr7DOYDysIkkw==",
"dev": true
},
"node_modules/@types/normalize-package-data": {
@ -921,9 +921,9 @@
}
},
"node_modules/@types/web-bluetooth": {
"version": "0.0.15",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.15.tgz",
"integrity": "sha512-w7hEHXnPMEZ+4nGKl/KDRVpxkwYxYExuHOYXyzIzCDzEZ9ZCGMAewulr9IqJu2LR4N37fcnb1XVeuZ09qgOxhA=="
"version": "0.0.16",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
"integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ=="
},
"node_modules/@types/webpack-env": {
"version": "1.18.0",
@ -1222,36 +1222,36 @@
}
},
"node_modules/@vue/compiler-core": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.40.tgz",
"integrity": "sha512-2Dc3Stk0J/VyQ4OUr2yEC53kU28614lZS+bnrCbFSAIftBJ40g/2yQzf4mPBiFuqguMB7hyHaujdgZAQ67kZYA==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.41.tgz",
"integrity": "sha512-oA4mH6SA78DT+96/nsi4p9DX97PHcNROxs51lYk7gb9Z4BPKQ3Mh+BLn6CQZBw857Iuhu28BfMSRHAlPvD4vlw==",
"dependencies": {
"@babel/parser": "^7.16.4",
"@vue/shared": "3.2.40",
"@vue/shared": "3.2.41",
"estree-walker": "^2.0.2",
"source-map": "^0.6.1"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.40.tgz",
"integrity": "sha512-OZCNyYVC2LQJy4H7h0o28rtk+4v+HMQygRTpmibGoG9wZyomQiS5otU7qo3Wlq5UfHDw2RFwxb9BJgKjVpjrQw==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.41.tgz",
"integrity": "sha512-xe5TbbIsonjENxJsYRbDJvthzqxLNk+tb3d/c47zgREDa/PCp6/Y4gC/skM4H6PIuX5DAxm7fFJdbjjUH2QTMw==",
"dependencies": {
"@vue/compiler-core": "3.2.40",
"@vue/shared": "3.2.40"
"@vue/compiler-core": "3.2.41",
"@vue/shared": "3.2.41"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.40.tgz",
"integrity": "sha512-tzqwniIN1fu1PDHC3CpqY/dPCfN/RN1thpBC+g69kJcrl7mbGiHKNwbA6kJ3XKKy8R6JLKqcpVugqN4HkeBFFg==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.41.tgz",
"integrity": "sha512-+1P2m5kxOeaxVmJNXnBskAn3BenbTmbxBxWOtBq3mQTCokIreuMULFantBUclP0+KnzNCMOvcnKinqQZmiOF8w==",
"dependencies": {
"@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.40",
"@vue/compiler-dom": "3.2.40",
"@vue/compiler-ssr": "3.2.40",
"@vue/reactivity-transform": "3.2.40",
"@vue/shared": "3.2.40",
"@vue/compiler-core": "3.2.41",
"@vue/compiler-dom": "3.2.41",
"@vue/compiler-ssr": "3.2.41",
"@vue/reactivity-transform": "3.2.41",
"@vue/shared": "3.2.41",
"estree-walker": "^2.0.2",
"magic-string": "^0.25.7",
"postcss": "^8.1.10",
@ -1267,12 +1267,12 @@
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.40.tgz",
"integrity": "sha512-80cQcgasKjrPPuKcxwuCx7feq+wC6oFl5YaKSee9pV3DNq+6fmCVwEEC3vvkf/E2aI76rIJSOYHsWSEIxK74oQ==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.41.tgz",
"integrity": "sha512-Y5wPiNIiaMz/sps8+DmhaKfDm1xgj6GrH99z4gq2LQenfVQcYXmHIOBcs5qPwl7jaW3SUQWjkAPKMfQemEQZwQ==",
"dependencies": {
"@vue/compiler-dom": "3.2.40",
"@vue/shared": "3.2.40"
"@vue/compiler-dom": "3.2.41",
"@vue/shared": "3.2.41"
}
},
"node_modules/@vue/component-compiler-utils": {
@ -1346,21 +1346,21 @@
"peer": true
},
"node_modules/@vue/reactivity": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.40.tgz",
"integrity": "sha512-N9qgGLlZmtUBMHF9xDT4EkD9RdXde1Xbveb+niWMXuHVWQP5BzgRmE3SFyUBBcyayG4y1lhoz+lphGRRxxK4RA==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.41.tgz",
"integrity": "sha512-9JvCnlj8uc5xRiQGZ28MKGjuCoPhhTwcoAdv3o31+cfGgonwdPNuvqAXLhlzu4zwqavFEG5tvaoINQEfxz+l6g==",
"dependencies": {
"@vue/shared": "3.2.40"
"@vue/shared": "3.2.41"
}
},
"node_modules/@vue/reactivity-transform": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.40.tgz",
"integrity": "sha512-HQUCVwEaacq6fGEsg2NUuGKIhUveMCjOk8jGHqLXPI2w6zFoPrlQhwWEaINTv5kkZDXKEnCijAp+4gNEHG03yw==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.41.tgz",
"integrity": "sha512-mK5+BNMsL4hHi+IR3Ft/ho6Za+L3FA5j8WvreJ7XzHrqkPq8jtF/SMo7tuc9gHjLDwKZX1nP1JQOKo9IEAn54A==",
"dependencies": {
"@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.40",
"@vue/shared": "3.2.40",
"@vue/compiler-core": "3.2.41",
"@vue/shared": "3.2.41",
"estree-walker": "^2.0.2",
"magic-string": "^0.25.7"
}
@ -1374,40 +1374,40 @@
}
},
"node_modules/@vue/runtime-core": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.40.tgz",
"integrity": "sha512-U1+rWf0H8xK8aBUZhnrN97yoZfHbjgw/bGUzfgKPJl69/mXDuSg8CbdBYBn6VVQdR947vWneQBFzdhasyzMUKg==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.41.tgz",
"integrity": "sha512-0LBBRwqnI0p4FgIkO9q2aJBBTKDSjzhnxrxHYengkAF6dMOjeAIZFDADAlcf2h3GDALWnblbeprYYpItiulSVQ==",
"dependencies": {
"@vue/reactivity": "3.2.40",
"@vue/shared": "3.2.40"
"@vue/reactivity": "3.2.41",
"@vue/shared": "3.2.41"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.40.tgz",
"integrity": "sha512-AO2HMQ+0s2+MCec8hXAhxMgWhFhOPJ/CyRXnmTJ6XIOnJFLrH5Iq3TNwvVcODGR295jy77I6dWPj+wvFoSYaww==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.41.tgz",
"integrity": "sha512-U7zYuR1NVIP8BL6jmOqmapRAHovEFp7CSw4pR2FacqewXNGqZaRfHoNLQsqQvVQ8yuZNZtxSZy0FFyC70YXPpA==",
"dependencies": {
"@vue/runtime-core": "3.2.40",
"@vue/shared": "3.2.40",
"@vue/runtime-core": "3.2.41",
"@vue/shared": "3.2.41",
"csstype": "^2.6.8"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.40.tgz",
"integrity": "sha512-gtUcpRwrXOJPJ4qyBpU3EyxQa4EkV8I4f8VrDePcGCPe4O/hd0BPS7v9OgjIQob6Ap8VDz9G+mGTKazE45/95w==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.41.tgz",
"integrity": "sha512-7YHLkfJdTlsZTV0ae5sPwl9Gn/EGr2hrlbcS/8naXm2CDpnKUwC68i1wGlrYAfIgYWL7vUZwk2GkYLQH5CvFig==",
"dependencies": {
"@vue/compiler-ssr": "3.2.40",
"@vue/shared": "3.2.40"
"@vue/compiler-ssr": "3.2.41",
"@vue/shared": "3.2.41"
},
"peerDependencies": {
"vue": "3.2.40"
"vue": "3.2.41"
}
},
"node_modules/@vue/shared": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.40.tgz",
"integrity": "sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ=="
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.41.tgz",
"integrity": "sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw=="
},
"node_modules/@vue/vue-loader-v15": {
"name": "vue-loader",
@ -1451,13 +1451,13 @@
"peer": true
},
"node_modules/@vueuse/core": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.3.0.tgz",
"integrity": "sha512-64Rna8IQDWpdrJxgitDg7yv1yTp41ZmvV8zlLEylK4QQLWAhz1OFGZDPZ8bU4lwcGgbEJ2sGi2jrdNh4LttUSQ==",
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.3.1.tgz",
"integrity": "sha512-xriyD+v3D2ObH/UtnkEl+1sbcLBVHNaZaLi/rqoNEe/B92hggDEFQIGXoQUjdRzYOjASHSezf9uCDtmd7LeWyA==",
"dependencies": {
"@types/web-bluetooth": "^0.0.15",
"@vueuse/metadata": "9.3.0",
"@vueuse/shared": "9.3.0",
"@types/web-bluetooth": "^0.0.16",
"@vueuse/metadata": "9.3.1",
"@vueuse/shared": "9.3.1",
"vue-demi": "*"
},
"funding": {
@ -1490,17 +1490,17 @@
}
},
"node_modules/@vueuse/metadata": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.3.0.tgz",
"integrity": "sha512-GnnfjbzIPJIh9ngL9s9oGU1+Hx/h5/KFqTfJykzh/1xjaHkedV9g0MASpdmPZIP+ynNhKAcEfA6g5i8KXwtoMA==",
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.3.1.tgz",
"integrity": "sha512-G1BPhtx3OHaL/y4OZBofh6Xt02G1VA9PuOO8nac9sTKMkMqfyez5VfkF3D9GUjSRNO7cVWyH4rceeGXfr2wdMg==",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.3.0.tgz",
"integrity": "sha512-caGUWLY0DpPC6l31KxeUy6vPVNA0yKxx81jFYLoMpyP6cF84FG5Dkf69DfSUqL57wX8JcUkJDMnQaQIZPWFEQQ==",
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.3.1.tgz",
"integrity": "sha512-YFu3qcnVeu0S2L4XdQJtBpDcjz6xwqHZtTv/XRhu66/yge1XVhxskUcc7VZbX52xF9A34V6KCfwncP9YDqYFiw==",
"dependencies": {
"vue-demi": "*"
},
@ -2307,9 +2307,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001418",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001418.tgz",
"integrity": "sha512-oIs7+JL3K9JRQ3jPZjlH6qyYDp+nBTCais7hjh0s+fuBwufc7uZ7hPYMXrDOJhV360KGMTcczMRObk0/iMqZRg==",
"version": "1.0.30001422",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001422.tgz",
"integrity": "sha512-hSesn02u1QacQHhaxl/kNMZwqVG35Sz/8DgvmgedxSH8z9UUpcDYSPYgsj3x5dQNRcNp6BwpSfQfVzYUTm+fog==",
"dev": true,
"funding": [
{
@ -3561,15 +3561,15 @@
"peer": true
},
"node_modules/electron-to-chromium": {
"version": "1.4.279",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.279.tgz",
"integrity": "sha512-xs7vEuSZ84+JsHSTFqqG0TE3i8EAivHomRQZhhcRvsmnjsh5C2KdhwNKf4ZRYtzq75wojpFyqb62m32Oam57wA==",
"version": "1.4.284",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
"integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==",
"dev": true
},
"node_modules/element-plus": {
"version": "2.2.17",
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.2.17.tgz",
"integrity": "sha512-MGwMIE/q+FFD3kgS23x8HIe5043tmD1cTRwjhIX9o6fim1avFnUkrsfYRvybbz4CkyqSb185EheZS5AUPpXh2g==",
"version": "2.2.18",
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.2.18.tgz",
"integrity": "sha512-2pK2zmVOwP14eFl3rGoR+3BWJwDyO+DZCvzjQ8L6qjUR+hVKwFhgxIcSkKJatbcHFw5Xui6UyN20jV+gQP7mLg==",
"dependencies": {
"@ctrl/tinycolor": "^3.4.1",
"@element-plus/icons-vue": "^2.0.6",
@ -5193,9 +5193,9 @@
}
},
"node_modules/is-core-module": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz",
"integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==",
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
"dev": true,
"dependencies": {
"has": "^1.0.3"
@ -6865,9 +6865,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.17",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.17.tgz",
"integrity": "sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==",
"version": "8.4.18",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
"integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
"funding": [
{
"type": "opencollective",
@ -8180,10 +8180,13 @@
}
},
"node_modules/shell-quote": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz",
"integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
"dev": true
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.4.tgz",
"integrity": "sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel": {
"version": "1.0.4",
@ -8886,9 +8889,9 @@
}
},
"node_modules/ufo": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-0.8.5.tgz",
"integrity": "sha512-e4+UtA5IRO+ha6hYklwj6r7BjiGMxS0O+UaSg9HbaTefg4kMkzj4tXzEBajRR+wkxf+golgAWKzLbytCUDMJAA==",
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-0.8.6.tgz",
"integrity": "sha512-fk6CmUgwKCfX79EzcDQQpSCMxrHstvbLswFChHS0Vump+kFkw7nJBfTZoC1j0bOGoY9I7R3n2DGek5ajbcYnOw==",
"dev": true
},
"node_modules/unimport": {
@ -9184,15 +9187,15 @@
}
},
"node_modules/vue": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.40.tgz",
"integrity": "sha512-1mGHulzUbl2Nk3pfvI5aXYYyJUs1nm4kyvuz38u4xlQkLUn1i2R7nDbI4TufECmY8v1qNBHYy62bCaM+3cHP2A==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.41.tgz",
"integrity": "sha512-uuuvnrDXEeZ9VUPljgHkqB5IaVO8SxhPpqF2eWOukVrBnRBx2THPSGQBnVRt0GrIG1gvCmFXMGbd7FqcT1ixNQ==",
"dependencies": {
"@vue/compiler-dom": "3.2.40",
"@vue/compiler-sfc": "3.2.40",
"@vue/runtime-dom": "3.2.40",
"@vue/server-renderer": "3.2.40",
"@vue/shared": "3.2.40"
"@vue/compiler-dom": "3.2.41",
"@vue/compiler-sfc": "3.2.41",
"@vue/runtime-dom": "3.2.41",
"@vue/server-renderer": "3.2.41",
"@vue/shared": "3.2.41"
}
},
"node_modules/vue-clipboard3": {
@ -10074,21 +10077,21 @@
"dev": true
},
"@babel/core": {
"version": "7.19.3",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.3.tgz",
"integrity": "sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==",
"version": "7.19.6",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.6.tgz",
"integrity": "sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==",
"dev": true,
"requires": {
"@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.19.3",
"@babel/generator": "^7.19.6",
"@babel/helper-compilation-targets": "^7.19.3",
"@babel/helper-module-transforms": "^7.19.0",
"@babel/helpers": "^7.19.0",
"@babel/parser": "^7.19.3",
"@babel/helper-module-transforms": "^7.19.6",
"@babel/helpers": "^7.19.4",
"@babel/parser": "^7.19.6",
"@babel/template": "^7.18.10",
"@babel/traverse": "^7.19.3",
"@babel/types": "^7.19.3",
"@babel/traverse": "^7.19.6",
"@babel/types": "^7.19.4",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@ -10097,9 +10100,9 @@
}
},
"@babel/generator": {
"version": "7.19.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.5.tgz",
"integrity": "sha512-DxbNz9Lz4aMZ99qPpO1raTbcrI1ZeYh+9NR9qhfkQIbFtVEqotHojEBxHzmxhVONkGt6VyrqVQcgpefMy9pqcg==",
"version": "7.19.6",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.6.tgz",
"integrity": "sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==",
"dev": true,
"requires": {
"@babel/types": "^7.19.4",
@ -10167,19 +10170,19 @@
}
},
"@babel/helper-module-transforms": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz",
"integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==",
"version": "7.19.6",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz",
"integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==",
"dev": true,
"requires": {
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-module-imports": "^7.18.6",
"@babel/helper-simple-access": "^7.18.6",
"@babel/helper-simple-access": "^7.19.4",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/helper-validator-identifier": "^7.18.6",
"@babel/helper-validator-identifier": "^7.19.1",
"@babel/template": "^7.18.10",
"@babel/traverse": "^7.19.0",
"@babel/types": "^7.19.0"
"@babel/traverse": "^7.19.6",
"@babel/types": "^7.19.4"
}
},
"@babel/helper-simple-access": {
@ -10241,9 +10244,9 @@
}
},
"@babel/parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.4.tgz",
"integrity": "sha512-qpVT7gtuOLjWeDTKLkJ6sryqLliBaFpAtGeqw5cs5giLldvh+Ch0plqnUMKoVAUS6ZEueQQiZV+p5pxtPitEsA=="
"version": "7.19.6",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.6.tgz",
"integrity": "sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA=="
},
"@babel/template": {
"version": "7.18.10",
@ -10257,18 +10260,18 @@
}
},
"@babel/traverse": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.4.tgz",
"integrity": "sha512-w3K1i+V5u2aJUOXBFFC5pveFLmtq1s3qcdDNC2qRI6WPBQIDaKFqXxDEqDO/h1dQ3HjsZoZMyIy6jGLq0xtw+g==",
"version": "7.19.6",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.6.tgz",
"integrity": "sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.19.4",
"@babel/generator": "^7.19.6",
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-function-name": "^7.19.0",
"@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/parser": "^7.19.4",
"@babel/parser": "^7.19.6",
"@babel/types": "^7.19.4",
"debug": "^4.1.0",
"globals": "^11.1.0"
@ -10309,9 +10312,9 @@
"integrity": "sha512-bO37brCPfteXQfFY0DyNDGB3+IMe4j150KFQcgJ5aBP295p9nBGeHEs/p0czrRbtlHq4Px/yoPXO/+dOCcF4uA=="
},
"@floating-ui/dom": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.0.2.tgz",
"integrity": "sha512-5X9WSvZ8/fjy3gDu8yx9HAA4KG1lazUN2P4/VnaXLxTO9Dz53HI1oYoh1OlhqFNlHgGDiwFX5WhFCc2ljbW3yA==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.0.3.tgz",
"integrity": "sha512-6H1kwjkOZKabApNtXRiYHvMmYJToJ1DV7rQ3xc/WJpOABhQIOJJOdz2AOejj8X+gcybaFmBpisVTZxBZAM3V0w==",
"requires": {
"@floating-ui/core": "^1.0.1"
}
@ -10383,9 +10386,9 @@
"dev": true
},
"@jridgewell/trace-mapping": {
"version": "0.3.16",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.16.tgz",
"integrity": "sha512-LCQ+NeThyJ4k1W2d+vIKdxuSt9R3pQSZ4P92m7EakaYuXcVWbHuT5bjNcqLd4Rdgi6xYWYDvBJZJLZSLanjDcA==",
"version": "0.3.17",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
"integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
"dev": true,
"requires": {
"@jridgewell/resolve-uri": "3.1.0",
@ -10604,9 +10607,9 @@
}
},
"@types/eslint": {
"version": "8.4.6",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz",
"integrity": "sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==",
"version": "8.4.7",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.7.tgz",
"integrity": "sha512-ehM7cCt2RSFs42mb+lcmhFT9ouIlV92PuaeRGn8N8c98oMjG4Z5pJHA9b1QiCcuqnbPSHcyfiD3mlhqMaHsQIw==",
"dev": true,
"requires": {
"@types/estree": "*",
@ -10705,9 +10708,9 @@
"peer": true
},
"@types/node": {
"version": "18.8.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.4.tgz",
"integrity": "sha512-WdlVphvfR/GJCLEMbNA8lJ0lhFNBj4SW3O+O5/cEGw9oYrv0al9zTwuQsq+myDUXgNx2jgBynoVgZ2MMJ6pbow==",
"version": "18.11.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.2.tgz",
"integrity": "sha512-BWN3M23gLO2jVG8g/XHIRFWiiV4/GckeFIqbU/C4V3xpoBBWSMk4OZomouN0wCkfQFPqgZikyLr7DOYDysIkkw==",
"dev": true
},
"@types/normalize-package-data": {
@ -10775,9 +10778,9 @@
}
},
"@types/web-bluetooth": {
"version": "0.0.15",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.15.tgz",
"integrity": "sha512-w7hEHXnPMEZ+4nGKl/KDRVpxkwYxYExuHOYXyzIzCDzEZ9ZCGMAewulr9IqJu2LR4N37fcnb1XVeuZ09qgOxhA=="
"version": "0.0.16",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
"integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ=="
},
"@types/webpack-env": {
"version": "1.18.0",
@ -10989,36 +10992,36 @@
}
},
"@vue/compiler-core": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.40.tgz",
"integrity": "sha512-2Dc3Stk0J/VyQ4OUr2yEC53kU28614lZS+bnrCbFSAIftBJ40g/2yQzf4mPBiFuqguMB7hyHaujdgZAQ67kZYA==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.41.tgz",
"integrity": "sha512-oA4mH6SA78DT+96/nsi4p9DX97PHcNROxs51lYk7gb9Z4BPKQ3Mh+BLn6CQZBw857Iuhu28BfMSRHAlPvD4vlw==",
"requires": {
"@babel/parser": "^7.16.4",
"@vue/shared": "3.2.40",
"@vue/shared": "3.2.41",
"estree-walker": "^2.0.2",
"source-map": "^0.6.1"
}
},
"@vue/compiler-dom": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.40.tgz",
"integrity": "sha512-OZCNyYVC2LQJy4H7h0o28rtk+4v+HMQygRTpmibGoG9wZyomQiS5otU7qo3Wlq5UfHDw2RFwxb9BJgKjVpjrQw==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.41.tgz",
"integrity": "sha512-xe5TbbIsonjENxJsYRbDJvthzqxLNk+tb3d/c47zgREDa/PCp6/Y4gC/skM4H6PIuX5DAxm7fFJdbjjUH2QTMw==",
"requires": {
"@vue/compiler-core": "3.2.40",
"@vue/shared": "3.2.40"
"@vue/compiler-core": "3.2.41",
"@vue/shared": "3.2.41"
}
},
"@vue/compiler-sfc": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.40.tgz",
"integrity": "sha512-tzqwniIN1fu1PDHC3CpqY/dPCfN/RN1thpBC+g69kJcrl7mbGiHKNwbA6kJ3XKKy8R6JLKqcpVugqN4HkeBFFg==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.41.tgz",
"integrity": "sha512-+1P2m5kxOeaxVmJNXnBskAn3BenbTmbxBxWOtBq3mQTCokIreuMULFantBUclP0+KnzNCMOvcnKinqQZmiOF8w==",
"requires": {
"@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.40",
"@vue/compiler-dom": "3.2.40",
"@vue/compiler-ssr": "3.2.40",
"@vue/reactivity-transform": "3.2.40",
"@vue/shared": "3.2.40",
"@vue/compiler-core": "3.2.41",
"@vue/compiler-dom": "3.2.41",
"@vue/compiler-ssr": "3.2.41",
"@vue/reactivity-transform": "3.2.41",
"@vue/shared": "3.2.41",
"estree-walker": "^2.0.2",
"magic-string": "^0.25.7",
"postcss": "^8.1.10",
@ -11036,12 +11039,12 @@
}
},
"@vue/compiler-ssr": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.40.tgz",
"integrity": "sha512-80cQcgasKjrPPuKcxwuCx7feq+wC6oFl5YaKSee9pV3DNq+6fmCVwEEC3vvkf/E2aI76rIJSOYHsWSEIxK74oQ==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.41.tgz",
"integrity": "sha512-Y5wPiNIiaMz/sps8+DmhaKfDm1xgj6GrH99z4gq2LQenfVQcYXmHIOBcs5qPwl7jaW3SUQWjkAPKMfQemEQZwQ==",
"requires": {
"@vue/compiler-dom": "3.2.40",
"@vue/shared": "3.2.40"
"@vue/compiler-dom": "3.2.41",
"@vue/shared": "3.2.41"
}
},
"@vue/component-compiler-utils": {
@ -11108,21 +11111,21 @@
}
},
"@vue/reactivity": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.40.tgz",
"integrity": "sha512-N9qgGLlZmtUBMHF9xDT4EkD9RdXde1Xbveb+niWMXuHVWQP5BzgRmE3SFyUBBcyayG4y1lhoz+lphGRRxxK4RA==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.41.tgz",
"integrity": "sha512-9JvCnlj8uc5xRiQGZ28MKGjuCoPhhTwcoAdv3o31+cfGgonwdPNuvqAXLhlzu4zwqavFEG5tvaoINQEfxz+l6g==",
"requires": {
"@vue/shared": "3.2.40"
"@vue/shared": "3.2.41"
}
},
"@vue/reactivity-transform": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.40.tgz",
"integrity": "sha512-HQUCVwEaacq6fGEsg2NUuGKIhUveMCjOk8jGHqLXPI2w6zFoPrlQhwWEaINTv5kkZDXKEnCijAp+4gNEHG03yw==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.41.tgz",
"integrity": "sha512-mK5+BNMsL4hHi+IR3Ft/ho6Za+L3FA5j8WvreJ7XzHrqkPq8jtF/SMo7tuc9gHjLDwKZX1nP1JQOKo9IEAn54A==",
"requires": {
"@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.40",
"@vue/shared": "3.2.40",
"@vue/compiler-core": "3.2.41",
"@vue/shared": "3.2.41",
"estree-walker": "^2.0.2",
"magic-string": "^0.25.7"
},
@ -11138,37 +11141,37 @@
}
},
"@vue/runtime-core": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.40.tgz",
"integrity": "sha512-U1+rWf0H8xK8aBUZhnrN97yoZfHbjgw/bGUzfgKPJl69/mXDuSg8CbdBYBn6VVQdR947vWneQBFzdhasyzMUKg==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.41.tgz",
"integrity": "sha512-0LBBRwqnI0p4FgIkO9q2aJBBTKDSjzhnxrxHYengkAF6dMOjeAIZFDADAlcf2h3GDALWnblbeprYYpItiulSVQ==",
"requires": {
"@vue/reactivity": "3.2.40",
"@vue/shared": "3.2.40"
"@vue/reactivity": "3.2.41",
"@vue/shared": "3.2.41"
}
},
"@vue/runtime-dom": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.40.tgz",
"integrity": "sha512-AO2HMQ+0s2+MCec8hXAhxMgWhFhOPJ/CyRXnmTJ6XIOnJFLrH5Iq3TNwvVcODGR295jy77I6dWPj+wvFoSYaww==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.41.tgz",
"integrity": "sha512-U7zYuR1NVIP8BL6jmOqmapRAHovEFp7CSw4pR2FacqewXNGqZaRfHoNLQsqQvVQ8yuZNZtxSZy0FFyC70YXPpA==",
"requires": {
"@vue/runtime-core": "3.2.40",
"@vue/shared": "3.2.40",
"@vue/runtime-core": "3.2.41",
"@vue/shared": "3.2.41",
"csstype": "^2.6.8"
}
},
"@vue/server-renderer": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.40.tgz",
"integrity": "sha512-gtUcpRwrXOJPJ4qyBpU3EyxQa4EkV8I4f8VrDePcGCPe4O/hd0BPS7v9OgjIQob6Ap8VDz9G+mGTKazE45/95w==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.41.tgz",
"integrity": "sha512-7YHLkfJdTlsZTV0ae5sPwl9Gn/EGr2hrlbcS/8naXm2CDpnKUwC68i1wGlrYAfIgYWL7vUZwk2GkYLQH5CvFig==",
"requires": {
"@vue/compiler-ssr": "3.2.40",
"@vue/shared": "3.2.40"
"@vue/compiler-ssr": "3.2.41",
"@vue/shared": "3.2.41"
}
},
"@vue/shared": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.40.tgz",
"integrity": "sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ=="
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.41.tgz",
"integrity": "sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw=="
},
"@vue/vue-loader-v15": {
"version": "npm:vue-loader@15.10.0",
@ -11201,13 +11204,13 @@
"peer": true
},
"@vueuse/core": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.3.0.tgz",
"integrity": "sha512-64Rna8IQDWpdrJxgitDg7yv1yTp41ZmvV8zlLEylK4QQLWAhz1OFGZDPZ8bU4lwcGgbEJ2sGi2jrdNh4LttUSQ==",
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.3.1.tgz",
"integrity": "sha512-xriyD+v3D2ObH/UtnkEl+1sbcLBVHNaZaLi/rqoNEe/B92hggDEFQIGXoQUjdRzYOjASHSezf9uCDtmd7LeWyA==",
"requires": {
"@types/web-bluetooth": "^0.0.15",
"@vueuse/metadata": "9.3.0",
"@vueuse/shared": "9.3.0",
"@types/web-bluetooth": "^0.0.16",
"@vueuse/metadata": "9.3.1",
"@vueuse/shared": "9.3.1",
"vue-demi": "*"
},
"dependencies": {
@ -11220,14 +11223,14 @@
}
},
"@vueuse/metadata": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.3.0.tgz",
"integrity": "sha512-GnnfjbzIPJIh9ngL9s9oGU1+Hx/h5/KFqTfJykzh/1xjaHkedV9g0MASpdmPZIP+ynNhKAcEfA6g5i8KXwtoMA=="
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.3.1.tgz",
"integrity": "sha512-G1BPhtx3OHaL/y4OZBofh6Xt02G1VA9PuOO8nac9sTKMkMqfyez5VfkF3D9GUjSRNO7cVWyH4rceeGXfr2wdMg=="
},
"@vueuse/shared": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.3.0.tgz",
"integrity": "sha512-caGUWLY0DpPC6l31KxeUy6vPVNA0yKxx81jFYLoMpyP6cF84FG5Dkf69DfSUqL57wX8JcUkJDMnQaQIZPWFEQQ==",
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.3.1.tgz",
"integrity": "sha512-YFu3qcnVeu0S2L4XdQJtBpDcjz6xwqHZtTv/XRhu66/yge1XVhxskUcc7VZbX52xF9A34V6KCfwncP9YDqYFiw==",
"requires": {
"vue-demi": "*"
},
@ -11849,9 +11852,9 @@
}
},
"caniuse-lite": {
"version": "1.0.30001418",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001418.tgz",
"integrity": "sha512-oIs7+JL3K9JRQ3jPZjlH6qyYDp+nBTCais7hjh0s+fuBwufc7uZ7hPYMXrDOJhV360KGMTcczMRObk0/iMqZRg==",
"version": "1.0.30001422",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001422.tgz",
"integrity": "sha512-hSesn02u1QacQHhaxl/kNMZwqVG35Sz/8DgvmgedxSH8z9UUpcDYSPYgsj3x5dQNRcNp6BwpSfQfVzYUTm+fog==",
"dev": true
},
"case-sensitive-paths-webpack-plugin": {
@ -12784,15 +12787,15 @@
"peer": true
},
"electron-to-chromium": {
"version": "1.4.279",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.279.tgz",
"integrity": "sha512-xs7vEuSZ84+JsHSTFqqG0TE3i8EAivHomRQZhhcRvsmnjsh5C2KdhwNKf4ZRYtzq75wojpFyqb62m32Oam57wA==",
"version": "1.4.284",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
"integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==",
"dev": true
},
"element-plus": {
"version": "2.2.17",
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.2.17.tgz",
"integrity": "sha512-MGwMIE/q+FFD3kgS23x8HIe5043tmD1cTRwjhIX9o6fim1avFnUkrsfYRvybbz4CkyqSb185EheZS5AUPpXh2g==",
"version": "2.2.18",
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.2.18.tgz",
"integrity": "sha512-2pK2zmVOwP14eFl3rGoR+3BWJwDyO+DZCvzjQ8L6qjUR+hVKwFhgxIcSkKJatbcHFw5Xui6UyN20jV+gQP7mLg==",
"requires": {
"@ctrl/tinycolor": "^3.4.1",
"@element-plus/icons-vue": "^2.0.6",
@ -13938,9 +13941,9 @@
}
},
"is-core-module": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz",
"integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==",
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
"dev": true,
"requires": {
"has": "^1.0.3"
@ -15224,9 +15227,9 @@
}
},
"postcss": {
"version": "8.4.17",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.17.tgz",
"integrity": "sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==",
"version": "8.4.18",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
"integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
"requires": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
@ -16155,9 +16158,9 @@
"dev": true
},
"shell-quote": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz",
"integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.4.tgz",
"integrity": "sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==",
"dev": true
},
"side-channel": {
@ -16688,9 +16691,9 @@
"peer": true
},
"ufo": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-0.8.5.tgz",
"integrity": "sha512-e4+UtA5IRO+ha6hYklwj6r7BjiGMxS0O+UaSg9HbaTefg4kMkzj4tXzEBajRR+wkxf+golgAWKzLbytCUDMJAA==",
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-0.8.6.tgz",
"integrity": "sha512-fk6CmUgwKCfX79EzcDQQpSCMxrHstvbLswFChHS0Vump+kFkw7nJBfTZoC1j0bOGoY9I7R3n2DGek5ajbcYnOw==",
"dev": true
},
"unimport": {
@ -16873,15 +16876,15 @@
}
},
"vue": {
"version": "3.2.40",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.40.tgz",
"integrity": "sha512-1mGHulzUbl2Nk3pfvI5aXYYyJUs1nm4kyvuz38u4xlQkLUn1i2R7nDbI4TufECmY8v1qNBHYy62bCaM+3cHP2A==",
"version": "3.2.41",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.41.tgz",
"integrity": "sha512-uuuvnrDXEeZ9VUPljgHkqB5IaVO8SxhPpqF2eWOukVrBnRBx2THPSGQBnVRt0GrIG1gvCmFXMGbd7FqcT1ixNQ==",
"requires": {
"@vue/compiler-dom": "3.2.40",
"@vue/compiler-sfc": "3.2.40",
"@vue/runtime-dom": "3.2.40",
"@vue/server-renderer": "3.2.40",
"@vue/shared": "3.2.40"
"@vue/compiler-dom": "3.2.41",
"@vue/compiler-sfc": "3.2.41",
"@vue/runtime-dom": "3.2.41",
"@vue/server-renderer": "3.2.41",
"@vue/shared": "3.2.41"
}
},
"vue-clipboard3": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -30,6 +30,7 @@
<PortForward v-if="global.currentPage.value=='#portforward'"></PortForward>
<PortForwardSet v-if="global.currentPage.value=='#portforwardset'"></PortForwardSet>
<WOL v-if="global.currentPage.value=='#wol'"></WOL>
<WOLServiceSet v-if="global.currentPage.value=='#wolserviceset'"></WOLServiceSet>
</el-main>
</el-container>

View File

@ -562,6 +562,16 @@ export function apiWOLDeviceWakeUp(key) {
})
}
export function apiWOLDeviceShutDown(key) {
return httpRequest({
url: '/api/wol/device/shutdown',
method: 'get',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf(),key:key}
})
}
export function apiAlterWOLDevice(data) {
return httpRequest({
url: '/api/wol/device',
@ -569,4 +579,44 @@ export function apiAlterWOLDevice(data) {
headers:{'Authorization':GetToken()},
data:data
})
}
export function apiGetWOLServiceConfigure() {
return httpRequest({
url: '/api/wol/service/configure',
method: 'get',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf()}
})
}
export function apiAlterWOLServiceConfigure(data) {
return httpRequest({
url: '/api/wol/service/configure',
method: 'put',
headers:{'Authorization':GetToken()},
data:data,
params:{_:new Date().valueOf()}
})
}
export function apiGetIPv4Interface() {
return httpRequest({
url: 'api/wol/service/getipv4interface',
method: 'get',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf()}
})
}
export function apiOptionsLuckyService(option) {
return httpRequest({
url: '/api/lucky/service',
method: 'put',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf(),option:option}
})
}

View File

@ -3,7 +3,7 @@
.PageRadius {
height: 90vh;
width: 100%;
max-width: 1300px;
max-width: 1600px;
border: 1px solid var(--el-border-color);
border-radius: 0;
margin: 20px

View File

@ -65,6 +65,19 @@
</el-form-item>
</div>
<div class="AdminListenDivRadius">
<p>全局设置</p>
<el-form-item label="Http(s) 客户端 安全证书验证" id="adminListen">
<el-switch v-model="form.HttpClientSecureVerify" class="mb-1" inline-prompt
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949" width="50px"
active-text="启用" inactive-text="禁用" />
</el-form-item>
<el-form-item label="HttpClient timeout(秒)" >
<el-input-number v-model="form.HttpClientTimeout" autocomplete="off" :min="1" :max="60" />
</el-form-item>
</div>
@ -189,6 +202,8 @@ const rawData = {
// GlobalMaxConnections: 1,
AllowInternetaccess: false,
LogMaxSize:1024,
HttpClientSecureVerify:false,
HttpClientTimeout:20,
}
const form = ref(rawData)

View File

@ -113,11 +113,13 @@
<template #title>网络唤醒设备列表</template>
</el-menu-item>
<el-menu-item index="#wolset">
<el-menu-item index="#wolserviceset">
<el-icon>
<setting />
</el-icon>
<template #title>网络唤醒设置</template>
<template #title>唤醒/关机服务设置</template>
</el-menu-item>
</el-sub-menu>

View File

@ -1033,6 +1033,10 @@ onMounted(() => {
})
onUnmounted(() => {
clearInterval(timerID)
})
</script>

View File

@ -154,7 +154,6 @@ const RequestAlterPortForwardConfigure = () => {
onMounted(() => {
queryPortForwardsConfigure()
})
</script>

View File

@ -7,90 +7,111 @@
<div class="itemradius" :style="{
borderRadius: 'base',
}" v-for="device in deviceList" >
}">
<el-descriptions :column="4" border >
<el-descriptions :column="4" border>
<div v-for="device in deviceList">
<el-descriptions-item label="设备" label-class-name="deviceNamelabelClass"
class-name="deviceNamecontentClass">
<el-button size="small" v-show="true">
{{ device.DeviceName == '' ? '未命名设备' : device.DeviceName }}
</el-button>
<el-descriptions-item label="设备操作">
<el-tooltip placement="bottom" effect="dark" :trigger-keys="[]" content="">
<template #content>
唤醒<br />
</template>
<el-button size="small" :icon="Bell" circle type="success" @click="wakeup(device)">
</el-button>
</el-tooltip>
<template #content>
<span v-html="deviceOnlieDeviceToBrHtml(device)"></span>
</template>
<el-button size="small" v-show="true">
{{device.State}}
</el-button>
</el-tooltip>
<el-tooltip placement="bottom" effect="dark" :trigger-keys="[]" content="">
<template #content>
关机<br />
</template>
<el-button size="small" :icon="SwitchButton" circle type="danger">
</el-button>
</el-tooltip>
&nbsp; &nbsp;
<el-button size="small" type="primary" @click="showAlterDeviceDialog(device)">
编辑
</el-button>
<el-button size="small" type="danger" @click="deleteDevice(device)">
删除
</el-button>
</el-descriptions-item>
<el-descriptions-item label="设备名称" >
<el-button size="default" v-show="true">
{{ device.DeviceName == '' ? '未命名设备' : device.DeviceName }}
</el-button>
</el-descriptions-item>
<el-descriptions-item label="操作" label-class-name="deviceOptlabelClass"
class-name="deviceOptcontentClass">
<el-descriptions-item label="设备MAC">
<el-tooltip placement="bottom" effect="dark" :trigger-keys="[]" content="">
<template #content>
<span v-html="StrArrayListToBrHtml(device.MacList)"></span>
</template>
<el-button size="small" v-show="true">
{{device.MacList.length==1?device.MacList[0]:device.MacList[0]+'...' }}
</el-button>
</el-tooltip>
</el-descriptions-item>
<el-descriptions-item label="魔法包广播地址">
<el-tooltip placement="bottom" effect="dark" :trigger-keys="[]" content="">
<template #content>
<span v-html="StrArrayListToBrHtml(device.BroadcastIPs)"></span>
</template>
<el-button size="small" v-show="true">
{{device.BroadcastIPs.length==1?device.BroadcastIPs[0]:device.BroadcastIPs[0]+'...' }}
</el-button>
</el-tooltip>
<el-tooltip class="box-item" effect="dark" :trigger-keys="[]" content="">
<template #content>
端口<br />
</template>
<el-button size="small" v-show="true">
{{device.Port}}
<el-button size="small" type="success" @click="wakeup(device)">
唤醒
</el-button>
</el-tooltip>
</el-descriptions-item>
<el-tooltip placement="bottom" effect="dark" :trigger-keys="[]" content="">
<template #content>
关机<br />
</template>
<el-button size="small" type="danger" @click="shutdown(device)"
:disabled="avalidShutDownButton(device.State)">
关机
</el-button>
</el-tooltip>
&nbsp; &nbsp;
<el-divider direction="vertical" />
&nbsp; &nbsp;
<el-button size="small" type="primary" @click="showAlterDeviceDialog(device)">
编辑
</el-button>
<el-button size="small" type="danger" @click="deleteDevice(device)">
删除
</el-button>
</el-descriptions-item>
<el-descriptions-item label="物理网卡地址" label-class-name="deviceMaclabelClass"
class-name="deviceMaccontentClass">
<el-tooltip placement="bottom" effect="dark" :trigger-keys="[]" content="">
<template #content>
物理网卡地址<br />
<span v-html="StrArrayListToBrHtml(device.MacList)"></span>
<br />
魔法包地址<br />
<span v-html="StrArrayListToBrHtml(device.BroadcastIPs)"></span>
<br />
端口: {{device.Port}} <br />
</template>
<el-button size="small" v-show="true">
{{device.MacList.length==1?device.MacList[0]:device.MacList[0]+'...' }}
</el-button>
</el-tooltip>
</el-descriptions-item>
<el-descriptions-item label="物联网平台" label-class-name="deviceIOTlabelClass"
class-name="deviceIOTcontentClass">
<el-tooltip placement="bottom" effect="dark" :trigger-keys="[]" content="">
<template #content>
<span v-html="showIOT_DianDengInfo(device)"></span>
</template>
<el-button size="small" v-show="device.IOT_DianDeng_Enable"
:type="showIOT_DianDengColor(device)">
点灯科技
</el-button>
</el-tooltip>
<el-tooltip placement="bottom" effect="dark" :trigger-keys="[]" content="">
<template #content>
<span v-html="showIOT_BemfaInfo(device)"></span>
</template>
<el-button size="small" v-show="device.IOT_Bemfa_Enable"
:type="showIOT_BemfaColor(device)">
巴法云
</el-button>
</el-tooltip>
</el-descriptions-item>
</div>
</el-descriptions>
@ -107,7 +128,7 @@
</el-affix>
<el-dialog v-if="deviceDialogShow" v-model="deviceDialogShow" :title=deviceDialogTitle draggable
:show-close="false" :close-on-click-modal="false" width="400px">
:show-close="true" :close-on-click-modal="false" width="400px">
<el-form-item label="设备名称" label-width="120px">
<el-input v-model="deviceForm.DeviceName" autocomplete="off" />
</el-form-item>
@ -176,6 +197,89 @@
</el-form-item>
</el-tooltip>
<div class="divRadius">
<p>第三方物联网平台对接-仅支持语音助手控制</p>
<div class="divIOTRadius">
<el-form-item label="点灯科技" label-width="120px" v-if="true">
<el-switch v-model="deviceForm.IOT_DianDeng_Enable" inline-prompt width="50px" active-text="启用"
inactive-text="禁用" />
</el-form-item>
<div v-show="deviceForm.IOT_DianDeng_Enable">
<el-tooltip class="box-item" effect="dark" :trigger-keys="[]" content="">
<template #content>
留空表示不启用该平台对接功能<br />
<br />
点灯科技官网:https://www.diandeng.tech/home<br />
<br />
在手机APP端注册登录-设备管理菜单-右上方+号按钮-添加点灯独立设备-选择网络接入-复制保存-secrte-key到下方<br />
目前仅支持点灯-独立设备类型,在设备管理修改设备名,这里的设备名就是你以后用语音助手控制操作设备时的名称.<br />
<br />
以小爱同学为例,在米家APP右下角"我的"-"其它平台设备"-右上方"添加"-选择"点灯科技"登录同步后即可.<br />
需要确保点灯secrte-key填写正确,在点灯APP已经显示设备在线再在米家执行同步,每次修改完设备名都要重新同步.<br />
小度/天猫精灵自行参考文档:https://www.diandeng.tech/doc/voice-assistant <br />
<br />
多个设备可以设置同一个secrte-key,表示多个待唤醒设备与同一个点灯设备绑定,一次语音操作同时控制多个设备的开关.不建议这样做.<br />
建议一个待唤醒设备对应一个点灯设备<br />
<br />
由于点灯接口偶尔发生变化,又不提供相关文档,所以哪天突然不能使用了也很正常.<br />
</template>
<el-form-item label-width="120px" label="设备密钥">
<el-input v-model="deviceForm.IOT_DianDeng_AUTHKEY" placeholder="设备密钥" type="text"
wrap="off">
</el-input>
</el-form-item>
</el-tooltip>
</div>
</div>
<div class="divIOTRadius">
<el-form-item label="巴法云" label-width="120px" v-if="true">
<el-switch v-model="deviceForm.IOT_Bemfa_Enable" inline-prompt width="50px" active-text="启用"
inactive-text="禁用" />
</el-form-item>
<div v-show="deviceForm.IOT_Bemfa_Enable">
<el-tooltip class="box-item" effect="dark" :trigger-keys="[]" content="">
<template #content>
留空表示不启用该平台对接功能<br />
<br />
巴法云官网:https://cloud.bemfa.com/<br />
<br />
注册登录后,在控制台右上方可查看私钥.<br />
目前仅支持MQTT设备云,记得创建的是MQTT设备云主题,而不是TCP创客云主题.主题名要以001结尾表示插座类型.<br />
<br />
以小爱同学为例,在米家APP右下角"我的"-"其它平台设备"-右上方"添加"-选择"巴法"登录同步后即可.<br />
需要确保私钥和主题填写正确,在控制台已经显示订阅设备在线再在米家执行同步,主题更多设置的右上方可修改昵称(在米家等平台显示的名称),每次修改完设备名都要重新同步.<br />
<br />
多个设备可以设置同一个主题,表示多个待唤醒设备与同一个主题绑定,一次语音操作同时控制多个设备的开关.不建议这样做.<br />
建议一个待唤醒设备对应一个主题<br />
<br />
<br />
</template>
<el-form-item label-width="120px" label="私钥">
<el-input v-model="deviceForm.IOT_Bemfa_SecretKey" placeholder="设备密钥" type="text"
wrap="off">
</el-input>
</el-form-item>
</el-tooltip>
<el-form-item label-width="120px" label="主题">
<el-input v-model="deviceForm.IOT_Bemfa_Topic" placeholder="订阅主题" type="text" wrap="off">
</el-input>
</el-form-item>
</div>
</div>
</div>
@ -193,11 +297,11 @@
</template>
<script lang="ts" setup>
import { ref, onMounted, computed } from 'vue'
import { ref, onMounted, onUnmounted, computed } from 'vue'
import { ElMessageBox } from 'element-plus'
import { MessageShow } from '../../utils/ui'
import { StrArrayListToBrHtml, StrArrayListToArea, StringToArrayList } from '../../utils/utils'
import { GetToken, apiGetWOLDeviceList, apiAddWOLDevice, apiDeleteWOLDevice, apiAlterWOLDevice,apiWOLDeviceWakeUp } from '../../apis/utils'
import { GetToken, apiGetWOLDeviceList, apiAddWOLDevice, apiDeleteWOLDevice, apiAlterWOLDevice, apiWOLDeviceWakeUp, apiWOLDeviceShutDown } from '../../apis/utils'
import {
SwitchButton,
@ -205,6 +309,16 @@ import {
Bell,
} from '@element-plus/icons-vue'
const cellclass = {
"min-width": "50px",
"word-break": "keep-all"
}
const CS = {
"min-width": "300px",
"word-break": "break-all"
}
const deviceDialogShow = ref(false)
const deviceDialogTitle = ref("")
const deviceDialogCommitButtonText = ref("")
@ -212,6 +326,51 @@ const deviceFormMacListArea = ref("")
const deviceFormBroadcastIPsArea = ref("")
const deviceFormActionType = ref("")
const showIOT_DianDengInfo = (device) => {
var res = ""
if (device.DianDengClientState == "未设置") {
res = "未设置"
return res
}
res += '设备密钥:' + device.IOT_DianDeng_AUTHKEY + '<br/>'
res += '服务器连接状态:' + device.DianDengClientState + ' <br/>'
if (device.DianDengClientState == "已连接") {
res += '支持小爱同学 小度 天猫精灵<br/>'
}
return res
}
const showIOT_BemfaInfo = (device) => {
var res = ""
if (device.BemfaClientState == "未设置") {
res = "未设置"
return res
}
res += '私钥:' + device.IOT_Bemfa_SecretKey + '<br/>'
res += '订阅主题:' + device.IOT_Bemfa_Topic + '<br/>'
res += '服务器连接状态:' + device.BemfaClientState + ' <br/>'
if (device.BemfaClientState == "已连接") {
res += '支持小爱同学 小度 天猫精灵 google语音 AmazonAlexa<br/>'
}
return res
}
const showIOT_DianDengColor = (device) => {
if (device.DianDengClientState == "已连接") {
return "success"
}
return "info"
}
const showIOT_BemfaColor = (device) => {
if (device.BemfaClientState == "已连接") {
return "success"
}
return "info"
}
const deviceList = ref([{
Key: "",
DeviceName: "",
@ -220,6 +379,15 @@ const deviceList = ref([{
Port: 9,
Relay: true,
Repeat: 5,
State: "",
OnlineMacList: [''],
IOT_DianDeng_Enable: false,
IOT_DianDeng_AUTHKEY: "",
DianDengClientState: "",
IOT_Bemfa_Enable: false,
IOT_Bemfa_SecretKey: "",
IOT_Bemfa_Topic: "",
BemFaClientState: "",
},])
const deviceForm = ref({
@ -230,12 +398,31 @@ const deviceForm = ref({
Port: 9,
Relay: true,
Repeat: 5,
IOT_DianDeng_Enable: false,
IOT_DianDeng_AUTHKEY: "",
IOT_Bemfa_SecretKey: "",
IOT_Bemfa_Topic: "",
IOT_Bemfa_Enable: false,
})
const deleteDevice = (device)=>{
const deviceOnlieDeviceToBrHtml = (device) => {
if (device.OnlineMacList == undefined || device.OnlineMacList == null || device.OnlineMacList.length <= 0) {
return "没有设备在线"
}
const deviceName = device.DeviceName==""?device.MacList[0]:device.DeviceName;
const deviceText = "[" + deviceName +"]"
var resHtml = "在线设备列表:<br />"
for (let i in device.OnlineMacList) {
resHtml += device.OnlineMacList[i] + '<br />'
}
return resHtml
}
const deleteDevice = (device) => {
const deviceName = device.DeviceName == "" ? device.MacList[0] : device.DeviceName;
const deviceText = "[" + deviceName + "]"
ElMessageBox.confirm(
'确认要删除待唤醒设备 ' + deviceText + "?",
@ -268,8 +455,23 @@ const deleteDevice = (device)=>{
}
const wakeup = (device)=>{
apiWOLDeviceWakeUp(device.Key).then((res) => {
const wakeup = (device) => {
const deviceName = device.DeviceName == "" ? device.MacList[0] : device.DeviceName;
const deviceText = "[" + deviceName + "]"
ElMessageBox.confirm(
'确认要唤醒设备 ' + deviceText + "?",
'Warning',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
apiWOLDeviceWakeUp(device.Key).then((res) => {
if (res.ret == 0) {
MessageShow("success", "唤醒指令已发送")
queryDeviceList();
@ -280,7 +482,54 @@ const wakeup = (device)=>{
console.log("唤醒指令发送失败,网络请求出错:" + error)
MessageShow("error", "唤醒指令发送失败,网络请求出错")
})
})
}
const shutdown = (device) => {
const deviceName = device.DeviceName == "" ? device.MacList[0] : device.DeviceName;
const deviceText = "[" + deviceName + "]"
ElMessageBox.confirm(
'确认要向设备 ' + deviceText + " 发送关机指令?",
'Warning',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
apiWOLDeviceShutDown(device.Key).then((res) => {
if (res.ret == 0) {
MessageShow("success", "已向在线设备发送关机指令")
queryDeviceList();
return
}
MessageShow("error", res.msg)
}).catch((error) => {
console.log("关机指令发送失败,网络请求出错:" + error)
MessageShow("error", "关机指令发送失败,网络请求出错")
})
})
}
const avalidShutDownButton = (state: string) => {
let res = state.indexOf("在线");
console.log("show: " + res)
if (res >= 0) {
return false
}
return true
}
const addOreAlterDevice = () => {
@ -304,8 +553,8 @@ const addOreAlterDevice = () => {
})
break;
case "alter":
apiAlterWOLDevice(deviceForm.value).then((res) => {
apiAlterWOLDevice(deviceForm.value).then((res) => {
if (res.ret == 0) {
deviceDialogShow.value = false;
MessageShow("success", "设备修改成功")
@ -323,16 +572,22 @@ const addOreAlterDevice = () => {
}
}
const showAlterDeviceDialog = (device)=>{
const showAlterDeviceDialog = (device) => {
deviceDialogCommitButtonText.value = "修改"
deviceForm.value = {
Key: device.Key,
DeviceName: device.DeviceName,
MacList:device.MacList,
MacList: device.MacList,
BroadcastIPs: device.BroadcastIPs,
Port: device.Port,
Relay: device.Relay,
Repeat: device.Repeat,
IOT_DianDeng_Enable: device.IOT_DianDeng_Enable,
IOT_DianDeng_AUTHKEY: device.IOT_DianDeng_AUTHKEY,
IOT_Bemfa_SecretKey: device.IOT_Bemfa_SecretKey,
IOT_Bemfa_Topic: device.IOT_Bemfa_Topic,
IOT_Bemfa_Enable: device.IOT_Bemfa_Enable,
}
deviceFormActionType.value = "alter"
deviceFormMacListArea.value = StrArrayListToArea(device.MacList)
@ -350,6 +605,11 @@ const showAddDeviceDialog = () => {
Port: 9,
Relay: true,
Repeat: 5,
IOT_DianDeng_Enable: false,
IOT_DianDeng_AUTHKEY: "",
IOT_Bemfa_Enable: false,
IOT_Bemfa_SecretKey: "",
IOT_Bemfa_Topic: "",
}
deviceFormActionType.value = "add"
@ -382,19 +642,80 @@ onMounted(() => {
})
onUnmounted(() => {
clearInterval(timerID)
})
</script>
<style scoped>
<style lang="scss">
.itemradius {
border: 1px solid var(--el-border-color);
border-radius: 0;
margin-left: 3px;
margin-top: 3px;
margin-right: 3px;
margin-bottom: 5px;
min-width: 1200px;
border: 1px solid var(--el-border-color);
border-radius: 0;
margin-left: 3px;
margin-top: 3px;
margin-right: 3px;
margin-bottom: 5px;
min-width: 1200px;
}
.divRadius {
border: 2px solid var(--el-border-color);
border-radius: 10px;
margin-left: 3px;
margin-top: 15px;
margin-right: 3px;
margin-bottom: 15px;
width: 330px;
padding-top: 9px;
padding-left: 9px;
padding-right: 9px;
}
.divIOTRadius {
border: 2px solid var(--el-border-color);
border-radius: 10px;
margin-left: 3px;
margin-top: 15px;
margin-right: 3px;
margin-bottom: 15px;
width: 300px;
padding-top: 9px;
padding-left: 9px;
padding-right: 9px;
}
.deviceNamelabelClass {
width: 55px,
}
.deviceNamecontentClass {
width: 230px,
}
.deviceOptlabelClass {
width: 55px,
}
.deviceOptcontentClass {
width: 320px,
}
.deviceMaclabelClass {
width: 110px,
}
.deviceMaccontentClass {
width: 150px,
}
.deviceIOTlabelClass {
width: 90px,
}
.deviceIOTcontentClass {
width: 180px,
}
</style>

View File

@ -0,0 +1,558 @@
<template>
<div class="PageRadius" :style="{
borderRadius: 'base',
}">
<el-scrollbar height="100%">
<div class="formradius" :style="{
borderRadius: 'base',
}">
<el-form :model="form" class="SetForm" label-width="auto">
<div class="AdminListenDivRadius">
<p>服务端设置</p>
<el-tooltip class="box-item" effect="dark" :trigger-keys="[]" content="">
<template #content>
被控制端不需要打开这个<br />
</template>
<el-form-item label="服务端开关">
<el-switch v-model="form.Server.Enable" class="mb-1" inline-prompt
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949" width="50px"
active-text="开启" inactive-text="关闭" />
</el-form-item>
</el-tooltip>
<div v-show="form.Server.Enable">
<el-tooltip class="box-item" effect="dark" :trigger-keys="[]" content="">
<template #content>
客户端登录需要填写Token一致<br />
</template>
<el-form-item label="认证Token">
<el-input v-model="form.Server.Token" placeholder="Token" autocomplete="off" />
</el-form-item>
</el-tooltip>
</div>
</div>
<div class="AdminListenDivRadius">
<p>客户端设置</p>
<el-form-item label="客户端开关">
<el-switch v-model="form.Client.Enable" class="mb-1" inline-prompt
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949" width="50px"
active-text="开启" inactive-text="关闭" />
<el-divider direction="vertical" />
<el-button size="small" v-show="true">
{{ clientState }}
</el-button>
<el-divider direction="vertical" v-show="clientstateMsg == '' ? false : true" />
<el-button size="small" v-show="clientstateMsg == '' ? false : true" type="danger">
{{ clientstateMsg }}
</el-button>
</el-form-item>
<div v-show="form.Client.Enable">
<el-tooltip class="box-item" effect="dark" :trigger-keys="[]" content="">
<template #content>
这里填你的lucky后台管理地址<br />
比如:<br />
http://192.168.31.1:16601<br />
或者<br />
https://192.168.31.1:16601<br />
保存配置后会自动转为ws://或者wss://开头的websocket协议地址.<br />
</template>
<el-form-item label="服务端地址">
<el-input v-model="form.Client.ServerURL" placeholder="服务器地址" autocomplete="off" />
</el-form-item>
</el-tooltip>
<el-form-item label="Token">
<el-input v-model="form.Client.Token" placeholder="Token" autocomplete="off" />
</el-form-item>
<el-tooltip class="box-item" effect="dark" :trigger-keys="[]" content="">
<template #content>
当lucky服务端和待唤醒设备不在同一局域网时,可以通过具备转发的客户端转发唤醒魔法包<br />
</template>
<el-form-item label="转发唤醒包">
<el-switch v-model="form.Client.Relay" class="mb-1" inline-prompt
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
width="50px" active-text="开启" inactive-text="关闭" />
</el-form-item>
</el-tooltip>
<el-tooltip class="box-item" effect="dark" :trigger-keys="[]" content="">
<template #content>
在lucky服务器网络唤醒设备列表显示的设备名称,不能为空<br />
</template>
<el-form-item label="设备名称">
<el-input v-model="form.Client.DeviceName" placeholder="设备名称" autocomplete="off" />
</el-form-item>
</el-tooltip>
<el-form-item label="网卡物理地址">
<el-input v-model="form.Client.Mac" placeholder="网卡物理地址" autocomplete="off">
<template #append>
<el-select v-model="form.Client.Mac" placeholder="网卡选择" style="width: auto"
@change="interfaceMacChange">
<div v-for="info in ipv4InterfaceList">
<el-tooltip class="box-item" effect="dark" :trigger-keys="[]"
content="">
<template #content>
<span
v-html="StrArrayListToBrHtml(getIPList(info.AddressList))"></span>
</template>
<el-option :label="info.NetInterfaceName"
:value=info.HardwareAddr />
</el-tooltip>
</div>
</el-select>
</template>
</el-input>
</el-form-item>
<el-tooltip class="box-item" effect="dark" :trigger-keys="[]" content="">
<template #content>
255.255.255.255在多数环境失效,不要使用<br />
</template>
<el-form-item label="广播地址">
<el-input v-model="form.Client.BroadcastIP" placeholder="广播地址" autocomplete="off">
<template #append>
<el-select v-model="form.Client.BroadcastIP" placeholder="局域网选择"
style="width: 180px">
<div v-for="info in BroadcastIPInputAddressList">
<el-option :label="info.IP" :value=info.BroadcastIP />
</div>
</el-select>
</template>
</el-input>
</el-form-item>
</el-tooltip>
<el-form-item label="端口">
<el-input-number v-model="form.Client.Port" autocomplete="off" />
</el-form-item>
<el-tooltip class="box-item" effect="dark" :trigger-keys="[]" content="">
<template #content>
唤醒魔方包重复发送次数<br />
</template>
<el-form-item label="重复次数">
<el-input-number v-model="form.Client.Repeat" autocomplete="off" />
</el-form-item>
</el-tooltip>
<el-tooltip class="box-item" effect="dark" :trigger-keys="[]" content="">
<template #content>
windows关机指令: Shutdown /s /t 0 <br />
linux关机指令: poweroff <br />
如果你发现你的windows关机后怎么也唤不醒,可以试试使用休眠/睡眠 指令: rundll32.exe powrprof.dll,SetSuspendState
0,1,0 <br />
注意新手注意别使用其它未知指令,比如"rm -rf"...<br />
数据丢失概不负责<br />
</template>
<el-form-item label="关机指令">
<el-input v-model="form.Client.PowerOffCMD" placeholder="关机指令" autocomplete="off" >
<template #append>
<el-select v-model="form.Client.PowerOffCMD" placeholder="关机指令"
style="width: 160px">
<div v-for="cmd in defualtCMDList">
<el-option :label="cmd.label" :value=cmd.value />
</div>
</el-select>
</template>
</el-input>
</el-form-item>
</el-tooltip>
</div>
</div>
<div class="AdminListenDivRadius" v-show="serviceStatus >= 0 ? true : false">
<el-tooltip class="box-item" effect="dark" :trigger-keys="[]" content="">
<template #content>
当前Lucky windows服务状态<br />
</template>
<el-button type="info" size="small">
{{ serviceStatus == 0 ? 'lucky服务未安装' : serviceStatus == 1 ? 'lucky服务已启动' : serviceStatus == 2 ? 'luck服务已停止' : '未知服务状态' }}
</el-button>
</el-tooltip>
<el-tooltip class="box-item" effect="dark" :trigger-keys="[]" content="">
<template #content>
安装windows服务,lukcy可能需要以管理员身份运行才能设置成功<br />
实现lucky开机自启<br/>
</template>
<el-button v-show='serviceStatus == 0 ? true : false' type="warning" round
@click="optionLuckyService('install')">安装Lucky windows服务</el-button>
</el-tooltip>
<el-button type="danger" v-show="serviceStatus>0?true:false" round @click="optionLuckyService('unstall')">删除Lucky windows服务
</el-button>
<div v-show="serviceStatus>0?true:false" >
<el-button type="info" size="small" @click="optionLuckyService('start')" v-show="serviceStatus==2?true:false">
启动lucky服务
</el-button>
<el-button type="info" size="small" @click="optionLuckyService('stop')" v-show="serviceStatus==1?true:false">
停止lucky服务
</el-button>
<el-button type="info" size="small" @click="optionLuckyService('restart')">
重启lucky服务
</el-button>
</div>
</div>
</el-form>
<el-button type="primary" round @click="RequestAlterConfigure">保存修改</el-button>
</div>
</el-scrollbar>
</div>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, computed, reactive } from 'vue'
import { apiGetWOLServiceConfigure, apiAlterWOLServiceConfigure, apiGetIPv4Interface, apiOptionsLuckyService, apiGetRestoreConfigureConfirm } from '../../apis/utils'
import { ElMessageBox } from 'element-plus'
import { StrArrayListToBrHtml } from '../../utils/utils'
import { MessageShow } from '../../utils/ui'
import FileSaver from 'file-saver'
import { anyTypeAnnotation } from '@babel/types'
const formLabelWidth = '10vw'
console.log("window.location.href " + window.location.href)
console.log("window.location.port " + window.location.port)
console.log("window.location.host " + window.location.host)
console.log("window.location " + JSON.stringify(window.location))
const disableRebootButton = ref(false)
const BroadcastIPInputAddressList = ref([{ IP: "", BroadcastIP: "" }])
const defualtCMDList = ref([
{label:"windows关机指令",value:"Shutdown /s /t 0"},
{label:"Linux关机指令",value:"poweroff"},
{label:"windows休眠指令",value:"rundll32.exe powrprof.dll,SetSuspendState 0,1,0"},
{label:"自定义",value:""}])
const ipv4InterfaceList = ref([{
NetInterfaceName: "",
HardwareAddr: "",
AddressList: [{ IP: "", BroadcastIP: "" },]
},])
const getIPList = (AddressList) => {
let iplist = new Array()
for (let i in AddressList) {
iplist.push(AddressList[i].IP)
}
return iplist
}
const interfaceMacChange = (val) => {
if (val == "") {
return
}
for (let i in ipv4InterfaceList.value) {
if (ipv4InterfaceList.value[i].HardwareAddr == val) {
BroadcastIPInputAddressList.value = ipv4InterfaceList.value[i].AddressList
break
}
}
if (BroadcastIPInputAddressList.value.length <= 0) {
return
}
form.value.Client.BroadcastIP = BroadcastIPInputAddressList.value[0].BroadcastIP
}
const rawData = {
Server: {
Enable: false,
Token: ""
},
Client: {
Enable: false,
ServerURL: "",
Token: "",
Relay: false,
Key: "",
DeviceName: "",
Mac: "",
BroadcastIP: "",
Port: 9,
Repeat: 5,
PowerOffCMD: "",
}
}
const clientState = ref("")
const clientstateMsg = ref("")
const serviceStatus = ref(-1)
const form = ref(rawData)
const preFormData = ref(rawData)
const queryIPv4InterfaceList = () => {
apiGetIPv4Interface().then((res) => {
if (res.ret == 0) {
ipv4InterfaceList.value = res.list
interfaceMacChange(form.value.Client.Mac)
return
}
MessageShow("error", "获取Ipv4网卡信息列表出错" + res.msg)
}).catch((error) => {
console.log("获取Ipv4网卡信息列表出错:" + error)
MessageShow("error", "获取Ipv4网卡信息列表出错")
})
}
const queryConfigure = () => {
apiGetWOLServiceConfigure().then((res) => {
if (res.ret == 0) {
form.value = res.configure
clientState.value = res.ClientState
clientstateMsg.value = res.ClientStateMsg
serviceStatus.value = res.serviceStatus
return
}
MessageShow("error", "获取唤醒服务配置出错")
}).catch((error) => {
console.log("获取唤醒服务配置出错:" + error)
MessageShow("error", "获取唤醒服务配置出错")
})
}
const optionLuckyService = (op) => {
var optionServiceText = "安装Lucky服务"
if (op == 'unstall') {
optionServiceText = "卸载Lucky服务"
}else if (op == 'start'){
optionServiceText = "启动Lucky服务"
}else if (op == 'stop'){
optionServiceText = '停止lucky服务'
}else if (op=="restart"){
optionServiceText = '重启lucky服务'
}
var warnText = ""
if (op=="install"){
warnText = "\n安装成功后lucky会重启并以windows后台服务方式启动,到时需重新登录后台"
}else if(op=="unstall"){
warnText="\n卸载后lucky也会随之退出,如有需要请手动启动"
}
ElMessageBox.confirm(
'确认要 ' + optionServiceText + " ?"+warnText,
'Warning',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
apiOptionsLuckyService(op).then((res) => {
if (res.ret == 0) {
MessageShow("success", optionServiceText + "成功")
if (op == 1) {
MessageShow("success", "请重新启动系统后执行输入后台网站,确认lucky服务已正常自启.")
}
if (res.msg!=""){
MessageShow("success", res.msg)
}
serviceStatus.value = res.status
return
}
console.log(optionServiceText + " 出错:" + res.msg)
MessageShow("error", res.msg)
}).catch((error) => {
console.log("服务状态修改出错,网络请求出错:" + error)
MessageShow("error", "服务状态修改出错,网络请求出错")
})
})
}
const RequestAlterConfigure = () => {
apiAlterWOLServiceConfigure(form.value).then((res) => {
if (res.ret == 0) {
MessageShow("success", "修改成功")
form.value = res.configure
setTimeout(() => {
queryConfigure()
}, 2000)
return
}
console.log("保存配置出错:" + res.msg)
MessageShow("error", res.msg)
}).catch((error) => {
console.log("配置修改失败,网络请求出错:" + error)
MessageShow("error", "配置修改失败,网络请求出错")
})
}
//var timerID: any
onMounted(() => {
queryConfigure()
queryIPv4InterfaceList()
// timerID = setInterval(() => {
// queryConfigure()
// queryIPv4InterfaceList()
// }, 2000);
})
onUnmounted(() => {
//clearInterval(timerID)
})
</script>
<style scoped>
.AdminListenDivRadius {
border: 2px solid var(--el-border-color);
border-radius: 10px;
margin-left: 3px;
margin-top: 15px;
margin-right: 3px;
margin-bottom: 15px;
width: 600px;
padding-top: 9px;
padding-left: 9px;
padding-right: 9px;
}
.SetForm {
margin-top: 15px;
margin-left: 20px;
}
.formradius {
border: 0px solid var(--el-border-color);
border-radius: 0;
margin: 0 auto;
width: fit-content;
padding: 15px;
}
#adminListen {
width: 360px;
}
#adminAccount {
width: 30vw;
max-width: 360px;
min-width: 300px;
}
#adminPassword {
width: 30vw;
max-width: 360px;
min-width: 300px;
}
#proxyCountLimit {
width: 360px;
}
#globalMaxConnections {
width: 360px;
}
.inline-block {
display: inline-block;
margin-right: 10px;
}
.margin-change {
display: inline-block;
margin-left: 10px;
}
</style>

View File

@ -12,10 +12,13 @@ export function isIP(ip :string){
}
const MenuIndexList = ["#status",
"#log","#whitelistset",
"#whitelists","#blacklists","#set",
"#login","#ddns","#ddnstasklist","#ddnsset",
"#about","#reverseproxylist","#ssl","#portforward","#portforwardset","#wol"]
"#log",
"#whitelistset","#whitelists","#blacklists",
"#set","#login","#about",
"#ddns","#ddnstasklist","#ddnsset",
"#reverseproxylist","#ssl",
"#portforward","#portforwardset",
"#wol","#wolserviceset"]
export function PageExist(page:string) {
for(let i in MenuIndexList){

View File

@ -6,17 +6,18 @@ import (
"strconv"
"strings"
"github.com/gdy666/lucky/config"
"github.com/gdy666/lucky/module/safe"
safeconf "github.com/gdy666/lucky/module/safe/conf"
"github.com/gdy666/lucky/thirdlib/gdylib/ginutils"
"github.com/gin-gonic/gin"
)
func whitelistConfigure(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ret": 0, "data": config.GetWhiteListBaseConfigure()})
c.JSON(http.StatusOK, gin.H{"ret": 0, "data": safe.GetWhiteListBaseConfigure()})
}
func alterWhitelistConfigure(c *gin.Context) {
var requestObj config.WhiteListBaseConfigure
var requestObj safeconf.WhiteListBaseConfigure
err := c.BindJSON(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "修改请求解析出错"})
@ -29,7 +30,7 @@ func alterWhitelistConfigure(c *gin.Context) {
return
}
err = config.SetWhiteListBaseConfigure(requestObj.ActivelifeDuration, requestObj.URL, requestObj.BasicAccount, requestObj.BasicPassword)
err = safe.SetWhiteListBaseConfigure(requestObj.ActivelifeDuration, requestObj.URL, requestObj.BasicAccount, requestObj.BasicPassword)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "保存白名单配置出错"})
@ -39,13 +40,13 @@ func alterWhitelistConfigure(c *gin.Context) {
}
func querywhitelist(c *gin.Context) {
resList := config.GetWhiteList()
resList := safe.GetWhiteList()
c.JSON(http.StatusOK, gin.H{"ret": 0, "data": resList})
}
func deleteblacklist(c *gin.Context) {
ip := c.Query("ip")
err := config.BlackListDelete(ip)
err := safe.BlackListDelete(ip)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "删除黑名单指定IP出错"})
return
@ -55,7 +56,7 @@ func deleteblacklist(c *gin.Context) {
func deletewhitelist(c *gin.Context) {
ip := c.Query("ip")
err := config.WhiteListDelete(ip)
err := safe.WhiteListDelete(ip)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "删除白名单指定IP出错"})
return
@ -68,7 +69,7 @@ func flushblacklist(c *gin.Context) {
activelifeDurationStr := c.Query("life")
life, _ := strconv.Atoi(activelifeDurationStr)
newTime, err := config.BlackListAdd(ip, int32(life))
newTime, err := safe.BlackListAdd(ip, int32(life))
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("刷新IP有效期出错:%s", err.Error())})
return
@ -81,7 +82,7 @@ func flushwhitelist(c *gin.Context) {
activelifeDurationStr := c.Query("life")
life, _ := strconv.Atoi(activelifeDurationStr)
newTime, err := config.WhiteListAdd(ip, int32(life))
newTime, err := safe.WhiteListAdd(ip, int32(life))
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("刷新IP有效期出错:%s", err.Error())})
return
@ -90,12 +91,12 @@ func flushwhitelist(c *gin.Context) {
}
func queryblacklist(c *gin.Context) {
resList := config.GetBlackList()
resList := safe.GetBlackList()
c.JSON(http.StatusOK, gin.H{"ret": 0, "data": resList})
}
func whitelistBasicAuth(c *gin.Context) {
bc := config.GetWhiteListBaseConfigure()
bc := safe.GetWhiteListBaseConfigure()
whilelistURL := c.Param("url")
if (c.Request.RequestURI == "/wl" && bc.URL != "") || whilelistURL != bc.URL {
c.AbortWithStatus(http.StatusNotFound)
@ -116,7 +117,7 @@ func whitelistBasicAuth(c *gin.Context) {
func whilelistAdd(c *gin.Context) {
lifeTime, err := config.WhiteListAdd(c.ClientIP(), 0)
lifeTime, err := safe.WhiteListAdd(c.ClientIP(), 0)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "记录白名单IP出错"})
return

View File

@ -5,14 +5,16 @@ import (
"net/http"
"strings"
"github.com/gdy666/lucky/config"
"github.com/gdy666/lucky/ddnscore.go"
ddnsconf "github.com/gdy666/lucky/module/ddns/conf"
"github.com/gdy666/lucky/module/ddns/ddnscore.go"
"github.com/gdy666/lucky/module/ddns/ddnsgo"
"github.com/gdy666/lucky/thirdlib/gdylib/dnsutils"
"github.com/gdy666/lucky/thirdlib/gdylib/service"
"github.com/gin-gonic/gin"
)
func addDDNS(c *gin.Context) {
var requestObj config.DDNSTask
var requestObj ddnsconf.DDNSTask
err := c.BindJSON(&requestObj)
if err != nil {
@ -20,7 +22,7 @@ func addDDNS(c *gin.Context) {
return
}
//fmt.Printf("addDDNS requestObj:%v\n", requestObj)
err = config.CheckDDNSTaskAvalid(&requestObj)
err = ddnsconf.CheckDDNSTaskAvalid(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": err.Error()})
return
@ -28,7 +30,7 @@ func addDDNS(c *gin.Context) {
dealRequestDDNSTask(&requestObj)
err = config.DDNSTaskListAdd(&requestObj)
err = ddnsgo.DDNSTaskListAdd(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "DDNS任务添加出错"})
return
@ -43,7 +45,7 @@ func addDDNS(c *gin.Context) {
func deleteDDNSTask(c *gin.Context) {
taskKey := c.Query("key")
err := config.DDNSTaskListDelete(taskKey)
err := ddnsgo.DDNSTaskListDelete(taskKey)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Errorf("删除DDNS任务出错:%s", err.Error())})
return
@ -78,34 +80,39 @@ func enableddns(c *gin.Context) {
}
func ddnsconfigure(c *gin.Context) {
conf := config.GetDDNSConfigure()
conf := ddnsgo.GetDDNSConfigure()
c.JSON(http.StatusOK, gin.H{"ret": 0, "ddnsconfigure": conf})
}
func alterDDNSTask(c *gin.Context) {
taskKey := c.Query("key")
var requestObj config.DDNSTask
var requestObj ddnsconf.DDNSTask
err := c.BindJSON(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "请求解析出错"})
return
}
err = config.CheckDDNSTaskAvalid(&requestObj)
err = ddnsconf.CheckDDNSTaskAvalid(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": err.Error()})
return
}
dealRequestDDNSTask(&requestObj)
err = config.UpdateTaskToDDNSTaskList(taskKey, requestObj)
err = ddnsgo.UpdateTaskToDDNSTaskList(taskKey, requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("更新DDNS任务出错:%s", err.Error())})
return
}
ddnscore.DDNSTaskInfoMapDelete(taskKey)
//ddnscore.DDNSTaskInfoMapDelete(taskKey)
//ddnscore.syncDomains()
t := ddnscore.GetDDNSTaskInfoByKey(taskKey)
if t != nil {
t.SyncDomains()
}
if requestObj.Enable {
service.Message("ddns", "syncDDNSTask", taskKey)
@ -117,7 +124,7 @@ func alterDDNSTask(c *gin.Context) {
func ddnsTaskList(c *gin.Context) {
conf := config.GetDDNSConfigure()
conf := ddnsgo.GetDDNSConfigure()
if !conf.Enable {
c.JSON(http.StatusOK, gin.H{"ret": 6, "msg": "请先在设置页面启用DDNS动态域名服务"})
@ -130,7 +137,7 @@ func ddnsTaskList(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ret": 0, "data": taskList})
}
func dealRequestDDNSTask(t *config.DDNSTask) {
func dealRequestDDNSTask(t *ddnsconf.DDNSTask) {
if t.DNS.Name == "callback" {
t.DNS.ID = ""
@ -139,7 +146,7 @@ func dealRequestDDNSTask(t *config.DDNSTask) {
//requestObj.DNS.Callback.CallbackSuccessContent = strings.TrimSpace(requestObj.DNS.Callback.CallbackSuccessContent)
t.DNS.Callback.RequestBody = strings.TrimSpace(t.DNS.Callback.RequestBody)
} else {
t.DNS.Callback = config.DNSCallback{}
t.DNS.Callback = ddnsconf.DNSCallback{}
}
if !t.DNS.ResolverDoaminCheck && len(t.DNS.DNSServerList) > 0 {
@ -148,9 +155,9 @@ func dealRequestDDNSTask(t *config.DDNSTask) {
if t.DNS.ResolverDoaminCheck && (len(t.DNS.DNSServerList) == 0 || (len(t.DNS.DNSServerList) == 1 && t.DNS.DNSServerList[0] == "")) {
if t.TaskType == "IPv6" {
t.DNS.DNSServerList = config.DefaultIPv6DNSServerList
t.DNS.DNSServerList = dnsutils.DefaultIPv6DNSServerList
} else {
t.DNS.DNSServerList = config.DefaultIPv4DNSServerList
t.DNS.DNSServerList = dnsutils.DefaultIPv4DNSServerList
}
}
@ -218,14 +225,14 @@ func dealRequestDDNSTask(t *config.DDNSTask) {
}
func alterDDNSConfigure(c *gin.Context) {
var requestObj config.DDNSConfigure
var requestObj ddnsconf.DDNSConfigure
err := c.BindJSON(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "请求解析出错"})
return
}
preConfigure := config.GetDDNSConfigure()
preConfigure := ddnsgo.GetDDNSConfigure()
if preConfigure.Enable != requestObj.Enable {
@ -238,7 +245,7 @@ func alterDDNSConfigure(c *gin.Context) {
}
err = config.SetDDNSConfigure(&requestObj)
err = ddnsgo.SetDDNSConfigure(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": "保存配置过程发生错误,请检测相关启动配置"})
return

View File

@ -6,14 +6,16 @@ import (
"net/http"
"strconv"
"github.com/gdy666/lucky/config"
"github.com/gdy666/lucky/socketproxy"
"github.com/gdy666/lucky/module/portforward"
portforwardconf "github.com/gdy666/lucky/module/portforward/conf"
"github.com/gdy666/lucky/module/portforward/socketproxy"
"github.com/gdy666/lucky/module/weblog"
"github.com/gdy666/lucky/thirdlib/gdylib/stringsp"
"github.com/gin-gonic/gin"
)
type ruleInfo struct {
config.PortForwardsRule
portforwardconf.PortForwardsRule
ProxyList []proxyInfo
LastLogs []any
}
@ -25,7 +27,7 @@ type proxyInfo struct {
}
func PortForwardsRuleList(c *gin.Context) {
ruleRawList := config.GetPortForwardsRuleList()
ruleRawList := portforward.GetPortForwardsRuleList()
var ruleList []ruleInfo
@ -50,7 +52,7 @@ func PortForwardsRuleList(c *gin.Context) {
}
func PortForwardsRuleAdd(c *gin.Context) {
var newRule config.PortForwardsRule
var newRule portforwardconf.PortForwardsRule
err := c.Bind(&newRule)
if err != nil {
log.Printf("请求解析出错:%s", err.Error())
@ -65,24 +67,24 @@ func PortForwardsRuleAdd(c *gin.Context) {
return
}
if int64(config.GetPortForwardsGlobalProxyCount()+newRule.ProxyCount()) > socketproxy.GetGlobalMaxPortForwardsCountLimit() {
if int64(portforward.GetPortForwardsGlobalProxyCount()+newRule.ProxyCount()) > socketproxy.GetGlobalMaxPortForwardsCountLimit() {
c.JSON(http.StatusOK, gin.H{"ret": 3, "msg": "超出全局端口转发最大数量限制"})
return
}
err = config.PortForwardsRuleListAdd(&newRule)
err = portforward.PortForwardsRuleListAdd(&newRule)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 4, "msg": fmt.Sprintf("添加转发规则出错:%s", err.Error())})
return
}
config.StartAllSocketProxysByRuleKey(newRule.Key)
portforward.StartAllSocketProxysByRuleKey(newRule.Key)
c.JSON(http.StatusOK, gin.H{"ret": 0})
}
func PortForwardsRuleAlter(c *gin.Context) {
var alterRule config.PortForwardsRule
var alterRule portforwardconf.PortForwardsRule
err := c.Bind(&alterRule)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("请求解析出错:%s", err.Error())})
@ -95,21 +97,21 @@ func PortForwardsRuleAlter(c *gin.Context) {
return
}
if int64(config.GetPortForwardsGlobalProxyCountExcept(alterRule.Key)+alterRule.ProxyCount()) > socketproxy.GetGlobalMaxPortForwardsCountLimit() {
if int64(portforward.GetPortForwardsGlobalProxyCountExcept(alterRule.Key)+alterRule.ProxyCount()) > socketproxy.GetGlobalMaxPortForwardsCountLimit() {
c.JSON(http.StatusOK, gin.H{"ret": 3, "msg": "超出全局端口转发最大数量限制"})
return
}
config.StopAllSocketProxysByRuleKey(alterRule.Key)
portforward.StopAllSocketProxysByRuleKey(alterRule.Key)
err = config.UpdatePortForwardsRuleToPortForwardsRuleList(alterRule.Key, &alterRule)
err = portforward.UpdatePortForwardsRuleToPortForwardsRuleList(alterRule.Key, &alterRule)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 4, "msg": fmt.Sprintf("修改转发规则出错:%s", err.Error())})
return
}
if alterRule.Enable {
config.StartAllSocketProxysByRuleKey(alterRule.Key)
portforward.StartAllSocketProxysByRuleKey(alterRule.Key)
}
c.JSON(http.StatusOK, gin.H{"ret": 0})
@ -124,7 +126,7 @@ func PortForwardsRuleEnable(c *gin.Context) {
enable = true
}
err := config.EnablePortForwardsRuleByKey(key, enable)
err := portforward.EnablePortForwardsRuleByKey(key, enable)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("开关转发规则出错:%s", err.Error())})
return
@ -136,32 +138,32 @@ func PortForwardsRuleEnable(c *gin.Context) {
func PortForwardsRuleDelete(c *gin.Context) {
key := c.Query("key")
err := config.PortForwardsRuleListDelete(key)
err := portforward.PortForwardsRuleListDelete(key)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("删除转发规则出错:%s", err.Error())})
return
}
config.TidyPortforwardLogsCache()
portforward.TidyPortforwardLogsCache()
c.JSON(http.StatusOK, gin.H{"ret": 0})
}
func portforwardConfigure(c *gin.Context) {
conf := config.GetPortForwardsConfigure()
conf := portforward.GetPortForwardsConfigure()
c.JSON(http.StatusOK, gin.H{"ret": 0, "configure": conf})
}
func alterPortForwardConfigure(c *gin.Context) {
var requestObj config.PortForwardsConfigure
var requestObj portforwardconf.PortForwardsConfigure
err := c.BindJSON(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "请求解析出错"})
return
}
err = config.SetPortForwardsConfigure(&requestObj)
err = portforward.SetPortForwardsConfigure(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": "保存配置过程发生错误,请检测相关启动配置"})
return
@ -180,11 +182,11 @@ func getPortwardRuleLogs(c *gin.Context) {
page = 1
}
rule := config.GetPortForwardsRuleByKey(key)
rule := portforward.GetPortForwardsRuleByKey(key)
if rule == nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("找不到key:%s对应的规则", key)})
return
}
total, logList := rule.GetLogsBuffer().GetLogsByLimit(config.WebLogConvert, pageSize, page)
total, logList := rule.GetLogsBuffer().GetLogsByLimit(weblog.WebLogConvert, pageSize, page)
c.JSON(http.StatusOK, gin.H{"ret": 0, "total": total, "page": page, "pageSize": pageSize, "logs": logList})
}

View File

@ -7,8 +7,8 @@ import (
"strconv"
"strings"
"github.com/gdy666/lucky/config"
"github.com/gdy666/lucky/reverseproxy"
"github.com/gdy666/lucky/module/reverseproxy"
reverseproxyconf "github.com/gdy666/lucky/module/reverseproxy/conf"
"github.com/gdy666/lucky/thirdlib/gdylib/stringsp"
"github.com/gin-gonic/gin"
)
@ -20,7 +20,7 @@ func reverseProxys(c *gin.Context) {
}
func addReverseProxyRule(c *gin.Context) {
var requestObj config.ReverseProxyRule
var requestObj reverseproxyconf.ReverseProxyRule
err := c.BindJSON(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "请求解析出错"})
@ -33,7 +33,7 @@ func addReverseProxyRule(c *gin.Context) {
return
}
err = config.ReverseProxyRuleListAdd(&requestObj)
err = reverseproxy.ReverseProxyRuleListAdd(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": fmt.Sprintf("添加反向代理规则失败:%s", err.Error())})
@ -48,7 +48,7 @@ func addReverseProxyRule(c *gin.Context) {
}
func alterReverseProxyRule(c *gin.Context) {
var requestObj config.ReverseProxyRule
var requestObj reverseproxyconf.ReverseProxyRule
err := c.BindJSON(&requestObj)
if err != nil {
fmt.Printf("fff:%s\n", err.Error())
@ -62,7 +62,7 @@ func alterReverseProxyRule(c *gin.Context) {
return
}
err = config.UpdateReverseProxyRulet(requestObj)
err = reverseproxy.UpdateReverseProxyRulet(requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": fmt.Sprintf("修改反向代理规则失败:%s", err.Error())})
@ -75,7 +75,7 @@ func alterReverseProxyRule(c *gin.Context) {
reverseproxy.EnableRuleByKey(requestObj.RuleKey, true)
}
config.TidyReverseProxyCache()
reverseproxy.TidyReverseProxyCache()
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": ""})
}
@ -90,13 +90,13 @@ func deleteReverseProxyRule(c *gin.Context) {
return
}
err = config.ReverseProxyRuleListDelete(ruleKey)
err = reverseproxy.ReverseProxyRuleListDelete(ruleKey)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 3, "msg": fmt.Sprintf("删除反向代理规则出错:%s", err.Error())})
return
}
config.TidyReverseProxyCache()
reverseproxy.TidyReverseProxyCache()
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": ""})
}
@ -127,7 +127,7 @@ func enableReverseProxyRule(c *gin.Context) {
return
}
err := config.EnableReverseProxySubRule(ruleKey, proxyKey, enable)
err := reverseproxy.EnableReverseProxySubRule(ruleKey, proxyKey, enable)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": err.Error()})
return
@ -136,7 +136,7 @@ func enableReverseProxyRule(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": ""})
}
func checkReverseProxyRuleRequest(rule *config.ReverseProxyRule) error {
func checkReverseProxyRuleRequest(rule *reverseproxyconf.ReverseProxyRule) error {
// if len(rule.ProxyList) <= 0 {
// return fmt.Errorf("至少添加一条反向代理转发规则")
// }

View File

@ -5,12 +5,13 @@ import (
"log"
"net/http"
"github.com/gdy666/lucky/config"
ssl "github.com/gdy666/lucky/module/sslcertficate"
sslconf "github.com/gdy666/lucky/module/sslcertficate/conf"
"github.com/gin-gonic/gin"
)
func addSSL(c *gin.Context) {
var requestObj config.SSLCertficate
var requestObj sslconf.SSLCertficate
err := c.BindJSON(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "请求解析出错"})
@ -25,7 +26,7 @@ func addSSL(c *gin.Context) {
}
//fmt.Printf("CertsInfo:%v\n", *requestObj.CertsInfo)
err = config.SSLCertficateListAdd(&requestObj)
err = ssl.SSLCertficateListAdd(&requestObj)
if err != nil {
log.Printf("config.SSLCertficateListAdd error:%s", err.Error())
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": fmt.Sprintf("添加SSL证书出错!:%s", err.Error())})
@ -37,15 +38,15 @@ func addSSL(c *gin.Context) {
}
type sslResInfo struct {
Key string `json:"Key"`
Remark string `json:"Remark"`
Enable bool `json:"Enable"`
AddTime string `json:"AddTime"`
CertsInfo *[]config.CertInfo `json:"CertsInfo"`
Key string `json:"Key"`
Remark string `json:"Remark"`
Enable bool `json:"Enable"`
AddTime string `json:"AddTime"`
CertsInfo *[]sslconf.CertInfo `json:"CertsInfo"`
}
func getSSLCertficateList(c *gin.Context) {
rawList := config.GetSSLCertficateList()
rawList := ssl.GetSSLCertficateList()
var res []sslResInfo
for i := range rawList {
info := sslResInfo{
@ -74,11 +75,11 @@ func alterSSLCertficate(c *gin.Context) {
if value == "true" {
enable = true
}
err = config.SSLCertficateEnable(key, enable)
err = ssl.SSLCertficateEnable(key, enable)
}
case "remark":
{
err = config.SSLCertficateAlterRemark(key, value)
err = ssl.SSLCertficateAlterRemark(key, value)
}
default:
err = fmt.Errorf("不支持修改的字段:%s", field)
@ -93,7 +94,7 @@ func alterSSLCertficate(c *gin.Context) {
func deleteSSLCertficate(c *gin.Context) {
key := c.Query("key")
err := config.SSLCertficateListDelete(key)
err := ssl.SSLCertficateListDelete(key)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": err.Error()})
return

View File

@ -19,9 +19,12 @@ import (
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"github.com/gdy666/lucky/config"
"github.com/gdy666/lucky/socketproxy"
"github.com/gdy666/lucky/module/portforward/socketproxy"
"github.com/gdy666/lucky/module/service"
ssl "github.com/gdy666/lucky/module/sslcertficate"
"github.com/gdy666/lucky/thirdlib/gdylib/fileutils"
"github.com/gdy666/lucky/thirdlib/gdylib/ginutils"
"github.com/gdy666/lucky/thirdlib/gdylib/logsbuffer"
@ -31,6 +34,8 @@ import (
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/process"
wolhttpapi "github.com/gdy666/lucky/module/wol/httpapi"
)
//go:embed adminviews/dist
@ -39,12 +44,17 @@ var stafs fs.FS
var loginErrorCount = int32(0)
var rebootOnce sync.Once
var logBuffer *logsbuffer.LogsBuffer
var logger *logrus.Logger
type LogItem struct {
Timestamp string `json:"timestamp"`
Content string `json:"log"`
}
func GetLogger() *logrus.Logger {
return logger
}
func logConvert(lg *logsbuffer.LogItem) any {
l := LogItem{Content: lg.Content, Timestamp: fmt.Sprintf("%d", lg.Timestamp)}
return l
@ -53,8 +63,11 @@ func logConvert(lg *logsbuffer.LogItem) any {
func init() {
stafs, _ = fs.Sub(staticFs, "adminviews/dist")
logBuffer = logsbuffer.Create(1024)
//logBuffer.SetLogItemConverFunc(logConvert)
log.SetOutput(io.MultiWriter(logBuffer, os.Stdout))
logger = logrus.New()
logger.SetOutput(logBuffer)
//logger.SetLevel(logrus.InfoLevel)
wolhttpapi.SetLogger(logger)
}
@ -134,17 +147,13 @@ func RunAdminWeb(conf *config.BaseConfigure) {
authorized.PUT("/api/ssl", alterSSLCertficate)
authorized.DELETE("/api/ssl", deleteSSLCertficate)
authorized.POST("/api/wol/device", addWOLDevice)
authorized.GET("/api/wol/device/wakeup", WOLDeviceWakeUp)
authorized.GET("/api/wol/devices", getWOLDeviceList)
authorized.PUT("/api/wol/device", alterWOLDevice)
authorized.DELETE("/api/wol/device", deleteWOLDevice)
authorized.GET("/api/info", info)
authorized.GET("/api/configure", configure)
authorized.POST("/api/configure", restoreConfigure)
authorized.POST("/api/getfilebase64", getFileBase64)
authorized.PUT("/api/lucky/service", optionService)
authorized.GET("/api/restoreconfigureconfirm", restoreConfigureConfirm)
r.PUT("/api/logout", logout)
}
@ -157,6 +166,8 @@ func RunAdminWeb(conf *config.BaseConfigure) {
//r.Use(func() *gin.Context {})
wolhttpapi.RegisterAPI(r, authorized)
go func() {
httpListen := fmt.Sprintf(":%d", conf.AdminWebListenPort)
log.Printf("AdminWeb(Http) listen on %s", httpListen)
@ -168,7 +179,7 @@ func RunAdminWeb(conf *config.BaseConfigure) {
}()
if conf.AdminWebListenTLS {
certlist := config.GetValidSSLCertficateList()
certlist := ssl.GetValidSSLCertficateList()
if len(certlist) <= 0 {
log.Printf("可用SSL证书列表为空,AdminWeb(Https) 监听服务中止运行")
return
@ -497,3 +508,78 @@ func isLocalIP(ipstr string) bool {
(ip4[0] == 169 && ip4[1] == 254) || // 169.254.0.0/16
(ip4[0] == 192 && ip4[1] == 168) // 192.168.0.0/16
}
func optionService(c *gin.Context) {
option := c.Query("option")
//fmt.Printf("当前option:%s\n", op)
//retStatus :=
switch option {
case "install": //安装服务
err := service.InstallService()
if err == nil {
c.JSON(http.StatusOK, gin.H{"ret": 0, "status": service.GetServiceState()})
go func() {
service.Start()
<-time.After(time.Second * 1)
os.Exit(0)
}()
return
}
msg := err.Error()
if strings.Contains(msg, "Access is denied") {
msg = "请以管理员身份运行lucky后再次安装"
}
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": fmt.Sprintf("安装Lucky Windows服务失败:%s", msg)})
case "unstall": //卸载服务
err := service.UninstallService()
if err == nil {
c.JSON(http.StatusOK, gin.H{"ret": 0, "status": service.GetServiceState()})
go func() {
<-time.After(time.Second * 2)
service.Stop()
}()
return
}
msg := err.Error()
if strings.Contains(msg, "Access is denied") {
msg = "请以管理员身份运行lucky后再次卸载"
}
c.JSON(http.StatusOK, gin.H{"ret": 3, "msg": fmt.Sprintf("卸载Lucky Windows服务失败:%s", msg)})
case "start": //启用
err := service.Start()
if err == nil {
c.JSON(http.StatusOK, gin.H{"ret": 0, "status": service.GetServiceState(), "msg": "启用服务成功,程序即将重启并已后台服务形式启动,请重新登录后台"})
go func() {
<-time.After(time.Second * 1)
os.Exit(0)
}()
return
}
c.JSON(http.StatusOK, gin.H{"ret": 5, "status": service.GetServiceState(), "msg": fmt.Sprintf("启用服务失败:%s", err.Error())})
case "stop":
go func() {
<-time.After(time.Second * 2)
service.Stop()
}()
c.JSON(http.StatusOK, gin.H{"ret": 0, "status": service.GetServiceState(), "msg": "2秒后停止lucky服务,成功后lucky会退出,后台无法登录"})
return
case "restart":
err := service.Restart()
if err == nil {
c.JSON(http.StatusOK, gin.H{"ret": 0, "status": service.GetServiceState(), "msg": "重启服务成功,lucky即将重启,请重新登录后台"})
go func() {
<-time.After(time.Second * 1)
os.Exit(0)
}()
return
}
c.JSON(http.StatusOK, gin.H{"ret": 5, "status": service.GetServiceState(), "msg": fmt.Sprintf("重启服务出错:%s", err.Error())})
default:
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("未知操作:%s,操作服务中止", option)})
return
}
}