using dodohold.core; using Microsoft.AspNetCore.Mvc; using molilian.core; using System.Text.Json; namespace molilian.api.Controllers { [ApiController] [MyAuthorize("admin")] [Route("api/[controller]/[action]")] public class DeeplinkReportController : ControllerBase { private const int MaxRangeDays = 90; readonly IAuthorizationProvider provider = new AdminProvider(); protected IHttpContextAccessor _accessor; public DeeplinkReportController(IHttpContextAccessor accessor) { _accessor = accessor; } [HttpPost] public async Task daily([FromBody] JsonElement form) { _ = provider.Get(_accessor.HttpContext); var queryDate = form.PathReadArray("query_date[]"); var channelName = form.Read("channel_name", string.Empty).Trim(); DateTime start = DateTime.Now.Date.AddDays(-6); DateTime end = DateTime.Now.Date; if (queryDate.Count == 2) { if (DateTime.TryParse(queryDate[0], out var parsedStart)) { start = parsedStart.Date; } if (DateTime.TryParse(queryDate[1], out var parsedEnd)) { end = parsedEnd.Date; } } if (end < start) { (start, end) = (end, start); } if ((end - start).TotalDays >= MaxRangeDays) { end = start.AddDays(MaxRangeDays - 1); } var accountName = string.IsNullOrWhiteSpace(channelName) ? "tool" : channelName; var reportChannels = GetReportChannels(channelName); var channelNames = reportChannels .Select(item => item.channel_name) .ToList(); var channelTotals = channelNames.ToDictionary( item => item, _ => 0L, StringComparer.OrdinalIgnoreCase ); var dailyRows = new List(); for (var date = start; date <= end; date = date.AddDays(1)) { var dateKey = date.ToString("yyyyMMdd"); var channelStats = await GetChannelStatsAsync(reportChannels, dateKey); foreach (var item in channelStats) { channelTotals[item.channel_name] += item.total_count; } var reportDate = date.ToString("yyyy-MM-dd"); var row = new DeeplinkDailyReportRow { row_key = reportDate, row_type = "daily", report_date = reportDate, total_count = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:{dateKey}"), success_count = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:success:{dateKey}"), fail_count = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:fail:{dateKey}") }; row.children = channelStats .Select(item => new DeeplinkDailyReportRow { row_key = $"{reportDate}:{item.channel_name}", row_type = "channel", report_date = item.display_name, channel_name = item.channel_name, display_name = item.display_name, total_count = item.total_count, success_count = item.success_count, fail_count = item.fail_count }) .ToList(); dailyRows.Add(row); } var channels = channelTotals .Where(item => !string.IsNullOrWhiteSpace(channelName) || item.Value > 0) .OrderByDescending(item => item.Value) .ThenBy(item => item.Key) .Select(item => new DeeplinkDailyReportChannel { channel_name = item.Key, display_name = GetDisplayName(reportChannels, item.Key), total_count = item.Value }) .ToList(); var activeChannelNames = channels .Select(item => item.channel_name) .ToHashSet(StringComparer.OrdinalIgnoreCase); foreach (var row in dailyRows) { row.children = (row.children ?? new List()) .Where(item => activeChannelNames.Contains(item.channel_name)) .OrderByDescending(item => item.total_count) .ThenBy(item => item.channel_name) .ToList(); if (!row.children.Any()) { row.children = null; } } var list = dailyRows .OrderByDescending(item => item.report_date) .ToList(); var summary = new DeeplinkDailyReportSummary { total_count = list.Sum(item => item.total_count), success_count = list.Sum(item => item.success_count), fail_count = list.Sum(item => item.fail_count) }; return new APIResult(new { data = new { list, count = list.Count, summary, channels, maxRangeDays = MaxRangeDays } }); } private static List GetReportChannels(string channelName) { var channels = new Dictionary(StringComparer.OrdinalIgnoreCase); try { var rules = new DBContext.Table("deeplink_parse_rule") .Where("status=@status", new { status = 1 }) .Select(); if (rules != null) { foreach (var rule in rules) { AddChannel(channels, rule.channel_name, rule.name); } } } catch { // Channel names are display metadata. Redis statistics remain usable if this lookup fails. } if (!string.IsNullOrWhiteSpace(channelName)) { AddChannel(channels, channelName, channelName); } else { foreach (var name in Enum.GetNames(typeof(TkChannelEnum))) { AddChannel(channels, name, name); } } return channels .OrderBy(item => item.Key) .Select(item => new DeeplinkDailyReportChannel { channel_name = item.Key, display_name = item.Value }) .ToList(); } private static void AddChannel( Dictionary channels, string channelName, string displayName ) { if (string.IsNullOrWhiteSpace(channelName)) return; var normalizedChannel = channelName.Trim(); if (normalizedChannel.Equals("all", StringComparison.OrdinalIgnoreCase)) return; if (normalizedChannel.Equals("tool", StringComparison.OrdinalIgnoreCase)) return; if (normalizedChannel.Equals("unknown", StringComparison.OrdinalIgnoreCase)) return; var normalizedDisplay = string.IsNullOrWhiteSpace(displayName) ? normalizedChannel : displayName.Trim(); if (!channels.ContainsKey(normalizedChannel) || channels[normalizedChannel].Equals(normalizedChannel, StringComparison.OrdinalIgnoreCase)) { channels[normalizedChannel] = normalizedDisplay; } } private static string GetDisplayName( List channels, string channelName ) { return channels .FirstOrDefault(item => item.channel_name.Equals( channelName, StringComparison.OrdinalIgnoreCase )) ?.display_name ?? channelName; } private static async Task> GetChannelStatsAsync( List channels, string dateKey ) { var tasks = channels.Select(async channel => new DeeplinkDailyReportChannel { channel_name = channel.channel_name, display_name = channel.display_name, total_count = await TkLogCore.GetTotalAsync($":parse_total:{channel.channel_name}:{dateKey}"), success_count = await TkLogCore.GetTotalAsync($":parse_total:{channel.channel_name}:success:{dateKey}"), fail_count = await TkLogCore.GetTotalAsync($":parse_total:{channel.channel_name}:fail:{dateKey}") }); var results = await Task.WhenAll(tasks); return results.ToList(); } } public class DeeplinkDailyReportChannel { public string channel_name { get; set; } = string.Empty; public string display_name { get; set; } = string.Empty; public long total_count { get; set; } = 0; public long success_count { get; set; } = 0; public long fail_count { get; set; } = 0; } public class DeeplinkDailyReportRow { public string row_key { get; set; } = string.Empty; public string row_type { get; set; } = "daily"; public string report_date { get; set; } = string.Empty; public string channel_name { get; set; } = string.Empty; public string display_name { get; set; } = string.Empty; public long total_count { get; set; } = 0; public long success_count { get; set; } = 0; public long fail_count { get; set; } = 0; public List? children { get; set; } } public class DeeplinkDailyReportSummary { public long total_count { get; set; } = 0; public long success_count { get; set; } = 0; public long fail_count { get; set; } = 0; } }