集成动态域名功能
|
@ -5,7 +5,7 @@
|
|||
*.so
|
||||
*.dylib
|
||||
relayports
|
||||
goports
|
||||
lucky
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
|
@ -16,6 +16,7 @@ goports
|
|||
*.log
|
||||
*.upx
|
||||
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
/dist/
|
||||
|
|
61
README.md
|
@ -1,10 +1,7 @@
|
|||
# goports
|
||||
|
||||
|
||||
一个主要功能和socat类似,主要实现公网ipv6 tcp/udp 转发至 内网ipv4 tcp/udp 的端口转发小工具.
|
||||
# Lucky(大吉)
|
||||
|
||||
<!-- TOC -->
|
||||
- [goports](#goports)
|
||||
- [Lucky(大吉)](#)
|
||||
- [特性](#特性)
|
||||
- [使用](#使用)
|
||||
- [转发规则格式](#转发规则格式)
|
||||
|
@ -17,19 +14,42 @@
|
|||
|
||||
## 特性
|
||||
|
||||
- 后端golang,前端vue3
|
||||
- 支持Windows、Linux系统,支持x86、ARM、MIPS、MIPSLE等架构
|
||||
- 支持界面化(web后台)管理转发规则,单条转发规则支持设置多个转发端口,一键开关指定转发规则
|
||||
- 单条规则支持黑白名单安全模式切换,白名单模式可以让没有安全验证的内网服务端口稍微安全一丢丢暴露到公网
|
||||
- Web后台支持查看最新100条日志
|
||||
- 另有精简版不带后台,支持命令行快捷设置转发规则,有利于空间有限的嵌入式设备运行.
|
||||
- 这是一个自用的,目前主要运行在自己的主路由(小米ax6000)里面的程序.
|
||||
|
||||
- 后端golang,前端vue3
|
||||
- 支持Windows、Linux系统,支持x86、ARM、MIPS、MIPSLE等架构
|
||||
|
||||
- 目前已经实现的功能有
|
||||
- 1.替代socat,主要用于公网IPv6 tcp/udp转 内网ipv4
|
||||
- 支持界面化(web后台)管理转发规则,单条转发规则支持设置多个转发端口,一键开关指定转发规则
|
||||
- 单条规则支持黑白名单安全模式切换,白名单模式可以让没有安全验证的内网服务端口稍微安全一丢丢暴露到公网
|
||||
- Web后台支持查看最新100条日志
|
||||
- 另有精简版不带后台,支持命令行快捷设置转发规则,有利于空间有限的嵌入式设备运行.(不再提供编译版本,如有需求可以自己编译)
|
||||
- 2.动态域名服务
|
||||
- 参考和部分代码来自 https://github.com/jeessy2/ddns-go
|
||||
- 在ddns-go的基础上主要改进/增加的功能有
|
||||
- 1.同时支持接入多个不同的DNS服务商
|
||||
- 2.支持http/https/socks5代理设置
|
||||
- 3.自定义(Callback)和Webhook支持自定义headers
|
||||
- 4.支持BasicAuth
|
||||
- 5.DDNS任务列表即可了解全部信息(包含错误信息),无需单独查看日志.
|
||||
- 6.调用DNS服务商接口更新域名信息前可以先通过DNS解析域名比较IP,减少对服务商接口调用.
|
||||
- 其它细节功能自己慢慢发现...
|
||||
- 没有文档,后台各处的提示信息已经足够多.
|
||||
|
||||
- 将要实现的功能
|
||||
- 有建议可联系作者.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 使用
|
||||
|
||||
|
||||
- [百度网盘下载地址](https://pan.baidu.com/s/1NfumD9XjYU3OTeVmbu6vOQ?pwd=6666)
|
||||
百度网盘版本可能会更新比较频繁,
|
||||
github release更新 只发布未经upx压缩的版本,精简版和upx压缩版会放上百度网盘
|
||||
|
||||
|
||||
- 默认后台管理地址 http://<运行设备IP>:16601
|
||||
默认登录账号: 666
|
||||
|
@ -38,15 +58,15 @@
|
|||
- 常规使用请用 -c <配置文件路径> 指定配置文件的路由方式运行 , -p <后台端口> 可以指定后台管理端口
|
||||
```bash
|
||||
#仅指定配置文件路径(如果配置文件不存在会自动创建),建议使用绝对路径
|
||||
goports -c 666.conf
|
||||
lucky -c 666.conf
|
||||
#同时指定后台端口 8899
|
||||
goports -c 666.conf -p 8899
|
||||
lucky -c 666.conf -p 8899
|
||||
```
|
||||
|
||||
- 命令行直接运行转发规则,注意后台无法编辑修改命令行启动的转发规则,主要用在不带后台的精简版
|
||||
```bash
|
||||
#指定后台端口8899
|
||||
goports -p 8899 <转发规则1> <转发规则2> <转发规则3>...<<转发规则N>
|
||||
lucky -p 8899 <转发规则1> <转发规则2> <转发规则3>...<<转发规则N>
|
||||
```
|
||||
|
||||
## 转发规则格式
|
||||
|
@ -96,6 +116,17 @@
|
|||

|
||||

|
||||

|
||||
#### 动态域名服务
|
||||
|
||||

|
||||
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
#开发编译
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ljymc/goports/thirdlib/gdylib/pool"
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/pool"
|
||||
)
|
||||
|
||||
type Proxy interface {
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
"github.com/ljymc/goports/thirdlib/gdylib/pool"
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/pool"
|
||||
)
|
||||
|
||||
const UDP_DEFAULT_PACKAGE_SIZE = 1500
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
cd ./web/goports-adminviews
|
||||
cd ./web/adminviews
|
||||
npm run build
|
45
config.json1
|
@ -1,45 +0,0 @@
|
|||
{
|
||||
"AdminWebListen": ":16601",
|
||||
"ProxyCountLimit": 128,
|
||||
"GlobalMaxConnections": 10240,
|
||||
"RelayRuleList": [
|
||||
{
|
||||
"Name": "FTP",
|
||||
"Configurestr": "tcp@:20000-20010,20021to192.168.31.180",
|
||||
"Enable": true,
|
||||
"Options": {
|
||||
"UDPPackageSize": 1500,
|
||||
"SingleProxyMaxConnections": 256
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Socket5 均衡负载",
|
||||
"Configurestr": "tcp@:6666to192.168.31.180:7890,192.168.31.148:7890",
|
||||
"Enable": true,
|
||||
"Options": {
|
||||
"UDPPackageSize": 1500,
|
||||
"SingleProxyMaxConnections": 256
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "FileBrowser",
|
||||
"Configurestr": "tcp@:28888to192.168.31.180",
|
||||
"Enable": true,
|
||||
"Options": {
|
||||
"UDPPackageSize": 1500,
|
||||
"SingleProxyMaxConnections": 256
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "DNS转发",
|
||||
"Configurestr": "tcp,udp@:53to192.168.31.1",
|
||||
"Enable": true,
|
||||
"Options": {
|
||||
"UDPPackageSize": 1496,
|
||||
"SingleProxyMaxConnections": 256,
|
||||
"UDPProxyPerformanceMode": true,
|
||||
"UDPShortMode": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -9,9 +9,9 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ljymc/goports/base"
|
||||
"github.com/ljymc/goports/thirdlib/gdylib/fileutils"
|
||||
"github.com/ljymc/goports/thirdlib/gdylib/stringsp"
|
||||
"github.com/gdy666/lucky/base"
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/fileutils"
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/stringsp"
|
||||
)
|
||||
|
||||
const defaultAdminAccount = "666"
|
||||
|
@ -60,6 +60,8 @@ type ProgramConfigure struct {
|
|||
RelayRuleList []ConfigureRelayRule `json:"RelayRuleList"`
|
||||
WhiteListConfigure WhiteListConfigure `json:"WhiteListConfigure"`
|
||||
BlackListConfigure BlackListConfigure `json:"BlackListConfigure"`
|
||||
DDNSConfigure DDNSConfigure `json:"DDNSConfigure"` //DDNS 参数设置
|
||||
DDNSTaskList []DDNSTask `json:"DDNSTaskList"`
|
||||
}
|
||||
|
||||
var programConfigureMutex sync.RWMutex
|
||||
|
@ -70,6 +72,10 @@ var configurePath string
|
|||
var checkConfigureFileOnce sync.Once
|
||||
var configureFileSign int8 = -1
|
||||
|
||||
// func GetConfigMutex() *sync.RWMutex {
|
||||
// return &programConfigureMutex
|
||||
// }
|
||||
|
||||
func GetAuthAccount() map[string]string {
|
||||
programConfigureMutex.RLock()
|
||||
defer programConfigureMutex.RUnlock()
|
||||
|
@ -103,6 +109,13 @@ func GetBaseConfigure() BaseConfigure {
|
|||
return baseConf
|
||||
}
|
||||
|
||||
func GetDDNSConfigure() DDNSConfigure {
|
||||
programConfigureMutex.RLock()
|
||||
defer programConfigureMutex.RUnlock()
|
||||
conf := programConfigure.DDNSConfigure
|
||||
return conf
|
||||
}
|
||||
|
||||
//保存基础配置
|
||||
func SetBaseConfigure(conf *BaseConfigure) error {
|
||||
programConfigureMutex.Lock()
|
||||
|
@ -115,6 +128,30 @@ func SetBaseConfigure(conf *BaseConfigure) error {
|
|||
return Save()
|
||||
}
|
||||
|
||||
func SetDDNSConfigure(conf *DDNSConfigure) error {
|
||||
programConfigureMutex.Lock()
|
||||
defer programConfigureMutex.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
|
||||
}
|
||||
|
||||
programConfigure.DDNSConfigure = *conf
|
||||
return Save()
|
||||
}
|
||||
|
||||
func Read(filePath string) (err error) {
|
||||
|
||||
pc, err := readProgramConfigure(filePath)
|
||||
|
@ -226,6 +263,14 @@ func loadDefaultConfigure(
|
|||
pc.BaseConfigure.AdminWebListenPort = defaultAdminListenPort
|
||||
}
|
||||
|
||||
if pc.DDNSConfigure.Intervals < 30 {
|
||||
pc.DDNSConfigure.Intervals = 30
|
||||
}
|
||||
|
||||
if pc.DDNSConfigure.FirstCheckDelay <= 0 {
|
||||
pc.DDNSConfigure.FirstCheckDelay = 0
|
||||
}
|
||||
|
||||
return &pc
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,617 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/stringsp"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
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"`
|
||||
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"`
|
||||
//-------------------------------------
|
||||
//IpCache IpCache `json:"-"`
|
||||
DomainsState DomainsState `json:"-"`
|
||||
}
|
||||
|
||||
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"`
|
||||
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服务器列表
|
||||
Callback DNSCallback `json:"Callback"`
|
||||
HttpClientProxyType string `json:"HttpClientProxyType"` //http client代理服务器设置
|
||||
HttpClientProxyAddr string `json:"HttpClientProxyAddr"` //代理服务器IP
|
||||
HttpClientProxyUser string `json:"HttpClientProxyUser"` //代理用户
|
||||
HttpClientProxyPassword string `json:"HttpClientProxyPassword"` //代理密码
|
||||
}
|
||||
|
||||
type DNSCallback struct {
|
||||
URL string `json:"URL"` //请求地址
|
||||
Method string `json:"Method"` //请求方法
|
||||
Headers []string `json:"Headers"`
|
||||
RequestBody string `json:"RequestBody"`
|
||||
Server string `json:"Server"` //预设服务商
|
||||
CallbackSuccessContent []string `json:"CallbackSuccessContent"` //接口调用成功包含内容
|
||||
}
|
||||
|
||||
//Check 检测IP是否有改变
|
||||
func (d *DDNSTask) IPChangeCheck(newAddr string) bool {
|
||||
if newAddr == "" {
|
||||
return true
|
||||
}
|
||||
// 地址改变
|
||||
if d.DomainsState.IpAddr != newAddr {
|
||||
//log.Printf("公网地址改变:[%s]===>[%s]", d.DomainsInfo.IpAddr, newAddr)
|
||||
d.DomainsState.IpAddr = newAddr
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
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"}
|
||||
|
||||
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 SetDDNSTaskIpCacheForceCompareByTaskKey(taskKey string, force bool) {
|
||||
// programConfigureMutex.Lock()
|
||||
// defer programConfigureMutex.Unlock()
|
||||
// taskIndex := -1
|
||||
|
||||
// for i := range programConfigure.DDNSTaskList {
|
||||
// if programConfigure.DDNSTaskList[i].TaskKey == taskKey {
|
||||
// taskIndex = i
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// if taskIndex == -1 {
|
||||
// return
|
||||
// }
|
||||
// programConfigure.DDNSTaskList[taskIndex].IpCache.ForceCompare = force
|
||||
// }
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func DDNSTaskListTaskDetailsInit() {
|
||||
programConfigureMutex.Lock()
|
||||
defer programConfigureMutex.Unlock()
|
||||
for i := range programConfigure.DDNSTaskList {
|
||||
programConfigure.DDNSTaskList[i].DomainsState.Init(programConfigure.DDNSTaskList[i].Domains)
|
||||
programConfigure.DDNSTaskList[i].DomainsState.SetDomainUpdateStatus(UpdateWaiting, "")
|
||||
|
||||
//
|
||||
if programConfigure.DDNSTaskList[i].DNS.ForceInterval < 60 {
|
||||
programConfigure.DDNSTaskList[i].DNS.ForceInterval = 60
|
||||
} else if programConfigure.DDNSTaskList[i].DNS.ForceInterval > 360000 {
|
||||
programConfigure.DDNSTaskList[i].DNS.ForceInterval = 360000
|
||||
}
|
||||
|
||||
if programConfigure.DDNSTaskList[i].HttpClientTimeout < 3 {
|
||||
programConfigure.DDNSTaskList[i].HttpClientTimeout = 3
|
||||
} else if programConfigure.DDNSTaskList[i].HttpClientTimeout > 60 {
|
||||
programConfigure.DDNSTaskList[i].HttpClientTimeout = 60
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func DDNSTaskIPCacheCheck(taskKey, ip string) (bool, error) {
|
||||
programConfigureMutex.Lock()
|
||||
defer programConfigureMutex.Unlock()
|
||||
taskIndex := -1
|
||||
|
||||
for i := range programConfigure.DDNSTaskList {
|
||||
if programConfigure.DDNSTaskList[i].TaskKey == taskKey {
|
||||
taskIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if taskIndex == -1 {
|
||||
return true, fmt.Errorf("找不到key对应的DDNS任务")
|
||||
}
|
||||
return programConfigure.DDNSTaskList[taskIndex].IPChangeCheck(ip), nil
|
||||
}
|
||||
|
||||
func DDNSTaskSetWebhookCallResult(taskKey string, result bool, message string) {
|
||||
programConfigureMutex.Lock()
|
||||
defer programConfigureMutex.Unlock()
|
||||
taskIndex := -1
|
||||
|
||||
for i := range programConfigure.DDNSTaskList {
|
||||
if programConfigure.DDNSTaskList[i].TaskKey == taskKey {
|
||||
taskIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if taskIndex == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("DDNSTaskSetWebhookCallResult %s", taskKey)
|
||||
|
||||
}
|
||||
|
||||
type DDNSTaskDetails struct {
|
||||
DDNSTask
|
||||
TaskState DomainsState `json:"TaskState"`
|
||||
}
|
||||
|
||||
func GetDDNSTaskList() []DDNSTaskDetails {
|
||||
programConfigureMutex.RLock()
|
||||
defer programConfigureMutex.RUnlock()
|
||||
|
||||
var resList []DDNSTaskDetails
|
||||
|
||||
for i := range programConfigure.DDNSTaskList {
|
||||
var info DDNSTaskDetails
|
||||
programConfigure.DDNSTaskList[i].DomainsState.Mutex.RLock()
|
||||
info.DDNSTask = programConfigure.DDNSTaskList[i]
|
||||
info.TaskState = programConfigure.DDNSTaskList[i].DomainsState
|
||||
programConfigure.DDNSTaskList[i].DomainsState.Mutex.RUnlock()
|
||||
resList = append(resList, info)
|
||||
}
|
||||
return resList
|
||||
}
|
||||
|
||||
func GetDDNSTaskByKey(taskKey string) *DDNSTaskDetails {
|
||||
programConfigureMutex.Lock()
|
||||
defer programConfigureMutex.Unlock()
|
||||
taskIndex := -1
|
||||
|
||||
for i := range programConfigure.DDNSTaskList {
|
||||
if programConfigure.DDNSTaskList[i].TaskKey == taskKey {
|
||||
taskIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if taskIndex == -1 {
|
||||
return nil
|
||||
}
|
||||
var info DDNSTaskDetails
|
||||
programConfigure.DDNSTaskList[taskIndex].DomainsState.Mutex.RLock()
|
||||
info.DDNSTask = programConfigure.DDNSTaskList[taskIndex]
|
||||
info.DomainsState = programConfigure.DDNSTaskList[taskIndex].DomainsState
|
||||
programConfigure.DDNSTaskList[taskIndex].DomainsState.Mutex.RUnlock()
|
||||
return &info
|
||||
}
|
||||
|
||||
func DDNSTaskListFlushDomainsDetails(taskKey string, state *DomainsState) {
|
||||
programConfigureMutex.Lock()
|
||||
defer programConfigureMutex.Unlock()
|
||||
taskIndex := -1
|
||||
|
||||
for i := range programConfigure.DDNSTaskList {
|
||||
if programConfigure.DDNSTaskList[i].TaskKey == taskKey {
|
||||
taskIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if taskIndex == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
var checkDomains []*Domain
|
||||
//防止有域名被删除
|
||||
for _, new := range state.Domains {
|
||||
for j, pre := range programConfigure.DDNSTaskList[taskIndex].DomainsState.Domains {
|
||||
if strings.Compare(new.String(), pre.String()) == 0 {
|
||||
checkDomains = append(checkDomains, programConfigure.DDNSTaskList[taskIndex].DomainsState.Domains[j])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.Domains = checkDomains
|
||||
|
||||
programConfigure.DDNSTaskList[taskIndex].DomainsState = *state
|
||||
}
|
||||
|
||||
func DDNSTaskListAdd(task *DDNSTask) error {
|
||||
programConfigureMutex.Lock()
|
||||
defer programConfigureMutex.Unlock()
|
||||
task.TaskKey = stringsp.GetRandomString(16)
|
||||
task.DomainsState.Init(task.Domains)
|
||||
task.DomainsState.SetDomainUpdateStatus(UpdateWaiting, "")
|
||||
programConfigure.DDNSTaskList = append(programConfigure.DDNSTaskList, *task)
|
||||
return Save()
|
||||
}
|
||||
|
||||
func DDNSTaskListDelete(taskKey string) error {
|
||||
programConfigureMutex.Lock()
|
||||
defer programConfigureMutex.Unlock()
|
||||
|
||||
taskIndex := -1
|
||||
|
||||
for i := range programConfigure.DDNSTaskList {
|
||||
if programConfigure.DDNSTaskList[i].TaskKey == taskKey {
|
||||
taskIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if taskIndex == -1 {
|
||||
return fmt.Errorf("找不到需要删除的DDNS任务")
|
||||
}
|
||||
|
||||
programConfigure.DDNSTaskList = DeleteDDNSTaskListlice(programConfigure.DDNSTaskList, taskIndex)
|
||||
return Save()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func EnableDDNSTaskByKey(taskKey string, enable bool) error {
|
||||
programConfigureMutex.Lock()
|
||||
defer programConfigureMutex.Unlock()
|
||||
taskIndex := -1
|
||||
|
||||
for i := range programConfigure.DDNSTaskList {
|
||||
if programConfigure.DDNSTaskList[i].TaskKey == taskKey {
|
||||
taskIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if taskIndex == -1 {
|
||||
return fmt.Errorf("开关DDNS任务失败,TaskKey不存在")
|
||||
}
|
||||
programConfigure.DDNSTaskList[taskIndex].Enable = enable
|
||||
if enable {
|
||||
programConfigure.DDNSTaskList[taskIndex].DomainsState.SetDomainUpdateStatus(UpdateWaiting, "")
|
||||
} else {
|
||||
programConfigure.DDNSTaskList[taskIndex].DomainsState.SetDomainUpdateStatus(UpdateStop, "")
|
||||
}
|
||||
return Save()
|
||||
}
|
||||
|
||||
func UpdateDomainsStateByTaskKey(taskKey string, status updateStatusType, message string) {
|
||||
programConfigureMutex.Lock()
|
||||
defer programConfigureMutex.Unlock()
|
||||
taskIndex := -1
|
||||
|
||||
for i := range programConfigure.DDNSTaskList {
|
||||
if programConfigure.DDNSTaskList[i].TaskKey == taskKey {
|
||||
taskIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if taskIndex == -1 {
|
||||
return
|
||||
}
|
||||
programConfigure.DDNSTaskList[taskIndex].DomainsState.SetDomainUpdateStatus(status, message)
|
||||
}
|
||||
|
||||
func UpdateTaskToDDNSTaskList(taskKey string, task DDNSTask) error {
|
||||
programConfigureMutex.Lock()
|
||||
defer programConfigureMutex.Unlock()
|
||||
taskIndex := -1
|
||||
|
||||
for i := range programConfigure.DDNSTaskList {
|
||||
if programConfigure.DDNSTaskList[i].TaskKey == taskKey {
|
||||
taskIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if taskIndex == -1 {
|
||||
return fmt.Errorf("找不到需要更新的DDNS任务")
|
||||
}
|
||||
|
||||
programConfigure.DDNSTaskList[taskIndex].TaskName = task.TaskName
|
||||
programConfigure.DDNSTaskList[taskIndex].TaskType = task.TaskType
|
||||
programConfigure.DDNSTaskList[taskIndex].Enable = task.Enable
|
||||
programConfigure.DDNSTaskList[taskIndex].GetType = task.GetType
|
||||
programConfigure.DDNSTaskList[taskIndex].URL = task.URL
|
||||
programConfigure.DDNSTaskList[taskIndex].NetInterface = task.NetInterface
|
||||
programConfigure.DDNSTaskList[taskIndex].IPReg = task.IPReg
|
||||
programConfigure.DDNSTaskList[taskIndex].Domains = task.Domains
|
||||
programConfigure.DDNSTaskList[taskIndex].DNS = task.DNS
|
||||
programConfigure.DDNSTaskList[taskIndex].Webhook = task.Webhook
|
||||
programConfigure.DDNSTaskList[taskIndex].TTL = task.TTL
|
||||
programConfigure.DDNSTaskList[taskIndex].DomainsState.IpAddr = ""
|
||||
programConfigure.DDNSTaskList[taskIndex].DomainsState.Init(task.Domains)
|
||||
programConfigure.DDNSTaskList[taskIndex].DomainsState.SetDomainUpdateStatus(UpdateWaiting, "")
|
||||
programConfigure.DDNSTaskList[taskIndex].HttpClientTimeout = task.HttpClientTimeout
|
||||
return Save()
|
||||
}
|
||||
|
||||
func DeleteDDNSTaskListlice(a []DDNSTask, deleteIndex int) []DDNSTask {
|
||||
j := 0
|
||||
for i := range a {
|
||||
if i != deleteIndex {
|
||||
a[j] = a[i]
|
||||
j++
|
||||
}
|
||||
}
|
||||
return a[:j]
|
||||
}
|
||||
|
||||
//****************************
|
||||
|
||||
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 = GetIPFromNetInterface("IPv4", d.NetInterface, d.IPReg)
|
||||
// 从网卡获取IP
|
||||
// ipv4, _, err := GetNetInterface()
|
||||
// if err != nil {
|
||||
// log.Println("从网卡获得IPv4失败!")
|
||||
// return
|
||||
// }
|
||||
|
||||
// for _, netInterface := range ipv4 {
|
||||
// if netInterface.NetInterfaceName == d.NetInterface && len(netInterface.AddressList) > 0 {
|
||||
// return netInterface.AddressList[0]
|
||||
// }
|
||||
// }
|
||||
|
||||
// log.Println("从网卡中获得IPv4失败! 网卡名: ", d.NetInterface)
|
||||
return
|
||||
}
|
||||
|
||||
ddnsGlobalConf := GetDDNSConfigure()
|
||||
|
||||
client, err := httputils.CreateHttpClient(
|
||||
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 := ioutil.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" {
|
||||
// 从网卡获取IP
|
||||
// _, ipv6, err := GetNetInterface()
|
||||
// if err != nil {
|
||||
// log.Println("从网卡获得IPv6失败!")
|
||||
// return
|
||||
// }
|
||||
|
||||
// for _, netInterface := range ipv6 {
|
||||
// if netInterface.NetInterfaceName == d.NetInterface && len(netInterface.AddressList) > 0 {
|
||||
// if d.IPReg != "" {
|
||||
// log.Printf("IPv6将使用正则表达式 %s 进行匹配\n", d.IPReg)
|
||||
// for i := 0; i < len(netInterface.AddressList); i++ {
|
||||
// matched, err := regexp.MatchString(d.IPReg, netInterface.AddressList[i])
|
||||
// if matched && err == nil {
|
||||
// log.Println("匹配成功! 匹配到地址: ", netInterface.AddressList[i])
|
||||
// return netInterface.AddressList[i]
|
||||
// }
|
||||
// log.Printf("第 %d 个地址 %s 不匹配, 将匹配下一个地址\n", i+1, netInterface.AddressList[i])
|
||||
// }
|
||||
// log.Println("没有匹配到任何一个IPv6地址, 将使用第一个地址")
|
||||
// }
|
||||
// return netInterface.AddressList[0]
|
||||
// }
|
||||
// }
|
||||
|
||||
// log.Println("从网卡中获得IPv6失败! 网卡名: ", d.NetInterface)
|
||||
result = GetIPFromNetInterface("IPv6", d.NetInterface, d.IPReg)
|
||||
return
|
||||
}
|
||||
|
||||
ddnsGlobalConf := GetDDNSConfigure()
|
||||
client, err := httputils.CreateHttpClient(
|
||||
!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 := ioutil.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
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// UpdatedNothing 未改变
|
||||
UpdatedNothing updateStatusType = "域名IP和公网IP一致"
|
||||
// UpdatedFailed 更新失败
|
||||
UpdatedFailed = "失败"
|
||||
// UpdatedSuccess 更新成功
|
||||
UpdatedSuccess = "成功"
|
||||
// UpdateStop 暂停
|
||||
UpdateStop = "暂停"
|
||||
// UpdateWaiting
|
||||
UpdateWaiting = "等待更新"
|
||||
)
|
||||
|
||||
// 固定的主域名
|
||||
var staticMainDomains = []string{"com.cn", "org.cn", "net.cn", "ac.cn", "eu.org"}
|
||||
|
||||
// 获取ip失败的次数
|
||||
|
||||
// Domains Ipv4/Ipv6 DomainsState
|
||||
type DomainsState struct {
|
||||
IpAddr string
|
||||
Domains []*Domain
|
||||
WebhookCallTime string `json:"WebhookCallTime"` //最后触发时间
|
||||
WebhookCallResult bool `json:"WebhookCallResult"` //触发结果
|
||||
WebhookCallErrorMsg string `json:"WebhookCallErrorMsg"` //触发错误信息
|
||||
LastSyncTime time.Time `json:"-"` //记录最新一次同步操作时间
|
||||
|
||||
IPAddrHistory []any `json:"IPAddrHistory"`
|
||||
WebhookCallHistroy []any `json:"WebhookCallHistroy"`
|
||||
|
||||
Mutex *sync.RWMutex `json:"-"`
|
||||
}
|
||||
|
||||
type IPAddrHistoryItem struct {
|
||||
IPaddr string
|
||||
RecordTime string
|
||||
}
|
||||
|
||||
// Domain 域名实体
|
||||
type Domain struct {
|
||||
DomainName string
|
||||
SubDomain string
|
||||
|
||||
UpdateStatus updateStatusType // 更新状态
|
||||
LastUpdateStatusTime string //最后更新状态的时间
|
||||
Message string
|
||||
|
||||
UpdateHistroy []any
|
||||
|
||||
rwmutex *sync.RWMutex
|
||||
}
|
||||
|
||||
type UpdateHistroyItem struct {
|
||||
UpdateStatus string
|
||||
UpdateTime string
|
||||
}
|
||||
|
||||
type WebhookCallHistroyItem struct {
|
||||
CallTime string
|
||||
CallResult string
|
||||
}
|
||||
|
||||
func (d *DomainsState) SetIPAddr(ipaddr string) {
|
||||
d.Mutex.Lock()
|
||||
defer d.Mutex.Unlock()
|
||||
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 (d Domain) String() string {
|
||||
if d.SubDomain != "" {
|
||||
return d.SubDomain + "." + d.DomainName
|
||||
}
|
||||
return d.DomainName
|
||||
}
|
||||
|
||||
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 *DomainsState) SetDomainUpdateStatus(status updateStatusType, message string) {
|
||||
for i := range d.Domains {
|
||||
d.Domains[i].SetDomainUpdateStatus(status, message)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DomainsState) 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 *Domain) SetDomainUpdateStatus(status updateStatusType, message string) {
|
||||
d.rwmutex.Lock()
|
||||
defer d.rwmutex.Unlock()
|
||||
d.UpdateStatus = status
|
||||
if status != UpdateWaiting && status != 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.Message = message
|
||||
|
||||
}
|
||||
|
||||
// GetFullDomain 获得全部的,子域名
|
||||
func (d Domain) GetFullDomain() string {
|
||||
if d.SubDomain != "" {
|
||||
return d.SubDomain + "." + d.DomainName
|
||||
}
|
||||
return "@" + "." + d.DomainName
|
||||
}
|
||||
|
||||
// GetSubDomain 获得子域名,为空返回@
|
||||
// 阿里云,dnspod需要
|
||||
func (d Domain) GetSubDomain() string {
|
||||
if d.SubDomain != "" {
|
||||
return d.SubDomain
|
||||
}
|
||||
return "@"
|
||||
}
|
||||
|
||||
func (d *DomainsState) Init(domains []string) {
|
||||
if d.Mutex == nil {
|
||||
d.Mutex = &sync.RWMutex{}
|
||||
}
|
||||
|
||||
d.Domains = d.checkParseDomains(domains)
|
||||
|
||||
}
|
||||
|
||||
// checkParseDomains 校验并解析用户输入的域名
|
||||
func (d *DomainsState) checkParseDomains(domainArr []string) (domains []*Domain) {
|
||||
for _, domainStr := range domainArr {
|
||||
domainStr = strings.TrimSpace(domainStr)
|
||||
if domainStr != "" {
|
||||
dp := strings.Split(domainStr, ":")
|
||||
dplen := len(dp)
|
||||
if dplen == 1 { // 自动识别域名
|
||||
domain := &Domain{}
|
||||
domain.rwmutex = d.Mutex
|
||||
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]
|
||||
}
|
||||
|
||||
domains = append(domains, domain)
|
||||
} else if dplen == 2 { // 主机记录:域名 格式
|
||||
domain := &Domain{}
|
||||
domain.rwmutex = d.Mutex
|
||||
sp := strings.Split(dp[1], ".")
|
||||
length := len(sp)
|
||||
if length <= 1 {
|
||||
log.Println(domainStr, "域名不正确")
|
||||
continue
|
||||
}
|
||||
domain.DomainName = dp[1]
|
||||
domain.SubDomain = dp[0]
|
||||
domains = append(domains, domain)
|
||||
} else {
|
||||
log.Println(domainStr, "域名不正确")
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CheckIPChange 检测公网IP是否改变
|
||||
func (domains *DomainsState) CheckIPChange(recordType, taskKey string, getAddrFunc func() string) (ipAddr string, change bool, retDomains []*Domain) {
|
||||
ipAddr = getAddrFunc()
|
||||
|
||||
checkIPChange, err := DDNSTaskIPCacheCheck(taskKey, domains.IpAddr)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("DDNSTaskIPCacheCheck 失败:%s", err.Error())
|
||||
}
|
||||
|
||||
if err != nil || checkIPChange {
|
||||
return ipAddr, true, domains.Domains
|
||||
}
|
||||
|
||||
//IP没变化
|
||||
return ipAddr, false, domains.Domains
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package config
|
||||
|
||||
import "runtime"
|
||||
|
||||
type AppInfo struct {
|
||||
AppName string
|
||||
Version string
|
||||
OS string
|
||||
ARCH string
|
||||
Date string
|
||||
}
|
||||
|
||||
var appInfo AppInfo
|
||||
|
||||
func GetAppInfo() *AppInfo {
|
||||
return &appInfo
|
||||
}
|
||||
|
||||
func InitAppInfo(version, date string) {
|
||||
appInfo.AppName = "Lucky(大吉)"
|
||||
appInfo.Version = version
|
||||
appInfo.Date = date
|
||||
appInfo.OS = runtime.GOOS
|
||||
appInfo.ARCH = runtime.GOARCH
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NetInterface 本机网络
|
||||
type NetInterface struct {
|
||||
NetInterfaceName string
|
||||
AddressList []string
|
||||
}
|
||||
|
||||
// GetNetInterface 获得网卡地址
|
||||
// 返回ipv4, ipv6地址
|
||||
func GetNetInterface() (ipv4NetInterfaces []NetInterface, ipv6NetInterfaces []NetInterface, err error) {
|
||||
allNetInterfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
//fmt.Println("net.Interfaces failed, err:", err.Error())
|
||||
return ipv4NetInterfaces, ipv6NetInterfaces, err
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/IPv6_address#General_allocation
|
||||
//_, ipv6Unicast, _ := net.ParseCIDR("2000::/3")
|
||||
|
||||
for i := 0; i < len(allNetInterfaces); i++ {
|
||||
if (allNetInterfaces[i].Flags & net.FlagUp) != 0 {
|
||||
addrs, _ := allNetInterfaces[i].Addrs()
|
||||
ipv4 := []string{}
|
||||
ipv6 := []string{}
|
||||
|
||||
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 == 128 {
|
||||
ipv6 = append(ipv6, ipnet.IP.String())
|
||||
}
|
||||
if bits == 32 {
|
||||
ipv4 = append(ipv4, ipnet.IP.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(ipv4) > 0 {
|
||||
ipv4NetInterfaces = append(
|
||||
ipv4NetInterfaces,
|
||||
NetInterface{
|
||||
NetInterfaceName: allNetInterfaces[i].Name,
|
||||
AddressList: ipv4,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if len(ipv6) > 0 {
|
||||
ipv6NetInterfaces = append(
|
||||
ipv6NetInterfaces,
|
||||
NetInterface{
|
||||
NetInterfaceName: allNetInterfaces[i].Name,
|
||||
AddressList: ipv6,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return ipv4NetInterfaces, ipv6NetInterfaces, nil
|
||||
}
|
||||
|
||||
func GetIPFromNetInterface(ipType, netinterface, ipreg string) string {
|
||||
ipv4NetInterfaces, ipv6NetInterfaces, err := GetNetInterface()
|
||||
if err != nil {
|
||||
log.Printf("获取网卡信息出错:%s", err.Error())
|
||||
return ""
|
||||
}
|
||||
|
||||
var netInterfaces []NetInterface
|
||||
|
||||
switch ipType {
|
||||
case "IPv6":
|
||||
netInterfaces = ipv6NetInterfaces
|
||||
case "IPv4":
|
||||
netInterfaces = ipv4NetInterfaces
|
||||
default:
|
||||
log.Printf("未知IP类型")
|
||||
return ""
|
||||
}
|
||||
|
||||
var addressList []string
|
||||
for i := range netInterfaces {
|
||||
if netInterfaces[i].NetInterfaceName == netinterface {
|
||||
addressList = netInterfaces[i].AddressList
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(addressList) <= 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if ipreg == "" { //默认返回第一个IP
|
||||
return addressList[0]
|
||||
}
|
||||
|
||||
ipN, err := strconv.Atoi(ipreg)
|
||||
if err == nil { //选择第N个IP
|
||||
if len(addressList) < ipN {
|
||||
log.Printf("当前选择网卡[%s]的第[%d]个IP,超出列表范围", netinterface, ipN)
|
||||
return ""
|
||||
}
|
||||
return addressList[ipN-1]
|
||||
}
|
||||
|
||||
for i := range addressList {
|
||||
matched, err := regexp.MatchString(ipreg, addressList[i])
|
||||
if matched && err == nil {
|
||||
log.Printf("正则匹配上")
|
||||
return addressList[i]
|
||||
}
|
||||
}
|
||||
|
||||
if len(ipreg) <= 1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if ipreg[len(ipreg)-1] == '*' {
|
||||
prefixStr := ipreg[:len(ipreg)-1]
|
||||
log.Printf("匹配以 %s 开头的IP", prefixStr)
|
||||
for i := range addressList {
|
||||
if strings.HasPrefix(addressList[i], prefixStr) {
|
||||
return addressList[i]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
if ipreg[0] == '*' {
|
||||
suffixStr := ipreg[1:]
|
||||
log.Printf("匹配以 %s 结尾的IP", suffixStr)
|
||||
for i := range addressList {
|
||||
if strings.HasSuffix(addressList[i], suffixStr) {
|
||||
return addressList[i]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
|
||||
)
|
||||
|
||||
// updateStatusType 更新状态
|
||||
type updateStatusType string
|
||||
|
||||
// ExecWebhook 添加或更新IPv4/IPv6记录
|
||||
func (d *DDNSTask) ExecWebhook(domains *DomainsState) {
|
||||
if !d.WebhookEnable {
|
||||
return
|
||||
}
|
||||
|
||||
if domains.IpAddr == "" && !d.WebhookCallOnGetIPfail {
|
||||
return
|
||||
}
|
||||
|
||||
tryUpdate := hasDomainTryToUpdate(domains.Domains)
|
||||
|
||||
if d.WebhookURL != "" && tryUpdate {
|
||||
|
||||
//log.Printf("DDNS任务【%s】触发Webhook", d.TaskName)
|
||||
|
||||
url := replaceWebhookPara(domains, d.WebhookURL)
|
||||
requestBody := replaceWebhookPara(domains, d.WebhookRequestBody)
|
||||
|
||||
//headersStr := cb.task.DNS.Callback.Headers
|
||||
var headerStrList []string
|
||||
for i := range d.WebhookHeaders {
|
||||
header := replaceWebhookPara(domains, d.WebhookHeaders[i])
|
||||
headerStrList = append(headerStrList, header)
|
||||
}
|
||||
|
||||
headers := httputils.CreateHeadersMap(headerStrList)
|
||||
|
||||
succcssCotentList := []string{}
|
||||
for i := range d.WebhookSuccessContent {
|
||||
content := replaceWebhookPara(domains, d.WebhookSuccessContent[i])
|
||||
succcssCotentList = append(succcssCotentList, content)
|
||||
}
|
||||
|
||||
callErr := d.webhookHttpClientDo(d.WebhookMethod, url, requestBody, headers, succcssCotentList)
|
||||
|
||||
if callErr != nil {
|
||||
//log.Printf("WebHook 调用出错:%s", callErr.Error())
|
||||
domains.SetWebhookResult(false, callErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
//log.Printf("Webhook 调用成功")
|
||||
domains.SetWebhookResult(true, "")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func WebhookTest(d *DDNSTask, url, method, WebhookRequestBody, proxy, addr, user, passwd string, headerList, successContentListraw []string) (string, error) {
|
||||
url = replaceWebhookTestPara(url)
|
||||
requestBody := replaceWebhookTestPara(WebhookRequestBody)
|
||||
|
||||
//headersStr := cb.task.DNS.Callback.Headers
|
||||
var headerStrList []string
|
||||
for i := range headerList {
|
||||
header := replaceWebhookTestPara(headerList[i])
|
||||
headerStrList = append(headerStrList, header)
|
||||
}
|
||||
|
||||
headers := httputils.CreateHeadersMap(headerStrList)
|
||||
|
||||
succcssCotentList := []string{}
|
||||
for i := range successContentListraw {
|
||||
content := replaceWebhookTestPara(successContentListraw[i])
|
||||
succcssCotentList = append(succcssCotentList, content)
|
||||
}
|
||||
|
||||
globalDDNSConf := 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(
|
||||
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 *DDNSTask) webhookHttpClientDo(method, url, requestBody string, headers map[string]string, callbackSuccessContent []string) error {
|
||||
|
||||
globalDDNSConf := 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
|
||||
respStr, err := httputils.GetStringGoutDoHttpRequest(
|
||||
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())
|
||||
}
|
||||
|
||||
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 string) (newPara string) {
|
||||
orgPara = strings.ReplaceAll(orgPara, "#{ipAddr}", "66.66.66.66")
|
||||
orgPara = strings.ReplaceAll(orgPara, "#{successDomains}", "baidu.com,google.com")
|
||||
orgPara = strings.ReplaceAll(orgPara, "#{failedDomains}", "weibo.com,github.com")
|
||||
return orgPara
|
||||
}
|
||||
|
||||
// replacePara 替换参数 #{successDomains},#{failedDomains}
|
||||
func replaceWebhookPara(d *DomainsState, orgPara string) (newPara string) {
|
||||
ipAddrText := d.IpAddr
|
||||
if ipAddrText == "" {
|
||||
ipAddrText = "获取IP失败"
|
||||
}
|
||||
orgPara = strings.ReplaceAll(orgPara, "#{ipAddr}", ipAddrText)
|
||||
successDomains, failedDomains := getDomainsStr(d.Domains)
|
||||
orgPara = strings.ReplaceAll(orgPara, "#{successDomains}", successDomains)
|
||||
orgPara = strings.ReplaceAll(orgPara, "#{failedDomains}", failedDomains)
|
||||
return orgPara
|
||||
}
|
||||
|
||||
// getDomainsStr 用逗号分割域名,分类域名返回,成功和失败的
|
||||
func getDomainsStr(domains []*Domain) (string, string) {
|
||||
var successDomainBuf strings.Builder
|
||||
var failedDomainsBuf strings.Builder
|
||||
for _, v46 := range domains {
|
||||
if v46.UpdateStatus == UpdatedFailed {
|
||||
if failedDomainsBuf.Len() > 0 {
|
||||
failedDomainsBuf.WriteString(",")
|
||||
}
|
||||
failedDomainsBuf.WriteString(v46.String())
|
||||
continue
|
||||
}
|
||||
|
||||
if successDomainBuf.Len() > 0 {
|
||||
successDomainBuf.WriteString(",")
|
||||
}
|
||||
successDomainBuf.WriteString(v46.String())
|
||||
}
|
||||
|
||||
return successDomainBuf.String(), failedDomainsBuf.String()
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
package ddns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/gdy666/lucky/config"
|
||||
"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 {
|
||||
DNSCommon
|
||||
TTL string
|
||||
}
|
||||
|
||||
// AlidnsSubDomainRecords 记录
|
||||
type AlidnsSubDomainRecords struct {
|
||||
TotalCount int
|
||||
DomainRecords struct {
|
||||
Record []struct {
|
||||
DomainName string
|
||||
RecordID string
|
||||
Value string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AlidnsResp 修改/添加返回结果
|
||||
type AlidnsResp struct {
|
||||
RecordID string
|
||||
RequestID string
|
||||
}
|
||||
|
||||
// Init 初始化
|
||||
func (ali *Alidns) Init(task *config.DDNSTask) {
|
||||
ali.DNSCommon.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 *config.Domain) {
|
||||
var record AlidnsSubDomainRecords
|
||||
// 获取当前域名信息
|
||||
params := url.Values{}
|
||||
params.Set("Action", "DescribeSubDomainRecords")
|
||||
params.Set("DomainName", domain.DomainName)
|
||||
params.Set("SubDomain", domain.GetFullDomain())
|
||||
params.Set("Type", recordType)
|
||||
err := ali.request(params, &record)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if record.TotalCount > 0 {
|
||||
// 存在,更新
|
||||
ali.modify(record, domain, recordType, ipAddr)
|
||||
} else {
|
||||
// 不存在,创建
|
||||
ali.create(domain, recordType, ipAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建
|
||||
func (ali *Alidns) create(domain *config.Domain, recordType string, ipAddr string) {
|
||||
params := url.Values{}
|
||||
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 != "" {
|
||||
//log.Printf("新增域名解析 %s 成功!IP: %s", domain, ipAddr)
|
||||
domain.SetDomainUpdateStatus(config.UpdatedSuccess, "")
|
||||
} else {
|
||||
//log.Printf("新增域名解析 %s 失败!", domain)
|
||||
domain.SetDomainUpdateStatus(config.UpdatedFailed, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 修改
|
||||
func (ali *Alidns) modify(record AlidnsSubDomainRecords, domain *config.Domain, recordType string, ipAddr string) {
|
||||
|
||||
// 相同不修改
|
||||
if len(record.DomainRecords.Record) > 0 && record.DomainRecords.Record[0].Value == ipAddr {
|
||||
//log.Printf("你的IP %s 没有变化, 域名 %s", ipAddr, domain)
|
||||
domain.SetDomainUpdateStatus(config.UpdatedNothing, "")
|
||||
return
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Set("Action", "UpdateDomainRecord")
|
||||
params.Set("RR", domain.GetSubDomain())
|
||||
params.Set("RecordId", record.DomainRecords.Record[0].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 != "" {
|
||||
//log.Printf("更新域名解析 %s 成功!IP: %s", domain, ipAddr)
|
||||
domain.SetDomainUpdateStatus(config.UpdatedSuccess, "")
|
||||
} else {
|
||||
//log.Printf("更新域名解析 %s 失败!", domain)
|
||||
domain.SetDomainUpdateStatus(config.UpdatedFailed, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// request 统一请求接口
|
||||
func (ali *Alidns) request(params url.Values, result interface{}) (err error) {
|
||||
|
||||
util.AliyunSigner(ali.task.DNS.ID, ali.task.DNS.Secret, ¶ms)
|
||||
|
||||
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)
|
||||
//err = util.GetHTTPResponse(resp, alidnsEndpoint, err, result)
|
||||
|
||||
err = httputils.GetAndParseJSONResponseFromHttpResponse(resp, result)
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
package ddns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gdy666/lucky/config"
|
||||
"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 {
|
||||
DNSCommon
|
||||
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 *config.DDNSTask) {
|
||||
baidu.DNSCommon.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 *config.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 {
|
||||
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 *config.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(config.UpdatedSuccess, "")
|
||||
} else {
|
||||
//log.Printf("新增域名解析 %s 失败!", domain)
|
||||
domain.SetDomainUpdateStatus(config.UpdatedFailed, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
//modify 更新解析
|
||||
func (baidu *BaiduCloud) modify(record BaiduRecord, domain *config.Domain, rdType string, ipAddr string) {
|
||||
//没有变化直接跳过
|
||||
if record.Rdata == ipAddr {
|
||||
//log.Printf("你的IP %s 没有变化, 域名 %s", ipAddr, domain)
|
||||
domain.SetDomainUpdateStatus(config.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(config.UpdatedSuccess, "")
|
||||
} else {
|
||||
//log.Printf("更新域名解析 %s 失败!", domain)
|
||||
domain.SetDomainUpdateStatus(config.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)
|
||||
err = httputils.GetAndParseJSONResponseFromHttpResponse(resp, result)
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package ddns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gdy666/lucky/config"
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
|
||||
)
|
||||
|
||||
type Callback struct {
|
||||
DNSCommon
|
||||
TTL string
|
||||
}
|
||||
|
||||
// Init 初始化
|
||||
func (cb *Callback) Init(task *config.DDNSTask) {
|
||||
cb.DNSCommon.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 *config.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(config.UpdatedFailed, callErr.Error())
|
||||
return
|
||||
}
|
||||
domain.SetDomainUpdateStatus(config.UpdatedSuccess, "")
|
||||
}
|
||||
|
||||
// replacePara 替换参数
|
||||
func replacePara(orgPara, ipAddr string, domain *config.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)
|
||||
|
||||
return orgPara
|
||||
}
|
||||
|
||||
func (cb *Callback) CallbackHttpClientDo(method, url, requestBody string, headers map[string]string, callbackSuccessContent []string) error {
|
||||
|
||||
globalDDNSConf := config.GetDDNSConfigure()
|
||||
dnsConf := cb.task.DNS
|
||||
respStr, err := httputils.GetStringGoutDoHttpRequest(
|
||||
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())
|
||||
}
|
||||
//log.Printf("接口[%s]调用响应:%s\n", url, respStr)
|
||||
|
||||
for _, successContent := range callbackSuccessContent {
|
||||
if strings.Contains(respStr, successContent) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("调用接口失败:\n%s", respStr)
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
package ddns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gdy666/lucky/config"
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
|
||||
)
|
||||
|
||||
const (
|
||||
zonesAPI string = "https://api.cloudflare.com/client/v4/zones"
|
||||
)
|
||||
|
||||
// Cloudflare Cloudflare实现
|
||||
type Cloudflare struct {
|
||||
DNSCommon
|
||||
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 *config.DDNSTask) {
|
||||
cf.DNSCommon.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 *config.Domain) {
|
||||
result, err := cf.getZones(domain)
|
||||
if err != nil || len(result.Result) != 1 {
|
||||
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 {
|
||||
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 *config.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(config.UpdatedSuccess, "")
|
||||
} else {
|
||||
//log.Printf("新增域名解析 %s 失败!Messages: %s", domain, status.Messages)
|
||||
domain.SetDomainUpdateStatus(config.UpdatedFailed, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 修改
|
||||
func (cf *Cloudflare) modify(result CloudflareRecordsResp, zoneID string, domain *config.Domain, recordType string, ipAddr string) {
|
||||
|
||||
for _, record := range result.Result {
|
||||
// 相同不修改
|
||||
if record.Content == ipAddr {
|
||||
//log.Printf("你的IP %s 没有变化, 域名 %s", ipAddr, domain)
|
||||
domain.SetDomainUpdateStatus(config.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(config.UpdatedSuccess, "")
|
||||
} else {
|
||||
//log.Printf("更新域名解析 %s 失败!Messages: %s", domain, status.Messages)
|
||||
errMsg := "更新失败"
|
||||
if err != nil {
|
||||
errMsg = err.Error()
|
||||
}
|
||||
domain.SetDomainUpdateStatus(config.UpdatedFailed, errMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获得域名记录列表
|
||||
func (cf *Cloudflare) getZones(domain *config.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)
|
||||
err = httputils.GetAndParseJSONResponseFromHttpResponse(resp, result)
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package ddns
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gdy666/lucky/config"
|
||||
"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 := config.GetDDNSTaskList()
|
||||
config.CleanIPUrlAddrMap()
|
||||
ddnsConf := config.GetDDNSConfigure()
|
||||
|
||||
for index := range ddnsTaskList {
|
||||
|
||||
task := ddnsTaskList[index]
|
||||
if !task.Enable {
|
||||
config.UpdateDomainsStateByTaskKey(task.TaskKey, config.UpdateStop, "")
|
||||
continue
|
||||
}
|
||||
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)
|
||||
}()
|
||||
syncDDNSTask(&task)
|
||||
}()
|
||||
|
||||
<-time.After(time.Second)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
//log.Printf("syncAllDomainsOnce 任务完成")
|
||||
DDNSService.Timer = time.NewTimer(time.Second * time.Duration(ddnsConf.Intervals))
|
||||
}
|
||||
|
||||
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)
|
||||
task := config.GetDDNSTaskByKey(taskKey)
|
||||
syncDDNSTask(task)
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func syncDDNSTask(task *config.DDNSTaskDetails) {
|
||||
if task == nil {
|
||||
return
|
||||
}
|
||||
var dnsSelected DNS
|
||||
switch task.DNS.Name {
|
||||
case "alidns":
|
||||
dnsSelected = &Alidns{}
|
||||
case "dnspod":
|
||||
dnsSelected = &Dnspod{}
|
||||
case "cloudflare":
|
||||
dnsSelected = &Cloudflare{}
|
||||
case "huaweicloud":
|
||||
dnsSelected = &Huaweicloud{}
|
||||
case "callback":
|
||||
dnsSelected = &Callback{}
|
||||
case "baiducloud":
|
||||
dnsSelected = &BaiduCloud{}
|
||||
default:
|
||||
return
|
||||
}
|
||||
dnsSelected.Init(&task.DDNSTask)
|
||||
dnsSelected.AddUpdateDomainRecords()
|
||||
|
||||
//task.DomainsState.IpAddr = ipaddr
|
||||
task.ExecWebhook(&task.DomainsState)
|
||||
config.DDNSTaskListFlushDomainsDetails(task.TaskKey, &task.DomainsState)
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package ddns
|
||||
|
||||
import "github.com/gdy666/lucky/config"
|
||||
|
||||
// DNS interface
|
||||
type DNS interface {
|
||||
Init(task *config.DDNSTask)
|
||||
// 添加或更新IPv4/IPv6记录
|
||||
AddUpdateDomainRecords() string
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
package ddns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gdy666/lucky/config"
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
type DNSCommon struct {
|
||||
//Domains *config.Domains
|
||||
createUpdateDomainFunc func(recordType, ipaddr string, domain *config.Domain)
|
||||
task *config.DDNSTask
|
||||
}
|
||||
|
||||
func (d *DNSCommon) SetCreateUpdateDomainFunc(f func(recordType, ipaddr string, domain *config.Domain)) {
|
||||
d.createUpdateDomainFunc = f
|
||||
}
|
||||
|
||||
func (d *DNSCommon) Init(task *config.DDNSTask) {
|
||||
d.task = task
|
||||
}
|
||||
|
||||
//添加或更新IPv4/IPv6记录
|
||||
func (d *DNSCommon) AddUpdateDomainRecords() string {
|
||||
if d.task.TaskType == "IPv6" {
|
||||
|
||||
return d.addUpdateDomainRecords("AAAA")
|
||||
}
|
||||
return d.addUpdateDomainRecords("A")
|
||||
}
|
||||
|
||||
func (d *DNSCommon) addUpdateDomainRecords(recordType string) string {
|
||||
ipAddr, change, domains := d.task.DomainsState.CheckIPChange(recordType, d.task.TaskKey, d.task.GetIpAddr)
|
||||
|
||||
d.task.DomainsState.SetIPAddr(ipAddr)
|
||||
|
||||
if ipAddr == "" {
|
||||
d.task.DomainsState.SetDomainUpdateStatus(config.UpdatedFailed, "获取公网IP失败")
|
||||
return ipAddr
|
||||
}
|
||||
|
||||
preFaildDomains := []*config.Domain{}
|
||||
|
||||
if time.Since(d.task.DomainsState.LastSyncTime) > time.Second*time.Duration(d.task.DNS.ForceInterval) {
|
||||
//log.Printf("DDNS任务[%s]强制更新", d.task.TaskName)
|
||||
change = true
|
||||
goto sync
|
||||
}
|
||||
|
||||
//设置原先状态成功的为继续成功
|
||||
//不成功的就更新
|
||||
if !change { //公网IP没有改变
|
||||
for i := range domains { //如果原先状态成功或不改变就刷新时间
|
||||
if domains[i].UpdateStatus == config.UpdatedNothing || domains[i].UpdateStatus == config.UpdatedSuccess {
|
||||
domains[i].SetDomainUpdateStatus(config.UpdatedNothing, "")
|
||||
continue
|
||||
}
|
||||
preFaildDomains = append(preFaildDomains, domains[i])
|
||||
}
|
||||
|
||||
if len(preFaildDomains) == 0 {
|
||||
return ipAddr
|
||||
}
|
||||
domains = preFaildDomains
|
||||
}
|
||||
|
||||
sync:
|
||||
if change {
|
||||
defer func() {
|
||||
//记录最近一次同步操作时间
|
||||
d.task.DomainsState.LastSyncTime = time.Now()
|
||||
}()
|
||||
}
|
||||
|
||||
for _, domain := range domains {
|
||||
|
||||
if d.createUpdateDomainFunc == nil {
|
||||
log.Printf("ddns createUpdateDomainFunc undefine")
|
||||
break
|
||||
}
|
||||
|
||||
if d.task.DNS.ResolverDoaminCheck {
|
||||
domainResolverIPaddr, _ := ResolveDomainAtServerList(recordType, domain.String(), d.task.DNS.DNSServerList)
|
||||
|
||||
if domainResolverIPaddr == ipAddr {
|
||||
domain.SetDomainUpdateStatus(config.UpdatedNothing, "")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
d.createUpdateDomainFunc(recordType, ipAddr, domain)
|
||||
}
|
||||
|
||||
return ipAddr
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
func (d *DNSCommon) CreateHTTPClient() (*http.Client, error) {
|
||||
ddnsGlobalConf := config.GetDDNSConfigure()
|
||||
return httputils.CreateHttpClient(
|
||||
!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)
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------
|
||||
func ResolveDomainAtServerList(queryType, domain string, dnsServerList []string) (string, error) {
|
||||
|
||||
if len(dnsServerList) == 0 {
|
||||
if queryType == "AAAA" {
|
||||
dnsServerList = config.DefaultIPv6DNSServerList
|
||||
} else {
|
||||
dnsServerList = config.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)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
package ddns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/gdy666/lucky/config"
|
||||
"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 {
|
||||
DNSCommon
|
||||
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 *config.DDNSTask) {
|
||||
dnspod.DNSCommon.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 *config.Domain) {
|
||||
result, err := dnspod.getRecordList(domain, recordType)
|
||||
if err != nil {
|
||||
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 *config.Domain, recordType string, ipAddr string) {
|
||||
status, err := dnspod.commonRequest(
|
||||
recordCreateAPI,
|
||||
url.Values{
|
||||
"login_token": {dnspod.task.DNS.ID + "," + dnspod.task.DNS.Secret},
|
||||
"domain": {domain.DomainName},
|
||||
"sub_domain": {domain.GetSubDomain()},
|
||||
"record_type": {recordType},
|
||||
"record_line": {"默认"},
|
||||
"value": {ipAddr},
|
||||
"ttl": {dnspod.TTL},
|
||||
"format": {"json"},
|
||||
},
|
||||
domain,
|
||||
)
|
||||
if err == nil && status.Status.Code == "1" {
|
||||
//log.Printf("新增域名解析 %s 成功!IP: %s", domain, ipAddr)
|
||||
domain.SetDomainUpdateStatus(config.UpdatedSuccess, "")
|
||||
} else {
|
||||
//log.Printf("新增域名解析 %s 失败!Code: %s, Message: %s", domain, status.Status.Code, status.Status.Message)
|
||||
domain.SetDomainUpdateStatus(config.UpdatedFailed, fmt.Sprintf("Code: %s, Message: %s", status.Status.Code, status.Status.Message))
|
||||
}
|
||||
}
|
||||
|
||||
// 修改
|
||||
func (dnspod *Dnspod) modify(result DnspodRecordListResp, domain *config.Domain, recordType string, ipAddr string) {
|
||||
for _, record := range result.Records {
|
||||
// 相同不修改
|
||||
if record.Value == ipAddr {
|
||||
//log.Printf("你的IP %s 没有变化, 域名 %s", ipAddr, domain)
|
||||
domain.SetDomainUpdateStatus(config.UpdatedNothing, "")
|
||||
continue
|
||||
}
|
||||
status, err := dnspod.commonRequest(
|
||||
recordModifyURL,
|
||||
url.Values{
|
||||
"login_token": {dnspod.task.DNS.ID + "," + dnspod.task.DNS.Secret},
|
||||
"domain": {domain.DomainName},
|
||||
"sub_domain": {domain.GetSubDomain()},
|
||||
"record_type": {recordType},
|
||||
"record_line": {"默认"},
|
||||
"record_id": {record.ID},
|
||||
"value": {ipAddr},
|
||||
"ttl": {dnspod.TTL},
|
||||
"format": {"json"},
|
||||
},
|
||||
domain,
|
||||
)
|
||||
if err == nil && status.Status.Code == "1" {
|
||||
//log.Printf("更新域名解析 %s 成功!IP: %s", domain, ipAddr)
|
||||
domain.SetDomainUpdateStatus(config.UpdatedSuccess, "")
|
||||
} else {
|
||||
//log.Printf("更新域名解析 %s 失败!Code: %s, Message: %s", domain, status.Status.Code, status.Status.Message)
|
||||
domain.SetDomainUpdateStatus(config.UpdatedFailed, fmt.Sprintf("Code: %s, Message: %s", status.Status.Code, status.Status.Message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 公共
|
||||
func (dnspod *Dnspod) commonRequest(apiAddr string, values url.Values, domain *config.Domain) (status DnspodStatus, err error) {
|
||||
resp, err := http.PostForm(
|
||||
apiAddr,
|
||||
values,
|
||||
)
|
||||
|
||||
err = httputils.GetAndParseJSONResponseFromHttpResponse(resp, &status)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 获得域名记录列表
|
||||
func (dnspod *Dnspod) getRecordList(domain *config.Domain, typ string) (result DnspodRecordListResp, err error) {
|
||||
values := url.Values{
|
||||
"login_token": {dnspod.task.DNS.ID + "," + dnspod.task.DNS.Secret},
|
||||
"domain": {domain.DomainName},
|
||||
"record_type": {typ},
|
||||
"sub_domain": {domain.GetSubDomain()},
|
||||
"format": {"json"},
|
||||
}
|
||||
|
||||
client, e := dnspod.CreateHTTPClient()
|
||||
if e != nil {
|
||||
err = e
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := client.PostForm(
|
||||
recordListAPI,
|
||||
values,
|
||||
)
|
||||
|
||||
err = httputils.GetAndParseJSONResponseFromHttpResponse(resp, result)
|
||||
return
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
package ddns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gdy666/lucky/config"
|
||||
"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 {
|
||||
DNSCommon
|
||||
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 *config.DDNSTask) {
|
||||
hw.DNSCommon.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 *config.Domain) {
|
||||
var records HuaweicloudRecordsResp
|
||||
|
||||
err := hw.request(
|
||||
"GET",
|
||||
fmt.Sprintf(huaweicloudEndpoint+"/v2/recordsets?type=%s&name=%s", recordType, domain),
|
||||
nil,
|
||||
&records,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
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 *config.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(config.UpdatedSuccess, "")
|
||||
} else {
|
||||
//log.Printf("新增域名解析 %s 失败!Status: %s", domain, result.Status)
|
||||
domain.SetDomainUpdateStatus(config.UpdatedFailed, result.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// 修改
|
||||
func (hw *Huaweicloud) modify(record HuaweicloudRecordsets, domain *config.Domain, recordType string, ipAddr string) {
|
||||
|
||||
// 相同不修改
|
||||
if len(record.Records) > 0 && record.Records[0] == ipAddr {
|
||||
//log.Printf("你的IP %s 没有变化, 域名 %s", ipAddr, domain)
|
||||
domain.SetDomainUpdateStatus(config.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(config.UpdatedSuccess, "")
|
||||
} else {
|
||||
//log.Printf("更新域名解析 %s 失败!Status: %s", domain, result.Status)
|
||||
domain.SetDomainUpdateStatus(config.UpdatedFailed, result.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// 获得域名记录列表
|
||||
func (hw *Huaweicloud) getZones(domain *config.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)
|
||||
err = httputils.GetAndParseJSONResponseFromHttpResponse(resp, result)
|
||||
|
||||
return
|
||||
}
|
2
debug.go
|
@ -6,7 +6,7 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ljymc/goports/thirdlib/gdylib/recoverutil"
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/recoverutil"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
22
go.mod
|
@ -1,17 +1,16 @@
|
|||
module github.com/ljymc/goports
|
||||
module github.com/gdy666/lucky
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/fatedier/golib v0.2.0
|
||||
golang.org/x/net v0.0.0-20220524220425-1d687d428aca // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/gzip v0.0.5
|
||||
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/shirou/gopsutil/v3 v3.22.5
|
||||
github.com/guonaihong/gout v0.2.12
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/shirou/gopsutil/v3 v3.22.6
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -28,17 +27,18 @@ require (
|
|||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||
github.com/tklauser/numcpus v0.4.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-20210711020723-a769d52b0f97 // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/ljymc/toolbox/fileutils => ../toolbox/fileutils/
|
||||
|
|
55
go.sum
|
@ -4,11 +4,11 @@ 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/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.5 h1:mhnVU32YnnBh2LPH2iqRqsA/eR7SAqRaD388jL2s/j0=
|
||||
github.com/gin-contrib/gzip v0.0.5/go.mod h1:OPIK6HR0Um2vNmBUTlayD7qle4yVVRZT0PyhdUigrKk=
|
||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
|
||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
|
@ -36,6 +36,10 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/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/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/guonaihong/gout v0.2.12 h1:ZNtg0Nq6yzlBNgDhFF2YaRdngZCIPuijgaA0s+rCvfY=
|
||||
github.com/guonaihong/gout v0.2.12/go.mod h1:25bJRA+9fErgSvEUF5UAazc9dME+VdkjrFu7yrrVQUg=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
|
@ -55,6 +59,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2
|
|||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
|
@ -63,6 +69,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
|
|||
github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
|
@ -70,15 +78,18 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om
|
|||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/shirou/gopsutil/v3 v3.22.5 h1:atX36I/IXgFiB81687vSiBI5zrMsxcIBkP9cQMJQoJA=
|
||||
github.com/shirou/gopsutil/v3 v3.22.5/go.mod h1:so9G9VzeHt/hsd0YwqprnjHnfARAUktauykSbr+y2gA=
|
||||
github.com/shirou/gopsutil/v3 v3.22.6 h1:FnHOFOh+cYAM0C30P+zysPISzlknLC5Z1G4EAElznfQ=
|
||||
github.com/shirou/gopsutil/v3 v3.22.6/go.mod h1:EdIubSnZhbAvBS1yJ7Xi+AShB/hxwLHOMz4MCYz7yMs=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
|
||||
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
|
||||
github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o=
|
||||
|
@ -88,24 +99,37 @@ github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6
|
|||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
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=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220524220425-1d687d428aca h1:xTaFYiPROfpPhqrfTIDXj0ri1SpfueYT951s4bAuDO8=
|
||||
golang.org/x/net v0.0.0-20220524220425-1d687d428aca/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-20200116001909-b77594299b42/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=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -116,12 +140,19 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
|
|||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
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 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
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=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -135,5 +166,5 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
21
main.go
|
@ -10,9 +10,10 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/ljymc/goports/base"
|
||||
"github.com/ljymc/goports/config"
|
||||
"github.com/ljymc/goports/rule"
|
||||
"github.com/gdy666/lucky/base"
|
||||
"github.com/gdy666/lucky/config"
|
||||
"github.com/gdy666/lucky/ddns"
|
||||
"github.com/gdy666/lucky/rule"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -28,17 +29,16 @@ var (
|
|||
|
||||
var (
|
||||
runMode = "prod"
|
||||
version = "unknown"
|
||||
version = "dev"
|
||||
commit = "none"
|
||||
date = "unknown"
|
||||
)
|
||||
|
||||
var runTime time.Time
|
||||
|
||||
// go build && ./goports 127.0.0.199:7000,443to192.168.31.1:80,443 20100-20110to192.168.31.1:20100-20110
|
||||
func main() {
|
||||
|
||||
flag.Parse()
|
||||
config.InitAppInfo(version, date)
|
||||
|
||||
err := config.Read(*configureFileURL)
|
||||
if err != nil {
|
||||
|
@ -81,6 +81,15 @@ func main() {
|
|||
LoadRuleFromConfigFile(gcf)
|
||||
|
||||
rule.EnableAllRelayRule() //开启规则
|
||||
|
||||
config.DDNSTaskListTaskDetailsInit()
|
||||
ddnsConf := config.GetDDNSConfigure()
|
||||
if ddnsConf.Enable {
|
||||
ddns.Run(time.Duration(ddnsConf.FirstCheckDelay)*time.Second, time.Duration(ddnsConf.Intervals)*time.Second)
|
||||
}
|
||||
|
||||
//ddns.RunTimer(time.Second, time.Second*30)
|
||||
|
||||
//initProxyList()
|
||||
|
||||
//*****************
|
||||
|
|
After Width: | Height: | Size: 141 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 23 KiB |
|
@ -5,8 +5,8 @@ import (
|
|||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/ljymc/goports/base"
|
||||
"github.com/ljymc/goports/config"
|
||||
"github.com/gdy666/lucky/base"
|
||||
"github.com/gdy666/lucky/config"
|
||||
)
|
||||
|
||||
var globalRelayRules *[]RelayRule
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
package rule
|
||||
|
||||
import (
|
||||
"github.com/ljymc/goports/base"
|
||||
"github.com/gdy666/lucky/base"
|
||||
)
|
||||
|
||||
type RelayRuleProxyInfo struct {
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ljymc/goports/base"
|
||||
"github.com/gdy666/lucky/base"
|
||||
)
|
||||
|
||||
type RelayRule struct {
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
package httputils
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
func GetAndParseJSONResponseFromHttpResponse(resp *http.Response, result interface{}) error {
|
||||
bytes, err := GetBytesFromHttpResponse(resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetBytesFromHttpResponse err:%s", err.Error())
|
||||
}
|
||||
if len(bytes) > 0 {
|
||||
err = json.Unmarshal(bytes, &result)
|
||||
if err != nil {
|
||||
//log.Printf("请求接口解析json结果失败! ERROR: %s\n", err)
|
||||
return fmt.Errorf("GetAndParseJSONResponseFromHttpResponse 解析JSON结果出错:%s", err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//GetStringFromHttpResponse 从response获取
|
||||
func GetBytesFromHttpResponse(resp *http.Response) ([]byte, error) {
|
||||
if resp.Body == nil {
|
||||
return []byte{}, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var body []byte
|
||||
var err error
|
||||
if resp.Header.Get("Content-Encoding") == "gzip" {
|
||||
reader, err := gzip.NewReader(resp.Body)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
body, err = ioutil.ReadAll(reader)
|
||||
return body, err
|
||||
}
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
return body, err
|
||||
}
|
||||
|
||||
//GetStringFromHttpResponse 从response获取
|
||||
func GetStringFromHttpResponse(resp *http.Response) (string, error) {
|
||||
respBytes, err := GetBytesFromHttpResponse(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(respBytes), nil
|
||||
}
|
||||
|
||||
func NewTransport(secureSkipVerify bool, proxyType, proxyUrl, user, passwd string) (*http.Transport, error) {
|
||||
var transport *http.Transport
|
||||
proxyType = strings.ToLower(proxyType)
|
||||
switch proxyType {
|
||||
case "http", "https":
|
||||
{
|
||||
if !strings.Contains(proxyUrl, "http") {
|
||||
proxyUrl = fmt.Sprintf("%s://%s", proxyType, proxyUrl)
|
||||
}
|
||||
urlProxy, err := url.Parse(proxyUrl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewTransport=>proxy url.Parse error:%s", err.Error())
|
||||
}
|
||||
|
||||
if user != "" && passwd != "" {
|
||||
urlProxy.User = url.UserPassword(user, passwd)
|
||||
}
|
||||
|
||||
transport = &http.Transport{
|
||||
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: secureSkipVerify},
|
||||
Proxy: http.ProxyURL(urlProxy),
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 10 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
IdleConnTimeout: 10 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
}
|
||||
case "socket5", "socks5":
|
||||
{
|
||||
var userAuth proxy.Auth
|
||||
if user != "" && passwd != "" {
|
||||
userAuth.User = user
|
||||
userAuth.Password = passwd
|
||||
}
|
||||
dialer, err := proxy.SOCKS5("tcp", proxyUrl, &userAuth, proxy.Direct)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewTransport=>proxy.SOCKS5 error:%s", err.Error())
|
||||
}
|
||||
|
||||
transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: secureSkipVerify},
|
||||
DialContext: func(ctx context.Context, network, addr string) (conn net.Conn, err error) {
|
||||
return dialer.Dial(network, addr)
|
||||
},
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 10 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
IdleConnTimeout: 10 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
}
|
||||
default:
|
||||
{
|
||||
transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: secureSkipVerify},
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 10 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
IdleConnTimeout: 10 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
func CreateHeadersMap(headers []string) map[string]string {
|
||||
hm := make(map[string]string)
|
||||
for _, header := range headers {
|
||||
kvSpliteIndex := strings.Index(header, ":")
|
||||
if kvSpliteIndex < 0 {
|
||||
continue
|
||||
}
|
||||
if kvSpliteIndex+1 > len(header) {
|
||||
continue
|
||||
}
|
||||
key := header[:kvSpliteIndex]
|
||||
value := header[kvSpliteIndex+1:]
|
||||
hm[key] = value
|
||||
}
|
||||
return hm
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package httputils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/guonaihong/gout"
|
||||
"github.com/guonaihong/gout/dataflow"
|
||||
)
|
||||
|
||||
func NewGout(secureSkipVerify bool, proxyType, proxyUrl, user, passwd string, timeout time.Duration) (*dataflow.Gout, error) {
|
||||
httpClient, err := CreateHttpClient(secureSkipVerify, proxyType, proxyUrl, user, passwd, timeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CreateHttpClient error:%s", err.Error())
|
||||
}
|
||||
|
||||
return gout.New(httpClient), nil
|
||||
}
|
||||
|
||||
func GetAndParseJSONResponseFromGoutDoHttpRequest(method, url, requestBody, proxyType, proxyUrl, user, passwd string, headers map[string]string, secureSkipVerify bool, timeout time.Duration, result interface{}) error {
|
||||
bytes, err := GetBytesFromGoutDoHttpRequest(method, url, requestBody, proxyType, proxyUrl, user, passwd, headers, secureSkipVerify, timeout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetBytesFromHttpResponse err:%s", err.Error())
|
||||
}
|
||||
if len(bytes) > 0 {
|
||||
err = json.Unmarshal(bytes, &result)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetAndParseJSONResponseFromHttpResponse 解析JSON结果出错:%s", err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetStringGoutDoHttpRequest(method, url, requestBody, proxyType, proxyUrl, user, passwd string, headers map[string]string, secureSkipVerify bool, timeout time.Duration) (string, error) {
|
||||
bytes, err := GetBytesFromGoutDoHttpRequest(method, url, requestBody, proxyType, proxyUrl, user, passwd, headers, secureSkipVerify, timeout)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
func GetBytesFromGoutDoHttpRequest(method, url, requestBody, proxyType, proxyUrl, user, passwd string, headers map[string]string, secureSkipVerify bool, timeout time.Duration) ([]byte, error) {
|
||||
gout, err := NewGout(
|
||||
secureSkipVerify,
|
||||
proxyType,
|
||||
proxyUrl,
|
||||
user,
|
||||
passwd, timeout)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("GoutDoHttpRequest err:%s", err.Error())
|
||||
}
|
||||
|
||||
switch strings.ToLower(method) {
|
||||
case "get":
|
||||
gout.GET(url)
|
||||
case "post":
|
||||
gout.POST(url)
|
||||
case "put":
|
||||
gout.PUT(url)
|
||||
case "delete":
|
||||
gout.DELETE(url)
|
||||
default:
|
||||
return []byte{}, fmt.Errorf("未支持的Callback请求方法:%s", method)
|
||||
}
|
||||
|
||||
basicAuthUserName, BasicAuthUserNameOk := headers["BasicAuthUserName"]
|
||||
basicAuthPassword, BasicAuthPasswordOk := headers["BasicAuthPassword"]
|
||||
if BasicAuthUserNameOk && BasicAuthPasswordOk {
|
||||
gout.SetBasicAuth(basicAuthUserName, basicAuthPassword)
|
||||
}
|
||||
delete(headers, "BasicAuthUserName")
|
||||
delete(headers, "BasicAuthPassword")
|
||||
|
||||
if len(requestBody) > 0 && method != "get" {
|
||||
if json.Valid([]byte(requestBody)) {
|
||||
gout.SetJSON(requestBody)
|
||||
} else {
|
||||
gout.SetWWWForm(requestBody)
|
||||
}
|
||||
}
|
||||
|
||||
gout.SetHeader(headers)
|
||||
//gout.SetTimeout(timeout)
|
||||
|
||||
resp, err := gout.Response()
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("gout.Response() error:%s", err.Error())
|
||||
}
|
||||
|
||||
return GetBytesFromHttpResponse(resp)
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package httputils
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func CreateHttpClient(secureSkipVerify bool, proxyType, proxyUrl, user, passwd string, timeout time.Duration) (*http.Client, error) {
|
||||
transport, err := NewTransport(secureSkipVerify, proxyType, proxyUrl, user, passwd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Timeout: timeout,
|
||||
Transport: transport}
|
||||
|
||||
return httpClient, nil
|
||||
}
|
|
@ -7,8 +7,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ljymc/goports/thirdlib/gdylib/fileutils"
|
||||
"github.com/ljymc/goports/thirdlib/gdylib/stderrredirect"
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/fileutils"
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/stderrredirect"
|
||||
)
|
||||
|
||||
//RecoverHandler 恢复处理
|
||||
|
|
|
@ -0,0 +1,297 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
//StateStop 未启动
|
||||
StateStop = 0
|
||||
//StateRunning 正在运行
|
||||
StateRunning = 1
|
||||
//StateStopping 正在结束
|
||||
StateStopping = 2
|
||||
)
|
||||
|
||||
//ServiceMsg 服务消息
|
||||
type ServiceMsg struct {
|
||||
Type string
|
||||
Params []any
|
||||
}
|
||||
|
||||
//var serviceMap map[string]*Service //用于保存已创建的服务
|
||||
//var serverMapRWLock sync.RWMutex
|
||||
var serviceMap = struct {
|
||||
sync.RWMutex
|
||||
services map[string]*Service
|
||||
}{
|
||||
services: map[string]*Service{},
|
||||
}
|
||||
|
||||
//Service 服务
|
||||
type Service struct {
|
||||
Name string //服务名称
|
||||
serviceMutex sync.Mutex
|
||||
|
||||
State uint8 //服务状态 //服务运行状态 , 0:未启动 1:正在运行 2:正在结束
|
||||
NextAction string //动作名称
|
||||
DefaultAction string //默认动作
|
||||
TimerFunc func(...any) //定时回调函数
|
||||
EventFunc func(...any) //事件回调函数
|
||||
StopFinishedCallback func(...any) //服务停止后的回调函数
|
||||
eventChan chan any
|
||||
Timer *time.Timer
|
||||
//Args []interface{}
|
||||
|
||||
context context.Context
|
||||
cancelFunc context.CancelFunc
|
||||
}
|
||||
|
||||
//NewService 创建服务对象
|
||||
func NewService(name string) (*Service, error) {
|
||||
serviceMap.Lock()
|
||||
defer serviceMap.Unlock()
|
||||
if _, ok := serviceMap.services[name]; ok {
|
||||
return nil, fmt.Errorf("命名为[%s]服务已存在", name)
|
||||
}
|
||||
|
||||
if _, ok := serviceMap.services[name]; !ok {
|
||||
service := Service{Name: name, State: StateStop}
|
||||
service.eventChan = make(chan any, 32)
|
||||
serviceMap.services[name] = &service
|
||||
return &service, nil
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("命名为[%s]服务已存在", name))
|
||||
//return nil, fmt.Errorf("命名为[%s]服务已存在", name)
|
||||
}
|
||||
|
||||
//SetDefaultAction 设置默认action
|
||||
func (s *Service) SetDefaultAction(action string) *Service {
|
||||
s.DefaultAction = action
|
||||
return s
|
||||
}
|
||||
|
||||
//SetTimerFunc 设置定时功能函数
|
||||
func (s *Service) SetTimerFunc(timerFunc func(...any)) *Service {
|
||||
s.TimerFunc = timerFunc
|
||||
return s
|
||||
}
|
||||
|
||||
//SetEventFunc 设置时间功能函数
|
||||
func (s *Service) SetEventFunc(eventFunc func(...any)) *Service {
|
||||
s.EventFunc = eventFunc
|
||||
return s
|
||||
}
|
||||
|
||||
//SetStopFinishedCallback 设置服务停止后的回调函数
|
||||
func (s *Service) SetStopFinishedCallback(f func(...any)) *Service {
|
||||
s.StopFinishedCallback = f
|
||||
return s
|
||||
}
|
||||
|
||||
//Start 服务启动
|
||||
func (s *Service) Start(vs ...any) error {
|
||||
s.serviceMutex.Lock()
|
||||
defer s.serviceMutex.Unlock()
|
||||
if s.State == StateRunning {
|
||||
text := fmt.Sprintf("服务 [%s]已启动,无需再次启动", s.Name)
|
||||
return fmt.Errorf(text)
|
||||
}
|
||||
|
||||
if s.State == StateStopping {
|
||||
text := fmt.Sprintf("服务[%s]正在结束,请结束后再次启动", s.Name)
|
||||
return fmt.Errorf(text)
|
||||
}
|
||||
|
||||
s.State = StateRunning
|
||||
s.NextAction = s.DefaultAction
|
||||
log.Printf("服务[%s] 启动", s.Name)
|
||||
s.context, s.cancelFunc = context.WithCancel(context.Background())
|
||||
|
||||
go s.loop(vs)
|
||||
return nil
|
||||
}
|
||||
|
||||
//Stop 服务结束
|
||||
func (s *Service) Stop() error {
|
||||
s.serviceMutex.Lock()
|
||||
defer s.serviceMutex.Unlock()
|
||||
if s.State == StateStop {
|
||||
text := fmt.Sprintf("服务[%s]未启动,无须停止", s.Name)
|
||||
return fmt.Errorf(text)
|
||||
}
|
||||
if s.State == StateStopping {
|
||||
text := fmt.Sprintf("服务[%s]正在结束,无须再次结束.", s.Name)
|
||||
return fmt.Errorf(text)
|
||||
}
|
||||
|
||||
if s.cancelFunc == nil {
|
||||
return fmt.Errorf("服务[%s]context nil", s.Name)
|
||||
}
|
||||
s.cancelFunc()
|
||||
|
||||
s.State = StateStopping
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) loop(params ...any) {
|
||||
defer func() {
|
||||
recoverErr := recover()
|
||||
if recoverErr == nil {
|
||||
return
|
||||
}
|
||||
log.Printf("service[%s] panic:\n%v", s.Name, recoverErr)
|
||||
s.State = StateStop
|
||||
log.Printf("server[%s] restart", s.Name)
|
||||
s.Start()
|
||||
}()
|
||||
|
||||
s.Timer = time.NewTimer(0)
|
||||
|
||||
for {
|
||||
|
||||
select {
|
||||
case <-s.Timer.C:
|
||||
{
|
||||
if s.TimerFunc != nil { //如果设置了定时回调的话
|
||||
s.TimerFunc(s, params)
|
||||
}
|
||||
}
|
||||
case <-s.context.Done():
|
||||
{
|
||||
if s.State == StateStopping {
|
||||
s.State = StateStop
|
||||
log.Printf("服务[%s] 停止", s.Name)
|
||||
|
||||
if s.StopFinishedCallback != nil {
|
||||
s.StopFinishedCallback()
|
||||
}
|
||||
|
||||
s.context = nil
|
||||
s.cancelFunc = nil
|
||||
return
|
||||
}
|
||||
|
||||
if s.State == StateStop {
|
||||
log.Printf("服务[%s] 状态有误,结束服务失败", s.Name)
|
||||
return
|
||||
}
|
||||
|
||||
if s.State == StateRunning {
|
||||
log.Printf("服务[%s] 状态有误,请使用正确途径结束", s.Name)
|
||||
return
|
||||
}
|
||||
}
|
||||
case msg := <-s.eventChan:
|
||||
{
|
||||
if s.EventFunc != nil {
|
||||
s.EventFunc(s, msg)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//Restart 重启服务
|
||||
func (s *Service) Restart(params ...any) error {
|
||||
s.Stop()
|
||||
|
||||
var timeout time.Duration
|
||||
if len(params) == 0 {
|
||||
timeout = time.Second * 15
|
||||
} else {
|
||||
timeout = params[0].(time.Duration)
|
||||
}
|
||||
|
||||
preTime := time.Now()
|
||||
for {
|
||||
waitTime := time.Now()
|
||||
if s.State == StateStop {
|
||||
return s.Start()
|
||||
}
|
||||
if waitTime.Sub(preTime) > timeout {
|
||||
return fmt.Errorf("重启服务[%s]超时", s.Name)
|
||||
}
|
||||
<-time.After(time.Millisecond * 100)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Message 发送消息给service
|
||||
//t 消息类型
|
||||
//params 消息内容
|
||||
func (s *Service) Message(t string, params ...any) error {
|
||||
|
||||
s.serviceMutex.Lock()
|
||||
defer s.serviceMutex.Unlock()
|
||||
|
||||
if s.State != StateRunning {
|
||||
return fmt.Errorf("[%s]服务已关闭或者正在关闭,无法处理消息 %v,", s.Name, params)
|
||||
}
|
||||
msg := ServiceMsg{Type: t}
|
||||
for i := range params {
|
||||
msg.Params = append(msg.Params, params[i])
|
||||
}
|
||||
select {
|
||||
case s.eventChan <- msg:
|
||||
default:
|
||||
return fmt.Errorf("[%s]服务EventChan阻塞,无法处理event", s.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//*************************************
|
||||
|
||||
//GetService 查询服务根据服务名称
|
||||
func GetService(name string) (*Service, error) {
|
||||
serviceMap.RLock()
|
||||
defer serviceMap.RUnlock()
|
||||
if _, ok := serviceMap.services[name]; !ok {
|
||||
return nil, fmt.Errorf("service[%s]不存在", name)
|
||||
}
|
||||
service := serviceMap.services[name]
|
||||
return service, nil
|
||||
}
|
||||
|
||||
//Message 发送消息到service,根据serviceName
|
||||
func Message(serviceName string, msgType string, params ...any) error {
|
||||
service, err := GetService(serviceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("service[%s] Message faild,:%s", serviceName, err.Error())
|
||||
}
|
||||
return service.Message(msgType, params...)
|
||||
}
|
||||
|
||||
//Stop 服务停止,根据serviceName
|
||||
func Stop(serviceName string) error {
|
||||
service, err := GetService(serviceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("service[%s] stop faild:%s", serviceName, err.Error())
|
||||
}
|
||||
return service.Stop()
|
||||
}
|
||||
|
||||
//Restart 服务重启
|
||||
func Restart(serviceName string) error {
|
||||
service, err := GetService(serviceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("restart servcie[%s] faild,:%s", serviceName, err.Error())
|
||||
}
|
||||
return service.Restart()
|
||||
}
|
||||
|
||||
//Start 服务启动
|
||||
func Start(serviceName string) error {
|
||||
service, err := GetService(serviceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("servcie[%s] start faild,:%s", serviceName, err.Error())
|
||||
}
|
||||
return service.Start()
|
||||
}
|
|
@ -7,7 +7,7 @@ import (
|
|||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/ljymc/goports/thirdlib/gdylib/fileutils"
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/fileutils"
|
||||
)
|
||||
|
||||
var PanicFile *os.File
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/ljymc/goports/thirdlib/gdylib/fileutils"
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/fileutils"
|
||||
)
|
||||
|
||||
//错误输出重定向,用于捕获闪退信息
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// https://github.com/rosbit/aliyun-sign/blob/master/aliyun-sign.go
|
||||
|
||||
var (
|
||||
signMethodMap = map[string]func() hash.Hash{
|
||||
"HMAC-SHA1": sha1.New,
|
||||
"HMAC-SHA256": sha256.New,
|
||||
"HMAC-MD5": md5.New,
|
||||
}
|
||||
)
|
||||
|
||||
func HmacSign(signMethod string, httpMethod string, appKeySecret string, vals url.Values) (signature []byte) {
|
||||
key := []byte(appKeySecret + "&")
|
||||
|
||||
var h hash.Hash
|
||||
if method, ok := signMethodMap[signMethod]; ok {
|
||||
h = hmac.New(method, key)
|
||||
} else {
|
||||
h = hmac.New(sha1.New, key)
|
||||
}
|
||||
makeDataToSign(h, httpMethod, vals)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func HmacSignToB64(signMethod string, httpMethod string, appKeySecret string, vals url.Values) (signature string) {
|
||||
return base64.StdEncoding.EncodeToString(HmacSign(signMethod, httpMethod, appKeySecret, vals))
|
||||
}
|
||||
|
||||
type strToEnc struct {
|
||||
s string
|
||||
e bool
|
||||
}
|
||||
|
||||
func makeDataToSign(w io.Writer, httpMethod string, vals url.Values) {
|
||||
in := make(chan *strToEnc)
|
||||
go func() {
|
||||
in <- &strToEnc{s: httpMethod}
|
||||
in <- &strToEnc{s: "&"}
|
||||
in <- &strToEnc{s: "/", e: true}
|
||||
in <- &strToEnc{s: "&"}
|
||||
in <- &strToEnc{s: vals.Encode(), e: true}
|
||||
close(in)
|
||||
}()
|
||||
|
||||
specialUrlEncode(in, w)
|
||||
}
|
||||
|
||||
var (
|
||||
encTilde = "%7E" // '~' -> "%7E"
|
||||
encBlank = []byte("%20") // ' ' -> "%20"
|
||||
tilde = []byte("~")
|
||||
)
|
||||
|
||||
func specialUrlEncode(in <-chan *strToEnc, w io.Writer) {
|
||||
for s := range in {
|
||||
if !s.e {
|
||||
io.WriteString(w, s.s)
|
||||
continue
|
||||
}
|
||||
|
||||
l := len(s.s)
|
||||
for i := 0; i < l; {
|
||||
ch := s.s[i]
|
||||
|
||||
switch ch {
|
||||
case '%':
|
||||
if encTilde == s.s[i:i+3] {
|
||||
w.Write(tilde)
|
||||
i += 3
|
||||
continue
|
||||
}
|
||||
fallthrough
|
||||
case '*', '/', '&', '=':
|
||||
fmt.Fprintf(w, "%%%02X", ch)
|
||||
case '+':
|
||||
w.Write(encBlank)
|
||||
default:
|
||||
fmt.Fprintf(w, "%c", ch)
|
||||
}
|
||||
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AliyunSigner AliyunSigner
|
||||
func AliyunSigner(accessKeyID, accessSecret string, params *url.Values) {
|
||||
// 公共参数
|
||||
params.Set("SignatureMethod", "HMAC-SHA1")
|
||||
params.Set("SignatureNonce", strconv.FormatInt(time.Now().UnixNano(), 10))
|
||||
params.Set("AccessKeyId", accessKeyID)
|
||||
params.Set("SignatureVersion", "1.0")
|
||||
params.Set("Timestamp", time.Now().UTC().Format("2006-01-02T15:04:05Z"))
|
||||
params.Set("Format", "JSON")
|
||||
params.Set("Version", "2015-01-09")
|
||||
params.Set("Signature", HmacSignToB64("HMAC-SHA1", "GET", accessSecret, *params))
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// https://cloud.baidu.com/doc/Reference/s/Njwvz1wot
|
||||
|
||||
const (
|
||||
BaiduDateFormat = "2006-01-02T15:04:05Z"
|
||||
expirationPeriod = "1800"
|
||||
)
|
||||
|
||||
func HmacSha256Hex(secret, message string) string {
|
||||
key := []byte(secret)
|
||||
|
||||
h := hmac.New(sha256.New, key)
|
||||
h.Write([]byte(message))
|
||||
sha := hex.EncodeToString(h.Sum(nil))
|
||||
return sha
|
||||
}
|
||||
|
||||
func BaiduCanonicalURI(r *http.Request) string {
|
||||
pattens := strings.Split(r.URL.Path, "/")
|
||||
var uri []string
|
||||
for _, v := range pattens {
|
||||
uri = append(uri, escape(v))
|
||||
}
|
||||
urlpath := strings.Join(uri, "/")
|
||||
if len(urlpath) == 0 || urlpath[len(urlpath)-1] != '/' {
|
||||
urlpath = urlpath + "/"
|
||||
}
|
||||
return urlpath[0 : len(urlpath)-1]
|
||||
}
|
||||
|
||||
// BaiduSigner set Authorization header
|
||||
func BaiduSigner(accessKeyID, accessSecret string, r *http.Request) {
|
||||
//format: bce-auth-v1/{accessKeyId}/{timestamp}/{expirationPeriodInSeconds}
|
||||
authStringPrefix := "bce-auth-v1/" + accessKeyID + "/" + time.Now().UTC().Format(BaiduDateFormat) + "/" + expirationPeriod
|
||||
baiduCanonicalURL := BaiduCanonicalURI(r)
|
||||
|
||||
//format: HTTP Method + "\n" + CanonicalURI + "\n" + CanonicalQueryString + "\n" + CanonicalHeaders
|
||||
//由于仅仅调用三个POST接口且不会更改,这里CanonicalQueryString和CanonicalHeaders直接写死
|
||||
CanonicalReq := fmt.Sprintf("%s\n%s\n%s\n%s", r.Method, baiduCanonicalURL, "", "host:bcd.baidubce.com")
|
||||
|
||||
signingKey := HmacSha256Hex(accessSecret, authStringPrefix)
|
||||
signature := HmacSha256Hex(signingKey, CanonicalReq)
|
||||
|
||||
//format: authStringPrefix/{signedHeaders}/{signature}
|
||||
authString := authStringPrefix + "/host/" + signature
|
||||
r.Header.Set(HeaderAuthorization, authString)
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// based on https://github.com/golang/go/blob/master/src/net/url/url.go
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package util
|
||||
|
||||
func shouldEscape(c byte) bool {
|
||||
if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c == '-' || c == '~' || c == '.' {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
func escape(s string) string {
|
||||
hexCount := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if shouldEscape(c) {
|
||||
hexCount++
|
||||
}
|
||||
}
|
||||
|
||||
if hexCount == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
t := make([]byte, len(s)+2*hexCount)
|
||||
j := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch c := s[i]; {
|
||||
case shouldEscape(c):
|
||||
t[j] = '%'
|
||||
t[j+1] = "0123456789ABCDEF"[c>>4]
|
||||
t[j+2] = "0123456789ABCDEF"[c&15]
|
||||
j += 3
|
||||
default:
|
||||
t[j] = s[i]
|
||||
j++
|
||||
}
|
||||
}
|
||||
return string(t)
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
// HWS API Gateway Signature
|
||||
// based on https://github.com/datastream/aws/blob/master/signv4.go
|
||||
// Copyright (c) 2014, Xianjie
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
BasicDateFormat = "20060102T150405Z"
|
||||
Algorithm = "SDK-HMAC-SHA256"
|
||||
HeaderXDate = "X-Sdk-Date"
|
||||
HeaderHost = "host"
|
||||
HeaderAuthorization = "Authorization"
|
||||
HeaderContentSha256 = "X-Sdk-Content-Sha256"
|
||||
)
|
||||
|
||||
func hmacsha256(key []byte, data string) ([]byte, error) {
|
||||
h := hmac.New(sha256.New, []byte(key))
|
||||
if _, err := h.Write([]byte(data)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.Sum(nil), nil
|
||||
}
|
||||
|
||||
// Build a CanonicalRequest from a regular request string
|
||||
//
|
||||
// CanonicalRequest =
|
||||
// HTTPRequestMethod + '\n' +
|
||||
// CanonicalURI + '\n' +
|
||||
// CanonicalQueryString + '\n' +
|
||||
// CanonicalHeaders + '\n' +
|
||||
// SignedHeaders + '\n' +
|
||||
// HexEncode(Hash(RequestPayload))
|
||||
func CanonicalRequest(r *http.Request, signedHeaders []string) (string, error) {
|
||||
var hexencode string
|
||||
var err error
|
||||
if hex := r.Header.Get(HeaderContentSha256); hex != "" {
|
||||
hexencode = hex
|
||||
} else {
|
||||
data, err := RequestPayload(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
hexencode, err = HexEncodeSHA256Hash(data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", r.Method, CanonicalURI(r), CanonicalQueryString(r), CanonicalHeaders(r, signedHeaders), strings.Join(signedHeaders, ";"), hexencode), err
|
||||
}
|
||||
|
||||
// CanonicalURI returns request uri
|
||||
func CanonicalURI(r *http.Request) string {
|
||||
pattens := strings.Split(r.URL.Path, "/")
|
||||
var uri []string
|
||||
for _, v := range pattens {
|
||||
uri = append(uri, escape(v))
|
||||
}
|
||||
urlpath := strings.Join(uri, "/")
|
||||
if len(urlpath) == 0 || urlpath[len(urlpath)-1] != '/' {
|
||||
urlpath = urlpath + "/"
|
||||
}
|
||||
return urlpath
|
||||
}
|
||||
|
||||
// CanonicalQueryString
|
||||
func CanonicalQueryString(r *http.Request) string {
|
||||
var keys []string
|
||||
query := r.URL.Query()
|
||||
for key := range query {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
var a []string
|
||||
for _, key := range keys {
|
||||
k := escape(key)
|
||||
sort.Strings(query[key])
|
||||
for _, v := range query[key] {
|
||||
kv := fmt.Sprintf("%s=%s", k, escape(v))
|
||||
a = append(a, kv)
|
||||
}
|
||||
}
|
||||
queryStr := strings.Join(a, "&")
|
||||
r.URL.RawQuery = queryStr
|
||||
return queryStr
|
||||
}
|
||||
|
||||
// CanonicalHeaders
|
||||
func CanonicalHeaders(r *http.Request, signerHeaders []string) string {
|
||||
var a []string
|
||||
header := make(map[string][]string)
|
||||
for k, v := range r.Header {
|
||||
header[strings.ToLower(k)] = v
|
||||
}
|
||||
for _, key := range signerHeaders {
|
||||
value := header[key]
|
||||
if strings.EqualFold(key, HeaderHost) {
|
||||
value = []string{r.Host}
|
||||
}
|
||||
sort.Strings(value)
|
||||
for _, v := range value {
|
||||
a = append(a, key+":"+strings.TrimSpace(v))
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s\n", strings.Join(a, "\n"))
|
||||
}
|
||||
|
||||
// SignedHeaders
|
||||
func SignedHeaders(r *http.Request) []string {
|
||||
var a []string
|
||||
for key := range r.Header {
|
||||
a = append(a, strings.ToLower(key))
|
||||
}
|
||||
sort.Strings(a)
|
||||
return a
|
||||
}
|
||||
|
||||
// RequestPayload
|
||||
func RequestPayload(r *http.Request) ([]byte, error) {
|
||||
if r.Body == nil {
|
||||
return []byte(""), nil
|
||||
}
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return []byte(""), err
|
||||
}
|
||||
r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
|
||||
return b, err
|
||||
}
|
||||
|
||||
// Create a "String to Sign".
|
||||
func StringToSign(canonicalRequest string, t time.Time) (string, error) {
|
||||
hash := sha256.New()
|
||||
_, err := hash.Write([]byte(canonicalRequest))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s\n%s\n%x",
|
||||
Algorithm, t.UTC().Format(BasicDateFormat), hash.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// Create the HWS Signature.
|
||||
func SignStringToSign(stringToSign string, signingKey []byte) (string, error) {
|
||||
hm, err := hmacsha256(signingKey, stringToSign)
|
||||
return fmt.Sprintf("%x", hm), err
|
||||
}
|
||||
|
||||
// HexEncodeSHA256Hash returns hexcode of sha256
|
||||
func HexEncodeSHA256Hash(body []byte) (string, error) {
|
||||
hash := sha256.New()
|
||||
if body == nil {
|
||||
body = []byte("")
|
||||
}
|
||||
_, err := hash.Write(body)
|
||||
return fmt.Sprintf("%x", hash.Sum(nil)), err
|
||||
}
|
||||
|
||||
// Get the finalized value for the "Authorization" header. The signature parameter is the output from SignStringToSign
|
||||
func AuthHeaderValue(signature, accessKey string, signedHeaders []string) string {
|
||||
return fmt.Sprintf("%s Access=%s, SignedHeaders=%s, Signature=%s", Algorithm, accessKey, strings.Join(signedHeaders, ";"), signature)
|
||||
}
|
||||
|
||||
// Signature HWS meta
|
||||
type Signer struct {
|
||||
Key string
|
||||
Secret string
|
||||
}
|
||||
|
||||
// SignRequest set Authorization header
|
||||
func (s *Signer) Sign(r *http.Request) error {
|
||||
var t time.Time
|
||||
var err error
|
||||
var dt string
|
||||
if dt = r.Header.Get(HeaderXDate); dt != "" {
|
||||
t, err = time.Parse(BasicDateFormat, dt)
|
||||
}
|
||||
if err != nil || dt == "" {
|
||||
t = time.Now()
|
||||
r.Header.Set(HeaderXDate, t.UTC().Format(BasicDateFormat))
|
||||
}
|
||||
signedHeaders := SignedHeaders(r)
|
||||
canonicalRequest, err := CanonicalRequest(r, signedHeaders)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stringToSign, err := StringToSign(canonicalRequest, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signature, err := SignStringToSign(stringToSign, []byte(s.Secret))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authValue := AuthHeaderValue(signature, s.Key, signedHeaders)
|
||||
r.Header.Set(HeaderAuthorization, authValue)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IsPrivateNetwork 是否为私有地址
|
||||
// https://en.wikipedia.org/wiki/Private_network
|
||||
func IsPrivateNetwork(remoteAddr string) bool {
|
||||
lastIndex := strings.LastIndex(remoteAddr, ":")
|
||||
if lastIndex < 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
remoteAddr = remoteAddr[:lastIndex]
|
||||
|
||||
// ipv6
|
||||
if strings.HasPrefix(remoteAddr, "[") && strings.HasSuffix(remoteAddr, "]") {
|
||||
remoteAddr = remoteAddr[1 : len(remoteAddr)-1]
|
||||
}
|
||||
|
||||
if ip := net.ParseIP(remoteAddr); ip != nil {
|
||||
return ip.IsLoopback() || // 127/8, ::1
|
||||
ip.IsPrivate() || // 10/8, 172.16/12, 192.168/16, fc00::/7
|
||||
ip.IsLinkLocalUnicast() // 169.254/16, fe80::/10
|
||||
}
|
||||
|
||||
// localhost
|
||||
if remoteAddr == "localhost" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
2
web.go
|
@ -5,7 +5,7 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ljymc/goports/web"
|
||||
"github.com/gdy666/lucky/web"
|
||||
"log"
|
||||
)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# goports-adminviews
|
||||
# lucky-adminviews
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
// Generated by 'unplugin-auto-import'
|
||||
export {}
|
||||
declare global {
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// generated by unplugin-vue-components
|
||||
// We suggest you to commit this file into source control
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
import '@vue/runtime-core'
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
About: typeof import('./src/components/About.vue')['default']
|
||||
BlackLists: typeof import('./src/components/BlackLists.vue')['default']
|
||||
DDNS: typeof import('./src/components/DDNS.vue')['default']
|
||||
DDNSSet: typeof import('./src/components/DDNSSet.vue')['default']
|
||||
ElAffix: typeof import('element-plus/es')['ElAffix']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
||||
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElHeader: typeof import('element-plus/es')['ElHeader']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElLink: typeof import('element-plus/es')['ElLink']
|
||||
ElMain: typeof import('element-plus/es')['ElMain']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
Loading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||
Log: typeof import('./src/components/Log.vue')['default']
|
||||
Login: typeof import('./src/components/Login.vue')['default']
|
||||
Pmenu: typeof import('./src/components/Pmenu.vue')['default']
|
||||
PSet: typeof import('./src/components/PSet.vue')['default']
|
||||
RelaySet: typeof import('./src/components/RelaySet.vue')['default']
|
||||
Status: typeof import('./src/components/Status.vue')['default']
|
||||
WhiteLists: typeof import('./src/components/WhiteLists.vue')['default']
|
||||
WhiteListSet: typeof import('./src/components/WhiteListSet.vue')['default']
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
@ -7,9 +7,9 @@
|
|||
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0" />
|
||||
|
||||
|
||||
<title>goports-admin</title>
|
||||
<script type="module" crossorigin src="/assets/index.8e296adf.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index.60201501.css">
|
||||
<title>Lucky(大吉)</title>
|
||||
<script type="module" crossorigin src="/assets/index.b93c8c58.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index.48f5287e.css">
|
||||
</head>
|
||||
<body style="margin:0">
|
||||
<div id="app"></div>
|
|
@ -7,7 +7,7 @@
|
|||
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0" />
|
||||
|
||||
|
||||
<title>goports-admin</title>
|
||||
<title>Lucky(大吉)</title>
|
||||
</head>
|
||||
<body style="margin:0">
|
||||
<div id="app"></div>
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "goports-adminviews",
|
||||
"name": "lucky-adminviews",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
@ -23,6 +23,9 @@
|
|||
<WhiteListSet v-if="global.currentPage.value=='#whitelistset'?true:false"></WhiteListSet>
|
||||
<WhiteLists v-if="global.currentPage.value=='#whitelists'?true:false"></WhiteLists>
|
||||
<BlackLists v-if="global.currentPage.value=='#blacklists'?true:false"></BlackLists>
|
||||
<DDNS v-if="global.currentPage.value=='#ddnstasklist'?true:false"></DDNS>
|
||||
<DDNSSet v-if="global.currentPage.value=='#ddnsset'?true:false"></DDNSSet>
|
||||
<About v-if="global.currentPage.value=='#about'?true:false"></About>
|
||||
</el-main>
|
||||
|
||||
</el-container>
|
||||
|
@ -45,9 +48,11 @@ import Login from './components/login.vue';
|
|||
import WhiteListSet from './components/WhiteListSet.vue';
|
||||
import WhiteLists from './components/WhiteLists.vue';
|
||||
import BlackLists from './components/BlackLists.vue';
|
||||
|
||||
import DDNS from './components/DDNS.vue';
|
||||
|
||||
import {apiGetVersion} from "./apis/utils.js"
|
||||
import DDNSSet from './components/DDNSSet.vue';
|
||||
|
||||
|
||||
//console.log("111")
|
||||
|
|
@ -15,13 +15,13 @@
|
|||
*/
|
||||
export default {
|
||||
getStorage () { // 先获取该项目的 命名存储空间 下的storage数据 maneger
|
||||
return JSON.parse(window.localStorage.getItem("goports") || "{}");
|
||||
return JSON.parse(window.localStorage.getItem("lucky") || "{}");
|
||||
},
|
||||
setItem (key, val) {
|
||||
let storage = this.getStorage()
|
||||
// console.log("setItem", storage);
|
||||
storage[key] = val; // 为当前对象添加 需要存储的值
|
||||
window.localStorage.setItem("goports", JSON.stringify(storage)) // 保存到本地
|
||||
window.localStorage.setItem("lucky", JSON.stringify(storage)) // 保存到本地
|
||||
},
|
||||
getItem (key) {
|
||||
return this.getStorage()[key]
|
|
@ -58,6 +58,45 @@ export function apiAddRule(data) {
|
|||
})
|
||||
}
|
||||
|
||||
export function apiAddDDNSTask(data) {
|
||||
return httpRequest({
|
||||
url: '/api/ddns',
|
||||
method: 'post',
|
||||
headers:{'Authorization':GetToken()},
|
||||
data:data
|
||||
})
|
||||
}
|
||||
|
||||
export function apiAlterDDNSTask(taskKey,data) {
|
||||
return httpRequest({
|
||||
url: '/api/ddns',
|
||||
method: 'put',
|
||||
headers:{'Authorization':GetToken()},
|
||||
data:data,
|
||||
params:{key:taskKey}
|
||||
})
|
||||
}
|
||||
|
||||
export function apiDeleteDDNSTask(taskKey) {
|
||||
return httpRequest({
|
||||
url: '/api/ddns',
|
||||
method: 'delete',
|
||||
headers:{'Authorization':GetToken()},
|
||||
params:{_:new Date().valueOf(),key:taskKey}
|
||||
})
|
||||
}
|
||||
|
||||
export function apiGetDDNSTaskList() {
|
||||
return httpRequest({
|
||||
url: '/api/ddnstasklist',
|
||||
method: 'get',
|
||||
headers:{'Authorization':GetToken()},
|
||||
params:{_:new Date().valueOf()}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function apiDeleteRule(configure) {
|
||||
return httpRequest({
|
||||
url: '/api/rule',
|
||||
|
@ -85,6 +124,15 @@ export function apiRuleEnable(key,enable) {
|
|||
})
|
||||
}
|
||||
|
||||
export function apiDDNSTaskEnable(key,enable) {
|
||||
return httpRequest({
|
||||
url: '/api/ddns/enable',
|
||||
method: 'get',
|
||||
headers:{'Authorization':GetToken()},
|
||||
params:{_:new Date().valueOf(),enable:enable,key:key}
|
||||
})
|
||||
}
|
||||
|
||||
export function apiQueryBaseConfigure() {
|
||||
return httpRequest({
|
||||
url: '/api/baseconfigure',
|
||||
|
@ -94,6 +142,15 @@ export function apiQueryBaseConfigure() {
|
|||
})
|
||||
}
|
||||
|
||||
export function apiQueryDDNSConfigure() {
|
||||
return httpRequest({
|
||||
url: '/api/ddns/configure',
|
||||
method: 'get',
|
||||
headers:{'Authorization':GetToken()},
|
||||
params:{_:new Date().valueOf()}
|
||||
})
|
||||
}
|
||||
|
||||
export function apiAlterBaseConfigure(data) {
|
||||
return httpRequest({
|
||||
url: '/api/baseconfigure',
|
||||
|
@ -103,6 +160,15 @@ export function apiAlterBaseConfigure(data) {
|
|||
})
|
||||
}
|
||||
|
||||
export function apiAlterDDNSConfigure(data) {
|
||||
return httpRequest({
|
||||
url: '/api/ddns/configure',
|
||||
method: 'put',
|
||||
headers:{'Authorization':GetToken()},
|
||||
data:data
|
||||
})
|
||||
}
|
||||
|
||||
export function apiLogin(data) {
|
||||
return httpRequest({
|
||||
url: '/api/login',
|
||||
|
@ -213,3 +279,43 @@ export function apiLogout() {
|
|||
headers:{'Authorization':GetToken()},
|
||||
})
|
||||
}
|
||||
|
||||
export function apiGetNetinterfaces() {
|
||||
return httpRequest({
|
||||
url: '/api/netinterfaces',
|
||||
method: 'get',
|
||||
headers:{'Authorization':GetToken()},
|
||||
params:{_:new Date().valueOf()}
|
||||
})
|
||||
}
|
||||
|
||||
export function apiGetIPRegTest(iptype,netinterface,ipreg) {
|
||||
return httpRequest({
|
||||
url: '/api/ipregtest',
|
||||
method: 'get',
|
||||
headers:{'Authorization':GetToken()},
|
||||
params:{_:new Date().valueOf(),iptype:iptype,netinterface:netinterface,ipreg:ipreg}
|
||||
})
|
||||
}
|
||||
|
||||
///api/webhooktest
|
||||
|
||||
|
||||
export function apiWebhookTest(taskKey,data) {
|
||||
return httpRequest({
|
||||
url: 'api/webhooktest',
|
||||
headers:{'Authorization':GetToken()},
|
||||
method: 'post',
|
||||
data:data,
|
||||
params:{key:taskKey}
|
||||
})
|
||||
}
|
||||
|
||||
export function apiGetAPPInfo() {
|
||||
return httpRequest({
|
||||
url: '/api/info',
|
||||
method: 'get',
|
||||
headers:{'Authorization':GetToken()},
|
||||
params:{_:new Date().valueOf()}
|
||||
})
|
||||
}
|
Before Width: | Height: | Size: 308 B After Width: | Height: | Size: 308 B |
|
@ -0,0 +1,106 @@
|
|||
<template>
|
||||
<div class="PageRadius" :style="{
|
||||
borderRadius: 'base',
|
||||
}">
|
||||
|
||||
|
||||
<div class="InfoDivRadius">
|
||||
<div class="line">
|
||||
{{Info.AppName}} version:{{Info.Version}}
|
||||
</div>
|
||||
<div class="line">
|
||||
{{Info.OS}}({{Info.ARCH}})
|
||||
</div>
|
||||
<div class="line">
|
||||
作者:古大羊 编译于{{Info.Date}}
|
||||
</div>
|
||||
|
||||
<div class="line">
|
||||
|
||||
<el-link type="primary" href="tencent://message/?uin=272288814&Site=&Menu=yes" target="_blank">QQ联系作者</el-link>
|
||||
邮箱: 272288814@qq.com
|
||||
|
||||
</div>
|
||||
|
||||
<div class="line">
|
||||
Github <el-link type="primary" href="https://github.com/gdy666/lucky" target="_blank">https://github.com/gdy666/lucky</el-link>
|
||||
</div>
|
||||
|
||||
<div class="line">
|
||||
Gitee <el-link type="primary" href="https://gitee.com/gdy666/lucky" target="_blank">https://gitee.com/gdy666/lucky</el-link>
|
||||
</div>
|
||||
|
||||
<div class="line">
|
||||
<el-link type="primary" href="https://pan.baidu.com/s/1NfumD9XjYU3OTeVmbu6vOQ?pwd=6666" target="_blank">最新版本可访问百度网盘</el-link>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
本项目借鉴引用或参考的第三方开源项目: <el-link type="primary" href="https://github.com/fatedier/frp" target="_blank">frp</el-link> <el-link type="primary" href="https://github.com/jeessy2/ddns-go" target="_blank">ddns-go</el-link>
|
||||
</div>
|
||||
|
||||
<div class="line">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
import { onMounted, onUnmounted, ref, computed, reactive } from 'vue'
|
||||
import { apiGetAPPInfo } from '../apis/utils'
|
||||
import {MessageShow} from '../utils/ui'
|
||||
|
||||
var Info = ref({
|
||||
AppName:"Lucky",
|
||||
Version:"1.0.0",
|
||||
OS:"unknow",
|
||||
ARCH:"unknow",
|
||||
Date:"2022-07-25"
|
||||
})
|
||||
|
||||
|
||||
const queryAPPInfo = ()=>{
|
||||
apiGetAPPInfo().then((res) => {
|
||||
if (res.ret==0){
|
||||
Info.value = res.info
|
||||
return
|
||||
}
|
||||
MessageShow("error", "获取App信息出错")
|
||||
}).catch((error) => {
|
||||
console.log("获获取App信息出错:" + error)
|
||||
MessageShow("error", "获取App信息出错")
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
queryAPPInfo()
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
|
||||
.InfoDivRadius {
|
||||
border: 2px solid var(--el-border-color);
|
||||
border-radius: 10px;
|
||||
margin:auto;
|
||||
margin-top:50px;
|
||||
width: 495px;
|
||||
height: fit-content;
|
||||
padding:auto;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 30px;
|
||||
|
||||
}
|
||||
|
||||
.line {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -48,7 +48,7 @@
|
|||
</el-scrollbar>
|
||||
|
||||
|
||||
<el-dialog v-model="addBlackListDialogVisible" title="添加黑名单IP" draggable :show-close="false" width="400px">
|
||||
<el-dialog v-model="addBlackListDialogVisible" title="添加黑名单IP" draggable :show-close="false" :close-on-click-modal="false" width="400px">
|
||||
|
||||
<el-form :model="addBlackListForm">
|
||||
<el-form-item label="IP" label-width="auto">
|
|
@ -0,0 +1,184 @@
|
|||
<template>
|
||||
|
||||
<div class="PageRadius" :style="{
|
||||
borderRadius: 'base',
|
||||
}" v-loading="logLoading" element-loading-background="transparent">
|
||||
|
||||
<el-scrollbar height="100%">
|
||||
|
||||
|
||||
<div class="formradius" :style="{
|
||||
borderRadius: 'base',
|
||||
}">
|
||||
|
||||
<el-form :model="form" class="SetForm" label-width="auto">
|
||||
|
||||
<el-tooltip content="如果不需要DDNS动态域名服务请不要打开这个开关" placement="top">
|
||||
|
||||
<el-form-item label="动态域名服务开关" id="adminListen">
|
||||
<el-switch v-model="form.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>
|
||||
|
||||
<el-tooltip content="多数嵌入式设备启用这个开关会导致https访问失败" placement="top">
|
||||
|
||||
<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-tooltip>
|
||||
|
||||
<el-tooltip content="首次执行任务延迟时间,最小0秒,最长3600秒" placement="top">
|
||||
<el-form-item label="首次执行任务延迟(秒)" label-width="auto" min="0" max="3600">
|
||||
<el-input-number v-model="form.FirstCheckDelay" autocomplete="off" />
|
||||
</el-form-item>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip content="DDNS任务每次执行的时间间隔,最小30秒,最长3600秒" placement="top">
|
||||
<el-form-item label="时间间隔(秒)" label-width="auto" :min="30" :max="3600">
|
||||
<el-input-number v-model="form.Intervals" autocomplete="off" />
|
||||
</el-form-item>
|
||||
</el-tooltip>
|
||||
|
||||
|
||||
</el-form>
|
||||
|
||||
<el-button type="primary" round @click="RequestAlterDDNSConfigure">保存修改</el-button>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
import { onMounted, onUnmounted, ref, computed, reactive } from 'vue'
|
||||
import { apiQueryDDNSConfigure, apiAlterDDNSConfigure } from '../apis/utils'
|
||||
|
||||
|
||||
import { MessageShow } from '../utils/ui'
|
||||
|
||||
|
||||
const logLoading = ref(true)
|
||||
|
||||
|
||||
const rawData = {
|
||||
Enable: false,
|
||||
HttpClientSecureVerify: false,
|
||||
Intervals: 0,
|
||||
FirstCheckDelay: 0,
|
||||
}
|
||||
|
||||
const form = ref(rawData)
|
||||
const preFormData = ref(rawData)
|
||||
|
||||
const resetFormData = () => {
|
||||
form.value.Enable = preFormData.value.Enable
|
||||
form.value.HttpClientSecureVerify = preFormData.value.HttpClientSecureVerify
|
||||
}
|
||||
|
||||
const syncToPreFormData = (data: any) => {
|
||||
preFormData.value.Enable = data.value.Enable
|
||||
preFormData.value.HttpClientSecureVerify = data.value.HttpClientSecureVerify
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const queryDDConfigure = () => {
|
||||
apiQueryDDNSConfigure().then((res) => {
|
||||
|
||||
if (res.ret == 0) {
|
||||
logLoading.value = false
|
||||
form.value = res.ddnsconfigure
|
||||
syncToPreFormData(form)
|
||||
return
|
||||
}
|
||||
MessageShow("error", "获取DDNS配置出错")
|
||||
}).catch((error) => {
|
||||
MessageShow("error", "获取DDNS配置出错")
|
||||
})
|
||||
}
|
||||
|
||||
const RequestAlterDDNSConfigure = () => {
|
||||
apiAlterDDNSConfigure(form.value).then((res) => {
|
||||
if (res.ret == 0) {
|
||||
MessageShow("success", "配置修改成功")
|
||||
//syncToPreFormData(form)
|
||||
return
|
||||
}
|
||||
resetFormData()
|
||||
MessageShow("error", res.msg)
|
||||
}).catch((error) => {
|
||||
console.log("配置修改失败,网络请求出错:" + error)
|
||||
MessageShow("error", "配置修改失败,网络请求出错")
|
||||
resetFormData()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
queryDDConfigure()
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.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;
|
||||
}
|
||||
</style>
|
|
@ -110,9 +110,11 @@ const Login = () => {
|
|||
|
||||
|
||||
const keydown = (e) => {
|
||||
if (e.keyCode == 13) {
|
||||
if (e.keyCode == 13 && global.currentPage.value=="#login") {
|
||||
Login()
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
onMounted(() => {
|
|
@ -115,7 +115,7 @@ const rebootProgram = () => {
|
|||
disableRebootButton.value = true;
|
||||
|
||||
ElMessageBox.confirm(
|
||||
'确定要重启goports?',
|
||||
'确定要重启lucky?',
|
||||
'Warning',
|
||||
{
|
||||
confirmButtonText: '确认',
|
|
@ -65,6 +65,35 @@
|
|||
|
||||
|
||||
|
||||
<el-sub-menu index="#ddns">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<Promotion />
|
||||
</el-icon>
|
||||
<span>动态域名</span>
|
||||
</template>
|
||||
|
||||
<el-menu-item index="#ddnstasklist">
|
||||
<el-icon>
|
||||
<List />
|
||||
</el-icon>
|
||||
<template #title>动态域名任务列表</template>
|
||||
</el-menu-item>
|
||||
|
||||
<el-menu-item index="#ddnsset">
|
||||
<el-icon>
|
||||
<Setting />
|
||||
</el-icon>
|
||||
<template #title>动态域名设置</template>
|
||||
</el-menu-item>
|
||||
|
||||
|
||||
|
||||
|
||||
</el-sub-menu>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -76,6 +105,16 @@
|
|||
</el-menu-item>
|
||||
|
||||
|
||||
<el-divider style="margin-top: 0px;margin-bottom: 0px;" />
|
||||
<el-menu-item index="#about">
|
||||
<el-icon>
|
||||
<Pointer />
|
||||
</el-icon>
|
||||
<template #title>关于</template>
|
||||
</el-menu-item>
|
||||
|
||||
|
||||
<el-divider style="margin-top: 0px;margin-bottom: 0px;" />
|
||||
|
||||
<el-menu-item index="#logout">
|
||||
<el-icon>
|
||||
|
@ -90,7 +129,7 @@
|
|||
|
||||
<div class="flex-grow" />
|
||||
|
||||
<el-menu-item index="#logo">goports {{ version }}</el-menu-item>
|
||||
<el-menu-item index="#logo">Lucky {{ version }}</el-menu-item>
|
||||
|
||||
</el-menu>
|
||||
</template>
|
||||
|
@ -154,7 +193,8 @@ function handleSelect(key, keyPath, item, routeResult) {
|
|||
})
|
||||
break;
|
||||
case "#logo":
|
||||
window.open("https://github.com/ljymc/goports", "_blank");
|
||||
//window.open("https://github.com/gdy666/lucky", "_blank");
|
||||
location.hash ="#about"
|
||||
break;
|
||||
default:
|
||||
SetHash(key)
|
|
@ -190,7 +190,7 @@
|
|||
|
||||
<!--添加/修改规则对话框-->
|
||||
<el-dialog v-model="dialogFormVisible" :title="dialogTitle" draggable :before-close="handleDialogClose"
|
||||
:show-close="false" width="650px">
|
||||
:show-close="false" :close-on-click-modal="false" width="650px">
|
||||
<el-form :model="form">
|
||||
<el-form-item label="名称" :label-width="formLabelWidth">
|
||||
<el-input v-model="form.Name" placeholder="转发规则名称,可留空" autocomplete="off" />
|
||||
|
@ -287,7 +287,7 @@ import { ElMessageBox } from 'element-plus'
|
|||
|
||||
|
||||
import {MessageShow} from '../utils/ui'
|
||||
import {isIP} from '../utils/utils'
|
||||
import {isIP,StringToArrayList} from '../utils/utils'
|
||||
|
||||
|
||||
//var timerID:any
|
||||
|
@ -401,7 +401,7 @@ const ruleEnableClick = (enable, rule) => {
|
|||
return
|
||||
}
|
||||
resolve(false)
|
||||
MessageShow("success", "规则 " + ruleName + " " + configure + enableText + "失败")
|
||||
MessageShow("error", "规则 " + ruleName + " " + configure + enableText + "失败")
|
||||
|
||||
if (res.syncres != undefined && res.syncres != "") {
|
||||
Notification("warn", res.syncres, 0)
|
||||
|
@ -409,7 +409,7 @@ const ruleEnableClick = (enable, rule) => {
|
|||
}).catch((error) => {
|
||||
resolve(false)
|
||||
console.log("规则 " + ruleName + " " + configure + enableText + "失败" + ":请求出错" + error)
|
||||
MessageShow("success", "规则 " + ruleName + " " + configure + enableText + "失败" + ":请求出错")
|
||||
MessageShow("error", "规则 " + ruleName + " " + configure + enableText + "失败" + ":请求出错")
|
||||
})
|
||||
|
||||
})
|
||||
|
@ -455,21 +455,14 @@ const addRule = () => {
|
|||
confirmText.value = "添加"
|
||||
}
|
||||
|
||||
const converAddressListTextToList = (listStr: string) => {
|
||||
let rawlist = listStr.split("\n")
|
||||
let resList = new Array()
|
||||
for (let i in rawlist) {
|
||||
resList.push(rawlist[i].replace(/^\s+|\s+$/g, '').replace(/<\/?.+?>/g, "").replace(/[\r\n]/g, ""))
|
||||
}
|
||||
return resList
|
||||
}
|
||||
|
||||
|
||||
const addOrAlterRuleConfirm = () => {
|
||||
|
||||
if (!form.value.IsBalanceRelayType) {
|
||||
form.value.BalanceTargetAddressList = []
|
||||
} else {
|
||||
form.value.BalanceTargetAddressList = converAddressListTextToList(formBalanceTargetAddressList.value)//formBalanceTargetAddressList.value.split(",")
|
||||
form.value.BalanceTargetAddressList = StringToArrayList(formBalanceTargetAddressList.value)//formBalanceTargetAddressList.value.split(",")
|
||||
}
|
||||
|
||||
if (!checkFormData()) {
|
||||
|
@ -961,9 +954,7 @@ var timerID: any
|
|||
|
||||
onMounted(() => {
|
||||
queryRuleList();
|
||||
console.log("relaySet onmounted")
|
||||
|
||||
queryRuleList();
|
||||
timerID = setInterval(() => {
|
||||
queryRuleList();
|
||||
}, 1000);
|
||||
|
@ -1013,7 +1004,7 @@ var flushBalanceOptionsView = () => {
|
|||
margin-top: 3px;
|
||||
margin-right: 3px;
|
||||
margin-bottom: 25px;
|
||||
width:1200px;
|
||||
min-width: 1200px;
|
||||
}
|
||||
|
||||
|
|
@ -4,8 +4,8 @@
|
|||
<p class="status">CPU全局使用率:{{ status.usedCPU }}</p>
|
||||
<p class="status">当前进程CPU使用率:{{ status.currentProcessUsedCPU }}</p>
|
||||
<p class="status">进程协程数:{{ status.goroutine }} 占用内存:{{ status.processUsedMem }}</p>
|
||||
<p class="status">goports全局连接数:{{ status.currentConnections }} </p>
|
||||
<p class="status">goports全局限制连接数:{{ status.maxConnections }}</p>
|
||||
<p class="status">Lucky 全局连接数:{{ status.currentConnections }} </p>
|
||||
<p class="status">Lucky 全局限制连接数:{{ status.maxConnections }}</p>
|
||||
|
||||
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
|
||||
|
||||
|
||||
<el-dialog v-model="addWhiteListDialogVisible" title="添加白名单IP" draggable :show-close="false" width="400px">
|
||||
<el-dialog v-model="addWhiteListDialogVisible" title="添加白名单IP" draggable :show-close="false" :close-on-click-modal="false" width="400px">
|
||||
|
||||
<el-form :model="addWhiteListForm">
|
||||
<el-form-item label="IP" label-width="auto">
|
|
@ -1,4 +1,4 @@
|
|||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
|
||||
|
||||
|
||||
// ElMessageBox.alert(message, {
|
||||
|
@ -24,3 +24,11 @@ export function MessageShow(type:any,message: string) {
|
|||
}
|
||||
|
||||
|
||||
export function Notification (type, message, duration) {
|
||||
ElNotification({
|
||||
title: type.substring(0, 1).toUpperCase() + type.substring(1),
|
||||
message: message,
|
||||
type: type,
|
||||
duration: duration,
|
||||
})
|
||||
}
|
|
@ -11,7 +11,10 @@ export function isIP(ip :string){
|
|||
return ipReg.test(ip)
|
||||
}
|
||||
|
||||
const MenuIndexList = ["#status","#log","#relayset","#whitelistset","#whitelists","#blacklists","#set","#login"]
|
||||
const MenuIndexList = ["#status",
|
||||
"#log","#relayset","#whitelistset",
|
||||
"#whitelists","#blacklists","#set",
|
||||
"#login","#ddns","#ddnstasklist","#ddnsset","#about"]
|
||||
|
||||
export function PageExist(page:string) {
|
||||
for(let i in MenuIndexList){
|
||||
|
@ -24,3 +27,16 @@ export function PageExist(page:string) {
|
|||
|
||||
export const CurrentPage = ref("")
|
||||
|
||||
|
||||
export function StringToArrayList(str : string){
|
||||
let rawlist = str.split("\n")
|
||||
let resList = new Array()
|
||||
for (let i in rawlist) {
|
||||
let item = rawlist[i].replace(/^\s+|\s+$/g, '').replace(/<\/?.+?>/g, "").replace(/[\r\n]/g, "")
|
||||
if (item==""){
|
||||
continue
|
||||
}
|
||||
resList.push(item)
|
||||
}
|
||||
return resList
|
||||
}
|
344
web/web.go
|
@ -21,18 +21,19 @@ import (
|
|||
"github.com/gin-contrib/gzip"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/gdy666/lucky/base"
|
||||
"github.com/gdy666/lucky/config"
|
||||
"github.com/gdy666/lucky/rule"
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/fileutils"
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/ginutils"
|
||||
"github.com/gdy666/lucky/thirdlib/gdylib/service"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/ljymc/goports/base"
|
||||
"github.com/ljymc/goports/config"
|
||||
"github.com/ljymc/goports/rule"
|
||||
"github.com/ljymc/goports/thirdlib/gdylib/fileutils"
|
||||
"github.com/ljymc/goports/thirdlib/gdylib/ginutils"
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
)
|
||||
|
||||
//go:embed goports-adminviews/dist
|
||||
//go:embed adminviews/dist
|
||||
var staticFs embed.FS
|
||||
var stafs fs.FS
|
||||
var loginErrorCount = int32(0)
|
||||
|
@ -43,8 +44,8 @@ var rebootOnce sync.Once
|
|||
//var cookieStore cookie.Store
|
||||
|
||||
func init() {
|
||||
stafs, _ = fs.Sub(staticFs, "goports-adminviews/dist")
|
||||
//cookieStore = cookie.NewStore([]byte("goports2022"))
|
||||
stafs, _ = fs.Sub(staticFs, "adminviews/dist")
|
||||
//cookieStore = cookie.NewStore([]byte("lucky2022"))
|
||||
}
|
||||
|
||||
func RunAdminWeb(listen string) {
|
||||
|
@ -62,7 +63,7 @@ func RunAdminWeb(listen string) {
|
|||
|
||||
r.Use(checkLocalIP)
|
||||
|
||||
//r.Use(sessions.Sessions("goportssession", cookieStore))
|
||||
//r.Use(sessions.Sessions("luckysession", cookieStore))
|
||||
|
||||
r.Use(gzip.Gzip(gzip.DefaultCompression))
|
||||
|
||||
|
@ -98,9 +99,21 @@ func RunAdminWeb(listen string) {
|
|||
authorized.GET("/api/blacklist", queryblacklist)
|
||||
authorized.PUT("/api/blacklist/flush", flushblacklist)
|
||||
authorized.DELETE("/api/blacklist", deleteblacklist)
|
||||
authorized.POST("/api/ddns", addDDNS)
|
||||
authorized.PUT("/api/ddns", alterDDNSTask)
|
||||
authorized.GET("/api/ddnstasklist", ddnsTaskList)
|
||||
authorized.DELETE("/api/ddns", deleteDDNSTask)
|
||||
authorized.GET("/api/ddns/enable", enableddns)
|
||||
authorized.GET("/api/ddns/configure", ddnsconfigure)
|
||||
authorized.PUT("/api/ddns/configure", alterDDNSConfigure)
|
||||
authorized.GET("/api/netinterfaces", netinterfaces)
|
||||
authorized.GET("/api/ipregtest", IPRegTest)
|
||||
authorized.POST("/api/webhooktest", webhookTest)
|
||||
authorized.GET("/api/info", info)
|
||||
r.PUT("/api/logout", logout)
|
||||
}
|
||||
r.POST("/api/login", login)
|
||||
//r.GET("/FreeOSMemory", FreeOSMemory)
|
||||
|
||||
r.GET("/wl", whitelistBasicAuth, whilelistAdd)
|
||||
r.GET("/wl/:url", whitelistBasicAuth, whilelistAdd)
|
||||
|
@ -117,6 +130,213 @@ func RunAdminWeb(listen string) {
|
|||
}
|
||||
}
|
||||
|
||||
// func FreeOSMemory(c *gin.Context) {
|
||||
// debug.FreeOSMemory()
|
||||
// c.JSON(http.StatusOK, gin.H{"ret": 0})
|
||||
// }
|
||||
|
||||
func info(c *gin.Context) {
|
||||
info := config.GetAppInfo()
|
||||
// var info struct {
|
||||
// Version string
|
||||
// OS string
|
||||
// ARCH string
|
||||
// Date string
|
||||
// }
|
||||
|
||||
// info.Version =
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 0, "info": *info})
|
||||
}
|
||||
|
||||
func enableddns(c *gin.Context) {
|
||||
enable := c.Query("enable")
|
||||
key := c.Query("key")
|
||||
|
||||
var err error
|
||||
|
||||
if enable == "true" {
|
||||
err = config.EnableDDNSTaskByKey(key, true)
|
||||
if err == nil {
|
||||
service.Message("ddns", "syncDDNSTask", key)
|
||||
}
|
||||
} else {
|
||||
err = config.EnableDDNSTaskByKey(key, false)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("开关DDNS任务出错:%s", err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": ""})
|
||||
}
|
||||
|
||||
func deleteDDNSTask(c *gin.Context) {
|
||||
taskKey := c.Query("key")
|
||||
err := config.DDNSTaskListDelete(taskKey)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Errorf("删除DDNS任务出错:%s", err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": ""})
|
||||
}
|
||||
|
||||
func ddnsTaskList(c *gin.Context) {
|
||||
|
||||
conf := config.GetDDNSConfigure()
|
||||
|
||||
if !conf.Enable {
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 6, "msg": "请先在设置页面启用DDNS动态域名服务"})
|
||||
return
|
||||
}
|
||||
|
||||
taskList := config.GetDDNSTaskList()
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 0, "data": taskList})
|
||||
}
|
||||
|
||||
func addDDNS(c *gin.Context) {
|
||||
var requestObj config.DDNSTask
|
||||
|
||||
err := c.BindJSON(&requestObj)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "请求解析出错"})
|
||||
return
|
||||
}
|
||||
//fmt.Printf("addDDNS requestObj:%v\n", requestObj)
|
||||
err = config.CheckDDNSTaskAvalid(&requestObj)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
dealRequestDDNSTask(&requestObj)
|
||||
|
||||
err = config.DDNSTaskListAdd(&requestObj)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "DDNS任务添加出错"})
|
||||
return
|
||||
}
|
||||
|
||||
if requestObj.Enable {
|
||||
service.Message("ddns", "syncDDNSTask", requestObj.TaskKey)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": ""})
|
||||
}
|
||||
|
||||
func alterDDNSTask(c *gin.Context) {
|
||||
taskKey := c.Query("key")
|
||||
var requestObj config.DDNSTask
|
||||
err := c.BindJSON(&requestObj)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "请求解析出错"})
|
||||
return
|
||||
}
|
||||
|
||||
err = config.CheckDDNSTaskAvalid(&requestObj)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": err.Error()})
|
||||
return
|
||||
}
|
||||
dealRequestDDNSTask(&requestObj)
|
||||
|
||||
err = config.UpdateTaskToDDNSTaskList(taskKey, requestObj)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("更新DDNS任务出错:%s", err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
if requestObj.Enable {
|
||||
service.Message("ddns", "syncDDNSTask", taskKey)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": ""})
|
||||
|
||||
}
|
||||
|
||||
func dealRequestDDNSTask(t *config.DDNSTask) {
|
||||
|
||||
if t.DNS.Name == "callback" {
|
||||
t.DNS.ID = ""
|
||||
t.DNS.Secret = ""
|
||||
t.DNS.Callback.URL = strings.TrimSpace(t.DNS.Callback.URL)
|
||||
//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{}
|
||||
}
|
||||
|
||||
if !t.DNS.ResolverDoaminCheck && len(t.DNS.DNSServerList) > 0 {
|
||||
t.DNS.DNSServerList = []string{}
|
||||
}
|
||||
|
||||
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
|
||||
} else {
|
||||
t.DNS.DNSServerList = config.DefaultIPv4DNSServerList
|
||||
}
|
||||
}
|
||||
|
||||
if t.DNS.HttpClientProxyType != "" && t.DNS.HttpClientProxyAddr == "" {
|
||||
t.DNS.HttpClientProxyType = ""
|
||||
}
|
||||
|
||||
if t.DNS.HttpClientProxyType == "" {
|
||||
t.DNS.HttpClientProxyAddr = ""
|
||||
t.DNS.HttpClientProxyUser = ""
|
||||
t.DNS.HttpClientProxyPassword = ""
|
||||
}
|
||||
|
||||
if t.GetType == "url" {
|
||||
t.NetInterface = ""
|
||||
t.IPReg = ""
|
||||
}
|
||||
|
||||
if t.GetType == "netInterface" {
|
||||
t.URL = []string{}
|
||||
}
|
||||
|
||||
if !t.WebhookEnable {
|
||||
t.WebhookHeaders = []string{}
|
||||
t.WebhookMethod = ""
|
||||
t.WebhookRequestBody = ""
|
||||
t.WebhookURL = ""
|
||||
t.WebhookSuccessContent = []string{}
|
||||
t.WebhookProxy = ""
|
||||
t.WebhookProxyAddr = ""
|
||||
t.WebhookProxyUser = ""
|
||||
t.WebhookProxyPassword = ""
|
||||
}
|
||||
|
||||
if t.WebhookEnable {
|
||||
if t.WebhookMethod == "get" {
|
||||
t.WebhookRequestBody = ""
|
||||
}
|
||||
|
||||
if t.WebhookProxy == "" {
|
||||
t.WebhookProxyAddr = ""
|
||||
t.WebhookProxyUser = ""
|
||||
t.WebhookProxyPassword = ""
|
||||
}
|
||||
}
|
||||
|
||||
if t.DNS.ForceInterval < 60 {
|
||||
t.DNS.ForceInterval = 60
|
||||
} else if t.DNS.ForceInterval > 360000 {
|
||||
t.DNS.ForceInterval = 360000
|
||||
}
|
||||
|
||||
if t.HttpClientTimeout < 3 {
|
||||
t.HttpClientTimeout = 3
|
||||
} else if t.HttpClientTimeout > 60 {
|
||||
t.HttpClientTimeout = 60
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func logout(c *gin.Context) {
|
||||
config.FlushLoginRandomKey()
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": "已注销登录"})
|
||||
|
@ -376,11 +596,113 @@ func alterBaseConfigure(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, gin.H{"ret": 0})
|
||||
}
|
||||
|
||||
func alterDDNSConfigure(c *gin.Context) {
|
||||
var requestObj config.DDNSConfigure
|
||||
err := c.BindJSON(&requestObj)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "请求解析出错"})
|
||||
return
|
||||
}
|
||||
|
||||
preConfigure := config.GetDDNSConfigure()
|
||||
|
||||
if preConfigure.Enable != requestObj.Enable {
|
||||
|
||||
//log.Printf("动态服务服务状态改变:%v", requestObj.Enable)
|
||||
if requestObj.Enable {
|
||||
service.Start("ddns")
|
||||
} else {
|
||||
service.Stop("ddns")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err = config.SetDDNSConfigure(&requestObj)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": "保存配置过程发生错误,请检测相关启动配置"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 0})
|
||||
}
|
||||
|
||||
func baseconfigure(c *gin.Context) {
|
||||
conf := config.GetBaseConfigure()
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 0, "baseconfigure": conf})
|
||||
}
|
||||
|
||||
func netinterfaces(c *gin.Context) {
|
||||
ipv4NetInterfaces, ipv6Netinterfaces, err := config.GetNetInterface()
|
||||
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, "data": gin.H{"IPv6NewInterfaces": ipv6Netinterfaces, "IPv4NewInterfaces": ipv4NetInterfaces}})
|
||||
}
|
||||
|
||||
func webhookTest(c *gin.Context) {
|
||||
key := c.Query("key")
|
||||
ddnsTask := config.GetDDNSTaskByKey(key)
|
||||
|
||||
if ddnsTask == nil {
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("找不到key对应的DDNS任务:%s", key)})
|
||||
return
|
||||
}
|
||||
|
||||
var request struct {
|
||||
WebhookURL string `json:"WebhookURL"`
|
||||
WebhookMethod string `json:"WebhookMethod"`
|
||||
WebhookHeaders []string `json:"WebhookHeaders"`
|
||||
WebhookRequestBody string `json:"WebhookRequestBody"`
|
||||
WebhookSuccessContent []string `json:"WebhookSuccessContent"` //接口调用成功包含的内容
|
||||
WebhookProxy string `json:"WebhookProxy"` //使用DNS代理设置 ""表示禁用,"dns"表示使用dns的代理设置
|
||||
WebhookProxyAddr string `json:"WebhookProxyAddr"` //代理服务器IP
|
||||
WebhookProxyUser string `json:"WebhookProxyUser"` //代理用户
|
||||
WebhookProxyPassword string `json:"WebhookProxyPassword"` //代理密码
|
||||
}
|
||||
err := c.Bind(&request)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("请求解析出错:%s", err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
responseStr, err := config.WebhookTest(&ddnsTask.DDNSTask,
|
||||
request.WebhookURL,
|
||||
request.WebhookMethod,
|
||||
request.WebhookRequestBody,
|
||||
request.WebhookProxy,
|
||||
request.WebhookProxyAddr,
|
||||
request.WebhookProxyUser,
|
||||
request.WebhookProxyPassword,
|
||||
request.WebhookHeaders,
|
||||
request.WebhookSuccessContent)
|
||||
|
||||
//fmt.Printf("request:%s\n", request)
|
||||
|
||||
msg := "Webhook接口调用成功"
|
||||
if err != nil {
|
||||
msg = err.Error()
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": msg, "Response": responseStr})
|
||||
}
|
||||
|
||||
func IPRegTest(c *gin.Context) {
|
||||
iptype := c.Query("iptype")
|
||||
netinterface := c.Query("netinterface")
|
||||
ipreg := c.Query("ipreg")
|
||||
|
||||
ip := config.GetIPFromNetInterface(iptype, netinterface, ipreg)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 0, "ip": ip})
|
||||
}
|
||||
|
||||
func ddnsconfigure(c *gin.Context) {
|
||||
conf := config.GetDDNSConfigure()
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"ret": 0, "ddnsconfigure": conf})
|
||||
}
|
||||
|
||||
func enablerule(c *gin.Context) {
|
||||
|
||||
enable := c.Query("enable")
|
||||
|
@ -483,10 +805,6 @@ func addrule(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// configureStr := fmt.Sprintf("%s@%s:%sto%s:%s",
|
||||
// requestRule.RelayType,
|
||||
// requestRule.ListenIP, requestRule.ListenPorts,
|
||||
// requestRule.TargetIP, requestRule.TargetPorts)
|
||||
configureStr := requestRule.CreateMainConfigure()
|
||||
|
||||
r, err := rule.CreateRuleByConfigureAndOptions(requestRule.Name, configureStr, requestRule.Options)
|
||||
|
|