DeeplinkReportController.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  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 accountTotalCount = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:{dateKey}");
  69. var row = new DeeplinkDailyReportRow
  70. {
  71. row_key = reportDate,
  72. row_type = "daily",
  73. report_date = reportDate,
  74. request_count = accountTotalCount,
  75. total_count = accountTotalCount,
  76. success_count = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:success:{dateKey}"),
  77. fail_count = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:fail:{dateKey}")
  78. };
  79. row.children = channelStats
  80. .Select(item => new DeeplinkDailyReportRow
  81. {
  82. row_key = $"{reportDate}:{item.channel_name}",
  83. row_type = "channel",
  84. report_date = item.display_name,
  85. channel_name = item.channel_name,
  86. display_name = item.display_name,
  87. request_count = item.request_count,
  88. total_count = item.total_count,
  89. success_count = item.success_count,
  90. fail_count = item.fail_count
  91. })
  92. .ToList();
  93. dailyRows.Add(row);
  94. }
  95. var channels = channelTotals
  96. .Where(item => !string.IsNullOrWhiteSpace(channelName) || item.Value > 0)
  97. .OrderByDescending(item => item.Value)
  98. .ThenBy(item => item.Key)
  99. .Select(item => new DeeplinkDailyReportChannel
  100. {
  101. channel_name = item.Key,
  102. display_name = GetDisplayName(reportChannels, item.Key),
  103. request_count = item.Value,
  104. total_count = item.Value
  105. })
  106. .ToList();
  107. var activeChannelNames = channels
  108. .Select(item => item.channel_name)
  109. .ToHashSet(StringComparer.OrdinalIgnoreCase);
  110. foreach (var row in dailyRows)
  111. {
  112. row.children = (row.children ?? new List<DeeplinkDailyReportRow>())
  113. .Where(item => activeChannelNames.Contains(item.channel_name))
  114. .OrderByDescending(item => item.total_count)
  115. .ThenBy(item => item.channel_name)
  116. .ToList();
  117. if (!row.children.Any())
  118. {
  119. row.children = null;
  120. }
  121. }
  122. var list = dailyRows
  123. .OrderByDescending(item => item.report_date)
  124. .ToList();
  125. var summary = new DeeplinkDailyReportSummary
  126. {
  127. request_count = list.Sum(item => item.request_count),
  128. total_count = list.Sum(item => item.total_count),
  129. success_count = list.Sum(item => item.success_count),
  130. fail_count = list.Sum(item => item.fail_count)
  131. };
  132. return new APIResult(new
  133. {
  134. data = new
  135. {
  136. list,
  137. count = list.Count,
  138. summary,
  139. channels,
  140. maxRangeDays = MaxRangeDays
  141. }
  142. });
  143. }
  144. private static List<DeeplinkDailyReportChannel> GetReportChannels(string channelName)
  145. {
  146. var channels = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  147. try
  148. {
  149. var rules = new DBContext.Table("deeplink_parse_rule")
  150. .Where("status=@status", new { status = 1 })
  151. .Select<DeeplinkParseRuleDTO>();
  152. if (rules != null)
  153. {
  154. foreach (var rule in rules)
  155. {
  156. AddChannel(channels, rule.channel_name, rule.name);
  157. }
  158. }
  159. }
  160. catch
  161. {
  162. // Channel names are display metadata. Redis statistics remain usable if this lookup fails.
  163. }
  164. if (!string.IsNullOrWhiteSpace(channelName))
  165. {
  166. AddChannel(channels, channelName, channelName);
  167. }
  168. else
  169. {
  170. foreach (var name in Enum.GetNames(typeof(TkChannelEnum)))
  171. {
  172. AddChannel(channels, name, name);
  173. }
  174. }
  175. return channels
  176. .OrderBy(item => item.Key)
  177. .Select(item => new DeeplinkDailyReportChannel
  178. {
  179. channel_name = item.Key,
  180. display_name = item.Value
  181. })
  182. .ToList();
  183. }
  184. private static void AddChannel(
  185. Dictionary<string, string> channels,
  186. string channelName,
  187. string displayName
  188. )
  189. {
  190. if (string.IsNullOrWhiteSpace(channelName)) return;
  191. var normalizedChannel = channelName.Trim();
  192. if (normalizedChannel.Equals("all", StringComparison.OrdinalIgnoreCase)) return;
  193. if (normalizedChannel.Equals("tool", StringComparison.OrdinalIgnoreCase)) return;
  194. if (normalizedChannel.Equals("unknown", StringComparison.OrdinalIgnoreCase)) return;
  195. var normalizedDisplay = string.IsNullOrWhiteSpace(displayName)
  196. ? normalizedChannel
  197. : displayName.Trim();
  198. if (!channels.ContainsKey(normalizedChannel) ||
  199. channels[normalizedChannel].Equals(normalizedChannel, StringComparison.OrdinalIgnoreCase))
  200. {
  201. channels[normalizedChannel] = normalizedDisplay;
  202. }
  203. }
  204. private static string GetDisplayName(
  205. List<DeeplinkDailyReportChannel> channels,
  206. string channelName
  207. )
  208. {
  209. return channels
  210. .FirstOrDefault(item => item.channel_name.Equals(
  211. channelName,
  212. StringComparison.OrdinalIgnoreCase
  213. ))
  214. ?.display_name ?? channelName;
  215. }
  216. private static async Task<List<DeeplinkDailyReportChannel>> GetChannelStatsAsync(
  217. List<DeeplinkDailyReportChannel> channels,
  218. string dateKey
  219. )
  220. {
  221. var tasks = channels.Select(async channel =>
  222. {
  223. var channelTotalCount = await TkLogCore.GetTotalAsync($":parse_total:{channel.channel_name}:{dateKey}");
  224. return new DeeplinkDailyReportChannel
  225. {
  226. channel_name = channel.channel_name,
  227. display_name = channel.display_name,
  228. request_count = channelTotalCount,
  229. total_count = channelTotalCount,
  230. success_count = await TkLogCore.GetTotalAsync($":parse_total:{channel.channel_name}:success:{dateKey}"),
  231. fail_count = await TkLogCore.GetTotalAsync($":parse_total:{channel.channel_name}:fail:{dateKey}")
  232. };
  233. });
  234. var results = await Task.WhenAll(tasks);
  235. return results.ToList();
  236. }
  237. }
  238. public class DeeplinkDailyReportChannel
  239. {
  240. public string channel_name { get; set; } = string.Empty;
  241. public string display_name { get; set; } = string.Empty;
  242. public long request_count { get; set; } = 0;
  243. public long total_count { get; set; } = 0;
  244. public long success_count { get; set; } = 0;
  245. public long fail_count { get; set; } = 0;
  246. }
  247. public class DeeplinkDailyReportRow
  248. {
  249. public string row_key { get; set; } = string.Empty;
  250. public string row_type { get; set; } = "daily";
  251. public string report_date { get; set; } = string.Empty;
  252. public string channel_name { get; set; } = string.Empty;
  253. public string display_name { get; set; } = string.Empty;
  254. public long request_count { get; set; } = 0;
  255. public long total_count { get; set; } = 0;
  256. public long success_count { get; set; } = 0;
  257. public long fail_count { get; set; } = 0;
  258. public List<DeeplinkDailyReportRow>? children { get; set; }
  259. }
  260. public class DeeplinkDailyReportSummary
  261. {
  262. public long request_count { get; set; } = 0;
  263. public long total_count { get; set; } = 0;
  264. public long success_count { get; set; } = 0;
  265. public long fail_count { get; set; } = 0;
  266. }
  267. }