网站首页 网站源码
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;
using Microsoft.Extensions.DependencyInjection;

namespace Dpz.Core.Service.RepositoryServiceImpl;

[method: ActivatorUtilitiesConstructor]
public class SteamGameService(
    IRepository<SteamGame> repository,
    IMapper mapper,
    HttpClient httpClient,
    IConfiguration configuration,
    ILogger<SteamGameService> logger,
    IHybridCachingProvider hybridCachingProvider)
    : ISteamGameService
{
    private readonly string _prefixKey = "Dpz.Core.Service.RepositoryServiceImpl.SteamGameService";
    
    
    public event LogoDownload? OnLogoDownloadComplete;
    public event AchievementIconGrayDownload? OnAchievementIconGrayDownloadComplete;
    public event AchievementIconDownload? OnAchievementIconDownloadComplete;


    public async Task UpdateGamesAsync()
    {
        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).ToArray();

        if (newGames.Any())
            await repository.InsertAsync(newGames);

        // DB已有游戏,待更新
        var updateGames = games.IntersectBy(dbGames.Select(x => x.Id), x => x.Id).ToList();

        await Parallel.ForEachAsync(updateGames,
            async (game, _) => { await UpdateGameAsync(game.Id, game.PlayTime, game.Achievements); });

        await hybridCachingProvider.RemoveByPrefixAsync(_prefixKey);

        // foreach (var game in updateGames)
        // {
        //     await UpdateGameAsync(game.Id, game.PlayTime, game.Achievements);
        // }
    }

    /// <summary>
    /// 更新DB中已有的游戏信息,一般变动只有游戏时长、成就有变动,所以只修改这两项和修改时间
    /// </summary>
    /// <param name="id"></param>
    /// <param name="playTime">游戏时长</param>
    /// <param name="achievements">成就</param>
    private async Task UpdateGameAsync(int id, uint playTime, IReadOnlyCollection<AchievementDetail> achievements)
    {
        var update = Builders<SteamGame>.Update
            .Set(x => x.PlayTime, playTime)
            .Set(x => x.LastUpdateTime, DateTime.Now);

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

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

    /// <summary>
    /// 获取游戏成就
    /// </summary>
    /// <param name="id"></param>
    /// <param name="dbGames"></param>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    private async Task<List<AchievementDetail>> FetchAchievementsAsync(int id, IReadOnlyCollection<SteamGame> dbGames)
    {
        var achievements = new List<AchievementDetail>();
        var appKey = configuration.GetSection("Steam")["AppKey"] ?? "";
        var steamId = configuration.GetSection("Steam")["SteamId"] ?? "";
        var queryString = new Dictionary<string, string?>
        {
            { "steamid", steamId },
            { "key", appKey },
            { "format", "json" },
            { "appid", id.ToString() },
            { "l", "schinese" }
        };

        var requestUri = QueryHelpers.AddQueryString("/ISteamUserStats/GetSchemaForGame/v2/", queryString);
        var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
        try
        {
            var response = await httpClient.SendAsync(request);
            if (response.IsSuccessStatusCode)
            {
                var jsonContent = await response.Content.ReadAsStringAsync();
                var root = JsonNode.Parse(jsonContent);
                achievements =
                    root?["game"]?["availableGameStats"]?["achievements"]?.Deserialize<List<AchievementDetail>>() ??
                    new List<AchievementDetail>();
            }
        }
        catch (Exception e)
        {
            logger.LogError(e, "fetch game achievements fail");
        }

        var unlockAchievements = await FetchUnlockAchievementAsync(queryString);


        await Parallel.ForEachAsync(achievements, async (achievement, _) =>
        {
            if (unlockAchievements.ContainsKey(achievement.Name) && unlockAchievements[achievement.Name] > 0)
            {
                achievement.UnlockTime = unlockAchievements[achievement.Name].ToDateTime();
            }

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

    /// <summary>
    /// 获取已解锁成就
    /// </summary>
    /// <param name="queryString"></param>
    /// <returns></returns>
    private async Task<Dictionary<string, long>> FetchUnlockAchievementAsync(IDictionary<string, string?> queryString)
    {
        var requestUri = QueryHelpers.AddQueryString("/ISteamUserStats/GetPlayerAchievements/v0001/", queryString);
        var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
        try
        {
            var response = await httpClient.SendAsync(request);
            if (response.IsSuccessStatusCode)
            {
                var jsonContent = await response.Content.ReadAsStringAsync();
                var root = JsonNode.Parse(jsonContent);
                return root?["playerstats"]?["achievements"]?.AsArray()
                           .Select(x =>
                           {
                               var name = x?["apiname"]?.GetValue<string>();
                               if (name == null)
                                   return (KeyValuePair<string, long>?)null;
                               var unlockTime = x?["unlocktime"]?.GetValue<long>() ?? 0;
                               if (x?["achieved"]?.GetValue<int>() != 1)
                               {
                                   unlockTime = 0;
                               }

                               return new KeyValuePair<string, long>(name, unlockTime);
                           })
                           .Where(x => x != null)
                           .Select(x => x!.Value)
                           .ToDictionary(x => x.Key, x => x.Value)
                       ?? new Dictionary<string, long>();
            }
        }
        catch (Exception e)
        {
            logger.LogError(e, "fetch game unlock achievements fail");
        }

        return new Dictionary<string, long>();
    }

    /// <summary>
    /// 从Steam API获取游戏库列表
    /// </summary>
    /// <returns></returns>
    private async Task<List<SteamGame>> FetchGamesAsync(IReadOnlyCollection<SteamGame> dbGames)
    {
        var games = new List<SteamGame>();
        var appKey = configuration.GetSection("Steam")["AppKey"] ?? "";
        var steamId = configuration.GetSection("Steam")["SteamId"] ?? "";
        var queryString = new Dictionary<string, string?>
        {
            { "steamid", steamId },
            { "key", appKey },
            { "include_appinfo", "1" },
            { "format", "json" }
        };
        var requestUri = QueryHelpers.AddQueryString("/IPlayerService/GetOwnedGames/v0001/", queryString);
        var request = new HttpRequestMessage(HttpMethod.Get, requestUri);

        try
        {
            var response = await httpClient.SendAsync(request);
            if (response.IsSuccessStatusCode)
            {
                var jsonContent = await response.Content.ReadAsStringAsync();
                var root = JsonNode.Parse(jsonContent);
                games = root?["response"]?["games"]?.Deserialize<List<SteamGame>>() ?? new List<SteamGame>();
            }
        }
        catch (Exception e)
        {
            logger.LogError(e, "fetch games fail");
        }

        await Parallel.ForEachAsync(games, async (game, _) =>
        {
            game.LastUpdateTime = DateTime.Now;
            game.Achievements = await FetchAchievementsAsync(game.Id, dbGames);

            if (!IsLogoDownload(dbGames, game.Id))
            {
                var stream =
                    await HttpDownloadAsync(
                        $"https://media.st.dl.eccdnx.com/steam/apps/{game.Id}/capsule_231x87.jpg");
                if (stream == null) return;
                if (OnLogoDownloadComplete != null)
                {
                    var url = await OnLogoDownloadComplete(stream, game.Id);
                    game.ImageLogo = url;
                    game.ImageIcon = url;
                }
            }
        });

        return games;
    }

    /// <summary>
    /// 下载游戏图片或者成就图标
    /// </summary>
    /// <param name="uri"></param>
    /// <returns></returns>
    private async Task<Stream?> HttpDownloadAsync(string uri)
    {
        var request = new HttpRequestMessage(HttpMethod.Get, uri);
        try
        {
            var response = await httpClient.SendAsync(request);
            return await response.Content.ReadAsStreamAsync();
        }
        catch (Exception e)
        {
            logger.LogError(e, "download fail");
        }

        return null;
    }

    bool IsLogoDownload(IReadOnlyCollection<SteamGame> games, int id)
    {
        var game = games.FirstOrDefault(x => x.Id == id);
        return game != null &&
               game.ImageIcon.StartsWith("https://cdn.dpangzi.com", StringComparison.CurrentCultureIgnoreCase) &&
               game.ImageLogo.StartsWith("https://cdn.dpangzi.com", StringComparison.CurrentCultureIgnoreCase);
    }

    async Task DownloadAchievementAsync(IEnumerable<SteamGame> dbGames, int id, AchievementDetail achievement)
    {
        var dbAchievement = dbGames.FirstOrDefault(x => x.Id == id)?.Achievements
            .FirstOrDefault(x => x.Name == achievement.Name);
        if (dbAchievement == null ||
            !dbAchievement.Icon.StartsWith("https://cdn.dpangzi.com", StringComparison.CurrentCultureIgnoreCase))
        {
            var icon = await HttpDownloadAsync(achievement.Icon);
            if (icon == null) return;
            if (OnAchievementIconDownloadComplete != null)
            {
                var url = await OnAchievementIconDownloadComplete(icon, id, achievement.Name);
                achievement.Icon = url;
            }
        }
        else
        {
            achievement.Icon = dbAchievement.Icon;
        }
    }

    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 == null ||
            !dbAchievement.IconGray.StartsWith("https://cdn.dpangzi.com", StringComparison.CurrentCultureIgnoreCase))
        {
            var icon = await HttpDownloadAsync(achievement.IconGray);
            if (icon == null) return;
            if (OnAchievementIconGrayDownloadComplete != null)
            {
                var url = await OnAchievementIconGrayDownloadComplete(icon, id, achievement.Name);
                achievement.IconGray = url;
            }
        }
        else
        {
            achievement.IconGray = dbAchievement.IconGray;
        }
    }

    public async Task<List<VmSteamGame>> GetGamesAsync()
    {
        var cacheKey = $"{_prefixKey}:GetGamesAsync()";
        var cache = await hybridCachingProvider.GetAsync<List<VmSteamGame>>(cacheKey);
        if (!cache.IsNull && cache.HasValue)
        {
            return cache.Value;
        }
        var list = await repository.SearchFor(x => true).ToListAsync();
        var result = mapper.Map<List<VmSteamGame>>(list);
        await hybridCachingProvider.SetAsync(cacheKey, result, TimeSpan.FromDays(1));
        return result;
    }

    public async Task<VmSteamGame?> GetGameAsync(int id)
    {
        var cacheKey = $"{_prefixKey}:GetGameAsync({id})";
        var cache = await hybridCachingProvider.GetAsync<VmSteamGame>(cacheKey);
        if (!cache.IsNull && cache.HasValue)
        {
            return cache.Value;
        }
        var game = await repository.SearchFor(x => x.Id == id).FirstOrDefaultAsync();
        if (game == null) return null;
        var result = mapper.Map<VmSteamGame>(game);
        await hybridCachingProvider.SetAsync(cacheKey, result, TimeSpan.FromDays(1));
        return result;
    }

    public void Test()
    {
        var serializerRegistry = BsonSerializer.SerializerRegistry;
        var documentSerializer = serializerRegistry.GetSerializer<SteamGame>();
        var update = Builders<SteamGame>.Update
            .Set(x => x.PlayTime, 100u);

        var updateArgs = new RenderArgs<SteamGame>(documentSerializer,serializerRegistry);
        
        Console.WriteLine(update.Render(updateArgs));
        update = update.Set(x => x.LastUpdateTime, DateTime.Now);

        Console.WriteLine(update.Render(updateArgs));
    }
}
loading