|
|
@@ -144,16 +144,18 @@ namespace molilian.core
|
|
|
/// <summary>
|
|
|
/// 曝光/点击触发:计入 Redis,失败不影响返回
|
|
|
/// </summary>
|
|
|
- public static Task<bool> TrackAsync(TrackLinkDTO link)
|
|
|
+ public static Task<bool> TrackAsync(TrackLinkDTO link, string scene = "")
|
|
|
{
|
|
|
|
|
|
string dateStr = DateTime.Now.ToString("yyyyMMdd");
|
|
|
string hourStr = DateTime.Now.ToString("yyyyMMddHH");
|
|
|
+ string metricScene = ResolveMetricScene(link, scene);
|
|
|
+ string indexValue = BuildMetricIndexValue(link.event_type, link.id, metricScene);
|
|
|
|
|
|
var metrics = new List<(string key, string indexKey, string indexValue, int expire)>
|
|
|
{
|
|
|
- ($"{RedisPrefix}:daily:{link.event_type}:{link.id}:{dateStr}", $"{RedisPrefix}:daily:index:{dateStr}:track", $"{link.event_type}|{link.id}", DailyExpireSeconds),
|
|
|
- ($"{RedisPrefix}:hour:{link.event_type}:{link.id}:{hourStr}", $"{RedisPrefix}:hour:index:{hourStr}:track", $"{link.event_type}|{link.id}", HourlyExpireSeconds),
|
|
|
+ (BuildDailyCountKey(link.event_type, link.id, dateStr, metricScene), $"{RedisPrefix}:daily:index:{dateStr}:track", indexValue, DailyExpireSeconds),
|
|
|
+ (BuildHourlyCountKey(link.event_type, link.id, hourStr, metricScene), $"{RedisPrefix}:hour:index:{hourStr}:track", indexValue, HourlyExpireSeconds),
|
|
|
};
|
|
|
|
|
|
try
|
|
|
@@ -188,19 +190,21 @@ namespace molilian.core
|
|
|
var indexMembers = await RedisHelper.SMembersAsync<string>(indexKey) ?? [];
|
|
|
if (indexMembers == null || indexMembers.Length == 0) return 0;
|
|
|
|
|
|
- int rows = 0;
|
|
|
+ var buckets = new Dictionary<string, DailyReportBucket>();
|
|
|
using var conn = DBContext.GetOpenConnection();
|
|
|
var linkCache = new Dictionary<int, TrackLinkDTO>();
|
|
|
foreach (var member in indexMembers)
|
|
|
{
|
|
|
var parts = member.Split('|');
|
|
|
- if (parts.Length != 2) continue;
|
|
|
+ if (parts.Length != 2 && parts.Length != 3) continue;
|
|
|
|
|
|
string eventType = NormalizeEventType(parts[0]);
|
|
|
if (!SupportEventTypes.Contains(eventType)) continue;
|
|
|
if (!int.TryParse(parts[1], out var trackId) || trackId <= 0) continue;
|
|
|
+ bool hasScenePart = parts.Length == 3;
|
|
|
+ string memberScene = hasScenePart ? DecodeIndexPart(parts[2]) : string.Empty;
|
|
|
|
|
|
- string countKey = $"{RedisPrefix}:daily:{eventType}:{trackId}:{dateStr}";
|
|
|
+ string countKey = BuildDailyCountKey(eventType, trackId, dateStr, hasScenePart ? memberScene : string.Empty);
|
|
|
int total = await RedisHelper.GetAsync<int>(countKey);
|
|
|
if (total <= 0) continue;
|
|
|
|
|
|
@@ -211,13 +215,32 @@ namespace molilian.core
|
|
|
if (link != null) linkCache[trackId] = link;
|
|
|
}
|
|
|
|
|
|
- await UpsertDailyReportAsync(conn, targetDate.Date, eventType, trackId, link, total);
|
|
|
+ string reportScene = ResolveMetricScene(link, memberScene);
|
|
|
+ string bucketKey = BuildMetricIndexValue(eventType, trackId, reportScene);
|
|
|
+ if (!buckets.TryGetValue(bucketKey, out var bucket))
|
|
|
+ {
|
|
|
+ bucket = new DailyReportBucket
|
|
|
+ {
|
|
|
+ EventType = eventType,
|
|
|
+ TrackId = trackId,
|
|
|
+ Scene = reportScene,
|
|
|
+ Link = link
|
|
|
+ };
|
|
|
+ buckets[bucketKey] = bucket;
|
|
|
+ }
|
|
|
+ bucket.Total += total;
|
|
|
+ }
|
|
|
+
|
|
|
+ int rows = 0;
|
|
|
+ foreach (var bucket in buckets.Values)
|
|
|
+ {
|
|
|
+ await UpsertDailyReportAsync(conn, targetDate.Date, bucket.EventType, bucket.TrackId, bucket.Scene, bucket.Link, bucket.Total);
|
|
|
rows++;
|
|
|
}
|
|
|
return rows;
|
|
|
}
|
|
|
|
|
|
- private static Task<int> UpsertDailyReportAsync(IDbConnection conn, DateTime reportDate, string eventType, int trackId, TrackLinkDTO link, int total)
|
|
|
+ private static Task<int> UpsertDailyReportAsync(IDbConnection conn, DateTime reportDate, string eventType, int trackId, string scene, TrackLinkDTO? link, int total)
|
|
|
{
|
|
|
const string sql = @"
|
|
|
INSERT INTO track_daily_report
|
|
|
@@ -239,14 +262,14 @@ ON DUPLICATE KEY UPDATE
|
|
|
trackId,
|
|
|
platform = link?.platform ?? string.Empty,
|
|
|
typename = link?.typename ?? string.Empty,
|
|
|
- scene = link?.scene ?? string.Empty,
|
|
|
+ scene,
|
|
|
uniqueId = link?.unique_id ?? string.Empty,
|
|
|
eventCount = total,
|
|
|
now = DateTime.Now
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- public static async Task<List<TrackHourlyReportDTO>> GetHourlyReportAsync(int trackId, DateTime targetDate)
|
|
|
+ public static async Task<List<TrackHourlyReportDTO>> GetHourlyReportAsync(int trackId, DateTime targetDate, string scene = "")
|
|
|
{
|
|
|
var result = new List<TrackHourlyReportDTO>();
|
|
|
if (trackId <= 0) return result;
|
|
|
@@ -257,12 +280,17 @@ ON DUPLICATE KEY UPDATE
|
|
|
string eventType = NormalizeEventType(link.event_type);
|
|
|
if (!SupportEventTypes.Contains(eventType)) return result;
|
|
|
|
|
|
+ string metricScene = ResolveMetricScene(link, scene);
|
|
|
+ string linkScene = ResolveMetricScene(link, string.Empty);
|
|
|
string dateStr = targetDate.ToString("yyyyMMdd");
|
|
|
for (int hour = 0; hour < 24; hour++)
|
|
|
{
|
|
|
string hourStr = $"{dateStr}{hour:00}";
|
|
|
- string countKey = $"{RedisPrefix}:hour:{eventType}:{trackId}:{hourStr}";
|
|
|
- int total = await RedisHelper.GetAsync<int>(countKey);
|
|
|
+ int total = await RedisHelper.GetAsync<int>(BuildHourlyCountKey(eventType, trackId, hourStr, metricScene));
|
|
|
+ if (!string.IsNullOrEmpty(metricScene) && metricScene == linkScene)
|
|
|
+ {
|
|
|
+ total += await RedisHelper.GetAsync<int>(BuildHourlyCountKey(eventType, trackId, hourStr, string.Empty));
|
|
|
+ }
|
|
|
|
|
|
result.Add(new TrackHourlyReportDTO
|
|
|
{
|
|
|
@@ -270,7 +298,7 @@ ON DUPLICATE KEY UPDATE
|
|
|
event_type = eventType,
|
|
|
platform = link.platform ?? string.Empty,
|
|
|
typename = link.typename ?? string.Empty,
|
|
|
- scene = link.scene ?? string.Empty,
|
|
|
+ scene = metricScene,
|
|
|
unique_id = link.unique_id ?? string.Empty,
|
|
|
report_date = targetDate.Date,
|
|
|
hour = hour,
|
|
|
@@ -284,28 +312,38 @@ ON DUPLICATE KEY UPDATE
|
|
|
public static string BuildPath(TrackType type, TkDataDTO result)
|
|
|
{
|
|
|
int trackId = type == TrackType.Click ? 1 : 2;
|
|
|
- if (result == null) return string.Empty;
|
|
|
- string unique_id = $"1|{result.itemId}_{result.mktId}";
|
|
|
- return BuildPath(trackId, unique_id);
|
|
|
- }
|
|
|
+ if (result == null) return string.Empty;
|
|
|
+ string unique_id = $"1|{result.itemId}_{result.mktId}";
|
|
|
+ return BuildPath(trackId, unique_id, GetTrackScene(result.parse_type, result.riskStrategy));
|
|
|
+ }
|
|
|
public static string BuildPath(TrackType type, JdDataDTO result)
|
|
|
- {
|
|
|
+ {
|
|
|
int trackId = type == TrackType.Click ? 19 : 20;
|
|
|
- if (result == null) return string.Empty;
|
|
|
- string unique_id = $"13|{result.shortLinkurl.UrlEncode()}";
|
|
|
- return BuildPath(trackId, unique_id);
|
|
|
- }
|
|
|
+ if (result == null) return string.Empty;
|
|
|
+ string unique_id = $"13|{result.shortLinkurl.UrlEncode()}";
|
|
|
+ return BuildPath(trackId, unique_id, GetTrackScene(result.parse_type, result.riskStrategy));
|
|
|
+ }
|
|
|
public static string BuildPath(TrackType type, PddDataDTO result)
|
|
|
- {
|
|
|
+ {
|
|
|
int trackId = type == TrackType.Click ? 21 : 22;
|
|
|
- if (result == null) return string.Empty;
|
|
|
- string unique_id = $"9|{result.shortLinkurl.UrlEncode()}";
|
|
|
- return BuildPath(trackId, unique_id);
|
|
|
+ if (result == null) return string.Empty;
|
|
|
+ string unique_id = $"9|{result.shortLinkurl.UrlEncode()}";
|
|
|
+ return BuildPath(trackId, unique_id, GetTrackScene(result.parse_type, result.riskStrategy));
|
|
|
}
|
|
|
-
|
|
|
- public static string BuildPath(int trackId, string unique_id = "")
|
|
|
+
|
|
|
+ public static string BuildPath(int trackId, string unique_id = "", string scene = "")
|
|
|
+ {
|
|
|
+ scene = NormalizeReportScene(scene);
|
|
|
+ string url = $"https://api.molilian.com/tracks/track?track_id={trackId}&unique_id={unique_id}";
|
|
|
+ if (!string.IsNullOrEmpty(scene)) url += $"&scene={scene.UrlEncode()}";
|
|
|
+ return url;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static string ResolveMetricScene(TrackLinkDTO? link, string scene = "")
|
|
|
{
|
|
|
- return $"https://api.molilian.com/tracks/track?track_id={trackId}&unique_id={unique_id}";
|
|
|
+ scene = NormalizeReportScene(scene);
|
|
|
+ if (!string.IsNullOrEmpty(scene)) return scene;
|
|
|
+ return NormalizeOrAll(link?.scene ?? string.Empty);
|
|
|
}
|
|
|
|
|
|
public static string GetLinkCacheKey(string eventType, string typename, string scene, string uniqueId)
|
|
|
@@ -411,6 +449,12 @@ ON DUPLICATE KEY UPDATE
|
|
|
return (value ?? string.Empty).Trim();
|
|
|
}
|
|
|
|
|
|
+ private static string NormalizeReportScene(string value)
|
|
|
+ {
|
|
|
+ var scene = Normalize(value);
|
|
|
+ return scene == "默认场景" ? string.Empty : scene;
|
|
|
+ }
|
|
|
+
|
|
|
private static string NormalizeOrAll(string value)
|
|
|
{
|
|
|
var result = Normalize(value);
|
|
|
@@ -421,7 +465,70 @@ ON DUPLICATE KEY UPDATE
|
|
|
{
|
|
|
return Normalize(value).ToLowerInvariant();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ private static string GetTrackScene(string parseType, string riskStrategy)
|
|
|
+ {
|
|
|
+ parseType = NormalizeReportScene(parseType);
|
|
|
+ if (!string.IsNullOrEmpty(parseType)) return parseType;
|
|
|
+
|
|
|
+ riskStrategy = NormalizeReportScene(riskStrategy);
|
|
|
+ if (!string.IsNullOrEmpty(riskStrategy)) return riskStrategy;
|
|
|
+
|
|
|
+ return DefaultDimensionValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static string BuildDailyCountKey(string eventType, int trackId, string dateStr, string scene)
|
|
|
+ {
|
|
|
+ eventType = NormalizeEventType(eventType);
|
|
|
+ scene = NormalizeReportScene(scene);
|
|
|
+ string key = $"{RedisPrefix}:daily:{eventType}:{trackId}:{dateStr}";
|
|
|
+ if (!string.IsNullOrEmpty(scene)) key += $":{EncodeIndexPart(scene)}";
|
|
|
+ return key;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static string BuildHourlyCountKey(string eventType, int trackId, string hourStr, string scene)
|
|
|
+ {
|
|
|
+ eventType = NormalizeEventType(eventType);
|
|
|
+ scene = NormalizeReportScene(scene);
|
|
|
+ string key = $"{RedisPrefix}:hour:{eventType}:{trackId}:{hourStr}";
|
|
|
+ if (!string.IsNullOrEmpty(scene)) key += $":{EncodeIndexPart(scene)}";
|
|
|
+ return key;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static string BuildMetricIndexValue(string eventType, int trackId, string scene)
|
|
|
+ {
|
|
|
+ eventType = NormalizeEventType(eventType);
|
|
|
+ scene = NormalizeReportScene(scene);
|
|
|
+ if (string.IsNullOrEmpty(scene)) return $"{eventType}|{trackId}";
|
|
|
+ return $"{eventType}|{trackId}|{EncodeIndexPart(scene)}";
|
|
|
+ }
|
|
|
+
|
|
|
+ private static string EncodeIndexPart(string value)
|
|
|
+ {
|
|
|
+ return Normalize(value).UrlEncode();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static string DecodeIndexPart(string value)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ return Normalize(value).UrlDecode();
|
|
|
+ }
|
|
|
+ catch
|
|
|
+ {
|
|
|
+ return Normalize(value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private sealed class DailyReportBucket
|
|
|
+ {
|
|
|
+ public string EventType { get; set; } = string.Empty;
|
|
|
+ public int TrackId { get; set; }
|
|
|
+ public string Scene { get; set; } = string.Empty;
|
|
|
+ public TrackLinkDTO? Link { get; set; }
|
|
|
+ public int Total { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
private static int GetTrackLinkId(IDbConnection conn, string eventType, string platform, string scene, string uniqueId)
|
|
|
{
|
|
|
try
|