improve: 使用标准项目结构

This commit is contained in:
Mmx233
2023-03-01 18:58:11 +08:00
parent cb11426bd6
commit 3c63e9ddc3
29 changed files with 178 additions and 183 deletions

View File

@@ -0,0 +1,182 @@
package aliyun
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
dnsUtil2 "github.com/Mmx233/BitSrunLoginGo/internal/pkg/dns/util"
"github.com/Mmx233/tool"
"math/rand"
"net/http"
"net/url"
"sort"
"time"
)
type DnsProvider struct {
TTL uint `mapstructure:"-"`
Http *tool.Http `mapstructure:"-"`
AccessKeyId string `mapstructure:"access_key_id"`
AccessKeySecret string `mapstructure:"access_key_secret"`
}
func New(ttl uint, conf map[string]interface{}, Http *http.Client) (*DnsProvider, error) {
var p = DnsProvider{
TTL: ttl,
Http: tool.NewHttpTool(Http),
}
e := dnsUtil2.DecodeConfig(conf, &p)
if e != nil {
return nil, e
}
if p.AccessKeyId == "" || p.AccessKeySecret == "" {
return nil, errors.New("aliyun AccessKey 不能为空")
}
return &p, nil
}
func (a DnsProvider) SendRequest(Type, Action string, data map[string]interface{}) (*http.Response, error) {
var reqOpt = tool.DoHttpReq{
Url: "https://alidns.aliyuncs.com",
}
rand.Seed(time.Now().UnixNano())
data["Format"] = "json"
data["Version"] = "2015-01-09"
data["SignatureMethod"] = "HMAC-SHA1"
data["SignatureVersion"] = "1.0"
data["SignatureNonce"] = fmt.Sprint(tool.Rand.Num(10000000, 90000000))
data["Timestamp"] = time.Now().UTC().Format("2006-01-02T15:04:05Z")
data["Action"] = Action
data["AccessKeyId"] = a.AccessKeyId
signStr := Type + "&" + url.QueryEscape("/") + "&"
var keys = make([]string, len(data))
var i int
for k := range data {
keys[i] = k
i++
}
sort.Strings(keys)
for i, k := range keys {
str := k + "=" + url.QueryEscape(fmt.Sprint(data[k]))
if i == 0 {
str = url.QueryEscape(str)
} else {
str = url.QueryEscape("&" + str)
}
signStr += str
}
mac := hmac.New(sha1.New, []byte(a.AccessKeySecret+"&"))
_, e := mac.Write([]byte(signStr))
if e != nil {
return nil, e
}
data["Signature"] = base64.StdEncoding.EncodeToString(mac.Sum(nil))
if Type == "GET" || Type == "DELETE" {
reqOpt.Query = data
} else {
reqOpt.Body = data
}
resp, e := a.Http.Request(Type, &reqOpt)
if e != nil {
return nil, e
}
if resp.StatusCode > 299 {
defer resp.Body.Close()
var res Response
if e = json.NewDecoder(resp.Body).Decode(&res); e != nil {
return nil, e
}
return nil, errors.New(res.Message)
}
return resp, nil
}
func (a DnsProvider) DomainRecordStatus(subDomain, rootDomain string) (*DomainStatus, bool, error) {
resp, e := a.SendRequest("GET", "DescribeDomainRecords", map[string]interface{}{
"DomainName": rootDomain,
"SearchMode": "EXACT",
"KeyWord": subDomain,
"PageSize": 1,
"Type": "A",
})
if e != nil {
return nil, false, e
}
defer resp.Body.Close()
var res DomainStatusRes
if e = json.NewDecoder(resp.Body).Decode(&res); e != nil {
return nil, false, e
}
if res.TotalCount == 0 || len(res.DomainRecords.Record) == 0 {
return nil, false, nil
}
return &res.DomainRecords.Record[0], true, nil
}
func (a DnsProvider) UpdateRecord(RecordId, subDomain, ip string) error {
resp, e := a.SendRequest("POST", "UpdateDomainRecord", map[string]interface{}{
"RecordId": RecordId,
"RR": subDomain,
"Type": "A",
"Value": ip,
"TTL": a.TTL,
})
if e != nil {
return e
}
defer resp.Body.Close()
return nil
}
func (a DnsProvider) NewRecord(subDomain, rootDomain, ip string) error {
resp, e := a.SendRequest("POST", "AddDomainRecord", map[string]interface{}{
"DomainName": rootDomain,
"RR": subDomain,
"Type": "A",
"Value": ip,
"TTL": a.TTL,
})
if e != nil {
return e
}
defer resp.Body.Close()
return nil
}
func (a DnsProvider) SetDomainRecord(domain, ip string) error {
subDomain, rootDomain, e := dnsUtil2.DecodeDomain(domain)
if e != nil {
return e
}
record, exist, e := a.DomainRecordStatus(subDomain, rootDomain)
if e != nil {
return e
}
if exist {
if record.Value == ip {
return nil
}
return a.UpdateRecord(record.RecordId, subDomain, ip)
} else {
return a.NewRecord(subDomain, rootDomain, ip)
}
}

View File

@@ -0,0 +1,19 @@
package aliyun
type Response struct {
Code string `json:"code"`
Message string `json:"message"`
}
type DomainStatus struct {
DomainName string `json:"DomainName"`
RecordId string `json:"RecordId"`
Value string `json:"Value"`
}
type DomainStatusRes struct {
TotalCount uint `json:"TotalCount"`
DomainRecords struct {
Record []DomainStatus `json:"Record"`
} `json:"DomainRecords"`
}

View File

@@ -0,0 +1,68 @@
package cloudflare
import (
"context"
"errors"
"github.com/Mmx233/BitSrunLoginGo/internal/pkg/dns/util"
"github.com/cloudflare/cloudflare-go"
"net/http"
)
type DnsProvider struct {
Api *cloudflare.API `mapstructure:"-"`
TTL int `mapstructure:"-"`
Zone string `mapstructure:"zone"`
ZoneResource *cloudflare.ResourceContainer `mapstructure:"-"`
Token string `mapstructure:"token"`
}
func New(ttl int, conf map[string]interface{}, Http *http.Client) (*DnsProvider, error) {
var p = DnsProvider{
TTL: ttl,
}
e := dnsUtil.DecodeConfig(conf, &p)
if e != nil {
return nil, e
}
if p.Zone == "" {
return nil, errors.New("cloudflare zone 不能为空")
}
p.ZoneResource = cloudflare.ZoneIdentifier(p.Zone)
if p.Token == "" {
return nil, errors.New("cloudflare token 不能为空")
}
p.Api, e = cloudflare.NewWithAPIToken(p.Token, cloudflare.HTTPClient(Http))
return &p, e
}
func (a DnsProvider) SetDomainRecord(domain, ip string) error {
records, _, e := a.Api.ListDNSRecords(context.Background(), a.ZoneResource, cloudflare.ListDNSRecordsParams{
Type: "A",
Name: domain,
})
if e != nil {
return e
}
if len(records) == 0 {
_, e = a.Api.CreateDNSRecord(context.Background(), a.ZoneResource, cloudflare.CreateDNSRecordParams{
Type: "A",
Name: domain,
Content: ip,
TTL: a.TTL,
})
return e
} else {
record := records[0]
if record.Content == ip {
return nil
}
return a.Api.UpdateDNSRecord(context.Background(), a.ZoneResource, cloudflare.UpdateDNSRecordParams{
ID: record.ID,
Content: ip,
})
}
}

View File

@@ -0,0 +1,71 @@
package dnspod
import (
dnsUtil2 "github.com/Mmx233/BitSrunLoginGo/internal/pkg/dns/util"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/regions"
dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323"
"net/http"
"strings"
)
type DnsProvider struct {
Client *dnspod.Client `mapstructure:"-"`
TTL uint64 `mapstructure:"-"`
SecretId string `mapstructure:"secret_id"`
SecretKey string `mapstructure:"secret_key"`
}
func New(ttl uint64, conf map[string]interface{}, Http http.RoundTripper) (*DnsProvider, error) {
var p = DnsProvider{TTL: ttl}
e := dnsUtil2.DecodeConfig(conf, &p)
if e != nil {
return nil, e
}
p.Client, e = dnspod.NewClient(common.NewCredential(p.SecretId, p.SecretKey), regions.Guangzhou, profile.NewClientProfile())
p.Client.WithHttpTransport(Http)
return &p, e
}
func (a DnsProvider) SetDomainRecord(domain, ip string) error {
subDomain, rootDomain, e := dnsUtil2.DecodeDomain(domain)
if e != nil {
return e
}
var (
recordType = "A"
recordLine = "默认"
limit uint64 = 1
)
reqRecordList := dnspod.NewDescribeRecordListRequest()
reqRecordList.Domain = &rootDomain
reqRecordList.Subdomain = &subDomain
reqRecordList.Limit = &limit
res, e := a.Client.DescribeRecordList(reqRecordList)
if (e != nil && strings.Contains(e.Error(), dnspod.RESOURCENOTFOUND_NODATAOFRECORD)) || (e == nil && len(res.Response.RecordList) == 0) {
reqNewRecord := dnspod.NewCreateRecordRequest()
reqNewRecord.TTL = &a.TTL
reqNewRecord.Domain = &rootDomain
reqNewRecord.RecordType = &recordType
reqNewRecord.RecordLine = &recordLine
reqNewRecord.Value = &ip
reqNewRecord.SubDomain = &subDomain
_, e = a.Client.CreateRecord(reqNewRecord)
return e
} else if e != nil {
return e
}
reqModifyRecord := dnspod.NewModifyRecordRequest()
reqModifyRecord.Domain = &rootDomain
reqModifyRecord.SubDomain = &subDomain
reqModifyRecord.Value = &ip
reqModifyRecord.RecordId = res.Response.RecordList[0].RecordId
reqModifyRecord.RecordLine = &recordLine
reqModifyRecord.RecordType = &recordType
_, e = a.Client.ModifyRecord(reqModifyRecord)
return e
}

View File

@@ -0,0 +1,55 @@
package dns
import (
"errors"
"fmt"
"github.com/Mmx233/BitSrunLoginGo/internal/pkg/dns/aliyun"
"github.com/Mmx233/BitSrunLoginGo/internal/pkg/dns/cloudflare"
"github.com/Mmx233/BitSrunLoginGo/internal/pkg/dns/dnspod"
log "github.com/sirupsen/logrus"
)
func Run(c *Config) error {
log.Debugln("开始 DDNS 流程")
if c.TTL == 0 {
c.TTL = 600
}
// 配置解析
var dns Provider
var e error
switch c.Provider {
case "aliyun":
dns, e = aliyun.New(c.TTL, c.Conf, c.Http)
case "cloudflare":
dns, e = cloudflare.New(int(c.TTL), c.Conf, c.Http)
case "dnspod":
dns, e = dnspod.New(uint64(c.TTL), c.Conf, c.Http.Transport)
default:
var msg string
if c.Provider == "" {
msg = "DDNS 模块 dns 运营商不能为空"
} else {
msg = fmt.Sprintf("DDNS 模块 dns 运营商 %s 不支持", c.Provider)
}
log.Warnln(msg)
return errors.New(msg)
}
if e != nil {
log.Warnf("解析 DDNS 配置失败:%v", e)
return e
}
// 修改 dns 记录
if e = dns.SetDomainRecord(c.Domain, c.IP); e != nil {
log.Warnf("设置 dns 解析记录失败:%v", e)
return e
}
log.Debugln("DDNS 配置应用成功")
return nil
}

View File

@@ -0,0 +1,16 @@
package dns
import "net/http"
type Provider interface {
SetDomainRecord(domain, ip string) error
}
type Config struct {
Provider string
IP string
Domain string
TTL uint
Conf map[string]interface{}
Http *http.Client
}

View File

@@ -0,0 +1,9 @@
package dnsUtil
import (
"github.com/mitchellh/mapstructure"
)
func DecodeConfig(conf map[string]interface{}, output interface{}) error {
return mapstructure.Decode(conf, output)
}

View File

@@ -0,0 +1,18 @@
package dnsUtil
import (
"errors"
"strings"
)
func DecodeDomain(domain string) (subStr string, rootDomain string, e error) {
t := strings.Split(domain, ".")
if len(t) == 1 {
return "", "", errors.New("域名不合法")
} else if len(t) == 2 {
return "@", domain, nil
}
l := len(t)
return strings.Join(t[:l-2], "."), strings.Join(t[l-2:l], "."), nil
}