网站首页 网站源码
website
站点相关全部源代码,隐藏了一些关于服务器的信息
using System.Net.Http;
using System.Text.Json.Nodes;
using Dpz.Core.Public.Entity.Steam;
using Dpz.Core.Public.ViewModel.Steam;
using Microsoft.AspNetCore.WebUtilities;

namespace Dpz.Core.Service.RepositoryServiceImpl;

public class SteamGameService(
    IRepository<SteamGame> repository,
    IMapper mapper,
    HttpClient httpClient,
    IConfiguration configuration,
    ILogger<SteamGameService> logger,
    IFusionCache fusionCache
) : AbstractCacheService(fusionCache), ISteamGameService
{
    protected override string CachePrefixKey =>
        "Dpz.Core.Service.RepositoryServiceImpl.SteamGameService";
    protected override TimeSpan CacheDefaultExpiration => TimeSpan.FromDays(1);

    private readonly IFusionCache _fusionCache = fusionCache;

    private record SteamConfig(string AppKey = "", string SteamId = "");

    private record CdnConfig
    {
        public string BaseUrl { get; init; } = "https://cdn.dpangzi.com";
        public string LogoUrlTemplate { get; init; } =
            "https://media.st.dl.eccdnx.com/steam/apps/{0}/capsule_231x87.jpg";
    }

    private readonly SteamConfig _steamConfig =
        configuration.GetSection("Steam").Get<SteamConfig>()
        ?? throw new InvalidConfigurationException(
            "Steam 配置缺失:请检查 AppKey 和 SteamId 是否配置。"
        );

    private readonly CdnConfig _cdnConfig =
        configuration.GetSection("Cdn").Get<CdnConfig>() ?? new CdnConfig();

    // API请求延迟时间,防止请求过于频繁
    private const int ApiDelayMs = 500;

    public event LogoDownload? OnLogoDownloadComplete;
    public event AchievementIconGrayDownload? OnAchievementIconGrayDownloadComplete;
    public event AchievementIconDownload? OnAchievementIconDownloadComplete;

    public async Task UpdateGamesAsync()
    {
        logger.LogInformation("开始更新Steam游戏数据");
        var dbGames = await repository.SearchFor(x => true).ToListAsync();
        var games = await FetchGamesAsync(dbGames);

        // 新增游戏
        var newGames = games.ExceptBy(dbGames.Select(x => x.Id), x => x.Id).ToList();
        if (newGames.Count > 0)
        {
            logger.LogInformation("发现{Count}个新游戏,准备添加", newGames.Count);
            await repository.InsertAsync(newGames);
        }

        // DB已有游戏,待更新
        var updateGames = games.IntersectBy(dbGames.Select(x => x.Id), x => x.Id).ToList();
        logger.LogInformation("开始更新{Count}个已有游戏", updateGames.Count);

        await Parallel.ForEachAsync(
            updateGames,
            new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount * 2 },
            async (game, _) => await UpdateGameAsync(game.Id, game.PlayTime, game.Achievements)
        );

        await _fusionCache.RemoveByTagAsync(
            [CachePrefixKey + nameof(GetGamesAsync), CachePrefixKey + nameof(GetGameAsync)]
        );
        logger.LogInformation("Steam游戏数据更新完成");
    }

    private async Task UpdateGameAsync(
        int id,
        uint playTime,
        IReadOnlyCollection<AchievementDetail> achievements
    )
    {
        logger.LogInformation("更新游戏 {GameId} 的数据", id);
        var update = Builders<SteamGame>
            .Update.Set(x => x.PlayTime, playTime)
            .Set(x => x.LastUpdateTime, DateTime.Now);

        if (achievements.Count > 0)
        {
            update = update.Set(x => x.Achievements, achievements.ToList());
        }

        await repository.UpdateAsync(x => x.Id == id, update);
    }

    private async Task<List<AchievementDetail>> FetchAchievementsAsync(
        int id,
        List<SteamGame> dbGames
    )
    {
        logger.LogInformation("获取游戏 {GameId} 的成就数据", id);
        var queryString = new Dictionary<string, string?>
        {
            { "steamid", _steamConfig.SteamId },
            { "key", _steamConfig.AppKey },
            { "format", "json" },
            { "appid", id.ToString() },
            { "l", "schinese" },
        };

        // 添加API请求延迟
        await Task.Delay(ApiDelayMs);

        var achievements =
            await ExecuteHttpRequestAsync<List<AchievementDetail>>(
                "/ISteamUserStats/GetSchemaForGame/v2/",
                queryString,
                root => root?["game"]?["availableGameStats"]?["achievements"]
            ) ?? [];

        var unlockAchievements = await FetchUnlockAchievementAsync(queryString);

        await Parallel.ForEachAsync(
            achievements,
            async (achievement, _) =>
            {
                if (
                    unlockAchievements.TryGetValue(achievement.Name, out var unlockTime)
                    && unlockTime > 0
                )
                {
                    achievement.UnlockTime = unlockTime.ToDateTime();
                }

                await DownloadAchievementAsync(dbGames, id, achievement);
                await DownloadAchievementGrayAsync(dbGames, id, achievement);
            }
        );

        logger.LogInformation("游戏 {GameId} 共获取到 {Count} 个成就", id, achievements.Count);
        return achievements;
    }

    private async Task<Dictionary<string, long>> FetchUnlockAchievementAsync(
        Dictionary<string, string?> queryString
    )
    {
        logger.LogInformation("获取已解锁的成就数据");

        // 添加API请求延迟
        await Task.Delay(ApiDelayMs);

        return await ExecuteHttpRequestAsync<Dictionary<string, long>>(
                "/ISteamUserStats/GetPlayerAchievements/v0001/",
                queryString,
                root =>
                {
                    var achievements = root
                        ?["playerstats"]?["achievements"]?.AsArray()
                        .Select(x =>
                        {
                            var name = x?["apiname"]?.GetValue<string>();
                            if (name == null)
                            {
                                logger.LogError(
                                    "Achievement name is null for {SteamId}",
                                    _steamConfig.SteamId
                                );
                                return (KeyValuePair<string, long>?)null;
                            }
                            var unlockTime = x?["unlocktime"]?.GetValue<long>() ?? 0;
                            return x?["achieved"]?.GetValue<int>() == 1
                                ? KeyValuePair.Create(name, unlockTime)
                                : KeyValuePair.Create(name, 0L);
                        })
                        .Where(x => x != null)
                        .Select(x => x!.Value)
                        .ToDictionary(x => x.Key, x => x.Value);
                    return JsonValue.Create(achievements);
                }
            ) ?? new Dictionary<string, long>();
    }

    private async Task<List<SteamGame>> FetchGamesAsync(List<SteamGame> dbGames)
    {
        logger.LogInformation("开始获取Steam游戏列表");
        var queryString = new Dictionary<string, string?>
        {
            { "steamid", _steamConfig.SteamId },
            { "key", _steamConfig.AppKey },
            { "include_appinfo", "1" },
            { "format", "json" },
        };

        // 添加API请求延迟
        await Task.Delay(ApiDelayMs);

        var games =
            await ExecuteHttpRequestAsync<List<SteamGame>>(
                "/IPlayerService/GetOwnedGames/v0001/",
                queryString,
                root => root?["response"]?["games"]?.AsArray()) ?? [];

        logger.LogInformation("获取到 {Count} 个Steam游戏", games.Count);
        await Parallel.ForEachAsync(games, async (game, _) => await UpdateGame(dbGames, game));

        return games;
    }

    private async Task UpdateGame(List<SteamGame> dbGames, SteamGame game)
    {
        logger.LogInformation("更新游戏 {GameId} 的基本信息", game.Id);
        game.LastUpdateTime = DateTime.Now;
        game.Achievements = await FetchAchievementsAsync(game.Id, dbGames);

        if (!IsLogoDownload(dbGames, game.Id))
        {
            logger.LogInformation("下载游戏 {GameId} 的Logo", game.Id);

            // 添加API请求延迟
            await Task.Delay(ApiDelayMs);

            await using var stream = await HttpDownloadAsync(
                string.Format(_cdnConfig.LogoUrlTemplate, game.Id)
            );
            if (stream != null && OnLogoDownloadComplete != null)
            {
                var url = await OnLogoDownloadComplete(stream, game.Id);
                if (!string.IsNullOrWhiteSpace(url))
                {
                    game.ImageLogo = url;
                    game.ImageIcon = url;
                }
            }
        }
    }

    private async Task<Stream?> HttpDownloadAsync(string uri)
    {
        try
        {
            var response = await httpClient.GetAsync(uri);
            response.EnsureSuccessStatusCode();
            var memoryStream = new MemoryStream();
            await response.Content.CopyToAsync(memoryStream);
            memoryStream.Position = 0;
            return memoryStream;
        }
        catch (Exception e)
        {
            logger.LogError(e, "Download failed for {Uri}", uri);
            return null;
        }
    }

    private bool IsLogoDownload(IEnumerable<SteamGame> games, int id)
    {
        var game = games.FirstOrDefault(x => x.Id == id);
        return game != null
            && game.ImageIcon.StartsWith(_cdnConfig.BaseUrl, StringComparison.OrdinalIgnoreCase)
            && game.ImageLogo.StartsWith(_cdnConfig.BaseUrl, StringComparison.OrdinalIgnoreCase);
    }

    private async Task DownloadAchievementAsync(
        List<SteamGame> dbGames,
        int id,
        AchievementDetail achievement
    )
    {
        var dbAchievement = dbGames
            .FirstOrDefault(x => x.Id == id)
            ?.Achievements.FirstOrDefault(x => x.Name == achievement.Name);

        if (
            dbAchievement?.Icon.StartsWith(_cdnConfig.BaseUrl, StringComparison.OrdinalIgnoreCase)
            ?? false
        )
        {
            achievement.Icon = dbAchievement.Icon;
            return;
        }

        logger.LogInformation(
            "下载游戏 {GameId} 成就 {AchievementName} 的图标",
            id,
            achievement.Name
        );

        // 添加API请求延迟
        await Task.Delay(ApiDelayMs);

        await using var icon = await HttpDownloadAsync(achievement.Icon);
        var handler = OnAchievementIconDownloadComplete;
        if (icon != null && handler != null)
        {
            var url = await handler.Invoke(icon, id, achievement.Name);
            if (!string.IsNullOrWhiteSpace(url))
            {
                achievement.Icon = url;
            }
        }
    }

    private async Task DownloadAchievementGrayAsync(
        IEnumerable<SteamGame> dbGames,
        int id,
        AchievementDetail achievement
    )
    {
        var dbAchievement = dbGames
            .FirstOrDefault(x => x.Id == id)
            ?.Achievements.FirstOrDefault(x => x.Name == achievement.Name);

        if (
            dbAchievement?.IconGray.StartsWith(
                _cdnConfig.BaseUrl,
                StringComparison.OrdinalIgnoreCase
            ) ?? false
        )
        {
            achievement.IconGray = dbAchievement.IconGray;
            return;
        }

        logger.LogInformation(
            "下载游戏 {GameId} 成就 {AchievementName} 的灰色图标",
            id,
            achievement.Name
        );

        // 添加API请求延迟
        await Task.Delay(ApiDelayMs);

        await using var icon = await HttpDownloadAsync(achievement.IconGray);
        var handler = OnAchievementIconGrayDownloadComplete;
        if (icon != null && handler != null)
        {
            var url = await handler.Invoke(icon, id, achievement.Name);
            if (!string.IsNullOrWhiteSpace(url))
            {
                achievement.IconGray = url;
            }
        }
    }

    public async Task<List<VmSteamGame>> GetGamesAsync()
    {
        return await GetOrSetCacheAsync<List<VmSteamGame>>(
            nameof(GetGamesAsync),
            async (_, cancellationToken) =>
            {
                var list = await repository.SearchFor(x => true).ToListAsync(cancellationToken);
                var result = mapper.Map<List<VmSteamGame>>(list);
                return result;
            }
        );
    }

    public async Task<VmSteamGame?> GetGameAsync(int id)
    {
        return await GetOrSetCacheAsync<VmSteamGame?>(
            nameof(GetGameAsync),
            async (_, cancellationToken) =>
            {
                var game = await repository
                    .SearchFor(x => x.Id == id)
                    .FirstOrDefaultAsync(cancellationToken);
                if (game == null)
                {
                    logger.LogWarning("未找到游戏 {GameId} 的信息", id);
                    return null;
                }
                var result = mapper.Map<VmSteamGame>(game);
                return result;
            },
            new { id }
        );
    }

    private async Task<T?> ExecuteHttpRequestAsync<T>(
        string endpoint,
        Dictionary<string, string?> queryParams,
        Func<JsonNode?, JsonNode?>? dataSelector = null
    )
    {
        var requestUri = QueryHelpers.AddQueryString(endpoint, queryParams);
        try
        {
            var response = await httpClient.GetAsync(requestUri);
            response.EnsureSuccessStatusCode();
            var jsonContent = await response.Content.ReadAsStringAsync();
            var jsonNode = JsonNode.Parse(jsonContent);
            if (jsonNode == null)
            {
                return default;
            }

            var data = dataSelector?.Invoke(jsonNode) ?? jsonNode;
            return data.Deserialize<T>();
        }
        catch (Exception e)
        {
            logger.LogError(e, "HTTP request failed for {Endpoint}", endpoint);
            return default;
        }
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

上述代码是一个 C# 类 SteamGameService 的实现,主要用于与 Steam API 交互,管理和更新 Steam 游戏的信息。以下是该类的主要功能和结构的详细解释:

主要功能

  1. 游戏信息更新

    • UpdateGamesAsync 方法用于更新数据库中的游戏信息。它首先从数据库中获取所有游戏,然后通过 Steam API 获取最新的游戏数据。
    • 新增的游戏会被插入到数据库中,而已有的游戏则会根据最新的游戏时长和成就进行更新。
  2. 游戏成就获取

    • FetchAchievementsAsync 方法用于获取特定游戏的成就信息。它会调用 Steam API 获取成就数据,并下载成就图标。
    • FetchUnlockAchievementAsync 方法用于获取玩家已解锁的成就。
  3. 游戏库获取

    • FetchGamesAsync 方法从 Steam API 获取用户的游戏库列表,并为每个游戏获取成就信息和下载游戏的图标。
  4. 图像下载

    • HttpDownloadAsync 方法用于下载游戏或成就的图像。
    • DownloadAchievementAsyncDownloadAchievementGrayAsync 方法用于下载成就的图标和灰色图标。
  5. 缓存管理

    • 使用 IHybridCachingProvider 进行缓存管理,以提高性能,避免频繁访问数据库和 Steam API。
    • GetGamesAsyncGetGameAsync 方法从缓存中获取游戏信息,如果缓存不存在,则从数据库中获取并更新缓存。
  6. 事件处理

    • 类中定义了多个事件(如 OnLogoDownloadCompleteOnAchievementIconDownloadComplete),用于在下载完成后执行特定操作。
  7. 测试方法

    • Test 方法用于测试 MongoDB 更新操作的序列化,输出更新操作的渲染结果。

代码结构

  • 构造函数:使用 ActivatorUtilitiesConstructor 特性,允许依赖注入。
  • 私有字段:如 _prefixKey 用于缓存键的前缀。
  • 异步方法:大部分方法都是异步的,使用 asyncawait 关键字,以支持非阻塞的 I/O 操作。
  • LINQ 查询:使用 LINQ 进行集合操作,如 ExceptByIntersectBy,以便于处理游戏的新增和更新。

总结

SteamGameService 类是一个功能丰富的服务类,主要用于管理 Steam 游戏的信息,包括获取、更新和缓存游戏及其成就数据。它通过与 Steam API 的交互,提供了一个完整的游戏信息管理解决方案,并通过事件和缓存机制提高了性能和可扩展性。

loading