|
@@ -0,0 +1,798 @@
|
|
|
|
|
+using System.Collections.Concurrent;
|
|
|
|
|
+using System.Data;
|
|
|
|
|
+using System.Threading;
|
|
|
|
|
+using Dapper;
|
|
|
|
|
+using dodohold.core;
|
|
|
|
|
+using YunhuiKit;
|
|
|
|
|
+
|
|
|
|
|
+namespace molilian.core
|
|
|
|
|
+{
|
|
|
|
|
+ public partial class TracksCore
|
|
|
|
|
+ {
|
|
|
|
|
+ private const string ParseMetricTypeName = "转链";
|
|
|
|
|
+ private const string ParseMetricRequest = "request";
|
|
|
|
|
+ private const string ParseMetricCall = "call";
|
|
|
|
|
+ private const string ParseMetricSuccess = "success";
|
|
|
|
|
+ private const int ParseMetricFlushIntervalMs = 1000;
|
|
|
|
|
+
|
|
|
|
|
+ private static readonly ConcurrentDictionary<ParseMetricCounterKey, ParseMetricCounter> PendingParseMetricCounters = new();
|
|
|
|
|
+ private static readonly object ParseMetricFlushTimerLock = new();
|
|
|
|
|
+ private static Timer? ParseMetricFlushTimer;
|
|
|
|
|
+ private static int ParseMetricFlushRunning;
|
|
|
|
|
+ private static int ParseMetricLifecycleHooked;
|
|
|
|
|
+
|
|
|
|
|
+ public static Task<bool> RecordParseRequestAsync(UnionParseRequest request)
|
|
|
|
|
+ {
|
|
|
|
|
+ var dimension = ResolveParseMetricDimension(request.Channel, request.RiskStrategy, request.LaunchScene);
|
|
|
|
|
+ return IncrementParseMetricAsync(ParseMetricRequest, dimension.Platform, dimension.RiskStrategy, dimension.LaunchScene, request.AccountId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static Task<bool> RecordParseAccountRequestAsync(string channel, string riskStrategy, int launchScene, int accountId)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (accountId <= 0) return Task.FromResult(false);
|
|
|
|
|
+
|
|
|
|
|
+ var dimension = ResolveParseMetricDimension(channel, riskStrategy, launchScene);
|
|
|
|
|
+ return IncrementParseAccountMetricAsync(ParseMetricRequest, dimension.Platform, dimension.RiskStrategy, dimension.LaunchScene, accountId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static Task<bool> RecordParseResultAsync(string channel, string riskStrategy, int launchScene, int accountId, bool success)
|
|
|
|
|
+ {
|
|
|
|
|
+ var dimension = ResolveParseMetricDimension(channel, riskStrategy, launchScene);
|
|
|
|
|
+ return IncrementParseResultMetricAsync(dimension.Platform, dimension.RiskStrategy, dimension.LaunchScene, accountId, success);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static async Task<TrackParseMetricReportResult> GetParseMetricReportAsync(
|
|
|
|
|
+ DateTime start,
|
|
|
|
|
+ DateTime end,
|
|
|
|
|
+ int page,
|
|
|
|
|
+ int size,
|
|
|
|
|
+ bool getTotal,
|
|
|
|
|
+ int accountId = -1,
|
|
|
|
|
+ bool accountBreakdownOnly = false,
|
|
|
|
|
+ string platform = "",
|
|
|
|
|
+ string riskStrategy = "",
|
|
|
|
|
+ int? launchScene = null)
|
|
|
|
|
+ {
|
|
|
|
|
+ start = start.Date;
|
|
|
|
|
+ end = end.Date;
|
|
|
|
|
+ if (end < start) end = start;
|
|
|
|
|
+ page = Math.Max(page, 1);
|
|
|
|
|
+ size = Math.Max(size, 1);
|
|
|
|
|
+
|
|
|
|
|
+ var list = new List<TrackParseMetricReportDTO>();
|
|
|
|
|
+ for (var date = start; date <= end; date = date.AddDays(1))
|
|
|
|
|
+ {
|
|
|
|
|
+ string dateStr = date.ToString("yyyyMMdd");
|
|
|
|
|
+ string indexKey = BuildParseMetricIndexKey("daily", dateStr);
|
|
|
|
|
+ string[] members = await TkLogCore.GetTotalKeysAsync(indexKey) ?? [];
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var member in members)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!TryParseParseMetricIndexValue(member, out var dimension)) continue;
|
|
|
|
|
+ if (!IsMatchedParseMetricDimension(dimension, accountId, accountBreakdownOnly, platform, riskStrategy, launchScene)) continue;
|
|
|
|
|
+
|
|
|
|
|
+ var requestTask = TkLogCore.GetTotalAsync(BuildParseMetricCountKey(ParseMetricRequest, "daily", dateStr, dimension.Platform, dimension.RiskStrategy, dimension.LaunchScene, dimension.AccountId));
|
|
|
|
|
+ var callTask = TkLogCore.GetTotalAsync(BuildParseMetricCountKey(ParseMetricCall, "daily", dateStr, dimension.Platform, dimension.RiskStrategy, dimension.LaunchScene, dimension.AccountId));
|
|
|
|
|
+ var successTask = TkLogCore.GetTotalAsync(BuildParseMetricCountKey(ParseMetricSuccess, "daily", dateStr, dimension.Platform, dimension.RiskStrategy, dimension.LaunchScene, dimension.AccountId));
|
|
|
|
|
+ await Task.WhenAll(requestTask, callTask, successTask);
|
|
|
|
|
+
|
|
|
|
|
+ int requestCount = requestTask.Result;
|
|
|
|
|
+ int callCount = callTask.Result;
|
|
|
|
|
+ int successCount = successTask.Result;
|
|
|
|
|
+ if (requestCount <= 0 && callCount <= 0 && successCount <= 0) continue;
|
|
|
|
|
+
|
|
|
|
|
+ list.Add(new TrackParseMetricReportDTO
|
|
|
|
|
+ {
|
|
|
|
|
+ report_date = date,
|
|
|
|
|
+ platform = dimension.Platform,
|
|
|
|
|
+ typename = ParseMetricTypeName,
|
|
|
|
|
+ risk_strategy = dimension.RiskStrategy,
|
|
|
|
|
+ launch_scene = dimension.LaunchScene,
|
|
|
|
|
+ account_id = dimension.AccountId,
|
|
|
|
|
+ request_count = requestCount,
|
|
|
|
|
+ call_count = callCount,
|
|
|
|
|
+ success_count = successCount
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ list = list
|
|
|
|
|
+ .OrderByDescending(item => item.report_date)
|
|
|
|
|
+ .ThenBy(item => item.platform)
|
|
|
|
|
+ .ThenBy(item => item.risk_strategy)
|
|
|
|
|
+ .ThenBy(item => item.launch_scene)
|
|
|
|
|
+ .ThenBy(item => item.account_id)
|
|
|
|
|
+ .ToList();
|
|
|
|
|
+
|
|
|
|
|
+ int count = list.Count;
|
|
|
|
|
+ list = list
|
|
|
|
|
+ .Skip((page - 1) * size)
|
|
|
|
|
+ .Take(size)
|
|
|
|
|
+ .ToList();
|
|
|
|
|
+
|
|
|
|
|
+ return new TrackParseMetricReportResult
|
|
|
|
|
+ {
|
|
|
|
|
+ list = list,
|
|
|
|
|
+ count = getTotal ? count : list.Count
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static async Task<List<TrackParseMetricHourlyDTO>> GetParseMetricHourlyReportAsync(
|
|
|
|
|
+ DateTime targetDate,
|
|
|
|
|
+ string platform,
|
|
|
|
|
+ string riskStrategy,
|
|
|
|
|
+ int? launchScene = null,
|
|
|
|
|
+ int accountId = 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ targetDate = targetDate.Date;
|
|
|
|
|
+ platform = NormalizeParseMetricPlatform(platform);
|
|
|
|
|
+ riskStrategy = NormalizeReportScene(riskStrategy);
|
|
|
|
|
+ accountId = Math.Max(accountId, 0);
|
|
|
|
|
+
|
|
|
|
|
+ var list = new List<TrackParseMetricHourlyDTO>();
|
|
|
|
|
+ string dateStr = targetDate.ToString("yyyyMMdd");
|
|
|
|
|
+ for (int hour = 0; hour < 24; hour++)
|
|
|
|
|
+ {
|
|
|
|
|
+ string hourStr = $"{dateStr}{hour:00}";
|
|
|
|
|
+ string indexKey = BuildParseMetricIndexKey("hour", hourStr);
|
|
|
|
|
+ string[] members = await TkLogCore.GetTotalKeysAsync(indexKey) ?? [];
|
|
|
|
|
+ long requestCount = 0;
|
|
|
|
|
+ long callCount = 0;
|
|
|
|
|
+ long successCount = 0;
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var member in members)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!TryParseParseMetricIndexValue(member, out var dimension)) continue;
|
|
|
|
|
+ if (!IsMatchedParseMetricDimension(dimension, accountId, false, platform, riskStrategy, launchScene)) continue;
|
|
|
|
|
+
|
|
|
|
|
+ var requestTask = TkLogCore.GetTotalAsync(BuildParseMetricCountKey(ParseMetricRequest, "hour", hourStr, dimension.Platform, dimension.RiskStrategy, dimension.LaunchScene, dimension.AccountId));
|
|
|
|
|
+ var callTask = TkLogCore.GetTotalAsync(BuildParseMetricCountKey(ParseMetricCall, "hour", hourStr, dimension.Platform, dimension.RiskStrategy, dimension.LaunchScene, dimension.AccountId));
|
|
|
|
|
+ var successTask = TkLogCore.GetTotalAsync(BuildParseMetricCountKey(ParseMetricSuccess, "hour", hourStr, dimension.Platform, dimension.RiskStrategy, dimension.LaunchScene, dimension.AccountId));
|
|
|
|
|
+ await Task.WhenAll(requestTask, callTask, successTask);
|
|
|
|
|
+
|
|
|
|
|
+ requestCount += requestTask.Result;
|
|
|
|
|
+ callCount += callTask.Result;
|
|
|
|
|
+ successCount += successTask.Result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ list.Add(new TrackParseMetricHourlyDTO
|
|
|
|
|
+ {
|
|
|
|
|
+ report_date = targetDate,
|
|
|
|
|
+ platform = platform,
|
|
|
|
|
+ typename = ParseMetricTypeName,
|
|
|
|
|
+ risk_strategy = riskStrategy,
|
|
|
|
|
+ launch_scene = launchScene ?? -1,
|
|
|
|
|
+ account_id = accountId,
|
|
|
|
|
+ hour = hour,
|
|
|
|
|
+ request_count = SafeInt(requestCount),
|
|
|
|
|
+ call_count = SafeInt(callCount),
|
|
|
|
|
+ success_count = SafeInt(successCount)
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return list;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static async Task<TrackParseMetricBackfillResult> BackfillParseMetricCountersAsync(
|
|
|
|
|
+ DateTime targetDate,
|
|
|
|
|
+ string platform = "",
|
|
|
|
|
+ string riskStrategy = "",
|
|
|
|
|
+ int? launchScene = null)
|
|
|
|
|
+ {
|
|
|
|
|
+ targetDate = targetDate.Date;
|
|
|
|
|
+ platform = NormalizeParseMetricPlatform(platform);
|
|
|
|
|
+ riskStrategy = NormalizeReportScene(riskStrategy);
|
|
|
|
|
+ bool hasPlatformFilter = !string.IsNullOrWhiteSpace(platform);
|
|
|
|
|
+ bool hasRiskStrategyFilter = !string.IsNullOrWhiteSpace(riskStrategy);
|
|
|
|
|
+ bool hasLaunchSceneFilter = launchScene.HasValue;
|
|
|
|
|
+ bool hasDimensionFilter = hasPlatformFilter || hasRiskStrategyFilter || hasLaunchSceneFilter;
|
|
|
|
|
+ var result = new TrackParseMetricBackfillResult
|
|
|
|
|
+ {
|
|
|
|
|
+ report_date = targetDate.ToString("yyyy-MM-dd")
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ var allSources = new[]
|
|
|
|
|
+ {
|
|
|
|
|
+ new ParseMetricBackfillSource("tb", $"tk_parse_logs_{targetDate:yyyyMMdd}"),
|
|
|
|
|
+ new ParseMetricBackfillSource("jd", $"jd_parse_logs_{targetDate:yyyyMMdd}"),
|
|
|
|
|
+ new ParseMetricBackfillSource("pdd", $"pdd_parse_logs_{targetDate:yyyyMMdd}")
|
|
|
|
|
+ };
|
|
|
|
|
+ var sources = hasDimensionFilter
|
|
|
|
|
+ ? allSources.Where(source => !hasPlatformFilter || string.Equals(source.Platform, platform, StringComparison.OrdinalIgnoreCase)).ToArray()
|
|
|
|
|
+ : allSources;
|
|
|
|
|
+
|
|
|
|
|
+ using var conn = DBContext.GetOpenConnection();
|
|
|
|
|
+ DateTime start = targetDate;
|
|
|
|
|
+ DateTime end = targetDate.AddDays(1);
|
|
|
|
|
+ result.redis_cleared_keys = hasDimensionFilter
|
|
|
|
|
+ ? await ClearParseMetricCountersForDimensionAsync(targetDate, platform, riskStrategy, launchScene)
|
|
|
|
|
+ : await ClearParseMetricCountersForDateAsync(targetDate);
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var source in sources)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!TableExists(conn, source.TableName))
|
|
|
|
|
+ {
|
|
|
|
|
+ result.details.Add(new { platform = source.Platform, table = source.TableName, exists = false, rows = 0, dimensions = 0 });
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const string riskStrategySql = "COALESCE(NULLIF(TRIM(riskStrategy), ''), '')";
|
|
|
|
|
+ const string rawLaunchSceneSql = "COALESCE(launchScene, -1)";
|
|
|
|
|
+ string launchSceneSql = $"CASE WHEN LOWER({riskStrategySql}) IN ('os','tbpush','brwsimilar','icon') AND {rawLaunchSceneSql}=-1 THEN 0 ELSE {rawLaunchSceneSql} END";
|
|
|
|
|
+ var dimensionWhereItems = new List<string>();
|
|
|
|
|
+ if (hasRiskStrategyFilter) dimensionWhereItems.Add($"{riskStrategySql}=@riskStrategy");
|
|
|
|
|
+ if (hasLaunchSceneFilter) dimensionWhereItems.Add($"{launchSceneSql}=@launchScene");
|
|
|
|
|
+ string dimensionWhere = dimensionWhereItems.Count > 0
|
|
|
|
|
+ ? "\n AND " + string.Join("\n AND ", dimensionWhereItems)
|
|
|
|
|
+ : string.Empty;
|
|
|
|
|
+ string sql = $@"
|
|
|
|
|
+SELECT
|
|
|
|
|
+ {riskStrategySql} AS risk_strategy,
|
|
|
|
|
+ {launchSceneSql} AS launch_scene,
|
|
|
|
|
+ COALESCE(accountId, 0) AS account_id,
|
|
|
|
|
+ DATE_FORMAT(create_time, '%Y%m%d%H') AS hour_key,
|
|
|
|
|
+ CAST(COUNT(1) AS SIGNED) AS call_count,
|
|
|
|
|
+ CAST(SUM(CASE WHEN success=1 THEN 1 ELSE 0 END) AS SIGNED) AS success_count
|
|
|
|
|
+FROM `{source.TableName}`
|
|
|
|
|
+WHERE create_time>=@start AND create_time<@end
|
|
|
|
|
+{dimensionWhere}
|
|
|
|
|
+GROUP BY
|
|
|
|
|
+ {riskStrategySql},
|
|
|
|
|
+ {launchSceneSql},
|
|
|
|
|
+ COALESCE(accountId, 0),
|
|
|
|
|
+ DATE_FORMAT(create_time, '%Y%m%d%H')";
|
|
|
|
|
+
|
|
|
|
|
+ var args = new DynamicParameters();
|
|
|
|
|
+ args.Add("start", start);
|
|
|
|
|
+ args.Add("end", end);
|
|
|
|
|
+ if (hasRiskStrategyFilter) args.Add("riskStrategy", riskStrategy);
|
|
|
|
|
+ if (hasLaunchSceneFilter) args.Add("launchScene", launchScene!.Value);
|
|
|
|
|
+
|
|
|
|
|
+ var rows = SqlMapper.Query<ParseMetricBackfillRow>(conn, sql, args).ToList();
|
|
|
|
|
+ int mysqlRows = rows.Sum(row => SafeInt(row.call_count));
|
|
|
|
|
+ int dimensions = ApplyParseMetricBackfillRows(source.Platform, targetDate, rows);
|
|
|
|
|
+
|
|
|
|
|
+ result.mysql_rows += mysqlRows;
|
|
|
|
|
+ result.redis_dimensions += dimensions;
|
|
|
|
|
+ result.details.Add(new { platform = source.Platform, table = source.TableName, exists = true, rows = mysqlRows, dimensions });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await Task.CompletedTask;
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Task<bool> IncrementParseMetricAsync(string metric, string platform, string riskStrategy, int launchScene, int accountId = 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ AddParseMetricCount(metric, DateTime.Now, platform, riskStrategy, launchScene, 0, 1);
|
|
|
|
|
+ if (accountId > 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ AddParseMetricCount(metric, DateTime.Now, platform, riskStrategy, launchScene, accountId, 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ return Task.FromResult(true);
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ _ = new LoggerLibrary("TracksCore", "RecordParseMetric")
|
|
|
|
|
+ .Info(ex.Message, ex.StackTrace)
|
|
|
|
|
+ .SaveAsync();
|
|
|
|
|
+ return Task.FromResult(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Task<bool> IncrementParseAccountMetricAsync(string metric, string platform, string riskStrategy, int launchScene, int accountId)
|
|
|
|
|
+ {
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ accountId = Math.Max(accountId, 0);
|
|
|
|
|
+ if (accountId <= 0) return Task.FromResult(false);
|
|
|
|
|
+
|
|
|
|
|
+ AddParseMetricCount(metric, DateTime.Now, platform, riskStrategy, launchScene, accountId, 1);
|
|
|
|
|
+ return Task.FromResult(true);
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ _ = new LoggerLibrary("TracksCore", "RecordParseAccountMetric")
|
|
|
|
|
+ .Info(ex.Message, ex.StackTrace)
|
|
|
|
|
+ .SaveAsync();
|
|
|
|
|
+ return Task.FromResult(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Task<bool> IncrementParseResultMetricAsync(string platform, string riskStrategy, int launchScene, int accountId, bool success)
|
|
|
|
|
+ {
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ accountId = Math.Max(accountId, 0);
|
|
|
|
|
+
|
|
|
|
|
+ AddParseMetricCount(ParseMetricCall, DateTime.Now, platform, riskStrategy, launchScene, 0, 1);
|
|
|
|
|
+ if (success) AddParseMetricCount(ParseMetricSuccess, DateTime.Now, platform, riskStrategy, launchScene, 0, 1);
|
|
|
|
|
+
|
|
|
|
|
+ if (accountId > 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ AddParseMetricCount(ParseMetricCall, DateTime.Now, platform, riskStrategy, launchScene, accountId, 1);
|
|
|
|
|
+ if (success) AddParseMetricCount(ParseMetricSuccess, DateTime.Now, platform, riskStrategy, launchScene, accountId, 1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return Task.FromResult(true);
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ _ = new LoggerLibrary("TracksCore", "RecordParseResultMetric")
|
|
|
|
|
+ .Info(ex.Message, ex.StackTrace)
|
|
|
|
|
+ .SaveAsync();
|
|
|
|
|
+ return Task.FromResult(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static int ApplyParseMetricBackfillRows(string platform, DateTime targetDate, List<ParseMetricBackfillRow> rows)
|
|
|
|
|
+ {
|
|
|
|
|
+ string dateStr = targetDate.ToString("yyyyMMdd");
|
|
|
|
|
+ int dimensions = 0;
|
|
|
|
|
+
|
|
|
|
|
+ var dailyGroups = rows.GroupBy(row => new
|
|
|
|
|
+ {
|
|
|
|
|
+ RiskStrategy = NormalizeReportScene(row.risk_strategy),
|
|
|
|
|
+ LaunchScene = NormalizeParseMetricLaunchScene(row.risk_strategy, row.launch_scene)
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var group in dailyGroups)
|
|
|
|
|
+ {
|
|
|
|
|
+ int callCount = group.Sum(row => SafeInt(row.call_count));
|
|
|
|
|
+ int successCount = group.Sum(row => SafeInt(row.success_count));
|
|
|
|
|
+ SetParseMetricCount(ParseMetricCall, "daily", dateStr, platform, group.Key.RiskStrategy, group.Key.LaunchScene, 0, callCount);
|
|
|
|
|
+ SetParseMetricCount(ParseMetricSuccess, "daily", dateStr, platform, group.Key.RiskStrategy, group.Key.LaunchScene, 0, successCount);
|
|
|
|
|
+ dimensions++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var dailyAccountGroups = rows
|
|
|
|
|
+ .Where(row => row.account_id > 0)
|
|
|
|
|
+ .GroupBy(row => new
|
|
|
|
|
+ {
|
|
|
|
|
+ RiskStrategy = NormalizeReportScene(row.risk_strategy),
|
|
|
|
|
+ launch_scene = NormalizeParseMetricLaunchScene(row.risk_strategy, row.launch_scene),
|
|
|
|
|
+ row.account_id
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var group in dailyAccountGroups)
|
|
|
|
|
+ {
|
|
|
|
|
+ int callCount = group.Sum(row => SafeInt(row.call_count));
|
|
|
|
|
+ int successCount = group.Sum(row => SafeInt(row.success_count));
|
|
|
|
|
+ SetParseMetricCount(ParseMetricRequest, "daily", dateStr, platform, group.Key.RiskStrategy, group.Key.launch_scene, group.Key.account_id, callCount);
|
|
|
|
|
+ SetParseMetricCount(ParseMetricCall, "daily", dateStr, platform, group.Key.RiskStrategy, group.Key.launch_scene, group.Key.account_id, callCount);
|
|
|
|
|
+ SetParseMetricCount(ParseMetricSuccess, "daily", dateStr, platform, group.Key.RiskStrategy, group.Key.launch_scene, group.Key.account_id, successCount);
|
|
|
|
|
+ dimensions++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var hourlyGroups = rows.GroupBy(row => new
|
|
|
|
|
+ {
|
|
|
|
|
+ RiskStrategy = NormalizeReportScene(row.risk_strategy),
|
|
|
|
|
+ launch_scene = NormalizeParseMetricLaunchScene(row.risk_strategy, row.launch_scene),
|
|
|
|
|
+ row.hour_key
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var group in hourlyGroups)
|
|
|
|
|
+ {
|
|
|
|
|
+ int callCount = group.Sum(row => SafeInt(row.call_count));
|
|
|
|
|
+ int successCount = group.Sum(row => SafeInt(row.success_count));
|
|
|
|
|
+ SetParseMetricCount(ParseMetricCall, "hour", group.Key.hour_key, platform, group.Key.RiskStrategy, group.Key.launch_scene, 0, callCount);
|
|
|
|
|
+ SetParseMetricCount(ParseMetricSuccess, "hour", group.Key.hour_key, platform, group.Key.RiskStrategy, group.Key.launch_scene, 0, successCount);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var hourlyAccountGroups = rows
|
|
|
|
|
+ .Where(row => row.account_id > 0)
|
|
|
|
|
+ .GroupBy(row => new
|
|
|
|
|
+ {
|
|
|
|
|
+ RiskStrategy = NormalizeReportScene(row.risk_strategy),
|
|
|
|
|
+ launch_scene = NormalizeParseMetricLaunchScene(row.risk_strategy, row.launch_scene),
|
|
|
|
|
+ row.account_id,
|
|
|
|
|
+ row.hour_key
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var group in hourlyAccountGroups)
|
|
|
|
|
+ {
|
|
|
|
|
+ int callCount = group.Sum(row => SafeInt(row.call_count));
|
|
|
|
|
+ int successCount = group.Sum(row => SafeInt(row.success_count));
|
|
|
|
|
+ SetParseMetricCount(ParseMetricRequest, "hour", group.Key.hour_key, platform, group.Key.RiskStrategy, group.Key.launch_scene, group.Key.account_id, callCount);
|
|
|
|
|
+ SetParseMetricCount(ParseMetricCall, "hour", group.Key.hour_key, platform, group.Key.RiskStrategy, group.Key.launch_scene, group.Key.account_id, callCount);
|
|
|
|
|
+ SetParseMetricCount(ParseMetricSuccess, "hour", group.Key.hour_key, platform, group.Key.RiskStrategy, group.Key.launch_scene, group.Key.account_id, successCount);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return dimensions;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void AddParseMetricCount(string metric, DateTime metricTime, string platform, string riskStrategy, int launchScene, int accountId, int count)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (count <= 0) return;
|
|
|
|
|
+
|
|
|
|
|
+ QueueParseMetricCount(metric, "daily", metricTime.ToString("yyyyMMdd"), platform, riskStrategy, launchScene, accountId, count);
|
|
|
|
|
+ QueueParseMetricCount(metric, "hour", metricTime.ToString("yyyyMMddHH"), platform, riskStrategy, launchScene, accountId, count);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void SetParseMetricCount(string metric, string bucketType, string bucketValue, string platform, string riskStrategy, int launchScene, int accountId, int count)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (count < 0) return;
|
|
|
|
|
+ SetOrIncrementParseMetricCount(metric, bucketType, bucketValue, platform, riskStrategy, launchScene, accountId, count, increment: false);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void SetOrIncrementParseMetricCount(string metric, string bucketType, string bucketValue, string platform, string riskStrategy, int launchScene, int accountId, long count, bool increment)
|
|
|
|
|
+ {
|
|
|
|
|
+ platform = Normalize(platform).ToLowerInvariant();
|
|
|
|
|
+ riskStrategy = NormalizeReportScene(riskStrategy);
|
|
|
|
|
+ launchScene = NormalizeParseMetricLaunchScene(riskStrategy, launchScene);
|
|
|
|
|
+ accountId = Math.Max(accountId, 0);
|
|
|
|
|
+
|
|
|
|
|
+ string countKey = BuildParseMetricCountKey(metric, bucketType, bucketValue, platform, riskStrategy, launchScene, accountId);
|
|
|
|
|
+ string indexKey = BuildParseMetricIndexKey(bucketType, bucketValue);
|
|
|
|
|
+ string indexValue = BuildParseMetricIndexValue(platform, riskStrategy, launchScene, accountId);
|
|
|
|
|
+ int expireSeconds = bucketType == "hour" ? HourlyExpireSeconds : DailyExpireSeconds;
|
|
|
|
|
+
|
|
|
|
|
+ if (increment)
|
|
|
|
|
+ {
|
|
|
|
|
+ IncrementParseMetricRedisKey(countKey, count);
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ RedisHelper.Set(countKey, count, expireSeconds);
|
|
|
|
|
+ }
|
|
|
|
|
+ RedisHelper.Expire(countKey, expireSeconds);
|
|
|
|
|
+ RedisHelper.SAdd(indexKey, indexValue);
|
|
|
|
|
+ RedisHelper.Expire(indexKey, expireSeconds);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static int FlushParseMetricCounters()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (Interlocked.Exchange(ref ParseMetricFlushRunning, 1) == 1) return 0;
|
|
|
|
|
+
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ int flushed = 0;
|
|
|
|
|
+ foreach (var item in PendingParseMetricCounters.ToArray())
|
|
|
|
|
+ {
|
|
|
|
|
+ long count = Interlocked.Exchange(ref item.Value.Count, 0);
|
|
|
|
|
+ if (count <= 0) continue;
|
|
|
|
|
+
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ SetOrIncrementParseMetricCount(
|
|
|
|
|
+ item.Key.Metric,
|
|
|
|
|
+ item.Key.BucketType,
|
|
|
|
|
+ item.Key.BucketValue,
|
|
|
|
|
+ item.Key.Platform,
|
|
|
|
|
+ item.Key.RiskStrategy,
|
|
|
|
|
+ item.Key.LaunchScene,
|
|
|
|
|
+ item.Key.AccountId,
|
|
|
|
|
+ count,
|
|
|
|
|
+ increment: true);
|
|
|
|
|
+ flushed++;
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ Interlocked.Add(ref item.Value.Count, count);
|
|
|
|
|
+ _ = new LoggerLibrary("TracksCore", "FlushParseMetricCounters")
|
|
|
|
|
+ .Info(ex.Message, ex.StackTrace)
|
|
|
|
|
+ .SaveAsync();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return flushed;
|
|
|
|
|
+ }
|
|
|
|
|
+ finally
|
|
|
|
|
+ {
|
|
|
|
|
+ Interlocked.Exchange(ref ParseMetricFlushRunning, 0);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void QueueParseMetricCount(string metric, string bucketType, string bucketValue, string platform, string riskStrategy, int launchScene, int accountId, int count)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (count <= 0) return;
|
|
|
|
|
+
|
|
|
|
|
+ var key = new ParseMetricCounterKey(
|
|
|
|
|
+ metric,
|
|
|
|
|
+ bucketType,
|
|
|
|
|
+ bucketValue,
|
|
|
|
|
+ Normalize(platform).ToLowerInvariant(),
|
|
|
|
|
+ NormalizeReportScene(riskStrategy),
|
|
|
|
|
+ NormalizeParseMetricLaunchScene(riskStrategy, launchScene),
|
|
|
|
|
+ Math.Max(accountId, 0));
|
|
|
|
|
+ var counter = PendingParseMetricCounters.GetOrAdd(key, _ => new ParseMetricCounter());
|
|
|
|
|
+ Interlocked.Add(ref counter.Count, count);
|
|
|
|
|
+ EnsureParseMetricFlushTimer();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void EnsureParseMetricFlushTimer()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (ParseMetricFlushTimer != null) return;
|
|
|
|
|
+
|
|
|
|
|
+ lock (ParseMetricFlushTimerLock)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (ParseMetricFlushTimer != null) return;
|
|
|
|
|
+
|
|
|
|
|
+ ParseMetricFlushTimer = new Timer(
|
|
|
|
|
+ _ => FlushParseMetricCounters(),
|
|
|
|
|
+ null,
|
|
|
|
|
+ ParseMetricFlushIntervalMs,
|
|
|
|
|
+ ParseMetricFlushIntervalMs);
|
|
|
|
|
+
|
|
|
|
|
+ if (Interlocked.Exchange(ref ParseMetricLifecycleHooked, 1) == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ AppDomain.CurrentDomain.ProcessExit += (_, _) => FlushParseMetricCounters();
|
|
|
|
|
+ AppDomain.CurrentDomain.UnhandledException += (_, _) => FlushParseMetricCounters();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void IncrementParseMetricRedisKey(string key, long count)
|
|
|
|
|
+ {
|
|
|
|
|
+ while (count > 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ int delta = count > int.MaxValue ? int.MaxValue : (int)count;
|
|
|
|
|
+ RedisHelper.IncrBy(key, delta);
|
|
|
|
|
+ count -= delta;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static async Task<int> ClearParseMetricCountersForDateAsync(DateTime targetDate)
|
|
|
|
|
+ {
|
|
|
|
|
+ string dateStr = targetDate.ToString("yyyyMMdd");
|
|
|
|
|
+ int cleared = await ClearParseMetricBucketAsync("daily", dateStr);
|
|
|
|
|
+
|
|
|
|
|
+ for (int hour = 0; hour < 24; hour++)
|
|
|
|
|
+ {
|
|
|
|
|
+ cleared += await ClearParseMetricBucketAsync("hour", $"{dateStr}{hour:00}");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return cleared;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static async Task<int> ClearParseMetricCountersForDimensionAsync(
|
|
|
|
|
+ DateTime targetDate,
|
|
|
|
|
+ string platform,
|
|
|
|
|
+ string riskStrategy,
|
|
|
|
|
+ int? launchScene)
|
|
|
|
|
+ {
|
|
|
|
|
+ string dateStr = targetDate.ToString("yyyyMMdd");
|
|
|
|
|
+ int cleared = await ClearParseMetricBucketAsync(
|
|
|
|
|
+ "daily",
|
|
|
|
|
+ dateStr,
|
|
|
|
|
+ dimension => IsSameParseMetricDimension(dimension, platform, riskStrategy, launchScene));
|
|
|
|
|
+
|
|
|
|
|
+ for (int hour = 0; hour < 24; hour++)
|
|
|
|
|
+ {
|
|
|
|
|
+ cleared += await ClearParseMetricBucketAsync(
|
|
|
|
|
+ "hour",
|
|
|
|
|
+ $"{dateStr}{hour:00}",
|
|
|
|
|
+ dimension => IsSameParseMetricDimension(dimension, platform, riskStrategy, launchScene));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return cleared;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static async Task<int> ClearParseMetricBucketAsync(
|
|
|
|
|
+ string bucketType,
|
|
|
|
|
+ string bucketValue,
|
|
|
|
|
+ Func<ParseMetricDimension, bool>? predicate = null)
|
|
|
|
|
+ {
|
|
|
|
|
+ string indexKey = BuildParseMetricIndexKey(bucketType, bucketValue);
|
|
|
|
|
+ string[] members = await TkLogCore.GetTotalKeysAsync(indexKey) ?? [];
|
|
|
|
|
+ int cleared = 0;
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var member in members)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!TryParseParseMetricIndexValue(member, out var dimension)) continue;
|
|
|
|
|
+ if (predicate != null && !predicate(dimension)) continue;
|
|
|
|
|
+
|
|
|
|
|
+ RedisHelper.Del(BuildParseMetricCountKey(ParseMetricCall, bucketType, bucketValue, dimension.Platform, dimension.RiskStrategy, dimension.LaunchScene, dimension.AccountId));
|
|
|
|
|
+ RedisHelper.Del(BuildParseMetricCountKey(ParseMetricSuccess, bucketType, bucketValue, dimension.Platform, dimension.RiskStrategy, dimension.LaunchScene, dimension.AccountId));
|
|
|
|
|
+ cleared += 2;
|
|
|
|
|
+ if (dimension.AccountId > 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ RedisHelper.Del(BuildParseMetricCountKey(ParseMetricRequest, bucketType, bucketValue, dimension.Platform, dimension.RiskStrategy, dimension.LaunchScene, dimension.AccountId));
|
|
|
|
|
+ cleared++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return cleared;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static string BuildParseMetricCountKey(string metric, string bucketType, string bucketValue, string platform, string riskStrategy, int launchScene, int accountId = 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ string key = $"{RedisPrefix}:parse_metric:{bucketType}:{metric}:{bucketValue}:{EncodeIndexPart(platform)}:{EncodeIndexPart(riskStrategy)}:{launchScene}";
|
|
|
|
|
+ if (accountId > 0) key += $":account:{accountId}";
|
|
|
|
|
+ return key;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static string BuildParseMetricIndexKey(string bucketType, string bucketValue)
|
|
|
|
|
+ {
|
|
|
|
|
+ return $"{RedisPrefix}:parse_metric:{bucketType}:index:{bucketValue}";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static string BuildParseMetricIndexValue(string platform, string riskStrategy, int launchScene, int accountId = 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ string value = $"{EncodeIndexPart(platform)}|{EncodeIndexPart(riskStrategy)}|{launchScene}";
|
|
|
|
|
+ if (accountId > 0) value += $"|{accountId}";
|
|
|
|
|
+ return value;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static bool TryParseParseMetricIndexValue(string value, out ParseMetricDimension dimension)
|
|
|
|
|
+ {
|
|
|
|
|
+ dimension = new ParseMetricDimension();
|
|
|
|
|
+ var parts = (value ?? string.Empty).Split('|');
|
|
|
|
|
+ if (parts.Length < 3 || parts.Length > 4) return false;
|
|
|
|
|
+ if (!int.TryParse(parts[2], out int launchScene)) return false;
|
|
|
|
|
+
|
|
|
|
|
+ int accountId = 0;
|
|
|
|
|
+ if (parts.Length == 4 && (!int.TryParse(parts[3], out accountId) || accountId <= 0)) return false;
|
|
|
|
|
+
|
|
|
|
|
+ dimension = new ParseMetricDimension
|
|
|
|
|
+ {
|
|
|
|
|
+ Platform = DecodeIndexPart(parts[0]),
|
|
|
|
|
+ RiskStrategy = DecodeIndexPart(parts[1]),
|
|
|
|
|
+ LaunchScene = launchScene,
|
|
|
|
|
+ AccountId = accountId
|
|
|
|
|
+ };
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static bool IsMatchedParseMetricDimension(
|
|
|
|
|
+ ParseMetricDimension dimension,
|
|
|
|
|
+ int accountId,
|
|
|
|
|
+ bool accountBreakdownOnly,
|
|
|
|
|
+ string platform,
|
|
|
|
|
+ string riskStrategy,
|
|
|
|
|
+ int? launchScene)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (accountBreakdownOnly)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (dimension.AccountId <= 0) return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (accountId > 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (dimension.AccountId != accountId) return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (dimension.AccountId > 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!string.IsNullOrWhiteSpace(platform) && !string.Equals(NormalizeParseMetricPlatform(platform), dimension.Platform, StringComparison.OrdinalIgnoreCase)) return false;
|
|
|
|
|
+ if (!string.IsNullOrWhiteSpace(riskStrategy) && !string.Equals(NormalizeReportScene(riskStrategy), dimension.RiskStrategy, StringComparison.OrdinalIgnoreCase)) return false;
|
|
|
|
|
+ if (launchScene.HasValue && NormalizeParseMetricLaunchScene(dimension.RiskStrategy, launchScene.Value) != NormalizeParseMetricLaunchScene(dimension.RiskStrategy, dimension.LaunchScene)) return false;
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static bool IsSameParseMetricDimension(
|
|
|
|
|
+ ParseMetricDimension dimension,
|
|
|
|
|
+ string platform,
|
|
|
|
|
+ string riskStrategy,
|
|
|
|
|
+ int? launchScene)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!string.IsNullOrWhiteSpace(platform)
|
|
|
|
|
+ && !string.Equals(NormalizeParseMetricPlatform(platform), dimension.Platform, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
+ {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!string.IsNullOrWhiteSpace(riskStrategy)
|
|
|
|
|
+ && !string.Equals(NormalizeReportScene(riskStrategy), dimension.RiskStrategy, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
+ {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (launchScene.HasValue
|
|
|
|
|
+ && NormalizeParseMetricLaunchScene(dimension.RiskStrategy, launchScene.Value) != NormalizeParseMetricLaunchScene(dimension.RiskStrategy, dimension.LaunchScene))
|
|
|
|
|
+ {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static ParseMetricDimension ResolveParseMetricDimension(string channel, string riskStrategy, int launchScene)
|
|
|
|
|
+ {
|
|
|
|
|
+ channel = Normalize(channel).ToLowerInvariant();
|
|
|
|
|
+ riskStrategy = NormalizeReportScene(riskStrategy);
|
|
|
|
|
+
|
|
|
|
|
+ switch (channel)
|
|
|
|
|
+ {
|
|
|
|
|
+ case "tbpush":
|
|
|
|
|
+ case "brwsimilar":
|
|
|
|
|
+ case "icon":
|
|
|
|
|
+ riskStrategy = channel;
|
|
|
|
|
+ launchScene = 0;
|
|
|
|
|
+ channel = "tb";
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ switch (riskStrategy)
|
|
|
|
|
+ {
|
|
|
|
|
+ case "tbpush":
|
|
|
|
|
+ case "brwsimilar":
|
|
|
|
|
+ case "icon":
|
|
|
|
|
+ if (launchScene == -1) launchScene = 0;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ launchScene = NormalizeParseMetricLaunchScene(riskStrategy, launchScene);
|
|
|
|
|
+
|
|
|
|
|
+ return new ParseMetricDimension
|
|
|
|
|
+ {
|
|
|
|
|
+ Platform = channel,
|
|
|
|
|
+ RiskStrategy = riskStrategy,
|
|
|
|
|
+ LaunchScene = launchScene,
|
|
|
|
|
+ AccountId = 0
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static int NormalizeParseMetricLaunchScene(string riskStrategy, int launchScene)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (launchScene != -1) return launchScene;
|
|
|
|
|
+
|
|
|
|
|
+ return NormalizeReportScene(riskStrategy).ToLowerInvariant() switch
|
|
|
|
|
+ {
|
|
|
|
|
+ "os" or "tbpush" or "brwsimilar" or "icon" => 0,
|
|
|
|
|
+ _ => launchScene
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static string NormalizeParseMetricPlatform(string platform)
|
|
|
|
|
+ {
|
|
|
|
|
+ string value = Normalize(platform).ToLowerInvariant();
|
|
|
|
|
+ return value switch
|
|
|
|
|
+ {
|
|
|
|
|
+ "淘宝" or "taobao" or "tb" or "1" => "tb",
|
|
|
|
|
+ "京东" or "jd" or "13" => "jd",
|
|
|
|
|
+ "拼多多" or "pdd" or "9" => "pdd",
|
|
|
|
|
+ _ => value
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static bool TableExists(IDbConnection conn, string tableName)
|
|
|
|
|
+ {
|
|
|
|
|
+ const string sql = @"
|
|
|
|
|
+SELECT COUNT(1)
|
|
|
|
|
+FROM information_schema.TABLES
|
|
|
|
|
+WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=@tableName";
|
|
|
|
|
+ return conn.ExecuteScalar<int>(sql, new { tableName }) > 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static int SafeInt(long value)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (value <= 0) return 0;
|
|
|
|
|
+ return value > int.MaxValue ? int.MaxValue : (int)value;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private sealed record ParseMetricCounterKey(
|
|
|
|
|
+ string Metric,
|
|
|
|
|
+ string BucketType,
|
|
|
|
|
+ string BucketValue,
|
|
|
|
|
+ string Platform,
|
|
|
|
|
+ string RiskStrategy,
|
|
|
|
|
+ int LaunchScene,
|
|
|
|
|
+ int AccountId);
|
|
|
|
|
+
|
|
|
|
|
+ private sealed class ParseMetricCounter
|
|
|
|
|
+ {
|
|
|
|
|
+ public long Count = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private sealed class ParseMetricDimension
|
|
|
|
|
+ {
|
|
|
|
|
+ public string Platform { get; set; } = string.Empty;
|
|
|
|
|
+ public string RiskStrategy { get; set; } = string.Empty;
|
|
|
|
|
+ public int LaunchScene { get; set; } = -1;
|
|
|
|
|
+ public int AccountId { get; set; } = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private sealed record ParseMetricBackfillSource(string Platform, string TableName);
|
|
|
|
|
+
|
|
|
|
|
+ private sealed class ParseMetricBackfillRow
|
|
|
|
|
+ {
|
|
|
|
|
+ public string risk_strategy { get; set; } = string.Empty;
|
|
|
|
|
+ public int launch_scene { get; set; } = -1;
|
|
|
|
|
+ public int account_id { get; set; } = 0;
|
|
|
|
|
+ public string hour_key { get; set; } = string.Empty;
|
|
|
|
|
+ public long call_count { get; set; } = 0;
|
|
|
|
|
+ public long success_count { get; set; } = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|