DNSPod和Alidns记录信息增加缓存

This commit is contained in:
GoEdgeLab
2022-10-20 21:47:21 +08:00
parent 5fc0cd688b
commit f5a62b80de
19 changed files with 474 additions and 26 deletions

View File

@@ -17,3 +17,26 @@ type Record struct {
Route string `json:"route"`
TTL int32 `json:"ttl"`
}
func (this *Record) Clone() *Record {
return &Record{
Id: this.Id,
Name: this.Name,
Type: this.Type,
Value: this.Value,
Route: this.Route,
TTL: this.TTL,
}
}
func (this *Record) Copy(anotherRecord *Record) {
if anotherRecord == nil {
return
}
this.Id = anotherRecord.Id
this.Name = anotherRecord.Name
this.Type = anotherRecord.Type
this.Value = anotherRecord.Value
this.Route = anotherRecord.Route
this.TTL = anotherRecord.TTL
}

View File

@@ -0,0 +1,202 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package dnsclients
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/types"
"sync"
"time"
)
func init() {
dbs.OnReadyDone(func() {
go func() {
var ticker = time.NewTicker(1 * time.Hour)
for range ticker.C {
sharedDomainRecordsCache.Clean()
}
}()
})
}
type recordList struct {
version int64
updatedAt int64
records []*dnstypes.Record
}
var sharedDomainRecordsCache = NewDomainRecordsCache()
// DomainRecordsCache 域名记录缓存
// TODO 自动清理没有使用的域名记录,以节约内存
type DomainRecordsCache struct {
domainRecordsMap map[string]*recordList // domain@providerId => record
locker sync.Mutex
}
func NewDomainRecordsCache() *DomainRecordsCache {
return &DomainRecordsCache{
domainRecordsMap: map[string]*recordList{},
}
}
// WriteDomainRecords 写入域名记录缓存
func (this *DomainRecordsCache) WriteDomainRecords(providerId int64, domain string, records []*dnstypes.Record) {
if providerId <= 0 || len(domain) == 0 {
return
}
domain = types.String(providerId) + "@" + domain
this.locker.Lock()
defer this.locker.Unlock()
// 版本号
var key = "DomainRecordsCache" + "@" + types.String(providerId) + "@" + domain
version, err := models.SharedSysLockerDAO.Increase(nil, key, 1)
if err != nil {
remotelogs.Error("dnsclients.BaseProvider", "WriteDomainRecordsCache: "+err.Error())
return
}
var clonedRecords = []*dnstypes.Record{}
for _, record := range records {
clonedRecords = append(clonedRecords, record)
}
this.domainRecordsMap[domain] = &recordList{
version: version,
updatedAt: time.Now().Unix(),
records: clonedRecords,
}
}
// QueryDomainRecord 从缓存中读取域名记录
func (this *DomainRecordsCache) QueryDomainRecord(providerId int64, domain string, recordName string, recordType string) (record *dnstypes.Record, hasRecords bool, ok bool) {
if providerId <= 0 || len(domain) == 0 {
return
}
domain = types.String(providerId) + "@" + domain
this.locker.Lock()
defer this.locker.Unlock()
// check version
var key = "DomainRecordsCache" + "@" + types.String(providerId) + "@" + domain
version, err := models.SharedSysLockerDAO.Read(nil, key)
if err != nil {
remotelogs.Error("dnsclients.BaseProvider", "ReadDomainRecordsCache: "+err.Error())
return
}
// find list
list, recordsOk := this.domainRecordsMap[domain]
if !recordsOk {
return
}
if version != list.version {
delete(this.domainRecordsMap, domain)
return
}
// check timestamp
if list.updatedAt < time.Now().Unix()-86400 /** 缓存有效期为一天 **/ {
delete(this.domainRecordsMap, domain)
return
}
hasRecords = true
for _, r := range list.records {
if r.Name == recordName && r.Type == recordType {
return r, true, true
}
}
return
}
// DeleteDomainRecord 删除域名记录缓存
func (this *DomainRecordsCache) DeleteDomainRecord(providerId int64, domain string, recordId string) {
if providerId <= 0 || len(domain) == 0 || len(recordId) == 0 {
return
}
domain = types.String(providerId) + "@" + domain
this.locker.Lock()
defer this.locker.Unlock()
list, ok := this.domainRecordsMap[domain]
if !ok {
return
}
var found = false
var newRecords = []*dnstypes.Record{}
for _, record := range list.records {
if record.Id == recordId {
found = true
continue
}
newRecords = append(newRecords, record)
}
if found {
list.records = newRecords
}
}
// AddDomainRecord 添加域名记录缓存
func (this *DomainRecordsCache) AddDomainRecord(providerId int64, domain string, record *dnstypes.Record) {
if providerId <= 0 || len(domain) == 0 || record == nil || len(record.Id) == 0 {
return
}
domain = types.String(providerId) + "@" + domain
this.locker.Lock()
defer this.locker.Unlock()
list, ok := this.domainRecordsMap[domain]
if ok {
list.records = append(list.records, record.Clone())
}
// 如果完全没有记录,则不保存
}
// UpdateDomainRecord 修改域名记录缓存
func (this *DomainRecordsCache) UpdateDomainRecord(providerId int64, domain string, record *dnstypes.Record) {
if providerId <= 0 || len(domain) == 0 || record == nil || len(record.Id) == 0 {
return
}
domain = types.String(providerId) + "@" + domain
this.locker.Lock()
defer this.locker.Unlock()
list, ok := this.domainRecordsMap[domain]
if !ok {
return
}
for _, r := range list.records {
if r.Id == record.Id {
r.Copy(record)
break
}
}
}
// Clean 清除过期缓存
func (this *DomainRecordsCache) Clean() {
this.locker.Lock()
defer this.locker.Unlock()
for domain, list := range this.domainRecordsMap {
if list.updatedAt < time.Now().Unix()-86400 {
delete(this.domainRecordsMap, domain)
}
}
}

View File

@@ -0,0 +1,58 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package dnsclients_test
import (
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes"
"github.com/iwind/TeaGo/dbs"
"testing"
)
func TestDomainRecordsCache_WriteDomainRecords(t *testing.T) {
dbs.NotifyReady()
var cache = dnsclients.NewDomainRecordsCache()
cache.WriteDomainRecords(1, "a", []*dnstypes.Record{
{
Id: "1",
Name: "hello",
Type: "A",
Value: "192.168.1.100",
},
})
//time.Sleep(30 * time.Second)
{
t.Log(cache.QueryDomainRecord(1, "a", "hello", "A"))
}
{
t.Log(cache.QueryDomainRecord(1, "a", "hello", "AAAA"))
}
{
t.Log(cache.QueryDomainRecord(1, "a", "hello2", "A"))
}
t.Log("======")
cache.DeleteDomainRecord(1, "a", "2")
cache.UpdateDomainRecord(1, "a", &dnstypes.Record{
Id: "1",
Name: "hello2",
Type: "A",
Value: "192.168.1.200",
})
{
t.Log(cache.QueryDomainRecord(1, "a", "hello2", "A"))
}
t.Log("======")
cache.AddDomainRecord(1, "a", &dnstypes.Record{
Id: "2",
Name: "hello",
Type: "AAAA",
Value: "::1",
})
{
t.Log(cache.QueryDomainRecord(1, "a", "hello", "AAAA"))
}
}

View File

@@ -15,6 +15,8 @@ import (
type AliDNSProvider struct {
BaseProvider
ProviderId int64
accessKeyId string
accessKeySecret string
regionId string
@@ -106,6 +108,11 @@ func (this *AliDNSProvider) GetRecords(domain string) (records []*dnstypes.Recor
}
}
// 写入缓存
if this.ProviderId > 0 {
sharedDomainRecordsCache.WriteDomainRecords(this.ProviderId, domain, records)
}
return
}
@@ -130,6 +137,14 @@ func (this *AliDNSProvider) GetRoutes(domain string) (routes []*dnstypes.Route,
// QueryRecord 查询单个记录
func (this *AliDNSProvider) QueryRecord(domain string, name string, recordType dnstypes.RecordType) (*dnstypes.Record, error) {
// 从缓存中读取
if this.ProviderId > 0 {
record, hasRecords, _ := sharedDomainRecordsCache.QueryDomainRecord(this.ProviderId, domain, name, recordType)
if hasRecords { // 有效的搜索
return record, nil
}
}
records, err := this.GetRecords(domain)
if err != nil {
return nil, err
@@ -162,6 +177,12 @@ func (this *AliDNSProvider) AddRecord(domain string, newRecord *dnstypes.Record)
}
if resp.IsSuccess() {
newRecord.Id = resp.RecordId
// 加入缓存
if this.ProviderId > 0 {
sharedDomainRecordsCache.AddDomainRecord(this.ProviderId, domain, newRecord)
}
return nil
}
@@ -183,7 +204,18 @@ func (this *AliDNSProvider) UpdateRecord(domain string, record *dnstypes.Record,
var resp = alidns.CreateUpdateDomainRecordResponse()
err := this.doAPI(req, resp)
return this.WrapError(err, domain, newRecord)
if err != nil {
return this.WrapError(err, domain, newRecord)
}
newRecord.Id = record.Id
// 修改缓存
if this.ProviderId > 0 {
sharedDomainRecordsCache.UpdateDomainRecord(this.ProviderId, domain, newRecord)
}
return nil
}
// DeleteRecord 删除记录
@@ -193,7 +225,16 @@ func (this *AliDNSProvider) DeleteRecord(domain string, record *dnstypes.Record)
var resp = alidns.CreateDeleteDomainRecordResponse()
err := this.doAPI(req, resp)
return this.WrapError(err, domain, record)
if err != nil {
return this.WrapError(err, domain, record)
}
// 删除缓存
if this.ProviderId > 0 {
sharedDomainRecordsCache.DeleteDomainRecord(this.ProviderId, domain, record.Id)
}
return nil
}
// DefaultRoute 默认线路

View File

@@ -32,6 +32,27 @@ func TestAliDNSProvider_GetRecords(t *testing.T) {
logs.PrintAsJSON(records, t)
}
func TestAliDNSProvider_QueryRecord(t *testing.T) {
provider, err := testAliDNSProvider()
if err != nil {
t.Fatal(err)
}
{
record, err := provider.QueryRecord("meloy.cn", "www", "A")
if err != nil {
t.Fatal(err)
}
t.Log(record)
}
{
record, err := provider.QueryRecord("meloy.cn", "www1", "A")
if err != nil {
t.Fatal(err)
}
t.Log(record)
}
}
func TestAliDNSProvider_DeleteRecord(t *testing.T) {
provider, err := testAliDNSProvider()
if err != nil {
@@ -100,6 +121,8 @@ func TestAliDNSProvider_UpdateRecord(t *testing.T) {
}
func testAliDNSProvider() (ProviderInterface, error) {
dbs.NotifyReady()
db, err := dbs.Default()
if err != nil {
return nil, err
@@ -113,7 +136,9 @@ func testAliDNSProvider() (ProviderInterface, error) {
if err != nil {
return nil, err
}
provider := &AliDNSProvider{}
provider := &AliDNSProvider{
ProviderId: one.GetInt64("id"),
}
err = provider.Auth(apiParams)
if err != nil {
return nil, err

View File

@@ -6,8 +6,7 @@ import (
"github.com/iwind/TeaGo/types"
)
type BaseProvider struct {
}
type BaseProvider struct{}
// WrapError 封装解析相关错误
func (this *BaseProvider) WrapError(err error, domain string, record *dnstypes.Record) error {
@@ -27,4 +26,3 @@ func (this *BaseProvider) WrapError(err error, domain string, record *dnstypes.R
}
return errors.New("record operation failed: '" + fullname + " " + record.Type + " " + record.Value + " " + types.String(record.TTL) + "': " + err.Error())
}

View File

@@ -36,6 +36,8 @@ var cloudFlareHTTPClient = &http.Client{
type CloudFlareProvider struct {
BaseProvider
ProviderId int64
apiKey string // API密钥
email string // 账号邮箱

View File

@@ -30,6 +30,8 @@ type CustomHTTPProvider struct {
url string
secret string
ProviderId int64
BaseProvider
}

View File

@@ -36,6 +36,8 @@ var dnsPodHTTPClient = &http.Client{
type DNSPodProvider struct {
BaseProvider
ProviderId int64
region string
apiId string
apiToken string
@@ -53,6 +55,7 @@ func (this *DNSPodProvider) Auth(params maps.Map) error {
if len(this.apiToken) == 0 {
return errors.New("'token' should not be empty")
}
return nil
}
@@ -120,6 +123,12 @@ func (this *DNSPodProvider) GetRecords(domain string) (records []*dnstypes.Recor
break
}
}
// 写入缓存
if this.ProviderId > 0 {
sharedDomainRecordsCache.WriteDomainRecords(this.ProviderId, domain, records)
}
return
}
@@ -160,6 +169,14 @@ func (this *DNSPodProvider) GetRoutes(domain string) (routes []*dnstypes.Route,
// QueryRecord 查询单个记录
func (this *DNSPodProvider) QueryRecord(domain string, name string, recordType dnstypes.RecordType) (*dnstypes.Record, error) {
// 从缓存中读取
if this.ProviderId > 0 {
record, hasRecords, _ := sharedDomainRecordsCache.QueryDomainRecord(this.ProviderId, domain, name, recordType)
if hasRecords { // 有效的搜索
return record, nil
}
}
records, err := this.GetRecords(domain)
if err != nil {
return nil, err
@@ -199,6 +216,12 @@ func (this *DNSPodProvider) AddRecord(domain string, newRecord *dnstypes.Record)
return this.WrapError(err, domain, newRecord)
}
newRecord.Id = types.String(resp.Record.Id)
// 加入缓存
if this.ProviderId > 0 {
sharedDomainRecordsCache.AddDomainRecord(this.ProviderId, domain, newRecord)
}
return nil
}
@@ -229,7 +252,18 @@ func (this *DNSPodProvider) UpdateRecord(domain string, record *dnstypes.Record,
}
var resp = new(dnspod.RecordModifyResponse)
err := this.doAPI("/Record.Modify", args, resp)
return this.WrapError(err, domain, newRecord)
if err != nil {
return this.WrapError(err, domain, newRecord)
}
newRecord.Id = record.Id
// 修改缓存
if this.ProviderId > 0 {
sharedDomainRecordsCache.UpdateDomainRecord(this.ProviderId, domain, newRecord)
}
return nil
}
// DeleteRecord 删除记录
@@ -244,7 +278,16 @@ func (this *DNSPodProvider) DeleteRecord(domain string, record *dnstypes.Record)
"record_id": record.Id,
}, resp)
return this.WrapError(err, domain, record)
if err != nil {
return this.WrapError(err, domain, record)
}
// 删除缓存
if this.ProviderId > 0 {
sharedDomainRecordsCache.DeleteDomainRecord(this.ProviderId, domain, record.Id)
}
return nil
}
// 发送请求

View File

@@ -75,6 +75,29 @@ func TestDNSPodProvider_AddRecord(t *testing.T) {
t.Log("ok, record id:", record.Id)
}
func TestDNSPodProvider_QueryRecord(t *testing.T) {
provider, _, err := testDNSPodProvider()
if err != nil {
t.Fatal(err)
}
{
record, err := provider.QueryRecord(DNSPodTestDomain, "hello-forward", dnstypes.RecordTypeCNAME)
if err != nil {
t.Fatal(err)
}
t.Log(record)
}
{
record, err := provider.QueryRecord(DNSPodTestDomain, "hello-forward2", dnstypes.RecordTypeCNAME)
if err != nil {
t.Fatal(err)
}
t.Log(record)
}
}
func TestDNSPodProvider_UpdateRecord(t *testing.T) {
provider, isInternational, err := testDNSPodProvider()
if err != nil {
@@ -122,6 +145,8 @@ func TestDNSPodProvider_DeleteRecord(t *testing.T) {
}
func testDNSPodProvider() (provider dnsclients.ProviderInterface, isInternational bool, err error) {
dbs.NotifyReady()
db, err := dbs.Default()
if err != nil {
return nil, false, err
@@ -130,12 +155,14 @@ func testDNSPodProvider() (provider dnsclients.ProviderInterface, isInternationa
if err != nil {
return nil, false, err
}
apiParams := maps.Map{}
var apiParams = maps.Map{}
err = json.Unmarshal([]byte(one.GetString("apiParams")), &apiParams)
if err != nil {
return nil, false, err
}
provider = &dnsclients.DNSPodProvider{}
provider = &dnsclients.DNSPodProvider{
ProviderId: one.GetInt64("id"),
}
err = provider.Auth(apiParams)
if err != nil {
return nil, false, err

View File

@@ -29,6 +29,10 @@ var edgeDNSHTTPClient = &http.Client{
}
type EdgeDNSAPIProvider struct {
BaseProvider
ProviderId int64
host string
accessKeyId string
accessKeySecret string

View File

@@ -39,6 +39,8 @@ var huaweiDNSHTTPClient = &http.Client{
type HuaweiDNSProvider struct {
BaseProvider
ProviderId int64
accessKeyId string
accessKeySecret string
}

View File

@@ -7,20 +7,32 @@ package dnsclients
import "github.com/iwind/TeaGo/maps"
// FindProvider 查找服务商实例
func FindProvider(providerType ProviderType) ProviderInterface {
func FindProvider(providerType ProviderType, providerId int64) ProviderInterface {
switch providerType {
case ProviderTypeDNSPod:
return &DNSPodProvider{}
return &DNSPodProvider{
ProviderId: providerId,
}
case ProviderTypeAliDNS:
return &AliDNSProvider{}
return &AliDNSProvider{
ProviderId: providerId,
}
case ProviderTypeHuaweiDNS:
return &HuaweiDNSProvider{}
return &HuaweiDNSProvider{
ProviderId: providerId,
}
case ProviderTypeCloudFlare:
return &CloudFlareProvider{}
return &CloudFlareProvider{
ProviderId: providerId,
}
case ProviderTypeCustomHTTP:
return &CustomHTTPProvider{}
return &CustomHTTPProvider{
ProviderId: providerId,
}
case ProviderTypeEdgeDNSAPI:
return &EdgeDNSAPIProvider{}
return &EdgeDNSAPIProvider{
ProviderId: providerId,
}
}
return nil