手把手教你用C#对接爱发电API:从零封装Afdian.Sdk到实战应用

在独立开发者的世界里,赞助系统往往是项目可持续发展的关键。想象一下:你刚刚发布了一个用C#编写的开源工具,用户反响热烈,但服务器成本却随着用户增长不断攀升。这时,一个优雅的赞助集成方案就显得尤为重要。爱发电作为国内流行的创作者赞助平台,其API的对接能力成为.NET开发者必须掌握的技能之一。

本文将带你从零开始,逐步拆解爱发电API的对接过程。不同于简单的功能罗列,我们会先理解原始HTTP请求的痛点,再引入Afdian.Sdk这个开源利器,最后在ASP.NET Core项目中实现完整的赞助通知处理流程。无论你是想为自己的个人项目添加赞助支持,还是需要在小团队中快速集成支付功能,这套方法论都能让你事半功倍。

1. 理解爱发电API的核心机制

爱发电的API设计遵循典型的RESTful风格,但有几个关键特性需要特别注意。首先,所有请求都需要进行 请求签名 ,这是保障API安全的重要机制。签名过程涉及用户ID、令牌和当前时间戳的组合加密,稍有不慎就会导致认证失败。

典型的API请求需要包含以下参数:

{
  "user_id": "你的用户ID",
  "params": "加密后的请求参数",
  "ts": "当前时间戳",
  "sign": "生成的签名"
}

手动实现这个过程相当繁琐,特别是当需要频繁调用不同接口时。以查询赞助订单为例,原始HTTP请求代码可能长这样:

var client = new HttpClient();
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var paramsJson = JsonSerializer.Serialize(new { page = 1 });
var sign = ComputeMd5($"{userId}token{paramsJson}{timestamp}");

var request = new HttpRequestMessage {
    Method = HttpMethod.Post,
    RequestUri = new Uri("https://afdian.net/api/open/query-order"),
    Content = new StringContent(JsonSerializer.Serialize(new {
        user_id = userId,
        params = paramsJson,
        ts = timestamp,
        sign = sign
    }), Encoding.UTF8, "application/json")
};

var response = await client.SendAsync(request);

这段代码暴露了几个明显痛点:

  • 签名计算需要重复编写
  • 参数序列化处理繁琐
  • 错误处理机制缺失
  • 响应反序列化需要额外工作

2. Afdian.Sdk的架构解析与核心功能

Afdian.Sdk作为非官方.NET库,其核心价值在于对底层HTTP交互的优雅封装。通过分析其源代码,可以发现它采用了典型的 门面模式 (Facade Pattern),将复杂的API调用简化为直观的方法调用。

库的核心类 AfdianClient 提供了两种风格的API:

  • 原始JSON响应 :直接返回字符串,适合需要自定义解析的场景
  • 强类型模型 :自动反序列化为C#对象,提升开发效率

主要功能对比:

功能类型 方法示例 返回类型 适用场景
基础验证 Ping() string 快速测试连接
订单查询 QueryOrder(page: 1) string 获取原始JSON数据
赞助者查询 QuerySponsorModel(page: 1) SponsorModel 强类型对象操作
异步操作 QueryOrderAsync(page: 1) Task 异步编程场景

安装过程极其简单,只需执行NuGet命令:

dotnet add package Afdian.Sdk

然后在代码中初始化客户端:

using Afdian.Sdk;

var afdianClient = new AfdianClient(
    userId: "你的用户ID", 
    token: "你的API令牌");

3. 实战:ASP.NET Core集成方案

现在让我们构建一个真实的Web应用场景。假设我们需要在ASP.NET Core项目中处理赞助通知,以下是完整的实现步骤。

3.1 配置依赖注入

在Startup.cs中配置AfdianClient为单例服务:

services.AddSingleton<AfdianClient>(provider => 
    new AfdianClient(
        Configuration["Afdian:UserId"],
        Configuration["Afdian:Token"]));

建议将敏感信息存储在安全的配置源中:

// appsettings.Development.json
{
  "Afdian": {
    "UserId": "your_user_id",
    "Token": "your_api_token"
  }
}

3.2 实现Webhook控制器

创建专门处理爱发电回调的API端点:

[ApiController]
[Route("api/afdian")]
public class AfdianWebhookController : ControllerBase
{
    private readonly AfdianClient _client;
    private readonly ILogger<AfdianWebhookController> _logger;

    public AfdianWebhookController(
        AfdianClient client,
        ILogger<AfdianWebhookController> logger)
    {
        _client = client;
        _logger = logger;
    }

    [HttpPost("webhook")]
    public async Task<IActionResult> HandleWebhook()
    {
        using var reader = new StreamReader(Request.Body);
        var json = await reader.ReadToEndAsync();
        
        try {
            var payload = JsonSerializer.Deserialize<WebhookPayload>(json);
            
            // 验证签名逻辑
            if (!_client.VerifyWebhookSignature(payload))
            {
                _logger.LogWarning("无效的Webhook签名");
                return Unauthorized();
            }

            // 处理不同类型的Webhook事件
            switch (payload.Data.Type)
            {
                case "order.new":
                    await HandleNewOrder(payload.Data.Order);
                    break;
                case "order.changed":
                    await HandleOrderUpdate(payload.Data.Order);
                    break;
            }

            return Ok(new { status = "success" });
        }
        catch (JsonException ex)
        {
            _logger.LogError(ex, "JSON解析失败");
            return BadRequest();
        }
    }
}

3.3 订单处理逻辑实现

订单处理是赞助系统的核心。以下是一个增强版的订单处理器实现:

private async Task HandleNewOrder(Order order)
{
    _logger.LogInformation($"收到新订单: {order.OrderId}");
    
    // 验证订单有效性
    var verifiedOrder = await _client.QueryOrderModel(order.OrderId);
    if (verifiedOrder == null || verifiedOrder.Status != "paid")
    {
        _logger.LogWarning($"无效订单状态: {order.OrderId}");
        return;
    }

    // 构建赞助者信息
    var sponsor = new SponsorInfo {
        UserId = order.UserId,
        Name = order.UserName,
        Avatar = order.UserAvatar,
        Amount = order.TotalAmount,
        PlanName = order.PlanName
    };

    // 持久化到数据库
    await _dbContext.Sponsors.AddAsync(sponsor);
    await _dbContext.SaveChangesAsync();

    // 发送通知
    await _notificationService.SendSponsorAlert(sponsor);
    
    _logger.LogInformation($"已处理订单: {order.OrderId}");
}

4. 高级技巧与最佳实践

4.1 性能优化策略

当赞助者数量增长时,API调用频率需要谨慎控制:

  • 批量查询 :利用分页参数减少单次请求数据量
  • 缓存机制 :对稳定的数据实现本地缓存
  • 指数退避 :对失败请求实现智能重试
// 带缓存的赞助者查询实现
public async Task<List<Sponsor>> GetSponsorsWithCache()
{
    const string cacheKey = "afdian_sponsors";
    if (_memoryCache.TryGetValue(cacheKey, out List<Sponsor> cached))
        return cached;

    var sponsors = new List<Sponsor>();
    int page = 1;
    
    do {
        var result = await _afdianClient.QuerySponsorModel(page);
        if (result?.List == null || !result.List.Any())
            break;

        sponsors.AddRange(result.List);
        page++;
    } while (page <= 3); // 限制最大页数

    _memoryCache.Set(cacheKey, sponsors, TimeSpan.FromMinutes(30));
    return sponsors;
}

4.2 错误处理与监控

完善的错误处理系统应该包含:

  • API限流处理 :429状态码的识别与等待
  • 签名失败重试 :自动刷新时间戳
  • 异常日志记录 :结构化日志输出
try
{
    var response = await _afdianClient.QueryOrderAsync(page);
    // 处理响应...
}
catch (AfdianApiException ex) when (ex.StatusCode == 429)
{
    _logger.LogWarning("API调用过于频繁,等待后重试");
    await Task.Delay(1000); // 1秒后退避
    return await GetOrdersWithRetry(page);
}
catch (JsonException ex)
{
    _logger.LogError(ex, "响应解析失败");
    throw;
}

4.3 测试策略

针对Afdian.Sdk的集成应该包含多层次的测试:

  1. 单元测试 :验证核心业务逻辑
  2. 集成测试 :测试与Afdian.Sdk的实际交互
  3. Webhook模拟测试 :使用真实数据格式验证端点

示例测试用例:

[Fact]
public async Task Should_Process_Valid_Webhook()
{
    // 准备
    var testPayload = new WebhookPayload {
        Data = new {
            type = "order.new",
            order = new {
                order_id = "test123",
                user_id = "user123",
                // 其他必要字段...
            }
        }
    };
    
    // 执行
    var result = await _controller.HandleWebhook(testPayload);
    
    // 断言
    Assert.IsType<OkResult>(result);
    _dbContext.Verify(x => x.SaveChangesAsync(), Times.Once);
}

5. 扩展应用场景

Afdian.Sdk的灵活性使其可以适应各种创新应用:

  • 徽章系统 :根据赞助金额自动授予用户特殊标识
  • 专属内容解锁 :集成到会员系统中控制内容访问
  • 自动化致谢 :在项目README中动态显示最新赞助者

一个有趣的实现是将赞助信息实时显示在控制台应用中:

public async Task DisplaySponsorWall()
{
    Console.WriteLine("特别感谢以下赞助者:");
    
    var sponsors = await _afdianClient.QuerySponsorModel(1);
    foreach (var sponsor in sponsors.List.Take(5))
    {
        Console.WriteLine($"- {sponsor.Name} ({sponsor.Amount}元)");
    }
    
    if (sponsors.TotalCount > 5)
        Console.WriteLine($"...以及另外 {sponsors.TotalCount - 5} 位赞助者");
}

在实际项目中,我发现最常遇到的问题往往是签名时间戳不同步。解决方案是在初始化AfdianClient时配置自动时间校准:

var client = new AfdianClient(userId, token) {
    AutoAdjustTime = true  // 自动同步服务器时间
};

更多推荐