Browse Source

feat(provider:edgeone): 支持腾讯EdgeOne Add Tencent Cloud EdgeOne DNS provider support (#521)

* Initial plan

* Implement Tencent Cloud EdgeOne DNS provider

Co-authored-by: NewFuture <[email protected]>

* Refactor EdgeOne provider to inherit from TencentCloudProvider and simplify API logic

Co-authored-by: NewFuture <[email protected]>

* Implement EdgeOne provider according to official acceleration domain APIs

Co-authored-by: NewFuture <[email protected]>

* refactor(provider:EdgeOne): streamline API calls and improve test coverage

* Update tests/test_provider_edgeone.py

Co-authored-by: Copilot <[email protected]>

* IP_DOMAIN upper case

* update comments

* fix: address EdgeOne provider feedback - update alias, logging, and add documentation

Co-authored-by: NewFuture <[email protected]>

* fix: simplify response handling in acceleration domain creation and update

* update docs

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: NewFuture <[email protected]>
Co-authored-by: New Future <[email protected]>
Co-authored-by: Copilot <[email protected]>
Copilot 6 months ago
parent
commit
5e1a8d5aaf

+ 2 - 1
README.en.md

@@ -46,7 +46,8 @@
   - [HE.net](https://dns.he.net/) ([Configuration Guide](doc/providers/he.en.md)) (@NN708) (Does not support auto-record creation)
   - [Huawei Cloud](https://huaweicloud.com/) ([Configuration Guide](doc/providers/huaweidns.en.md)) (@cybmp3) ⚡
   - [NameSilo](https://www.namesilo.com/) ([Configuration Guide](doc/providers/namesilo.en.md))
-  - [Tencent Cloud](https://cloud.tencent.com/) ([Configuration Guide](doc/providers/tencentcloud.en.md)) ⚡
+  - [Tencent Cloud DNS](https://cloud.tencent.com/) ([Configuration Guide](doc/providers/tencentcloud.en.md)) ⚡
+  - [Tencent Cloud EdgeOne](https://cloud.tencent.com/product/teo) ([Configuration Guide](doc/providers/edgeone.en.md)) ⚡
   - [No-IP](https://www.noip.com/) ([Configuration Guide](doc/providers/noip.en.md))
   - Custom Callback API ([Configuration Guide](doc/providers/callback.en.md))
   

+ 3 - 1
README.md

@@ -47,6 +47,7 @@
   - [华为云](https://huaweicloud.com/) ([配置指南](doc/providers/huaweidns.md)) (@cybmp3) ⚡
   - [NameSilo](https://www.namesilo.com/) ([配置指南](doc/providers/namesilo.md))
   - [腾讯云](https://cloud.tencent.com/) ([配置指南](doc/providers/tencentcloud.md)) ⚡
+  - [腾讯云 EdgeOne](https://cloud.tencent.com/product/teo) ([配置指南](doc/providers/edgeone.md)) ⚡
   - [No-IP](https://www.noip.com/) ([配置指南](doc/providers/noip.md))
   - 自定义回调 API ([配置指南](doc/providers/callback.md))
   
@@ -127,6 +128,7 @@
    - **华为云 DNS**: [APIKEY 申请](https://console.huaweicloud.com/iam/)(点左边访问密钥,然后点新增访问密钥) | [详细配置文档](doc/providers/huaweidns.md)
    - **NameSilo**: [API Key](https://www.namesilo.com/account/api-manager)(API Manager 中获取 API Key) | [详细配置文档](doc/providers/namesilo.md)
    - **腾讯云 DNS**: [详细配置文档](doc/providers/tencentcloud.md)
+   - **腾讯云 EdgeOne**: [详细配置文档](doc/providers/edgeone.md)
    - **No-IP**: [用户名和密码](https://www.noip.com/)(使用 No-IP 账户的用户名和密码) | [详细配置文档](doc/providers/noip.md)
    - **自定义回调**: 参数填写方式请查看下方的自定义回调配置说明
 
@@ -178,7 +180,7 @@ python -m ddns -c /path/to/config.json
 | :----: | :----------------: | :------: | :---------: | :----------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
 |   id   |       string       |    √     |     无      |    api 访问 ID     | Cloudflare 为邮箱(使用 Token 时留空)<br>HE.net 可留空<br>华为云为 Access Key ID (AK)                                                                                                   |
 | token  |       string       |    √     |     无      |   api 授权 token   | 部分平台叫 secret key,**反馈粘贴时删除**                                                                                                                                                |
-|  dns   |       string       |    No    | `"dnspod"`  |     dns 服务商     | 阿里 DNS 为 `alidns`,阿里ESA为 `aliesa`,Cloudflare 为 `cloudflare`,dns.com 为 `dnscom`,DNSPOD 国内为 `dnspod`,DNSPOD 国际为 `dnspod_com`,HE.net 为 `he`,华为云为 `huaweidns`,NameSilo 为 `namesilo`,腾讯云为 `tencentcloud`,No-IP 为 `noip`,自定义回调为 `callback`。部分服务商有[详细配置文档](doc/providers/) |
+|  dns   |       string       |    No    | `"dnspod"`  |     dns 服务商     | 阿里 DNS 为 `alidns`,阿里ESA为 `aliesa`,Cloudflare 为 `cloudflare`,dns.com 为 `dnscom`,DNSPOD 国内为 `dnspod`,DNSPOD 国际为 `dnspod_com`,HE.net 为 `he`,华为云为 `huaweidns`,NameSilo 为 `namesilo`,腾讯云为 `tencentcloud`,腾讯云EdgeOne为 `edgeone`,No-IP 为 `noip`,自定义回调为 `callback`。部分服务商有[详细配置文档](doc/providers/) |
 |  ipv4  |       array        |    No    |    `[]`     |   ipv4 域名列表    | 为 `[]` 时,不会获取和更新 IPv4 地址                                                                                                                                                     |
 |  ipv6  |       array        |    No    |    `[]`     |   ipv6 域名列表    | 为 `[]` 时,不会获取和更新 IPv6 地址                                                                                                                                                     |
 | index4 | string\|int\|array |    No    | `"default"` |   ipv4 获取方式    | 可设置 `网卡`、`内网`、`公网`、`正则` 等方式                                                                                                                                             |

+ 1 - 0
ddns/config/cli.py

@@ -143,6 +143,7 @@ def load_config(description, doc, version, date):
             "dnscom",
             "dnspod_com",
             "dnspod",
+            "edgeone",
             "he",
             "huaweidns",
             "namesilo",

+ 5 - 0
ddns/provider/__init__.py

@@ -8,6 +8,7 @@ from .debug import DebugProvider
 from .dnscom import DnscomProvider
 from .dnspod import DnspodProvider
 from .dnspod_com import DnspodComProvider
+from .edgeone import EdgeOneProvider
 from .he import HeProvider
 from .huaweidns import HuaweiDNSProvider
 from .namesilo import NamesiloProvider
@@ -37,6 +38,10 @@ def get_provider_class(provider_name):
         "tencentcloud": TencentCloudProvider,
         "tencent": TencentCloudProvider,  # 兼容tencent
         "qcloud": TencentCloudProvider,  # 兼容qcloud
+        # tencent cloud edgeone
+        "edgeone": EdgeOneProvider,
+        "teo": EdgeOneProvider,  # 兼容teo (EdgeOne产品的API名称)
+        "tencentedgeone": EdgeOneProvider,  # 兼容tencentedgeone
         # cloudflare
         "cloudflare": CloudflareProvider,
         # aliyun alidns

+ 82 - 0
ddns/provider/edgeone.py

@@ -0,0 +1,82 @@
+# coding=utf-8
+"""
+Tencent Cloud EdgeOne API
+腾讯云 EdgeOne (边缘安全速平台) API
+API Documentation: https://cloud.tencent.com/document/api/1552/80731
+@author: NewFuture
+"""
+from ddns.provider._base import join_domain
+from .tencentcloud import TencentCloudProvider
+
+
+class EdgeOneProvider(TencentCloudProvider):
+    """
+    腾讯云 EdgeOne API 提供商
+    Tencent Cloud EdgeOne API Provider
+    """
+
+    endpoint = "https://teo.tencentcloudapi.com"
+    # 腾讯云 EdgeOne API 配置
+    service = "teo"
+    version_date = "2022-09-01"
+
+    def _query_zone_id(self, domain):
+        # type: (str) -> str | None
+        """查询域名的加速域名信息获取 ZoneId https://cloud.tencent.com/document/api/1552/80713"""
+        # 首先尝试直接查找域名
+        filters = [{"Name": "zone-name", "Values": [domain], "Fuzzy": False}]  # type: Any
+        response = self._request("DescribeZones", Filters=filters)
+
+        if response and "Zones" in response:
+            for zone in response.get("Zones", []):
+                if zone.get("ZoneName") == domain:
+                    zone_id = zone.get("ZoneId")
+                    if zone_id:
+                        self.logger.debug("Found acceleration domain %s with Zone ID: %s", domain, zone_id)
+                        return zone_id
+
+        self.logger.debug("Acceleration domain not found for: %s", domain)
+        return None
+
+    def _query_record(self, zone_id, subdomain, main_domain, record_type, line, extra):
+        # type: (str, str, str, str, str | None, dict) -> dict | None
+        """查询加速域名信息 https://cloud.tencent.com/document/api/1552/86336"""
+        domain = join_domain(subdomain, main_domain)
+        filters = [{"Name": "domain-name", "Values": [domain], "Fuzzy": False}]  # type: Any
+        response = self._request("DescribeAccelerationDomains", ZoneId=zone_id, Filters=filters)
+
+        if response and "AccelerationDomains" in response:
+            for domain_info in response.get("AccelerationDomains", []):
+                if domain_info.get("DomainName") == domain:
+                    self.logger.debug("Found acceleration domain: %s", domain_info)
+                    return domain_info
+
+        self.logger.warning("No acceleration domain found for: %s, response: %s", domain, response)
+        return None
+
+    def _create_record(self, zone_id, subdomain, main_domain, value, record_type, ttl, line, extra):
+        # type: (str, str, str, str, str, int, str | None, dict) -> bool
+        """创建新的加速域名记录 https://cloud.tencent.com/document/api/1552/86338"""
+        domain = join_domain(subdomain, main_domain)
+        origin = {"OriginType": "IP_DOMAIN", "Origin": value}  # type: Any
+        res = self._request("CreateAccelerationDomain", ZoneId=zone_id, DomainName=domain, OriginInfo=origin, **extra)
+        if res:
+            self.logger.info("Acceleration domain created (%s)", res.get("RequestId"))
+            return True
+
+        self.logger.error("Failed to create acceleration domain, response: %s", res)
+        return False
+
+    def _update_record(self, zone_id, old_record, value, record_type, ttl, line, extra):
+        """更新加速域名的源站 IP 地址 https://cloud.tencent.com/document/api/1552/86335"""
+        domain = old_record.get("DomainName")
+        # 构建源站信息
+        backup = old_record.get("OriginDetail", {}).get("BackupOrigin", "")
+        origin = {"OriginType": "IP_DOMAIN", "Origin": value, "BackupOrigin": backup}  # type: Any
+        response = self._request("ModifyAccelerationDomain", ZoneId=zone_id, DomainName=domain, OriginInfo=origin)
+
+        if response:
+            self.logger.info("Acceleration domain updated (%s)", response.get("RequestId"))
+            return True
+        self.logger.error("Failed to update acceleration domain origin, response: %s", response)
+        return False

+ 1 - 1
ddns/provider/namesilo.py

@@ -39,7 +39,7 @@ class NamesiloProvider(BaseProvider):
         self.logger.warning("NameSilo provider implementation is pending verification - please test thoroughly")
 
     def _request(self, operation, **params):
-        # type: (str, **(str | int | bytes | bool | None)) -> dict
+        # type: (str, **(str | int | bytes | bool | None)) -> dict|None
         """
         Send request to NameSilo API
 

+ 2 - 1
doc/cli.en.md

@@ -27,7 +27,7 @@ python -m ddns -h
 | `-c, --config`  |    String   | Specify the path to the configuration file                                                                                                                                | `--config config.json`                                   |
 | `--new-config`  | Flag/String | Generate a new config file (optional file path)                                                                                                                           | `--new-config` <br> `--new-config=config.json`           |
 | `--debug`       |     Flag    | Enable debug mode                                                                                                                                                         | `--debug`                                                |
-| `--dns`         |    Choice   | [DNS Providers](providers/README.en.md) include:<br>51dns, alidns, aliesa, callback, cloudflare,<br>debug, dnscom, dnspod\_com, dnspod, he,<br>huaweidns, noip, tencentcloud | `--dns cloudflare`                                       |
+| `--dns`         |    Choice   | [DNS Providers](providers/README.en.md) include:<br>51dns, alidns, aliesa, callback, cloudflare,<br>debug, dnscom, dnspod\_com, dnspod, edgeone, he,<br>huaweidns, noip, tencentcloud | `--dns cloudflare`                                       |
 | `--endpoint`    |    String   | Custom API endpoint URL (useful for self-hosted services)                                                                                                                 | `--endpoint https://api.private.com`                     |
 | `--id`          |    String   | API Access ID, email, or Access Key                                                                                                                                       | `--id [email protected]`                                  |
 | `--token`       |    String   | API token or secret key                                                                                                                                                   | `--token abcdef123456`                                   |
@@ -66,6 +66,7 @@ DDNS supports the following DNS providers:
 - **huaweidns**: Huawei Cloud DNS
 - **noip**: No-IP Dynamic DNS
 - **tencentcloud**: Tencent Cloud DNS
+- **edgeone**: Tencent Cloud EdgeOne
 
 ## IP Detection Methods
 

+ 4 - 1
doc/cli.md

@@ -27,7 +27,7 @@ python3 -m ddns -h
 | `-c, --config`  | 字符串      | 指定配置文件路径                                                                                                                                 | `--config config.json`                                   |
 | `--new-config`  | 标志/字符串   | 生成新的配置文件(可指定路径)                                                                                                                          | `--new-config` <br> `--new-config=config.json`           |
 | `--debug`       | 标志       | 开启调试模式                                                                                                                                   | `--debug`                                                |
-| `--dns`         | 选择项      | [DNS服务提供商](providers/README.md)包括:<br>51dns, alidns, aliesa, callback, cloudflare,<br>debug, dnscom, dnspod\_com, dnspod, he,<br>huaweidns, noip, tencentcloud | `--dns cloudflare`                                       |
+| `--dns`         | 选择项      | [DNS服务提供商](providers/README.md)包括:<br>51dns, alidns, aliesa, callback, cloudflare,<br>debug, dnscom, dnspod\_com, dnspod, edgeone, he,<br>huaweidns, noip, tencentcloud | `--dns cloudflare`                                       |
 | `--endpoint`    | 字符串      | 自定义API 端点 URL(更换服务节点)                                                                                                            | `--endpoint https://api.private.com`                     |
 | `--id`          | 字符串      | API 访问 ID、邮箱或 Access ID                                                                                                                 | `--id [email protected]`                                  |
 | `--token`       | 字符串      | API 授权令牌或密钥(Secret Key)                                                                                                                  | `--token abcdef123456`                                   |
@@ -312,6 +312,9 @@ ddns --dns dnscom --id your_user_id --token your_api_token --ipv4 example.com
 # 腾讯云DNS
 ddns --dns tencentcloud --id your_secret_id --token your_secret_key --ipv4 example.com
 
+# 腾讯云 EdgeOne
+ddns --dns edgeone --id your_secret_id --token your_secret_key --ipv4 example.com
+
 # NoIP
 ddns --dns noip --id your_username --token your_password --ipv4 example.com
 

+ 2 - 2
doc/env.en.md

@@ -13,7 +13,7 @@ All environment variables use the `DDNS_` prefix followed by the parameter name
 
 | Environment Variable     | Accepted Values                                                                                     | Description                              | Example                                                     |
 |--------------------------|------------------------------------------------------------------------------------------------------|------------------------------------------|-------------------------------------------------------------|
-| `DDNS_DNS`               | `51dns`, `alidns`, `aliesa`, `callback`, `cloudflare`, `debug`, `dnscom`, `dnspod_com`, `dnspod`, `he`, `huaweidns`, `noip`, `tencentcloud` | [DNS Provider](./providers/README.en.md)    | `DDNS_DNS=cloudflare`                                       |
+| `DDNS_DNS`               | `51dns`, `alidns`, `aliesa`, `callback`, `cloudflare`, `debug`, `dnscom`, `dnspod_com`, `dnspod`, `edgeone`, `he`, `huaweidns`, `noip`, `tencentcloud` | [DNS Provider](./providers/README.en.md)    | `DDNS_DNS=cloudflare`                                       |
 | `DDNS_ID`                | Depends on the provider                                                                              | API account or ID                        | `DDNS_ID="[email protected]"`                                |
 | `DDNS_TOKEN`             | Depends on the provider                                                                              | API token or secret                      | `DDNS_TOKEN="abcdef123456"`                                 |
 | `DDNS_ENDPOINT`          | URL (starting with http or https)                                                                   | Custom API endpoint                       | `DDNS_ENDPOINT=https://api.dns.cn`                          |
@@ -82,7 +82,7 @@ All environment variables use the `DDNS_` prefix followed by the parameter name
 - **Type**: String
 - **Required**: No
 - **Default**: `dnspod`
-- **Available Values**: `51dns`, `alidns`, `aliesa`, `callback`, `cloudflare`, `debug`, `dnscom`, `dnspod`, `dnspod_com`, `he`, `huaweidns`, `noip`, `tencentcloud`
+- **Available Values**: `51dns`, `alidns`, `aliesa`, `callback`, `cloudflare`, `debug`, `dnscom`, `dnspod`, `dnspod_com`, `edgeone`, `he`, `huaweidns`, `noip`, `tencentcloud`
 - **Description**: DNS service provider
 - **Examples**:
 

+ 3 - 2
doc/env.md

@@ -13,7 +13,7 @@ DDNS 支持通过环境变量进行配置,环境变量的优先级为:**[命
 
 | 环境变量               | 参数格式                                                                                             | 描述                              | 示例                                                     |
 |------------------------|------------------------------------------------------------------------------------------------------|-----------------------------------|----------------------------------------------------------|
-| `DDNS_DNS`             | `51dns`、`alidns`、`aliesa`、`callback`、`cloudflare`、`debug`、`dnscom`、`dnspod_com`、`dnspod`、`he`、`huaweidns`、`noip`、`tencentcloud` | [DNS 服务商](./providers/README.md) | `DDNS_DNS=cloudflare`                                    |
+| `DDNS_DNS`             | `51dns`、`alidns`、`aliesa`、`callback`、`cloudflare`、`debug`、`dnscom`、`dnspod_com`、`dnspod`、`edgeone`、`he`、`huaweidns`、`noip`、`tencentcloud` | [DNS 服务商](./providers/README.md) | `DDNS_DNS=cloudflare`                                    |
 | `DDNS_ID`              | 依 DNS 服务商而定                                                                                   | API 账号 或 ID                    | `DDNS_ID="[email protected]"`                             |
 | `DDNS_TOKEN`           | 依 DNS 服务商而定                                                                                   | API 授权令牌或 Secret             | `DDNS_TOKEN="abcdef123456"`                              |
 | `DDNS_ENDPOINT`        | URL(http 或 https 协议)                                                                           | 自定义 API 地址                   | `DDNS_ENDPOINT=https://api.dns.cn`                       |
@@ -42,7 +42,7 @@ DDNS 支持通过环境变量进行配置,环境变量的优先级为:**[命
 - **类型**: 字符串
 - **必需**: 否
 - **默认值**: `dnspod`
-- **可选值**: `51dns`, `alidns`, `aliesa`, `callback`, `cloudflare`, `debug`, `dnscom`, `dnspod`, `dnspod_com`, `he`, `huaweidns`, `noip`, `tencentcloud`
+- **可选值**: `51dns`, `alidns`, `aliesa`, `callback`, `cloudflare`, `debug`, `dnscom`, `dnspod`, `dnspod_com`, `edgeone`, `he`, `huaweidns`, `noip`, `tencentcloud`
 - **说明**: DNS 服务提供商
 - **示例**:
 
@@ -58,6 +58,7 @@ DDNS 支持通过环境变量进行配置,环境变量的优先级为:**[命
   export DDNS_DNS="huaweidns"     # 华为云 DNS
   export DDNS_DNS="noip"          # NoIP
   export DDNS_DNS="tencentcloud"  # 腾讯云 DNS
+  export DDNS_DNS="edgeone"       # 腾讯云 EdgeOne
   export DDNS_DNS="callback"      # 自定义回调
   export DDNS_DNS="debug"         # 调试模式
   ```

+ 1 - 1
doc/json.en.md

@@ -42,7 +42,7 @@ Configuration Parameters Table
 
 | Key Name | Type | Required | Default Value | Parameter Description | Notes |
 | :------: | :--: | :------: | :-----------: | :------------------: | ----- |
-| dns | string | No | None | DNS Provider | Available values: 51dns, alidns, aliesa, callback, cloudflare, debug, dnscom, dnspod_com, dnspod, he, huaweidns, namesilo, noip, tencentcloud |
+| dns | string | No | None | DNS Provider | Available values: 51dns, alidns, aliesa, callback, cloudflare, debug, dnscom, dnspod_com, dnspod, edgeone, he, huaweidns, namesilo, noip, tencentcloud |
 | id | string | Yes | None | API Access ID | Configure according to provider documentation (e.g., AccessKeyID) |
 | token | string | Yes | None | API Authorization Token | Configure according to provider documentation (e.g., AccessSecret) |
 | endpoint | string | No | None | API Endpoint URL | For custom or private deployment API addresses, uses default endpoint when empty |

+ 1 - 1
doc/json.md

@@ -42,7 +42,7 @@ DDNS配置文件遵循JSON模式(Schema),推荐在配置文件中添加`$schem
 
 |  键名    |        类型        | 必需 |   默认值    |    参数说明          | 备注                                                                                                         |
 | :------: | :----------------: | :--: | :---------: | :---------------: | ------------------------------------------------------------------------------------------------------------ |
-|  dns     |       string       |  否  |     无      |    DNS服务商      | 可选值: 51dns, alidns, aliesa, callback, cloudflare, debug, dnscom, dnspod_com, dnspod, he, huaweidns, namesilo, noip, tencentcloud |
+|  dns     |       string       |  否  |     无      |    DNS服务商      | 可选值: 51dns, alidns, aliesa, callback, cloudflare, debug, dnscom, dnspod_com, dnspod, edgeone, he, huaweidns, namesilo, noip, tencentcloud |
 |   id     |       string       |  是  |     无      |   API 访问 ID    | 请根据服务商说明配置(如 AccessKeyID)  |
 |  token   |       string       |  是  |     无      |  API 授权令牌     | 请根据服务商说明配置(如 AccessSecret)  |
 | endpoint |       string       |  否  |     无      |   API端点URL      | 用于自定义或私有部署的API地址,为空时使用默认端点                                                           |

+ 1 - 0
doc/providers/README.en.md

@@ -18,6 +18,7 @@ This directory contains detailed configuration guides for various DNS providers.
 | `huaweidns` | [Huawei Cloud DNS](https://www.huaweicloud.com/product/dns.html) | [huaweidns 中文文档](huaweidns.md) | [huaweidns English Doc](huaweidns.en.md) | ⚠️ Pending verification |
 | `noip` | [No-IP](https://www.noip.com/) | [noip 中文文档](noip.md) | [noip English Doc](noip.en.md) | Popular dynamic DNS service |
 | `tencentcloud` | [Tencent Cloud DNSPod](https://cloud.tencent.com/product/dns) | [tencentcloud 中文文档](tencentcloud.md) | [tencentcloud English Doc](tencentcloud.en.md) | Tencent Cloud DNSPod service |
+| `edgeone` | [Tencent Cloud EdgeOne](https://edgeone.ai) | [edgeone 中文文档](edgeone.md) | [edgeone English Doc](edgeone.en.md) | Tencent Cloud Edge Security Platform |
 
 > To add a new provider, [create an issue and fill in the template](https://github.com/NewFuture/DDNS/issues/new?template=new-dns-provider.md)
 

+ 4 - 0
doc/providers/README.md

@@ -19,6 +19,7 @@
 | `namesilo` | [NameSilo](https://www.namesilo.com/) | [namesilo 中文文档](namesilo.md) | [namesilo English Doc](namesilo.en.md) | ⚠️ 等待验证 |
 | `noip` | [No-IP](https://www.noip.com/) | [noip 中文文档](noip.md) | [noip English Doc](noip.en.md) | 不支持自动创建记录 |
 | `tencentcloud` | [腾讯云 DNSPod](https://cloud.tencent.com/product/dns) | [tencentcloud 中文文档](tencentcloud.md) | [tencentcloud English Doc](tencentcloud.en.md) | 腾讯云DNSPod服务 |
+| `edgeone` | [腾讯云 EdgeOne](https://cloud.tencent.com/product/teo) | [edgeone 中文文档](edgeone.md) | [edgeone English Doc](edgeone.en.md) | 腾讯云边缘安全加速平台 |
 
 > 添加新的Provider, [创建Issue,并按照模板填好链接](https://github.com/NewFuture/DDNS/issues/new?template=new-dns-provider.md)
 
@@ -48,6 +49,9 @@ ddns --dns cloudflare --id [email protected] --token your_api_key --ipv4 ddns.new
 # Cloudflare (使用Token)
 ddns --dns cloudflare --token your_api_token --ipv4 ddns.newfuture.cc
 
+# 腾讯云EdgeOne
+ddns --dns edgeone --id your_secret_id --token your_secret_key --ipv4 ddns.newfuture.cc
+
 # No-IP
 ddns --dns noip --id your_username --token your_password --ipv4 ddns.newfuture.cc
 ```

+ 125 - 0
doc/providers/edgeone.en.md

@@ -0,0 +1,125 @@
+# Tencent Cloud EdgeOne Configuration Guide
+
+## Overview
+
+Tencent Cloud EdgeOne is an edge computing and acceleration service provided by Tencent Cloud, supporting dynamic management of acceleration domain origin server IP addresses. This DDNS project dynamically updates origin server IP addresses of acceleration domains through the EdgeOne API.
+
+Official Links:
+
+- EdgeOne International: <https://edgeone.ai>
+- Official Website: <https://cloud.tencent.com/product/teo>
+- Service Console: <https://console.cloud.tencent.com/edgeone>
+
+## Authentication
+
+### SecretId/SecretKey Authentication
+
+Uses Tencent Cloud SecretId and SecretKey for authentication, same as Tencent Cloud DNS.
+
+> Same as [Tencent Cloud DNS](tencentcloud.en.md), EdgeOne uses SecretId and SecretKey for authentication. However, the permission requirements are different, and you need to ensure that the account has EdgeOne operation permissions.
+
+#### Getting Authentication Information
+
+1. Log in to [Tencent Cloud Console](https://console.cloud.tencent.com/)
+2. Visit [API Key Management](https://console.cloud.tencent.com/cam/capi)
+3. Click "Create Key" button
+4. Copy the generated **SecretId** and **SecretKey**, keep them secure
+5. Ensure the account has EdgeOne operation permissions
+
+```jsonc
+{
+    "dns": "edgeone",
+    "id": "SecretId",          // Tencent Cloud SecretId
+    "token": "SecretKey"       // Tencent Cloud SecretKey
+}
+```
+
+## Permission Requirements
+
+Ensure the Tencent Cloud account has the following permissions:
+
+- **QcloudTEOFullAccess**: EdgeOne full access permission (recommended)
+- **QcloudTEOReadOnlyAccess + Custom write permissions**: Fine-grained permission control
+
+Permissions can be viewed and configured in [Access Management](https://console.cloud.tencent.com/cam/policy).
+
+## Complete Configuration Example
+
+```jsonc
+{
+    "$schema": "https://ddns.newfuture.cc/schema/v4.0.json", // Format validation
+    "dns": "edgeone",                       // Current provider
+    "id": "your_secret_id",                 // Tencent Cloud SecretId
+    "token": "your_secret_key",             // Tencent Cloud SecretKey
+    "index4": ["url:http://api.ipify.cn", "public"], // IPv4 address source
+    "index6": "public",                     // IPv6 address source
+    "ipv4": ["ddns.newfuture.cc"],          // IPv4 domains
+    "ipv6": ["ipv6.ddns.newfuture.cc"],     // IPv6 domains
+    "endpoint": "https://teo.intl.tencentcloudapi.com" // API endpoint
+}
+```
+
+### Parameter Description
+
+| Parameter | Description       | Type           | Value Range/Options                    | Default   | Parameter Type |
+| :-------: | :---------------- | :------------- | :------------------------------------- | :-------- | :------------- |
+| dns       | Provider ID       | String         | `edgeone`                              | None      | Provider       |
+| id        | Authentication ID | String         | Tencent Cloud SecretId                 | None      | Provider       |
+| token     | Authentication Key| String         | Tencent Cloud SecretKey                | None      | Provider       |
+| index4    | IPv4 Source       | Array          | [Reference](../json.en.md#ipv4-ipv6)  | `default` | Common Config  |
+| index6    | IPv6 Source       | Array          | [Reference](../json.en.md#ipv4-ipv6)  | `default` | Common Config  |
+| ipv4      | IPv4 Domains      | Array          | Domain list                            | None      | Common Config  |
+| ipv6      | IPv6 Domains      | Array          | Domain list                            | None      | Common Config  |
+| endpoint  | API Endpoint      | URL            | [Reference below](#endpoint)           | `https://teo.tencentcloudapi.com` | Provider  |
+| proxy     | Proxy Settings    | Array          | [Reference](../json.en.md#proxy)       | None      | Common Network |
+| ssl       | SSL Verification  | Boolean/String | `"auto"`, `true`, `false`              | `auto`    | Common Network |
+| cache     | Cache Settings    | Boolean/String | `true`, `false`, `filepath`            | `true`    | Common Config  |
+| log       | Log Configuration | Object         | [Reference](../json.en.md#log)        | None      | Common Config  |
+
+> **Parameter Type Description**:  
+>
+> - **Common Config**: Standard DNS configuration parameters applicable to all supported DNS providers  
+> - **Common Network**: Network setting parameters applicable to all supported DNS providers  
+> - **Provider**: Parameters specific to the current provider
+>
+> EdgeOne TTL actual caching strategy is managed by the EdgeOne platform.
+
+### endpoint
+
+Tencent Cloud EdgeOne supports domestic and international API endpoints, which can be selected based on region and account type:
+
+#### Domestic Version
+
+- **Default (Recommended)**: `https://teo.tencentcloudapi.com`
+
+#### International Version
+
+- **International**: `https://teo.intl.tencentcloudapi.com`
+
+> **Note**: Please choose the corresponding endpoint according to your Tencent Cloud account type. Domestic accounts use the domestic endpoint, and international accounts use the international endpoint. If you are unsure, it is recommended to use the default domestic endpoint.
+
+## Troubleshooting
+
+### Debug Mode
+
+Enable debug logging for detailed information:
+
+```sh
+ddns -c config.json --debug
+```
+
+### Common Issues
+
+- **Authentication failure**: Check if SecretId and SecretKey are correct, confirm account permissions
+- **Site not found**: Ensure domain has been added to EdgeOne site with normal status
+- **Acceleration domain does not exist**: Confirm domain has been configured as acceleration domain in EdgeOne
+- **Insufficient permissions**: Ensure account has EdgeOne management permissions
+
+## Support and Resources
+
+- [Tencent Cloud EdgeOne Product Documentation](https://cloud.tencent.com/document/product/1552)
+- [EdgeOne API Documentation](https://cloud.tencent.com/document/api/1552)
+- [EdgeOne Console](https://console.cloud.tencent.com/edgeone)
+- [Tencent Cloud Technical Support](https://cloud.tencent.com/document/product/282)
+
+> **Note**: EdgeOne is primarily designed for edge acceleration scenarios. For traditional DNS resolution services, consider using [Tencent Cloud DNS](./tencentcloud.en.md).

+ 125 - 0
doc/providers/edgeone.md

@@ -0,0 +1,125 @@
+# 腾讯云 EdgeOne 配置指南
+
+## 概述
+
+腾讯云 EdgeOne(边缘安全速平台)是腾讯云提供的边缘计算和加速服务,支持动态管理加速域名的源站 IP 地址。本 DDNS 项目通过 EdgeOne API 进行加速域名的源站 IP 地址动态更新。
+
+官网链接:
+
+- 官方网站:<https://cloud.tencent.com/product/teo>
+- EdgeOne 国际版:<https://edgeone.ai>
+- 服务商控制台:<https://console.cloud.tencent.com/edgeone>
+
+## 认证信息
+
+### SecretId/SecretKey 认证
+
+使用腾讯云 SecretId 和 SecretKey 进行认证,与腾讯云 DNS 使用相同的认证方式。
+
+> 与[腾讯云 DNS](tencentcloud.md) 相同,EdgeOne 使用 SecretId 和 SecretKey 进行认证。但是权限要求不同,需要确保账号具有 EdgeOne 的操作权限。
+
+#### 获取认证信息
+
+1. 登录 [腾讯云控制台](https://console.cloud.tencent.com/)
+2. 访问 [API密钥管理](https://console.cloud.tencent.com/cam/capi)
+3. 点击"新建密钥"按钮
+4. 复制生成的 **SecretId** 和 **SecretKey**,请妥善保存
+5. 确保账号具有 EdgeOne 的操作权限
+
+```jsonc
+{
+    "dns": "edgeone",
+    "id": "SecretId",     // 腾讯云 SecretId
+    "token": "SecretKey"  // 腾讯云 SecretKey
+}
+```
+
+## 权限要求
+
+确保使用的腾讯云账号具有以下权限:
+
+- **QcloudTEOFullAccess**:EdgeOne 完全访问权限(推荐)
+- **QcloudTEOReadOnlyAccess + 自定义写权限**:精细化权限控制
+
+可以在 [访问管理](https://console.cloud.tencent.com/cam/policy) 中查看和配置权限。
+
+## 完整配置示例
+
+```jsonc
+{
+    "$schema": "https://ddns.newfuture.cc/schema/v4.0.json", // 格式验证
+    "dns": "edgeone",                       // 当前服务商
+    "id": "your_secret_id",                 // 腾讯云 SecretId
+    "token": "your_secret_key",             // 腾讯云 SecretKey
+    "index4": ["url:http://api.ipify.cn", "public"], // IPv4地址来源
+    "index6": "public",                     // IPv6地址来源
+    "ipv4": ["ddns.newfuture.cc"],          // IPv4 域名
+    "ipv6": ["ipv6.ddns.newfuture.cc"],     // IPv6 域名
+    "endpoint": "https://teo.tencentcloudapi.com" // API端点
+}
+```
+
+### 参数说明
+
+| 参数    | 说明         | 类型           | 取值范围/选项                       | 默认值    | 参数类型   |
+| :-----: | :----------- | :------------- | :--------------------------------- | :-------- | :--------- |
+| dns     | 服务商标识   | 字符串         | `edgeone`                          | 无        | 服务商参数 |
+| id      | 认证 ID      | 字符串         | 腾讯云 SecretId                    | 无        | 服务商参数 |
+| token   | 认证密钥     | 字符串         | 腾讯云 SecretKey                   | 无        | 服务商参数 |
+| index4  | IPv4 来源     | 数组           | [参考配置](../json.md#ipv4-ipv6)  | `default` | 公用配置   |
+| index6  | IPv6 来源     | 数组           | [参考配置](../json.md#ipv4-ipv6)   | `default` | 公用配置   |
+| ipv4    | IPv4 域名     | 数组           | 域名列表                           | 无        | 公用配置   |
+| ipv6    | IPv6 域名     | 数组           | 域名列表                           | 无        | 公用配置   |
+| endpoint| API 端点      | URL            | [参考下方](#endpoint)              | `https://teo.tencentcloudapi.com` | 服务商参数 |
+| proxy   | 代理设置      | 数组           | [参考配置](../json.md#proxy)        | 无        | 公用网络   |
+| ssl     | SSL 验证方式  | 布尔/字符串    | `"auto"`、`true`、`false`            | `auto`    | 公用网络   |
+| cache   | 缓存设置      | 布尔/字符串    | `true`、`false`、`filepath`        | `true`    | 公用配置   |
+| log     | 日志配置      | 对象           | [参考配置](../json.md#log)             | 无        | 公用配置   |
+
+> **参数类型说明**:  
+>
+> - **公用配置**:所有支持的DNS服务商均适用的标准DNS配置参数  
+> - **公用网络**:所有支持的DNS服务商均适用的网络设置参数  
+> - **服务商参数**:当前服务商支持,值与当前服务商相关
+>
+> EdgeOne 的 TTL 实际的缓存策略由 EdgeOne 平台管理。
+
+### endpoint
+
+腾讯云 EdgeOne 支持国内和国际版API端点,可根据区域和账号类型选择:
+
+#### 国内版
+
+- **默认(推荐)**:`https://teo.tencentcloudapi.com`
+
+#### 国际版
+
+- **国际版**:`https://teo.intl.tencentcloudapi.com`
+
+> **注意**:请根据您的腾讯云账号类型选择对应的端点。国内账号使用国内版端点,国际账号使用国际版端点。如果不确定,建议使用默认的国内版端点。
+
+## 故障排除
+
+### 调试模式
+
+启用调试日志查看详细信息:
+
+```sh
+ddns -c config.json --debug
+```
+
+### 常见问题
+
+- **认证失败**:检查 SecretId 和 SecretKey 是否正确,确认账号权限
+- **站点未找到**:确保域名已添加到 EdgeOne 站点,域名状态正常
+- **加速域名不存在**:确认域名已在 EdgeOne 中配置为加速域名
+- **权限不足**:确保账号具有 EdgeOne 的管理权限
+
+## 支持与资源
+
+- [腾讯云 EdgeOne 产品文档](https://cloud.tencent.com/document/product/1552)
+- [EdgeOne API 文档](https://cloud.tencent.com/document/api/1552)
+- [EdgeOne 控制台](https://console.cloud.tencent.com/edgeone)
+- [腾讯云技术支持](https://cloud.tencent.com/document/product/282)
+
+> **注意**:EdgeOne 主要用于边缘加速场景,如需传统 DNS 解析服务,建议使用 [腾讯云 DNS](./tencentcloud.md)。

+ 2 - 1
schema/v4.0.json

@@ -48,7 +48,7 @@
       "$id": "/properties/dns",
       "type": "string",
       "title": "DNS Provider",
-      "description": "dns服务商:阿里为alidns,阿里ESA为aliesa,DNS.COM为dnscom,DNSPOD国际版为(dnspod_com),cloudflare,HE.net为he,华为DNS为huaweidns,NameSilo为namesilo,自定义回调为callback",
+      "description": "dns服务商:阿里为alidns,阿里ESA为aliesa,DNS.COM为dnscom,DNSPOD国际版为(dnspod_com),cloudflare,HE.net为he,华为DNS为huaweidns,NameSilo为namesilo,腾讯云为tencentcloud,腾讯云EdgeOne为edgeone,自定义回调为callback",
       "default": "dnspod",
       "examples": [
         "dnspod",
@@ -64,6 +64,7 @@
         "dnscom",
         "dnspod_com",
         "dnspod",
+        "edgeone",
         "he",
         "huaweidns",
         "namesilo",

+ 507 - 0
tests/test_provider_edgeone.py

@@ -0,0 +1,507 @@
+# coding=utf-8
+"""
+Unit tests for EdgeOneProvider
+腾讯云 EdgeOne 提供商单元测试
+
+@author: NewFuture
+"""
+
+from base_test import BaseProviderTestCase, unittest, patch, MagicMock
+from ddns.provider.edgeone import EdgeOneProvider
+
+
+class TestEdgeOneProvider(BaseProviderTestCase):
+    """Test EdgeOneProvider functionality"""
+
+    def setUp(self):
+        """Set up test fixtures"""
+        super(TestEdgeOneProvider, self).setUp()
+        self.provider = EdgeOneProvider(self.authid, self.token)
+        self.logger = self.mock_logger(self.provider)
+
+    def test_init(self):
+        """Test provider initialization"""
+        self.assertProviderInitialized(self.provider)
+        self.assertEqual(self.provider.service, "teo")
+        self.assertEqual(self.provider.version_date, "2022-09-01")
+        self.assertEqual(self.provider.endpoint, "https://teo.tencentcloudapi.com")
+        self.assertEqual(self.provider.content_type, "application/json")
+
+    def test_validate_success(self):
+        """Test successful validation"""
+        # Should not raise any exception
+        self.provider._validate()
+
+    def test_validate_missing_id(self):
+        """Test validation with missing id"""
+        with self.assertRaises(ValueError) as context:
+            EdgeOneProvider("", self.token, self.logger)
+        self.assertIn("id", str(context.exception))
+
+    def test_validate_missing_token(self):
+        """Test validation with missing token"""
+        with self.assertRaises(ValueError) as context:
+            EdgeOneProvider(self.authid, "", self.logger)
+        self.assertIn("token", str(context.exception))
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_query_zone_id_success(self, mock_request):
+        """Test successful zone ID query"""
+        domain = "example.com"
+        expected_zone_id = "zone-123456789"
+
+        mock_request.return_value = {
+            "Zones": [{"ZoneId": expected_zone_id, "ZoneName": domain, "ActiveStatus": "active", "Status": "active"}]
+        }
+
+        zone_id = self.provider._query_zone_id(domain)
+        self.assertEqual(zone_id, expected_zone_id)
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_query_zone_id_not_found(self, mock_request):
+        """Test zone ID query when domain not found"""
+        mock_request.return_value = {"Zones": []}
+
+        zone_id = self.provider._query_zone_id("nonexistent.com")
+        self.assertIsNone(zone_id)
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_query_zone_id_empty_zones(self, mock_request):
+        """Test zone ID query with empty zones list"""
+        mock_request.return_value = {"Zones": []}
+
+        zone_id = self.provider._query_zone_id("example.com")
+        self.assertIsNone(zone_id)
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_query_zone_id_invalid_response(self, mock_request):
+        """Test zone ID query with invalid response format"""
+        mock_request.return_value = None
+
+        zone_id = self.provider._query_zone_id("example.com")
+        self.assertIsNone(zone_id)
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_query_record_found(self, mock_request):
+        """Test successful acceleration domain query"""
+        mock_request.return_value = {
+            "AccelerationDomains": [
+                {
+                    "ZoneId": "zone-123456789",
+                    "DomainName": "www.example.com",
+                    "DomainStatus": "online",
+                    "OriginDetail": {"OriginType": "ip_domain", "Origin": "1.2.3.4", "BackupOrigin": ""},
+                }
+            ]
+        }
+
+        record = self.provider._query_record("zone-123456789", "www", "example.com", "A", None, {})
+
+        self.assertIsNotNone(record)
+        if record:  # Type narrowing for mypy
+            self.assertEqual(record["ZoneId"], "zone-123456789")
+            self.assertEqual(record["DomainName"], "www.example.com")
+            self.assertEqual(record["OriginDetail"]["Origin"], "1.2.3.4")
+
+        # Verify request call was made correctly
+        mock_request.assert_called_once()
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_query_record_not_found(self, mock_request):
+        """Test acceleration domain query when domain not found"""
+        mock_request.return_value = {"AccelerationDomains": []}
+
+        record = self.provider._query_record(
+            "zone-123456789", "www", "example.com", "A", None, {}
+        )  # type: dict # type: ignore
+
+        self.assertIsNone(record)
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_query_record_root_domain(self, mock_request):
+        """Test acceleration domain query for root domain (@)"""
+        mock_request.return_value = {
+            "AccelerationDomains": [
+                {
+                    "ZoneId": "zone-123456789",
+                    "DomainName": "example.com",
+                    "DomainStatus": "online",
+                    "OriginDetail": {"OriginType": "ip_domain", "Origin": "1.2.3.4", "BackupOrigin": ""},
+                }
+            ]
+        }
+
+        record = self.provider._query_record(
+            "zone-123456789", "@", "example.com", "A", None, {}
+        )  # type: dict # type: ignore
+
+        self.assertIsNotNone(record)
+        self.assertEqual(record["DomainName"], "example.com")
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_create_record_success(self, mock_request):
+        """Test successful acceleration domain creation"""
+        mock_request.return_value = {"Response": {"RequestId": "req-123456789"}}
+
+        result = self.provider._create_record("zone-123456789", "www", "example.com", "1.2.3.4", "A", 600, None, {})
+
+        self.assertTrue(result)
+        # Verify request call was made correctly
+        mock_request.assert_called_once()
+        args, kwargs = mock_request.call_args
+        self.assertEqual(args[0], "CreateAccelerationDomain")
+        self.assertEqual(kwargs["ZoneId"], "zone-123456789")
+        self.assertEqual(kwargs["DomainName"], "www.example.com")
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_create_record_root_domain(self, mock_request):
+        """Test acceleration domain creation for root domain"""
+        mock_request.return_value = {"Response": {"RequestId": "req-123456789"}}
+
+        result = self.provider._create_record("zone-123456789", "@", "example.com", "1.2.3.4", "A", 300, None, {})
+
+        self.assertTrue(result)
+        # Verify domain name is correct for root
+        args, kwargs = mock_request.call_args
+        self.assertEqual(kwargs["DomainName"], "example.com")
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_create_record_with_extra_params(self, mock_request):
+        """Test acceleration domain creation with extra parameters"""
+        mock_request.return_value = {"Response": {"RequestId": "req-123456789"}}
+
+        result = self.provider._create_record(
+            "zone-123456789", "mail", "example.com", "mail.example.com", "MX", 300, None, {"Priority": 10}
+        )
+
+        self.assertTrue(result)
+        # Verify extra parameters are passed through
+        args, kwargs = mock_request.call_args
+        self.assertEqual(kwargs["Priority"], 10)
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_create_record_failure(self, mock_request):
+        """Test acceleration domain creation failure"""
+        mock_request.return_value = None
+
+        result = self.provider._create_record("zone-123456789", "www", "example.com", "1.2.3.4", "A", 300, None, {})
+
+        self.assertFalse(result)
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_update_record_success(self, mock_request):
+        """Test successful acceleration domain origin update"""
+        mock_request.return_value = {"Response": {"RequestId": "req-123456789"}}
+
+        old_record = {
+            "ZoneId": "zone-123456789",
+            "DomainName": "www.example.com",
+            "DomainStatus": "online",
+            "OriginDetail": {"OriginType": "ip_domain", "Origin": "1.2.3.4", "BackupOrigin": ""},
+        }
+
+        result = self.provider._update_record("zone-123456789", old_record, "5.6.7.8", "A", 600, None, {})
+
+        self.assertTrue(result)
+        # Verify request call was made
+        mock_request.assert_called_once()
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_update_record_preserve_backup_origin(self, mock_request):
+        """Test acceleration domain update preserves backup origin"""
+        mock_request.return_value = {"Response": {"RequestId": "req-123456789"}}
+
+        old_record = {
+            "ZoneId": "zone-123456789",
+            "DomainName": "www.example.com",
+            "DomainStatus": "online",
+            "OriginDetail": {"OriginType": "ip_domain", "Origin": "1.2.3.4", "BackupOrigin": "backup.example.com"},
+        }
+
+        result = self.provider._update_record("zone-123456789", old_record, "5.6.7.8", "A", None, None, {})
+
+        self.assertTrue(result)
+        # Verify request call was made
+        mock_request.assert_called_once()
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_update_record_missing_domain_name(self, mock_request):
+        """Test acceleration domain update with missing domain name"""
+        mock_request.return_value = None  # Simulate API failure due to missing domain name
+
+        old_record = {
+            "ZoneId": "zone-123456789",
+            # Missing DomainName
+            "OriginDetail": {"OriginType": "ip_domain", "Origin": "1.2.3.4"},
+        }
+
+        result = self.provider._update_record("zone-123456789", old_record, "5.6.7.8", "A", None, None, {})
+
+        # Should fail because domain name is None and API call will fail
+        self.assertFalse(result)
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_update_record_failure(self, mock_request):
+        """Test acceleration domain update failure"""
+        mock_request.return_value = None  # API call failed
+
+        old_record = {
+            "ZoneId": "zone-123456789",
+            "DomainName": "www.example.com",
+            "OriginDetail": {"OriginType": "ip_domain", "Origin": "1.2.3.4"},
+        }
+
+        result = self.provider._update_record("zone-123456789", old_record, "5.6.7.8", "A", None, None, {})
+
+        self.assertFalse(result)
+
+    @patch("ddns.provider.tencentcloud.strftime")
+    @patch("ddns.provider.tencentcloud.time")
+    @patch.object(EdgeOneProvider, "_http")
+    def test_request_success(self, mock_http, mock_time, mock_strftime):
+        """Test successful API request"""
+        # Mock time functions to get consistent results
+        mock_time.return_value = 1609459200
+        mock_strftime.return_value = "20210101"
+        mock_http.return_value = {"Response": {"ZoneId": "zone-123456", "RequestId": "test-request-id"}}
+
+        result = self.provider._request(
+            "DescribeZones", Filters=[{"Name": "zone-name", "Values": ["example.com"]}]  # type: ignore[arg-type]
+        )
+
+        self.assertIsNotNone(result)
+        if result:  # Type narrowing for mypy
+            self.assertEqual(result["ZoneId"], "zone-123456")
+        mock_http.assert_called_once()
+
+    @patch("ddns.provider.tencentcloud.strftime")
+    @patch("ddns.provider.tencentcloud.time")
+    @patch.object(EdgeOneProvider, "_http")
+    def test_request_api_error(self, mock_http, mock_time, mock_strftime):
+        """Test API request with error response"""
+        mock_time.return_value = 1609459200
+        mock_strftime.return_value = "20210101"
+        mock_http.return_value = {"Response": {"Error": {"Code": "InvalidParameter", "Message": "Invalid zone name"}}}
+
+        result = self.provider._request(
+            "DescribeZones", Filters=[{"Name": "zone-name", "Values": ["invalid"]}]  # type: ignore[arg-type]
+        )
+
+        self.assertIsNone(result)
+
+    @patch("ddns.provider.tencentcloud.strftime")
+    @patch("ddns.provider.tencentcloud.time")
+    @patch.object(EdgeOneProvider, "_http")
+    def test_request_unexpected_response(self, mock_http, mock_time, mock_strftime):
+        """Test API request with unexpected response format"""
+        mock_time.return_value = 1609459200
+        mock_strftime.return_value = "20210101"
+        mock_http.return_value = {"UnexpectedField": "value"}
+
+        result = self.provider._request(
+            "DescribeZones", Filters=[{"Name": "zone-name", "Values": ["example.com"]}]  # type: ignore[arg-type]
+        )
+
+        self.assertIsNone(result)
+
+    @patch("ddns.provider.tencentcloud.strftime")
+    @patch("ddns.provider.tencentcloud.time")
+    @patch.object(EdgeOneProvider, "_http")
+    def test_request_exception(self, mock_http, mock_time, mock_strftime):
+        """Test API request with exception"""
+        mock_time.return_value = 1609459200
+        mock_strftime.return_value = "20210101"
+        mock_http.side_effect = Exception("Network error")
+
+        # The implementation doesn't catch exceptions, so it will propagate
+        with self.assertRaises(Exception) as cm:
+            self.provider._request(
+                "DescribeZones", Filters=[{"Name": "zone-name", "Values": ["example.com"]}]  # type: ignore[arg-type]
+            )
+
+        self.assertEqual(str(cm.exception), "Network error")
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_set_record_create_new(self, mock_request):
+        """Test set_record creating a new acceleration domain"""
+        # Mock HTTP responses for the workflow
+        responses = [
+            # DescribeZones response (get zone ID for main domain)
+            {"Zones": [{"ZoneId": "zone-123456789", "ZoneName": "example.com"}]},
+            # DescribeAccelerationDomains response (no existing acceleration domain for subdomain)
+            {"AccelerationDomains": []},
+            # CreateAccelerationDomain response (acceleration domain created successfully)
+            {"Response": {"RequestId": "req-123456789"}},
+        ]
+        mock_request.side_effect = responses
+
+        result = self.provider.set_record("www.example.com", "1.2.3.4", "A")
+
+        # Should succeed because EdgeOne supports creating new acceleration domains
+        self.assertTrue(result)
+        self.assertEqual(mock_request.call_count, 3)  # Zone lookup, record query, and create calls
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_set_record_update_existing(self, mock_request):
+        """Test set_record updating an existing acceleration domain"""
+        # Mock HTTP responses for the workflow
+        responses = [
+            # DescribeZones response (get zone ID for main domain)
+            {"Zones": [{"ZoneId": "zone-123456789", "ZoneName": "example.com"}]},
+            # DescribeAccelerationDomains response (existing acceleration domain found)
+            {
+                "AccelerationDomains": [
+                    {
+                        "ZoneId": "zone-123456789",
+                        "DomainName": "www.example.com",
+                        "DomainStatus": "online",
+                        "OriginDetail": {"OriginType": "ip_domain", "Origin": "1.2.3.4", "BackupOrigin": ""},
+                    }
+                ]
+            },
+            # ModifyAccelerationDomain response (acceleration domain updated successfully)
+            {"Response": {"RequestId": "req-123456789"}},
+        ]
+        mock_request.side_effect = responses
+
+        result = self.provider.set_record("www.example.com", "5.6.7.8", "A")
+
+        self.assertTrue(result)
+        self.assertEqual(mock_request.call_count, 3)
+
+
+class TestEdgeOneProviderIntegration(BaseProviderTestCase):
+    """Integration tests for EdgeOneProvider"""
+
+    def setUp(self):
+        """Set up test fixtures"""
+        super(TestEdgeOneProviderIntegration, self).setUp()
+        self.provider = EdgeOneProvider(self.authid, self.token)
+        self.logger = self.mock_logger(self.provider)
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_full_domain_resolution_flow(self, mock_request):
+        """Test complete domain resolution flow for creating new domains"""
+        # Mock request responses for the workflow
+        responses = [
+            # DescribeZones response (get zone ID for main domain)
+            {"Zones": [{"ZoneId": "zone-123456789", "ZoneName": "example.com"}]},
+            # DescribeAccelerationDomains response (no existing acceleration domain for subdomain)
+            {"AccelerationDomains": []},
+            # CreateAccelerationDomain response (acceleration domain created successfully)
+            {"Response": {"RequestId": "req-123456789"}},
+        ]
+        mock_request.side_effect = responses
+
+        result = self.provider.set_record("test.example.com", "1.2.3.4", "A", ttl=600)
+
+        # Should succeed because EdgeOne supports creating new acceleration domains
+        self.assertTrue(result)
+        self.assertEqual(mock_request.call_count, 3)  # Zone lookup, record query, and create calls
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_custom_domain_format(self, mock_request):
+        """Test custom domain format with ~ separator (create new domain)"""
+        # Mock request responses
+        responses = [
+            # DescribeZones response (get zone ID for main domain)
+            {"Zones": [{"ZoneId": "zone-123456789", "ZoneName": "example.com"}]},
+            # DescribeAccelerationDomains response (no existing acceleration domain for subdomain)
+            {"AccelerationDomains": []},
+            # CreateAccelerationDomain response (acceleration domain created successfully)
+            {"Response": {"RequestId": "req-123456789"}},
+        ]
+        mock_request.side_effect = responses
+
+        result = self.provider.set_record("test~example.com", "1.2.3.4", "A")
+
+        # Should succeed because EdgeOne supports creating new acceleration domains
+        self.assertTrue(result)
+
+        # Zone lookup, record query, and create calls should be made
+        self.assertEqual(mock_request.call_count, 3)
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_update_existing_record(self, mock_request):
+        """Test updating an existing acceleration domain"""
+        # Mock request responses for the workflow
+        responses = [
+            # DescribeZones response (get zone ID for main domain)
+            {"Zones": [{"ZoneId": "zone-123456789", "ZoneName": "example.com"}]},
+            # DescribeAccelerationDomains response (existing acceleration domain found)
+            {
+                "AccelerationDomains": [
+                    {
+                        "ZoneId": "zone-123456789",
+                        "DomainName": "test.example.com",
+                        "DomainStatus": "online",
+                        "OriginDetail": {"OriginType": "ip_domain", "Origin": "1.2.3.4", "BackupOrigin": ""},
+                    }
+                ]
+            },
+            # ModifyAccelerationDomain response (acceleration domain updated successfully)
+            {"Response": {"RequestId": "req-123456789"}},
+        ]
+        mock_request.side_effect = responses
+
+        result = self.provider.set_record("test.example.com", "5.6.7.8", "A", ttl=300)
+
+        self.assertTrue(result)
+        self.assertEqual(mock_request.call_count, 3)
+
+        # Verify the ModifyAccelerationDomain call
+        modify_call = mock_request.call_args_list[2]
+        self.assertEqual(modify_call[0][0], "ModifyAccelerationDomain")
+
+    @patch.object(EdgeOneProvider, "_request")
+    def test_api_error_handling(self, mock_request):
+        """Test API error handling"""
+        # Mock API error response - the _request method returns None on error
+        mock_request.return_value = None
+
+        # This should return False because zone_id cannot be resolved
+        result = self.provider.set_record("test.example.com", "1.2.3.4", "A")
+        self.assertFalse(result)
+        # At least one call should be made to try to resolve zone ID
+        self.assertGreater(mock_request.call_count, 0)
+
+
+class TestEdgeOneProviderRealRequest(BaseProviderTestCase):
+    """EdgeOne Provider 真实请求测试类"""
+
+    def setUp(self):
+        """Set up test fixtures"""
+        super(TestEdgeOneProviderRealRequest, self).setUp()
+
+    def test_auth_failure_real_request(self):
+        """Test authentication failure with real API request"""
+        # 使用无效的认证信息创建 provider
+        invalid_provider = EdgeOneProvider("invalid_id", "invalid_token")
+
+        # Mock logger to capture error logs
+        invalid_provider.logger = MagicMock()
+
+        # 尝试查询域名信息,应该返回认证失败
+        result = invalid_provider._query_zone_id("example.com")
+
+        # 认证失败时应该返回 None (因为 API 会返回错误)
+        self.assertIsNone(result)
+
+        # 验证错误日志被记录
+        # 应该有错误日志调用,因为 API 返回认证错误
+        self.assertGreaterEqual(invalid_provider.logger.error.call_count, 1)
+
+        # 检查日志内容包含认证相关的错误信息
+        error_calls = invalid_provider.logger.error.call_args_list
+        logged_messages = [str(call) for call in error_calls]
+
+        # 至少有一个日志应该包含EdgeOne API 错误信息
+        has_auth_error = any(
+            "edgeone api error" in msg.lower() or "authfailure" in msg.lower() or "unauthorized" in msg.lower()
+            for msg in logged_messages
+        )
+        self.assertTrue(has_auth_error, "Expected EdgeOne authentication error in logs: {0}".format(logged_messages))
+
+
+if __name__ == "__main__":
+    unittest.main()