为了保留日志记录,我们可以使用 Rust 的 logenv_logger 库来记录程序的运行情况。以下是一个修改后的示例程序,展示了如何实现这一功能。

首先,确保你已经安装了 reqwesttokioserdeserde_jsontomllogenv_logger 库。你可以在 Cargo.toml 文件中添加以下依赖项:

[dependencies]
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.5"
log = "0.4"
env_logger = "0.9"

然后,你可以编写以下 Rust 代码来实现动态域名解析并记录日志:

use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::fs;
use std::time::Duration;
use tokio::time::interval;
use log::{info, error};
 
#[derive(Serialize, Deserialize, Debug)]
struct Config {
    api_key: String,
    api_secret: String,
    domain: String,
    record_type: String,
    record_name: String,
}
 
#[derive(Serialize, Deserialize, Debug)]
struct DnsRecord {
    data: String,
    ttl: u32,
}
 
async fn get_public_ip() -> Result<String, Box<dyn std::error::Error>> {
    let response = reqwest::get("https://api.ipify.org")
        .await?
        .text()
        .await?;
    Ok(response)
}
 
async fn update_dns_record(
    client: &Client,
    api_key: &str,
    api_secret: &str,
    domain: &str,
    record_type: &str,
    record_name: &str,
    new_ip: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    let record = DnsRecord {
        data: new_ip.to_string(),
        ttl: 600,
    };
 
    let url = format!(
        "https://api.godaddy.com/v1/domains/{}/records/{}/{}",
        domain, record_type, record_name
    );
 
    let response = client
        .put(&url)
        .header("Authorization", format!("sso-key {}:{}", api_key, api_secret))
        .json(&[record])
        .send()
        .await?;
 
    if response.status().is_success() {
        info!("DNS record updated successfully!");
    } else {
        error!("Failed to update DNS record: {:?}", response.text().await?);
    }
 
    Ok(())
}
 
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 初始化日志记录
    env_logger::init();
 
    // 读取配置文件
    let config_str = fs::read_to_string("config.toml")?;
    let config: Config = toml::from_str(&config_str)?;
 
    // 创建一个HTTP客户端
    let client = Client::new();
 
    // 设置定时任务
    let mut interval = interval(Duration::from_secs(60 * 10)); // 每10分钟检查一次
 
    loop {
        interval.tick().await;
 
        // 获取当前机器的公共IP地址
        let public_ip = match get_public_ip().await {
            Ok(ip) => ip,
            Err(e) => {
                error!("Failed to get public IP: {}", e);
                continue;
            }
        };
 
        // 更新DNS记录
        if let Err(e) = update_dns_record(
            &client,
            &config.api_key,
            &config.api_secret,
            &config.domain,
            &config.record_type,
            &config.record_name,
            &public_ip,
        )
        .await
        {
            error!("Failed to update DNS record: {}", e);
        }
    }
}

在这个示例中,我们使用了 logenv_logger 库来记录程序的运行情况。你需要在程序启动时初始化日志记录器,并在适当的地方使用 info!error! 宏来记录信息和错误。

运行这个程序之前,确保你已经安装了所有依赖项,并且配置文件 config.toml 位于程序的运行目录中。你可以使用以下命令来运行程序:

RUST_LOG=info cargo run

这样,你就可以使用配置文件来管理 API 密钥、域名和记录信息,并实现动态域名解析,同时保留日志记录了。

当然,让我们详细解析一下 async fn update_dns_record() 这段代码。

函数签名

async fn update_dns_record(
    client: &Client,
    api_key: &str,
    api_secret: &str,
    domain: &str,
    record_type: &str,
    record_name: &str,
    new_ip: &str,
) -> Result<(), Box<dyn std::error::Error>> {
  • async fn: 这是一个异步函数,意味着它可以在等待 I/O 操作时让出执行权,从而允许其他任务运行。
  • client: &Client: 这是一个 reqwest::Client 的引用,用于发送 HTTP 请求。
  • api_key: &str, api_secret: &str: 这是 GoDaddy API 的密钥和密钥,用于身份验证。
  • domain: &str, record_type: &str, record_name: &str: 这些参数用于指定要更新的 DNS 记录的域名、记录类型和记录名称。
  • new_ip: &str: 这是要更新的新 IP 地址。
  • -> Result<(), Box<dyn std::error::Error>>: 这个函数返回一个 Result 类型,如果成功则返回 Ok(()),如果失败则返回一个错误。

函数体

  1. 创建 DNS 记录对象

    let record = DnsRecord {
        data: new_ip.to_string(),
        ttl: 600,
    };

    这里创建了一个 DnsRecord 对象,其中 data 字段是新的 IP 地址,ttl 字段是 600 秒(10分钟)。

  2. 构建请求 URL

    let url = format!(
        "https://api.godaddy.com/v1/domains/{}/records/{}/{}",
        domain, record_type, record_name
    );

    这里使用 format! 宏构建了 GoDaddy API 的 URL,指定了要更新的域名、记录类型和记录名称。

  3. 发送 HTTP PUT 请求

    let response = client
        .put(&url)
        .header("Authorization", format!("sso-key {}:{}", api_key, api_secret))
        .json(&[record])
        .send()
        .await?;
    • client.put(&url): 使用 reqwest::Client 发送一个 PUT 请求到指定的 URL。
    • .header("Authorization", format!("sso-key {}:{}", api_key, api_secret)): 添加一个 Authorization 头,用于身份验证。
    • .json(&[record]): 将 DnsRecord 对象序列化为 JSON 并作为请求体发送。
    • .send().await?: 发送请求并等待响应。如果发生错误,使用 ? 操作符将错误传播出去。
  4. 处理响应

    if response.status().is_success() {
        info!("DNS record updated successfully!");
    } else {
        error!("Failed to update DNS record: {:?}", response.text().await?);
    }
    • response.status().is_success(): 检查响应的状态码是否表示成功。
    • info!("DNS record updated successfully!");: 如果成功,记录一条信息日志。
    • error!("Failed to update DNS record: {:?}", response.text().await?);: 如果失败,记录一条错误日志,并包含响应的文本内容。
  5. 返回结果

    Ok(())

    最后,返回 Ok(()) 表示函数成功执行。

总结

async fn update_dns_record() 函数的主要功能是使用 GoDaddy API 更新 DNS 记录。它构建了一个包含新 IP 地址的 DNS 记录对象,发送一个 PUT 请求到 GoDaddy API,并根据响应的状态码记录成功或失败的信息。