Files
-Fonu/internal/ddns/volcengine.go
T
mrlee 278f8267b7 chore: 初始提交 Fonu 1.0.2
- 公网反代:Nginx 状态卡片(版本 / 运行状态 / 配置校验 / 端口监听)
- 部署设置第一、二梯队(HTTPS 开关、安全头、限流、连接数、仅中国大陆 IP)
- 隧道检测兼容 frps v0.68 / v0.71(V2 与旧版端点)
- 恢复被清空的 internal/frp/store.go、internal/api/ddns_handler.go、web/src/views/DdnsView.vue
2026-09-23 09:40:59 +08:00

202 lines
5.5 KiB
Go

package ddns
import (
"context"
"errors"
"fmt"
"strings"
"github.com/volcengine/volcengine-go-sdk/service/dns"
"github.com/volcengine/volcengine-go-sdk/volcengine"
"github.com/volcengine/volcengine-go-sdk/volcengine/credentials"
"github.com/volcengine/volcengine-go-sdk/volcengine/session"
"github.com/volcengine/volcengine-go-sdk/volcengine/volcengineerr"
)
const volcengineDNSRegion = "cn-beijing"
type Volcengine struct{}
func NewVolcengine() *Volcengine { return &Volcengine{} }
func (v *Volcengine) Name() string { return "volcengine" }
func (v *Volcengine) Verify(ctx context.Context, cred Credentials) error {
client, err := v.client(cred)
if err != nil {
return err
}
_, err = client.ListZonesWithContext(ctx, &dns.ListZonesInput{
PageNumber: volcengine.Int32(1),
PageSize: volcengine.Int32(1),
})
return wrapVolcengineErr(err)
}
func (v *Volcengine) HasZone(ctx context.Context, cred Credentials, zone string) (bool, error) {
client, err := v.client(cred)
if err != nil {
return false, err
}
_, err = v.zoneID(ctx, client, zone)
return zoneLookupOK(err)
}
func (v *Volcengine) GetRecordIP(ctx context.Context, cred Credentials, rootDomain, recordName, recordType string) (string, error) {
client, err := v.client(cred)
if err != nil {
return "", err
}
zid, err := v.zoneID(ctx, client, rootDomain)
if err != nil {
return "", err
}
rec, err := v.findRecord(ctx, client, zid, volcengineHost(recordName), recordType)
if err != nil {
return "", err
}
if rec != nil && rec.Value != nil {
return *rec.Value, nil
}
return "", nil
}
func (v *Volcengine) UpdateRecord(ctx context.Context, cred Credentials, rootDomain, recordName, recordType, ip string) error {
client, err := v.client(cred)
if err != nil {
return err
}
zid, err := v.zoneID(ctx, client, rootDomain)
if err != nil {
return err
}
host := volcengineHost(recordName)
existing, err := v.findRecord(ctx, client, zid, host, recordType)
if err != nil {
return err
}
if existing != nil && existing.RecordID != nil {
line := volcengineLine(existing)
_, err = client.UpdateRecordWithContext(ctx, &dns.UpdateRecordInput{
RecordID: existing.RecordID,
Host: volcengine.String(host),
Line: volcengine.String(line),
Type: volcengine.String(recordType),
Value: volcengine.String(ip),
TTL: volcengine.Int32(600),
})
return wrapVolcengineErr(err)
}
_, err = client.CreateRecordWithContext(ctx, &dns.CreateRecordInput{
ZID: volcengine.Int64(zid),
Host: volcengine.String(host),
Type: volcengine.String(recordType),
Value: volcengine.String(ip),
Line: volcengine.String("default"),
TTL: volcengine.Int32(600),
})
return wrapVolcengineErr(err)
}
func (v *Volcengine) client(cred Credentials) (*dns.DNS, error) {
if cred.Token == "" || cred.Secret == "" {
return nil, fmt.Errorf("火山引擎 DNS 凭证未配置")
}
cfg := volcengine.NewConfig().
WithRegion(volcengineDNSRegion).
WithCredentials(credentials.NewStaticCredentials(cred.Token, cred.Secret, ""))
sess, err := session.NewSession(cfg)
if err != nil {
return nil, err
}
return dns.New(sess), nil
}
func (v *Volcengine) zoneID(ctx context.Context, client *dns.DNS, rootDomain string) (int64, error) {
rootDomain = strings.ToLower(strings.TrimSpace(rootDomain))
var page int32 = 1
const pageSize int32 = 100
for {
out, err := client.ListZonesWithContext(ctx, &dns.ListZonesInput{
Key: volcengine.String(rootDomain),
PageNumber: volcengine.Int32(page),
PageSize: volcengine.Int32(pageSize),
})
if err != nil {
return 0, wrapVolcengineErr(err)
}
for _, zone := range out.Zones {
if zone.ZoneName != nil && strings.EqualFold(*zone.ZoneName, rootDomain) && zone.ZID != nil {
return int64(*zone.ZID), nil
}
}
total := int32(0)
if out.Total != nil {
total = *out.Total
}
if len(out.Zones) == 0 || page*pageSize >= total {
break
}
page++
}
return 0, fmt.Errorf("未找到域名 %s 对应的火山引擎 DNS 域名", rootDomain)
}
func (v *Volcengine) findRecord(ctx context.Context, client *dns.DNS, zid int64, host, recordType string) (*dns.RecordForListRecordsOutput, error) {
var page int32 = 1
const pageSize int32 = 100
for {
out, err := client.ListRecordsWithContext(ctx, &dns.ListRecordsInput{
ZID: volcengine.Int64(zid),
Host: volcengine.String(host),
Type: volcengine.String(recordType),
PageNumber: volcengine.Int32(page),
PageSize: volcengine.Int32(pageSize),
})
if err != nil {
return nil, wrapVolcengineErr(err)
}
for _, rec := range out.Records {
if rec.Host != nil && *rec.Host == host && rec.Type != nil && strings.EqualFold(*rec.Type, recordType) {
return rec, nil
}
}
total := int32(0)
if out.TotalCount != nil {
total = *out.TotalCount
}
if len(out.Records) == 0 || page*pageSize >= total {
break
}
page++
}
return nil, nil
}
func volcengineHost(recordName string) string {
return subDomainForRecord(recordName)
}
func volcengineLine(rec *dns.RecordForListRecordsOutput) string {
if rec != nil && rec.Line != nil && strings.TrimSpace(*rec.Line) != "" {
return *rec.Line
}
return "default"
}
func wrapVolcengineErr(err error) error {
if err == nil {
return nil
}
var sdkErr volcengineerr.RequestFailure
if errors.As(err, &sdkErr) {
return fmt.Errorf("火山引擎 DNS API 错误:%s", sdkErr.Message())
}
var apiErr volcengineerr.Error
if errors.As(err, &apiErr) {
return fmt.Errorf("火山引擎 DNS API 错误:%s", apiErr.Message())
}
return err
}