mirror of
https://github.com/jeessy2/ddns-go.git
synced 2025-10-20 15:33:46 +08:00
fix: nowcn,eranet,更换接口路径 (#1520)
* fix: v1接口会收到ip白名单限制,改用v2接口 * remove: 去掉打印测试 --------- Co-authored-by: dsuzejian <dsuzejian@now.cn>
This commit is contained in:
187
dns/eranet.go
187
dns/eranet.go
@ -1,22 +1,23 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jeessy2/ddns-go/v6/config"
|
||||
"github.com/jeessy2/ddns-go/v6/util"
|
||||
)
|
||||
|
||||
const (
|
||||
eranetRecordListAPI string = "http://api.eranet.com:2080/api/dns/describe-record-index.json"
|
||||
eranetRecordModifyURL string = "http://api.eranet.com:2080/api/dns/update-domain-record.json"
|
||||
eranetRecordCreateAPI string = "http://api.eranet.com:2080/api/dns/add-domain-record.json"
|
||||
)
|
||||
|
||||
// https://partner.tnet.hk/adminCN/mode_Http_Api_detail.php
|
||||
// Eranet DNS实现
|
||||
type Eranet struct {
|
||||
DNS config.DNS
|
||||
@ -36,11 +37,11 @@ type EranetRecord struct {
|
||||
}
|
||||
|
||||
type EranetRecordListResp struct {
|
||||
EranetStatus
|
||||
EranetBaseResult
|
||||
Data []EranetRecord
|
||||
}
|
||||
|
||||
type EranetStatus struct {
|
||||
type EranetBaseResult struct {
|
||||
RequestId string `json:"RequestId"`
|
||||
Id int `json:"Id"`
|
||||
Error string `json:"error"`
|
||||
@ -104,22 +105,28 @@ func (eranet *Eranet) addUpdateDomainRecords(recordType string) {
|
||||
|
||||
// create 创建DNS记录
|
||||
func (eranet *Eranet) create(domain *config.Domain, recordType string, ipAddr string) {
|
||||
param := map[string]any{
|
||||
param := map[string]string{
|
||||
"Domain": domain.DomainName,
|
||||
"Host": domain.GetSubDomain(),
|
||||
"Type": recordType,
|
||||
"Value": ipAddr,
|
||||
"Ttl": eranet.TTL,
|
||||
}
|
||||
res, err := eranet.request(eranetRecordCreateAPI, param)
|
||||
res, err := eranet.request("/api/Dns/AddDomainRecord", param, "GET")
|
||||
if err != nil {
|
||||
util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, err.Error())
|
||||
domain.UpdateStatus = config.UpdatedFailed
|
||||
} else if res.Error != "" {
|
||||
util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, res.Error)
|
||||
}
|
||||
var result NowcnBaseResult
|
||||
err = json.Unmarshal(res, &result)
|
||||
if err != nil {
|
||||
util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, err.Error())
|
||||
domain.UpdateStatus = config.UpdatedFailed
|
||||
}
|
||||
if result.Error != "" {
|
||||
util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, result.Error)
|
||||
domain.UpdateStatus = config.UpdatedFailed
|
||||
} else {
|
||||
util.Log("新增域名解析 %s 成功! IP: %s", domain, ipAddr)
|
||||
domain.UpdateStatus = config.UpdatedSuccess
|
||||
}
|
||||
}
|
||||
@ -131,20 +138,27 @@ func (eranet *Eranet) modify(record EranetRecord, domain *config.Domain, recordT
|
||||
util.Log("你的IP %s 没有变化, 域名 %s", ipAddr, domain)
|
||||
return
|
||||
}
|
||||
param := map[string]any{
|
||||
"Id": record.ID,
|
||||
param := map[string]string{
|
||||
"Id": strconv.Itoa(record.ID),
|
||||
"Domain": domain.DomainName,
|
||||
"Host": domain.GetSubDomain(),
|
||||
"Type": recordType,
|
||||
"Value": ipAddr,
|
||||
"Ttl": eranet.TTL,
|
||||
}
|
||||
res, err := eranet.request(eranetRecordModifyURL, param)
|
||||
res, err := eranet.request("/api/Dns/UpdateDomainRecord", param, "GET")
|
||||
if err != nil {
|
||||
util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, err.Error())
|
||||
domain.UpdateStatus = config.UpdatedFailed
|
||||
} else if res.Error != "" {
|
||||
util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, res.Error)
|
||||
}
|
||||
var result NowcnBaseResult
|
||||
err = json.Unmarshal(res, &result)
|
||||
if err != nil {
|
||||
util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, err.Error())
|
||||
domain.UpdateStatus = config.UpdatedFailed
|
||||
}
|
||||
if result.Error != "" {
|
||||
util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, result.Error)
|
||||
domain.UpdateStatus = config.UpdatedFailed
|
||||
} else {
|
||||
util.Log("更新域名解析 %s 成功! IP: %s", domain, ipAddr)
|
||||
@ -152,43 +166,15 @@ func (eranet *Eranet) modify(record EranetRecord, domain *config.Domain, recordT
|
||||
}
|
||||
}
|
||||
|
||||
// request 发送HTTP请求
|
||||
func (eranet *Eranet) request(apiAddr string, param map[string]any) (status EranetStatus, err error) {
|
||||
param["auth-userid"] = eranet.DNS.ID
|
||||
param["api-key"] = eranet.DNS.Secret
|
||||
|
||||
fullURL := apiAddr + "?" + eranet.queryParams(param)
|
||||
client := util.CreateHTTPClient()
|
||||
resp, err := client.Get(fullURL)
|
||||
|
||||
// 处理响应
|
||||
err = util.GetHTTPResponse(resp, err, &status)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// getRecordList 获取域名记录列表
|
||||
func (eranet *Eranet) getRecordList(domain *config.Domain, typ string) (result EranetRecordListResp, err error) {
|
||||
param := map[string]any{
|
||||
"Domain": domain.DomainName,
|
||||
"auth-userid": eranet.DNS.ID,
|
||||
"api-key": eranet.DNS.Secret,
|
||||
}
|
||||
fullURL := eranetRecordListAPI + "?" + eranet.queryParams(param)
|
||||
client := util.CreateHTTPClient()
|
||||
resp, err := client.Get(fullURL)
|
||||
var response EranetRecordListResp
|
||||
result = EranetRecordListResp{
|
||||
Data: make([]EranetRecord, 0),
|
||||
}
|
||||
err = util.GetHTTPResponse(resp, err, &response)
|
||||
for _, v := range response.Data {
|
||||
if v.Host == domain.GetSubDomain() {
|
||||
result.Data = append(result.Data, v)
|
||||
break
|
||||
}
|
||||
|
||||
param := map[string]string{
|
||||
"Domain": domain.DomainName,
|
||||
"Type": typ,
|
||||
"Host": domain.GetSubDomain(),
|
||||
}
|
||||
res, err := eranet.request("/api/Dns/DescribeRecordIndex", param, "GET")
|
||||
err = json.Unmarshal(res, &result)
|
||||
return
|
||||
}
|
||||
|
||||
@ -205,3 +191,98 @@ func (eranet *Eranet) queryParams(param map[string]any) string {
|
||||
}
|
||||
return strings.Join(queryParams, "&")
|
||||
}
|
||||
|
||||
func (t *Eranet) sign(params map[string]string, method string) (string, error) {
|
||||
// 添加公共参数
|
||||
params["AccessKeyID"] = t.DNS.ID
|
||||
params["SignatureMethod"] = "HMAC-SHA1"
|
||||
params["SignatureNonce"] = fmt.Sprintf("%d", time.Now().UnixNano())
|
||||
params["Timestamp"] = time.Now().UTC().Format("2006-01-02T15:04:05Z")
|
||||
|
||||
// 1. 排序参数(按首字母顺序)
|
||||
var keys []string
|
||||
for k := range params {
|
||||
if k != "Signature" { // 排除Signature参数
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
// 2. 构造规范化请求字符串
|
||||
var canonicalizedQuery []string
|
||||
for _, k := range keys {
|
||||
// URL编码参数名和参数值
|
||||
encodedKey := util.PercentEncode(k)
|
||||
encodedValue := util.PercentEncode(params[k])
|
||||
canonicalizedQuery = append(canonicalizedQuery, encodedKey+"="+encodedValue)
|
||||
}
|
||||
canonicalizedQueryString := strings.Join(canonicalizedQuery, "&")
|
||||
|
||||
// 3. 构造待签名字符串
|
||||
stringToSign := method + "&" + util.PercentEncode("/") + "&" + util.PercentEncode(canonicalizedQueryString)
|
||||
|
||||
// 4. 计算HMAC-SHA1签名
|
||||
key := t.DNS.Secret + "&"
|
||||
h := hmac.New(sha1.New, []byte(key))
|
||||
h.Write([]byte(stringToSign))
|
||||
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
|
||||
// 5. 添加签名到参数中
|
||||
params["Signature"] = signature
|
||||
|
||||
// 6. 重新构造最终的查询字符串(包含签名)
|
||||
keys = append(keys, "Signature")
|
||||
sort.Strings(keys)
|
||||
var finalQuery []string
|
||||
for _, k := range keys {
|
||||
encodedKey := util.PercentEncode(k)
|
||||
encodedValue := util.PercentEncode(params[k])
|
||||
finalQuery = append(finalQuery, encodedKey+"="+encodedValue)
|
||||
}
|
||||
|
||||
return strings.Join(finalQuery, "&"), nil
|
||||
}
|
||||
|
||||
func (t *Eranet) request(apiPath string, params map[string]string, method string) ([]byte, error) {
|
||||
// 生成签名
|
||||
queryString, err := t.sign(params, method)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成签名失败: %v", err)
|
||||
}
|
||||
|
||||
// 构造完整URL
|
||||
baseURL := "https://www.eranet.com"
|
||||
fullURL := baseURL + apiPath + "?" + queryString
|
||||
|
||||
// 创建HTTP请求
|
||||
req, err := http.NewRequest(method, fullURL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建请求失败: %v", err)
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
// 发送请求
|
||||
client := &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取响应失败: %v", err)
|
||||
}
|
||||
|
||||
// 检查HTTP状态码
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("API请求失败,状态码: %d, 响应: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
|
197
dns/nowcn.go
197
dns/nowcn.go
@ -1,22 +1,23 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jeessy2/ddns-go/v6/config"
|
||||
"github.com/jeessy2/ddns-go/v6/util"
|
||||
)
|
||||
|
||||
const (
|
||||
nowcnRecordListAPI string = "https://todapi.now.cn:2443/api/dns/describe-record-index.json"
|
||||
nowcnRecordModifyURL string = "https://todapi.now.cn:2443/api/dns/update-domain-record.json"
|
||||
nowcnRecordCreateAPI string = "https://todapi.now.cn:2443/api/dns/add-domain-record.json"
|
||||
)
|
||||
|
||||
// https://www.todaynic.com/partner/mode_Http_Api_detail.php?target_id=d15d8028-7c4f-4a5c-9d15-3a4481c4178e
|
||||
// https://www.todaynic.com/docApi/
|
||||
// Nowcn nowcn DNS实现
|
||||
type Nowcn struct {
|
||||
DNS config.DNS
|
||||
@ -38,12 +39,12 @@ type NowcnRecord struct {
|
||||
|
||||
// NowcnRecordListResp 记录列表响应
|
||||
type NowcnRecordListResp struct {
|
||||
NowcnStatus
|
||||
NowcnBaseResult
|
||||
Data []NowcnRecord
|
||||
}
|
||||
|
||||
// NowcnStatus API响应状态
|
||||
type NowcnStatus struct {
|
||||
type NowcnBaseResult struct {
|
||||
RequestId string `json:"RequestId"`
|
||||
Id int `json:"Id"`
|
||||
Error string `json:"error"`
|
||||
@ -107,22 +108,28 @@ func (nowcn *Nowcn) addUpdateDomainRecords(recordType string) {
|
||||
|
||||
// create 创建DNS记录
|
||||
func (nowcn *Nowcn) create(domain *config.Domain, recordType string, ipAddr string) {
|
||||
param := map[string]any{
|
||||
param := map[string]string{
|
||||
"Domain": domain.DomainName,
|
||||
"Host": domain.GetSubDomain(),
|
||||
"Type": recordType,
|
||||
"Value": ipAddr,
|
||||
"Ttl": nowcn.TTL,
|
||||
}
|
||||
res, err := nowcn.request(nowcnRecordCreateAPI, param)
|
||||
res, err := nowcn.request("/api/Dns/AddDomainRecord", param, "GET")
|
||||
if err != nil {
|
||||
util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, err.Error())
|
||||
domain.UpdateStatus = config.UpdatedFailed
|
||||
} else if res.Error != "" {
|
||||
util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, res.Error)
|
||||
}
|
||||
var result NowcnBaseResult
|
||||
err = json.Unmarshal(res, &result)
|
||||
if err != nil {
|
||||
util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, err.Error())
|
||||
domain.UpdateStatus = config.UpdatedFailed
|
||||
}
|
||||
if result.Error != "" {
|
||||
util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, result.Error)
|
||||
domain.UpdateStatus = config.UpdatedFailed
|
||||
} else {
|
||||
util.Log("新增域名解析 %s 成功! IP: %s", domain, ipAddr)
|
||||
domain.UpdateStatus = config.UpdatedSuccess
|
||||
}
|
||||
}
|
||||
@ -134,20 +141,27 @@ func (nowcn *Nowcn) modify(record NowcnRecord, domain *config.Domain, recordType
|
||||
util.Log("你的IP %s 没有变化, 域名 %s", ipAddr, domain)
|
||||
return
|
||||
}
|
||||
param := map[string]any{
|
||||
"Id": record.ID,
|
||||
param := map[string]string{
|
||||
"Id": strconv.Itoa(record.ID),
|
||||
"Domain": domain.DomainName,
|
||||
"Host": domain.GetSubDomain(),
|
||||
"Type": recordType,
|
||||
"Value": ipAddr,
|
||||
"Ttl": nowcn.TTL,
|
||||
}
|
||||
res, err := nowcn.request(nowcnRecordModifyURL, param)
|
||||
res, err := nowcn.request("/api/Dns/UpdateDomainRecord", param, "GET")
|
||||
if err != nil {
|
||||
util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, err.Error())
|
||||
domain.UpdateStatus = config.UpdatedFailed
|
||||
} else if res.Error != "" {
|
||||
util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, res.Error)
|
||||
}
|
||||
var result NowcnBaseResult
|
||||
err = json.Unmarshal(res, &result)
|
||||
if err != nil {
|
||||
util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, err.Error())
|
||||
domain.UpdateStatus = config.UpdatedFailed
|
||||
}
|
||||
if result.Error != "" {
|
||||
util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, result.Error)
|
||||
domain.UpdateStatus = config.UpdatedFailed
|
||||
} else {
|
||||
util.Log("更新域名解析 %s 成功! IP: %s", domain, ipAddr)
|
||||
@ -155,56 +169,109 @@ func (nowcn *Nowcn) modify(record NowcnRecord, domain *config.Domain, recordType
|
||||
}
|
||||
}
|
||||
|
||||
// request 发送HTTP请求
|
||||
func (nowcn *Nowcn) request(apiAddr string, param map[string]any) (status NowcnStatus, err error) {
|
||||
param["auth-userid"] = nowcn.DNS.ID
|
||||
param["api-key"] = nowcn.DNS.Secret
|
||||
|
||||
fullURL := apiAddr + "?" + nowcn.queryParams(param)
|
||||
client := util.CreateHTTPClient()
|
||||
resp, err := client.Get(fullURL)
|
||||
|
||||
// 处理响应
|
||||
err = util.GetHTTPResponse(resp, err, &status)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// getRecordList 获取域名记录列表
|
||||
func (nowcn *Nowcn) getRecordList(domain *config.Domain, typ string) (result NowcnRecordListResp, err error) {
|
||||
param := map[string]any{
|
||||
"Domain": domain.DomainName,
|
||||
"auth-userid": nowcn.DNS.ID,
|
||||
"api-key": nowcn.DNS.Secret,
|
||||
}
|
||||
fullURL := nowcnRecordListAPI + "?" + nowcn.queryParams(param)
|
||||
client := util.CreateHTTPClient()
|
||||
resp, err := client.Get(fullURL)
|
||||
var response NowcnRecordListResp
|
||||
result = NowcnRecordListResp{
|
||||
Data: make([]NowcnRecord, 0),
|
||||
}
|
||||
err = util.GetHTTPResponse(resp, err, &response)
|
||||
for _, v := range response.Data {
|
||||
if v.Host == domain.GetSubDomain() {
|
||||
result.Data = append(result.Data, v)
|
||||
break
|
||||
}
|
||||
|
||||
param := map[string]string{
|
||||
"Domain": domain.DomainName,
|
||||
"Type": typ,
|
||||
"Host": domain.GetSubDomain(),
|
||||
}
|
||||
res, err := nowcn.request("/api/Dns/DescribeRecordIndex", param, "GET")
|
||||
err = json.Unmarshal(res, &result)
|
||||
return
|
||||
}
|
||||
|
||||
func (nowcn *Nowcn) queryParams(param map[string]any) string {
|
||||
var queryParams []string
|
||||
for key, value := range param {
|
||||
// 只对键进行URL编码,值保持原样(特别是@符号)
|
||||
encodedKey := url.QueryEscape(key)
|
||||
valueStr := fmt.Sprintf("%v", value)
|
||||
// 对值进行选择性编码,保留@符号
|
||||
encodedValue := strings.ReplaceAll(url.QueryEscape(valueStr), "%40", "@")
|
||||
encodedValue = strings.ReplaceAll(encodedValue, "%3A", ":")
|
||||
queryParams = append(queryParams, encodedKey+"="+encodedValue)
|
||||
func (t *Nowcn) sign(params map[string]string, method string) (string, error) {
|
||||
// 添加公共参数
|
||||
params["AccessKeyID"] = t.DNS.ID
|
||||
params["SignatureMethod"] = "HMAC-SHA1"
|
||||
params["SignatureNonce"] = fmt.Sprintf("%d", time.Now().UnixNano())
|
||||
params["Timestamp"] = time.Now().UTC().Format("2006-01-02T15:04:05Z")
|
||||
|
||||
// 1. 排序参数(按首字母顺序)
|
||||
var keys []string
|
||||
for k := range params {
|
||||
if k != "Signature" { // 排除Signature参数
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
return strings.Join(queryParams, "&")
|
||||
sort.Strings(keys)
|
||||
|
||||
// 2. 构造规范化请求字符串
|
||||
var canonicalizedQuery []string
|
||||
for _, k := range keys {
|
||||
// URL编码参数名和参数值
|
||||
encodedKey := util.PercentEncode(k)
|
||||
encodedValue := util.PercentEncode(params[k])
|
||||
canonicalizedQuery = append(canonicalizedQuery, encodedKey+"="+encodedValue)
|
||||
}
|
||||
canonicalizedQueryString := strings.Join(canonicalizedQuery, "&")
|
||||
|
||||
// 3. 构造待签名字符串
|
||||
stringToSign := method + "&" + util.PercentEncode("/") + "&" + util.PercentEncode(canonicalizedQueryString)
|
||||
|
||||
// 4. 计算HMAC-SHA1签名
|
||||
key := t.DNS.Secret + "&"
|
||||
h := hmac.New(sha1.New, []byte(key))
|
||||
h.Write([]byte(stringToSign))
|
||||
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
|
||||
// 5. 添加签名到参数中
|
||||
params["Signature"] = signature
|
||||
|
||||
// 6. 重新构造最终的查询字符串(包含签名)
|
||||
keys = append(keys, "Signature")
|
||||
sort.Strings(keys)
|
||||
var finalQuery []string
|
||||
for _, k := range keys {
|
||||
encodedKey := util.PercentEncode(k)
|
||||
encodedValue := util.PercentEncode(params[k])
|
||||
finalQuery = append(finalQuery, encodedKey+"="+encodedValue)
|
||||
}
|
||||
|
||||
return strings.Join(finalQuery, "&"), nil
|
||||
}
|
||||
|
||||
func (t *Nowcn) request(apiPath string, params map[string]string, method string) ([]byte, error) {
|
||||
// 生成签名
|
||||
queryString, err := t.sign(params, method)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成签名失败: %v", err)
|
||||
}
|
||||
|
||||
// 构造完整URL
|
||||
baseURL := "https://api.now.cn"
|
||||
fullURL := baseURL + apiPath + "?" + queryString
|
||||
|
||||
// 创建HTTP请求
|
||||
req, err := http.NewRequest(method, fullURL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建请求失败: %v", err)
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
// 发送请求
|
||||
client := &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取响应失败: %v", err)
|
||||
}
|
||||
|
||||
// 检查HTTP状态码
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("API请求失败,状态码: %d, 响应: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package util
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// WriteString creates a new string using [strings.Builder].
|
||||
func WriteString(strs ...string) string {
|
||||
@ -32,3 +35,16 @@ func SplitLines(s string) []string {
|
||||
|
||||
return strings.Split(s, "\n")
|
||||
}
|
||||
|
||||
func PercentEncode(value string) string {
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
// 使用Go标准库进行URL编码
|
||||
encoded := url.QueryEscape(value)
|
||||
// 按照RFC3986规则调整编码
|
||||
encoded = strings.ReplaceAll(encoded, "+", "%20")
|
||||
encoded = strings.ReplaceAll(encoded, "*", "%2A")
|
||||
encoded = strings.ReplaceAll(encoded, "%7E", "~")
|
||||
return encoded
|
||||
}
|
||||
|
Reference in New Issue
Block a user