DeeplinkReportController.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. using dodohold.core;
  2. using Microsoft.AspNetCore.Mvc;
  3. using molilian.core;
  4. using System.Text.Json;
  5. namespace molilian.api.Controllers
  6. {
  7. [ApiController]
  8. [MyAuthorize("admin")]
  9. [Route("api/[controller]/[action]")]
  10. public class DeeplinkReportController : ControllerBase
  11. {
  12. private const int MaxRangeDays = 90;
  13. readonly IAuthorizationProvider provider = new AdminProvider();
  14. protected IHttpContextAccessor _accessor;
  15. public DeeplinkReportController(IHttpContextAccessor accessor)
  16. {
  17. _accessor = accessor;
  18. }
  19. [HttpPost]
  20. public async Task<ActionResult> daily([FromBody] JsonElement form)
  21. {
  22. _ = provider.Get(_accessor.HttpContext);
  23. var queryDate = form.PathReadArray<string>("query_date[]");
  24. var channelName = form.Read("channel_name", string.Empty).Trim();
  25. DateTime start = DateTime.Now.Date.AddDays(-6);
  26. DateTime end = DateTime.Now.Date;
  27. if (queryDate.Count == 2)
  28. {
  29. if (DateTime.TryParse(queryDate[0], out var parsedStart))
  30. {
  31. start = parsedStart.Date;
  32. }
  33. if (DateTime.TryParse(queryDate[1], out var parsedEnd))
  34. {
  35. end = parsedEnd.Date;
  36. }
  37. }
  38. if (end < start)
  39. {
  40. (start, end) = (end, start);
  41. }
  42. if ((end - start).TotalDays >= MaxRangeDays)
  43. {
  44. end = start.AddDays(MaxRangeDays - 1);
  45. }
  46. var accountName = string.IsNullOrWhiteSpace(channelName)
  47. ? "tool"
  48. : channelName;
  49. var reportChannels = GetReportChannels(channelName);
  50. var channelNames = reportChannels
  51. .Select(item => item.channel_name)
  52. .ToList();
  53. var channelTotals = channelNames.ToDictionary(
  54. item => item,
  55. _ => 0L,
  56. StringComparer.OrdinalIgnoreCase
  57. );
  58. var dailyRows = new List<DeeplinkDailyReportRow>();
  59. for (var date = start; date <= end; date = date.AddDays(1))
  60. {
  61. var dateKey = date.ToString("yyyyMMdd");
  62. var channelStats = await GetChannelStatsAsync(reportChannels, dateKey);
  63. foreach (var item in channelStats)
  64. {
  65. channelTotals[item.channel_name] += item.total_count;
  66. }
  67. var reportDate = date.ToString("yyyy-MM-dd");
  68. var row = new DeeplinkDailyReportRow
  69. {
  70. row_key = reportDate,
  71. row_type = "daily",
  72. report_date = reportDate,
  73. total_count = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:{dateKey}"),
  74. success_count = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:success:{dateKey}"),
  75. fail_count = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:fail:{dateKey}")
  76. };
  77. row.children = channelStats
  78. .Select(item => new DeeplinkDailyReportRow
  79. {
  80. row_key = $"{reportDate}:{item.channel_name}",
  81. row_type = "channel",
  82. report_date = item.display_name,
  83. channel_name = item.channel_name,
  84. display_name = item.display_name,
  85. total_count = item.total_count,
  86. success_count = item.success_count,
  87. fail_count = item.fail_count
  88. })
  89. .ToList();
  90. dailyRows.Add(row);
  91. }
  92. var channels = channelTotals
  93. .Where(item => !string.IsNullOrWhiteSpace(channelName) || item.Value > 0)
  94. .OrderByDescending(item => item.Value)
  95. .ThenBy(item => item.Key)
  96. .Select(item => new DeeplinkDailyReportChannel
  97. {
  98. channel_name = item.Key,
  99. display_name = GetDisplayName(reportChannels, item.Key),
  100. total_count = item.Value
  101. })
  102. .ToList();
  103. var activeChannelNames = channels
  104. .Select(item => item.channel_name)
  105. .ToHashSet(StringComparer.OrdinalIgnoreCase);
  106. foreach (var row in dailyRows)
  107. {
  108. row.children = (row.children ?? new List<DeeplinkDailyReportRow>())
  109. .Where(item => activeChannelNames.Contains(item.channel_name))
  110. .OrderByDescending(item => item.total_count)
  111. .ThenBy(item => item.channel_name)
  112. .ToList();
  113. if (!row.children.Any())
  114. {
  115. row.children = null;
  116. }
  117. }
  118. var list = dailyRows
  119. .OrderByDescending(item => item.report_date)
  120. .ToList();
  121. var summary = new DeeplinkDailyReportSummary
  122. {
  123. total_count = list.Sum(item => item.total_count),
  124. success_count = list.Sum(item => item.success_count),
  125. fail_count = list.Sum(item => item.fail_count)
  126. };
  127. return new APIResult(new
  128. {
  129. data = new
  130. {
  131. list,
  132. count = list.Count,
  133. summary,
  134. channels,
  135. maxRangeDays = MaxRangeDays
  136. }
  137. });
  138. }
  139. private static List<DeeplinkDailyReportChannel> GetReportChannels(string channelName)
  140. {
  141. var channels = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  142. try
  143. {
  144. var rules = new DBContext.Table("deeplink_parse_rule")
  145. .Where("status=@status", new { status = 1 })
  146. .Select<DeeplinkParseRuleDTO>();
  147. if (rules != null)
  148. {
  149. foreach (var rule in rules)
  150. {
  151. AddChannel(channels, rule.channel_name, rule.name);
  152. }
  153. }
  154. }
  155. catch
  156. {
  157. // Channel names are display metadata. Redis statistics remain usable if this lookup fails.
  158. }
  159. if (!string.IsNullOrWhiteSpace(channelName))
  160. {
  161. AddChannel(channels, channelName, channelName);
  162. }
  163. else
  164. {
  165. foreach (var name in Enum.GetNames(typeof(TkChannelEnum)))
  166. {
  167. AddChannel(channels, name, name);
  168. }
  169. }
  170. return channels
  171. .OrderBy(item => item.Key)
  172. .Select(item => new DeeplinkDailyReportChannel
  173. {
  174. channel_name = item.Key,
  175. display_name = item.Value
  176. })
  177. .ToList();
  178. }
  179. private static void AddChannel(
  180. Dictionary<string, string> channels,
  181. string channelName,
  182. string displayName
  183. )
  184. {
  185. if (string.IsNullOrWhiteSpace(channelName)) return;
  186. var normalizedChannel = channelName.Trim();
  187. if (normalizedChannel.Equals("all", StringComparison.OrdinalIgnoreCase)) return;
  188. if (normalizedChannel.Equals("tool", StringComparison.OrdinalIgnoreCase)) return;
  189. if (normalizedChannel.Equals("unknown", StringComparison.OrdinalIgnoreCase)) return;
  190. var normalizedDisplay = string.IsNullOrWhiteSpace(displayName)
  191. ? normalizedChannel
  192. : displayName.Trim();
  193. if (!channels.ContainsKey(normalizedChannel) ||
  194. channels[normalizedChannel].Equals(normalizedChannel, StringComparison.OrdinalIgnoreCase))
  195. {
  196. channels[normalizedChannel] = normalizedDisplay;
  197. }
  198. }
  199. private static string GetDisplayName(
  200. List<DeeplinkDailyReportChannel> channels,
  201. string channelName
  202. )
  203. {
  204. return channels
  205. .FirstOrDefault(item => item.channel_name.Equals(
  206. channelName,
  207. StringComparison.OrdinalIgnoreCase
  208. ))
  209. ?.display_name ?? channelName;
  210. }
  211. private static async Task<List<DeeplinkDailyReportChannel>> GetChannelStatsAsync(
  212. List<DeeplinkDailyReportChannel> channels,
  213. string dateKey
  214. )
  215. {
  216. var tasks = channels.Select(async channel => new
  217. DeeplinkDailyReportChannel
  218. {
  219. channel_name = channel.channel_name,
  220. display_name = channel.display_name,
  221. total_count = await TkLogCore.GetTotalAsync($":parse_total:{channel.channel_name}:{dateKey}"),
  222. success_count = await TkLogCore.GetTotalAsync($":parse_total:{channel.channel_name}:success:{dateKey}"),
  223. fail_count = await TkLogCore.GetTotalAsync($":parse_total:{channel.channel_name}:fail:{dateKey}")
  224. });
  225. var results = await Task.WhenAll(tasks);
  226. return results.ToList();
  227. }
  228. }
  229. public class DeeplinkDailyReportChannel
  230. {
  231. public string channel_name { get; set; } = string.Empty;
  232. public string display_name { get; set; } = string.Empty;
  233. public long total_count { get; set; } = 0;
  234. public long success_count { get; set; } = 0;
  235. public long fail_count { get; set; } = 0;
  236. }
  237. public class DeeplinkDailyReportRow
  238. {
  239. public string row_key { get; set; } = string.Empty;
  240. public string row_type { get; set; } = "daily";
  241. public string report_date { get; set; } = string.Empty;
  242. public string channel_name { get; set; } = string.Empty;
  243. public string display_name { get; set; } = string.Empty;
  244. public long total_count { get; set; } = 0;
  245. public long success_count { get; set; } = 0;
  246. public long fail_count { get; set; } = 0;
  247. public List<DeeplinkDailyReportRow>? children { get; set; }
  248. }
  249. public class DeeplinkDailyReportSummary
  250. {
  251. public long total_count { get; set; } = 0;
  252. public long success_count { get; set; } = 0;
  253. public long fail_count { get; set; } = 0;
  254. }
  255. }