using System.Collections.Specialized;
using System.Net.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Dpz.Core.Service;

public interface IBingWallpaper
{
    /// <summary>
    /// 随机获取一张壁纸
    /// </summary>
    /// <returns></returns>
    Task<Wallpaper> GetRandomWallpaperAsync();

    /// <summary>
    /// 获取今日的所有壁纸
    /// </summary>
    /// <returns></returns>
    Task<List<Wallpaper>> GetTodayWallpapersAsync();

    /// <summary>
    /// 清除缓存
    /// </summary>
    /// <returns></returns>
    Task ClearCacheAsync();
}

public class Wallpaper
{
    private const string Host = "https://cn.bing.com";

    private string _url = "";

    [JsonProperty("url")]
    public string Url
    {
        get => _url.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? _url : Host + _url;
        set => _url = value;
    }

    [JsonProperty("copyright")]
    public string? CopyRight { get; set; }

    [JsonProperty("copyrightlink")]
    public string? CopyRightLink { get; set; }
}

public class BingWallpaper(
    ILogger<BingWallpaper> logger,
    IHttpClientFactory httpClientFactory,
    IFusionCache fusionCache
) : IBingWallpaper
{
    private const string CachePrefix = "Dpz.Core.Service.BingWallpaper";

    private readonly Wallpaper _defaultWallpaper = new()
    {
        Url = "/th?id=OHR.RedAlley_ZH-CN2795378972_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp",
        CopyRight = "武侯祠内红墙和竹林掩映下的小巷,中国成都 (© Eastimages/Getty Images)",
        CopyRightLink =
            "https://www.bing.com/search?q=%E6%AD%A6%E4%BE%AF%E7%A5%A0&form=hpcapt&mkt=zh-cn",
    };

    private async Task<List<Wallpaper>> FetchWallpaperAsync(
        CancellationToken cancellationToken = default
    )
    {
        try
        {
            //var request = new RestRequest("/HPImageArchive.aspx", Method.GET);

            var parameters = new NameValueCollection
            {
                { "format", "js" },
                //请求图片截止天数 0 今天 -1 截止中明天 (预准备的) 1 截止至昨天,类推(目前最多获取到7天前的图片)
                { "idx", "0" },
                //1-8 返回请求数量,目前最多一次获取8张
                { "n", "8" },
                //地区
                { "mkt", "zh-CN" },
            };

            var httpClient = httpClientFactory.CreateClient("edge");
            var request = new HttpRequestMessage(
                HttpMethod.Get,
                $"https://cn.bing.com/HPImageArchive.aspx?{parameters.ToQueryString()}"
            );

            var response = await httpClient.SendAsync(request, cancellationToken);
            var content = await response.Content.ReadAsStringAsync(cancellationToken);
            var result = JObject.Parse(content)["images"]?.ToObject<List<Wallpaper>>() ?? [];
            return result;
        }
        catch (Exception e)
        {
            logger.LogError(e, "获取bing壁纸失败");
            var result = new List<Wallpaper> { _defaultWallpaper };
            return result;
        }
    }

    private async Task<List<Wallpaper>> GetWallpaperAsync()
    {
        return await fusionCache.GetOrSetAsync<List<Wallpaper>>(
            BuildCacheKey(nameof(GetWallpaperAsync)),
            async (context, cancellationToken) =>
            {
                context.Tags = [CachePrefix, BuildMethodTag(nameof(GetWallpaperAsync))];
                return await FetchWallpaperAsync(cancellationToken);
            },
            options => options.SetDuration(TimeSpan.FromDays(1))
        );
    }

    public async Task<Wallpaper> GetRandomWallpaperAsync()
    {
        return await fusionCache.GetOrSetAsync<Wallpaper>(
            BuildCacheKey(nameof(GetRandomWallpaperAsync)),
            async (context, _) =>
            {
                context.Tags = [CachePrefix, BuildMethodTag(nameof(GetRandomWallpaperAsync))];
                var list = await GetWallpaperAsync();
                var result = list.OrderBy(_ => Guid.NewGuid()).FirstOrDefault();
                return result ?? _defaultWallpaper;
            },
            options => options.SetDuration(TimeSpan.FromDays(1))
        );
    }

    public async Task<List<Wallpaper>> GetTodayWallpapersAsync()
    {
        return await GetWallpaperAsync();
    }

    public async Task ClearCacheAsync()
    {
        await fusionCache.RemoveByTagAsync(CachePrefix);
    }

    private static string BuildCacheKey(string methodName) => CachePrefix + ":" + methodName;

    private static string BuildMethodTag(string methodName) => CachePrefix + ":" + methodName;
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

代码解释

这是一个用于获取 Bing 每日壁纸的服务实现,采用了接口与实现分离的设计模式。以下是详细说明:


一、接口定义 (IBingWallpaper)

定义了三个核心方法:

  • GetRandomWallpaperAsync(): 随机获取一张壁纸
  • GetTodayWallpapersAsync(): 获取今日的所有壁纸(最多8张)
  • ClearCacheAsync(): 清除缓存

二、数据模型 (Wallpaper)

用于承载壁纸信息:

public class Wallpaper
{
    public string Url { get; set; }           // 壁纸URL(自动补全域名)
    public string? CopyRight { get; set; }     // 版权信息
    public string? CopyRightLink { get; set; } // 版权链接
}

关键特性

  • Url 属性使用了智能 getter:如果 URL 不是以 http 开头,会自动拼接 https://cn.bing.com
  • 使用 JSON 序列化特性方便与 API 响应映射

三、核心实现 (BingWallpaper)

1. 依赖注入

通过主构造函数注入三个依赖:

  • ILogger: 日志记录
  • IHttpClientFactory: HTTP 客户端工厂
  • IFusionCache: 缓存组件

2. FetchWallpaperAsync() - 数据获取

private async Task<List<Wallpaper>> FetchWallpaperAsync(...)
  • 调用 Bing API:https://cn.bing.com/HPImageArchive.aspx
  • 请求参数:
    • format=js: JSON 格式
    • idx=0: 获取今天的壁纸
    • n=8: 返回最多8张
    • mkt=zh-CN: 中国区域
  • 解析 JSON 响应中的 images 数组
  • 异常处理:失败时返回默认壁纸(成都武侯祠)

3. GetWallpaperAsync() - 带缓存的获取

private async Task<List<Wallpaper>> GetWallpaperAsync()
  • 使用 FusionCache 实现缓存机制
  • 缓存策略
    • 缓存时长:1天
    • 缓存键:Dpz.Core.Service.BingWallpaper:GetWallpaperAsync
    • 标签:支持按标签批量清除

4. GetRandomWallpaperAsync() - 随机壁纸

public async Task<Wallpaper> GetRandomWallpaperAsync()
  • 获取今日所有壁纸
  • 使用 OrderBy(_ => Guid.NewGuid()) 实现随机排序
  • 返回第一个元素(失败时返回默认壁纸)
  • 同样带有1天缓存

5. ClearCacheAsync() - 清除缓存

public async Task ClearCacheAsync()
  • 通过标签 CachePrefix 清除所有相关缓存

四、设计亮点

  1. 缓存分层

    • 原始数据缓存(壁纸列表)
    • 随机结果缓存(避免频繁随机计算)
  2. 容错机制

    • API 请求失败时提供默认壁纸
    • 避免服务完全不可用
  3. 可测试性

    • 接口与实现分离
    • 依赖注入便于 Mock
  4. 缓存管理

    • 统一的缓存键和标签命名规则
    • 支持批量清除缓存
  5. 性能优化

    • 使用 HttpClientFactory 避免 Socket 耗尽
    • 缓存减少 API 调用频率

五、使用场景

适用于需要展示 Bing 每日壁纸的应用场景,如:

  • 网站首页背景
  • 移动应用启动屏
  • 桌面客户端壁纸更新
评论加载中...