DeeplinkReportController.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  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. private const string ReportAccountName = "tool";
  14. readonly IAuthorizationProvider provider = new AdminProvider();
  15. protected IHttpContextAccessor _accessor;
  16. public DeeplinkReportController(IHttpContextAccessor accessor)
  17. {
  18. _accessor = accessor;
  19. }
  20. [HttpPost]
  21. public async Task<ActionResult> daily([FromBody] JsonElement form)
  22. {
  23. _ = provider.Get(_accessor.HttpContext);
  24. var queryDate = form.PathReadArray<string>("query_date[]");
  25. var channelName = form.Read("channel_name", string.Empty).Trim();
  26. DateTime start = DateTime.Now.Date.AddDays(-6);
  27. DateTime end = DateTime.Now.Date;
  28. if (queryDate.Count == 2)
  29. {
  30. if (DateTime.TryParse(queryDate[0], out var parsedStart))
  31. {
  32. start = parsedStart.Date;
  33. }
  34. if (DateTime.TryParse(queryDate[1], out var parsedEnd))
  35. {
  36. end = parsedEnd.Date;
  37. }
  38. }
  39. if (end < start)
  40. {
  41. (start, end) = (end, start);
  42. }
  43. if ((end - start).TotalDays >= MaxRangeDays)
  44. {
  45. end = start.AddDays(MaxRangeDays - 1);
  46. }
  47. var isChannelFiltered = !string.IsNullOrWhiteSpace(channelName);
  48. var reportChannels = GetReportChannels(channelName);
  49. var channelNames = reportChannels
  50. .Select(item => item.channel_name)
  51. .ToList();
  52. var channelTotals = channelNames.ToDictionary(
  53. item => item,
  54. _ => 0L,
  55. StringComparer.OrdinalIgnoreCase
  56. );
  57. var dailyRows = new List<DeeplinkDailyReportRow>();
  58. for (var date = start; date <= end; date = date.AddDays(1))
  59. {
  60. var dateKey = date.ToString("yyyyMMdd");
  61. var indexedChannelNames = await TkLogCore.GetTotalKeysAsync(
  62. $":parse_total:{ReportAccountName}:channels:{dateKey}"
  63. );
  64. var useLegacyChannelStats = indexedChannelNames.Length == 0;
  65. var dailyReportChannels = isChannelFiltered
  66. ? reportChannels
  67. : GetIndexedReportChannels(reportChannels, indexedChannelNames);
  68. var channelStats = useLegacyChannelStats
  69. ? await GetLegacyChannelStatsAsync(dailyReportChannels, dateKey)
  70. : await GetChannelStatsAsync(dailyReportChannels, dateKey);
  71. var reportDate = date.ToString("yyyy-MM-dd");
  72. var accountTotalCount = channelStats.Sum(item => item.total_count);
  73. var accountSuccessCount = channelStats.Sum(item => item.success_count);
  74. var accountFailCount = channelStats.Sum(item => item.fail_count);
  75. foreach (var item in channelStats)
  76. {
  77. channelTotals.TryAdd(item.channel_name, 0);
  78. channelTotals[item.channel_name] += item.total_count;
  79. }
  80. var row = new DeeplinkDailyReportRow
  81. {
  82. row_key = reportDate,
  83. row_type = "daily",
  84. report_date = reportDate,
  85. request_count = accountTotalCount,
  86. total_count = accountTotalCount,
  87. success_count = accountSuccessCount,
  88. fail_count = accountFailCount
  89. };
  90. row.children = channelStats
  91. .Select(item => new DeeplinkDailyReportRow
  92. {
  93. row_key = $"{reportDate}:{item.channel_name}",
  94. row_type = "channel",
  95. report_date = item.display_name,
  96. channel_name = item.channel_name,
  97. display_name = item.display_name,
  98. request_count = item.request_count,
  99. total_count = item.total_count,
  100. success_count = item.success_count,
  101. fail_count = item.fail_count
  102. })
  103. .ToList();
  104. dailyRows.Add(row);
  105. }
  106. var channels = channelTotals
  107. .Where(item => !string.IsNullOrWhiteSpace(channelName) || item.Value > 0)
  108. .OrderByDescending(item => item.Value)
  109. .ThenBy(item => item.Key)
  110. .Select(item => new DeeplinkDailyReportChannel
  111. {
  112. channel_name = item.Key,
  113. display_name = GetDisplayName(reportChannels, item.Key),
  114. request_count = item.Value,
  115. total_count = item.Value
  116. })
  117. .ToList();
  118. var activeChannelNames = channels
  119. .Select(item => item.channel_name)
  120. .ToHashSet(StringComparer.OrdinalIgnoreCase);
  121. foreach (var row in dailyRows)
  122. {
  123. row.children = (row.children ?? new List<DeeplinkDailyReportRow>())
  124. .Where(item => activeChannelNames.Contains(item.channel_name))
  125. .OrderByDescending(item => item.total_count)
  126. .ThenBy(item => item.channel_name)
  127. .ToList();
  128. if (!row.children.Any())
  129. {
  130. row.children = null;
  131. }
  132. }
  133. var list = dailyRows
  134. .OrderByDescending(item => item.report_date)
  135. .ToList();
  136. var summary = new DeeplinkDailyReportSummary
  137. {
  138. request_count = list.Sum(item => item.request_count),
  139. total_count = list.Sum(item => item.total_count),
  140. success_count = list.Sum(item => item.success_count),
  141. fail_count = list.Sum(item => item.fail_count)
  142. };
  143. return new APIResult(new
  144. {
  145. data = new
  146. {
  147. list,
  148. count = list.Count,
  149. summary,
  150. channels,
  151. maxRangeDays = MaxRangeDays
  152. }
  153. });
  154. }
  155. private static List<DeeplinkDailyReportChannel> GetReportChannels(string channelName)
  156. {
  157. var channels = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  158. try
  159. {
  160. var rules = new DBContext.Table("deeplink_parse_rule")
  161. .Select<DeeplinkParseRuleDTO>();
  162. if (rules != null)
  163. {
  164. foreach (var rule in rules)
  165. {
  166. AddChannel(channels, rule.channel_name, rule.name);
  167. }
  168. }
  169. }
  170. catch
  171. {
  172. // Channel names are display metadata. Redis statistics remain usable if this lookup fails.
  173. }
  174. if (!string.IsNullOrWhiteSpace(channelName))
  175. {
  176. var selectedDisplayName = channels.TryGetValue(channelName, out var configuredDisplayName)
  177. ? configuredDisplayName
  178. : channelName;
  179. channels.Clear();
  180. AddChannel(channels, channelName, selectedDisplayName);
  181. }
  182. else
  183. {
  184. foreach (var name in Enum.GetNames(typeof(TkChannelEnum)))
  185. {
  186. AddChannel(channels, name, name);
  187. }
  188. }
  189. return channels
  190. .OrderBy(item => item.Key)
  191. .Select(item => new DeeplinkDailyReportChannel
  192. {
  193. channel_name = item.Key,
  194. display_name = item.Value
  195. })
  196. .ToList();
  197. }
  198. private static void AddChannel(
  199. Dictionary<string, string> channels,
  200. string channelName,
  201. string displayName
  202. )
  203. {
  204. if (string.IsNullOrWhiteSpace(channelName)) return;
  205. var normalizedChannel = channelName.Trim();
  206. var normalizedDisplay = string.IsNullOrWhiteSpace(displayName)
  207. ? normalizedChannel
  208. : displayName.Trim();
  209. if (!channels.ContainsKey(normalizedChannel) ||
  210. channels[normalizedChannel].Equals(normalizedChannel, StringComparison.OrdinalIgnoreCase))
  211. {
  212. channels[normalizedChannel] = normalizedDisplay;
  213. }
  214. }
  215. private static string GetDisplayName(
  216. List<DeeplinkDailyReportChannel> channels,
  217. string channelName
  218. )
  219. {
  220. return channels
  221. .FirstOrDefault(item => item.channel_name.Equals(
  222. channelName,
  223. StringComparison.OrdinalIgnoreCase
  224. ))
  225. ?.display_name ?? channelName;
  226. }
  227. private static async Task<List<DeeplinkDailyReportChannel>> GetChannelStatsAsync(
  228. List<DeeplinkDailyReportChannel> channels,
  229. string dateKey
  230. )
  231. {
  232. var tasks = channels.Select(async channel =>
  233. {
  234. var channelBucket = $"{ReportAccountName}:channel:{channel.channel_name}";
  235. var channelTotalCount = await TkLogCore.GetTotalAsync($":parse_total:{channelBucket}:{dateKey}");
  236. return new DeeplinkDailyReportChannel
  237. {
  238. channel_name = channel.channel_name,
  239. display_name = channel.display_name,
  240. request_count = channelTotalCount,
  241. total_count = channelTotalCount,
  242. success_count = await TkLogCore.GetTotalAsync($":parse_total:{channelBucket}:success:{dateKey}"),
  243. fail_count = await TkLogCore.GetTotalAsync($":parse_total:{channelBucket}:fail:{dateKey}")
  244. };
  245. });
  246. var results = await Task.WhenAll(tasks);
  247. return results.ToList();
  248. }
  249. private static async Task<List<DeeplinkDailyReportChannel>> GetLegacyChannelStatsAsync(
  250. List<DeeplinkDailyReportChannel> channels,
  251. string dateKey
  252. )
  253. {
  254. var tasks = channels
  255. .Where(channel => IsLegacyReportSourceChannel(channel.channel_name))
  256. .Select(async channel =>
  257. {
  258. var channelTotalCount = await TkLogCore.GetTotalAsync(
  259. $":parse_total:{channel.channel_name}:{dateKey}"
  260. );
  261. return new DeeplinkDailyReportChannel
  262. {
  263. channel_name = channel.channel_name,
  264. display_name = channel.display_name,
  265. request_count = channelTotalCount,
  266. total_count = channelTotalCount,
  267. success_count = await TkLogCore.GetTotalAsync(
  268. $":parse_total:{channel.channel_name}:success:{dateKey}"
  269. ),
  270. fail_count = await TkLogCore.GetTotalAsync(
  271. $":parse_total:{channel.channel_name}:fail:{dateKey}"
  272. )
  273. };
  274. });
  275. var results = await Task.WhenAll(tasks);
  276. return results.ToList();
  277. }
  278. private static bool IsLegacyReportSourceChannel(string channelName)
  279. {
  280. return !channelName.Equals("all", StringComparison.OrdinalIgnoreCase) &&
  281. !channelName.Equals(ReportAccountName, StringComparison.OrdinalIgnoreCase);
  282. }
  283. private static List<DeeplinkDailyReportChannel> GetIndexedReportChannels(
  284. List<DeeplinkDailyReportChannel> configuredChannels,
  285. IEnumerable<string> indexedChannelNames
  286. )
  287. {
  288. var channels = configuredChannels.ToDictionary(
  289. item => item.channel_name,
  290. item => item.display_name,
  291. StringComparer.OrdinalIgnoreCase
  292. );
  293. foreach (var indexedChannelName in indexedChannelNames)
  294. {
  295. AddChannel(channels, indexedChannelName, indexedChannelName);
  296. }
  297. return channels
  298. .OrderBy(item => item.Key)
  299. .Select(item => new DeeplinkDailyReportChannel
  300. {
  301. channel_name = item.Key,
  302. display_name = item.Value
  303. })
  304. .ToList();
  305. }
  306. }
  307. public class DeeplinkDailyReportChannel
  308. {
  309. public string channel_name { get; set; } = string.Empty;
  310. public string display_name { get; set; } = string.Empty;
  311. public long request_count { get; set; } = 0;
  312. public long total_count { get; set; } = 0;
  313. public long success_count { get; set; } = 0;
  314. public long fail_count { get; set; } = 0;
  315. }
  316. public class DeeplinkDailyReportRow
  317. {
  318. public string row_key { get; set; } = string.Empty;
  319. public string row_type { get; set; } = "daily";
  320. public string report_date { get; set; } = string.Empty;
  321. public string channel_name { get; set; } = string.Empty;
  322. public string display_name { get; set; } = string.Empty;
  323. public long request_count { get; set; } = 0;
  324. public long total_count { get; set; } = 0;
  325. public long success_count { get; set; } = 0;
  326. public long fail_count { get; set; } = 0;
  327. public List<DeeplinkDailyReportRow>? children { get; set; }
  328. }
  329. public class DeeplinkDailyReportSummary
  330. {
  331. public long request_count { get; set; } = 0;
  332. public long total_count { get; set; } = 0;
  333. public long success_count { get; set; } = 0;
  334. public long fail_count { get; set; } = 0;
  335. }
  336. }