|
|
@@ -5,6 +5,7 @@ using System.Net;
|
|
|
using System.Threading.Tasks;
|
|
|
using CSRedis;
|
|
|
using dodohold.core;
|
|
|
+using System.Text.Json;
|
|
|
using YunhuiKit;
|
|
|
|
|
|
namespace molilian.core
|
|
|
@@ -16,7 +17,10 @@ namespace molilian.core
|
|
|
private const int HourlyExpireSeconds = 7 * 86400; // keep a week of hourly buckets
|
|
|
private const string DefaultDimensionValue = "";
|
|
|
private const int LinkCacheExpireSeconds = 30 * 86400;
|
|
|
+ private const string TrackRequestLogKey = ":tracks:request:logs";
|
|
|
private static readonly HashSet<string> SupportEventTypes = new(StringComparer.OrdinalIgnoreCase) { "expose", "click" };
|
|
|
+ internal static int ExposeTrackId = 2;
|
|
|
+ internal static int ClickTrackId = 1;
|
|
|
|
|
|
/// <summary>
|
|
|
/// 生成一个监测链接(仅返回 path,不包含域名),同时落地到 track_links
|
|
|
@@ -66,7 +70,7 @@ namespace molilian.core
|
|
|
if (exist != null)
|
|
|
{
|
|
|
exist.description = description;
|
|
|
- string path = BuildPath(exist.id, eventType);
|
|
|
+ string path = BuildPath(exist.id, uniqueId);
|
|
|
if (string.IsNullOrEmpty(exist.path) || !exist.path.Contains("track_id"))
|
|
|
{
|
|
|
exist.path = path;
|
|
|
@@ -98,7 +102,7 @@ namespace molilian.core
|
|
|
if (id != null && int.TryParse(id.ToString(), out var linkId))
|
|
|
{
|
|
|
item.id = linkId;
|
|
|
- item.path = BuildPath(linkId, eventType);
|
|
|
+ item.path = BuildPath(linkId, uniqueId);
|
|
|
new DBContext.Table(conn, "track_links")
|
|
|
.Add("path", item.path)
|
|
|
.Add("description", description)
|
|
|
@@ -122,48 +126,16 @@ namespace molilian.core
|
|
|
/// <summary>
|
|
|
/// 曝光/点击触发:计入 Redis,失败不影响返回
|
|
|
/// </summary>
|
|
|
- public static Task<bool> TrackAsync(string eventType, string platform, string scene, string uniqueId)
|
|
|
+ public static Task<bool> TrackAsync(TrackLinkDTO link)
|
|
|
{
|
|
|
- eventType = NormalizeEventType(eventType);
|
|
|
- platform = Normalize(platform);
|
|
|
- scene = NormalizeOrAll(scene);
|
|
|
- uniqueId = NormalizeOrAll(uniqueId);
|
|
|
-
|
|
|
- if (!SupportEventTypes.Contains(eventType))
|
|
|
- {
|
|
|
- return Task.FromResult(false);
|
|
|
- }
|
|
|
|
|
|
string dateStr = DateTime.Now.ToString("yyyyMMdd");
|
|
|
string hourStr = DateTime.Now.ToString("yyyyMMddHH");
|
|
|
|
|
|
var metrics = new List<(string key, string indexKey, string indexValue, int expire)>
|
|
|
{
|
|
|
- // Daily buckets
|
|
|
- ($"{RedisPrefix}:daily:{eventType}:{platform}:{dateStr}",
|
|
|
- $"{RedisPrefix}:daily:index:{dateStr}:platform",
|
|
|
- $"{eventType}|{platform}", DailyExpireSeconds),
|
|
|
-
|
|
|
- ($"{RedisPrefix}:daily:{eventType}:{platform}:{scene}:{dateStr}",
|
|
|
- $"{RedisPrefix}:daily:index:{dateStr}:scene",
|
|
|
- $"{eventType}|{platform}|{scene}", DailyExpireSeconds),
|
|
|
-
|
|
|
- ($"{RedisPrefix}:daily:{eventType}:{platform}:{scene}:{uniqueId}:{dateStr}",
|
|
|
- $"{RedisPrefix}:daily:index:{dateStr}:unique",
|
|
|
- $"{eventType}|{platform}|{scene}|{uniqueId}", DailyExpireSeconds),
|
|
|
-
|
|
|
- // Hourly buckets
|
|
|
- ($"{RedisPrefix}:hour:{eventType}:{platform}:{hourStr}",
|
|
|
- $"{RedisPrefix}:hour:index:{hourStr}:platform",
|
|
|
- $"{eventType}|{platform}", HourlyExpireSeconds),
|
|
|
-
|
|
|
- ($"{RedisPrefix}:hour:{eventType}:{platform}:{scene}:{hourStr}",
|
|
|
- $"{RedisPrefix}:hour:index:{hourStr}:scene",
|
|
|
- $"{eventType}|{platform}|{scene}", HourlyExpireSeconds),
|
|
|
-
|
|
|
- ($"{RedisPrefix}:hour:{eventType}:{platform}:{scene}:{uniqueId}:{hourStr}",
|
|
|
- $"{RedisPrefix}:hour:index:{hourStr}:unique",
|
|
|
- $"{eventType}|{platform}|{scene}|{uniqueId}", HourlyExpireSeconds),
|
|
|
+ ($"{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),
|
|
|
};
|
|
|
|
|
|
try
|
|
|
@@ -193,7 +165,7 @@ namespace molilian.core
|
|
|
public static async Task<int> FlushDailyAsync(DateTime targetDate)
|
|
|
{
|
|
|
string dateStr = targetDate.ToString("yyyyMMdd");
|
|
|
- string indexKey = $"{RedisPrefix}:daily:index:{dateStr}:unique";
|
|
|
+ string indexKey = $"{RedisPrefix}:daily:index:{dateStr}:track";
|
|
|
|
|
|
var indexMembers = await RedisHelper.SMembersAsync<string>(indexKey) ?? [];
|
|
|
if (indexMembers == null || indexMembers.Length == 0) return 0;
|
|
|
@@ -203,36 +175,33 @@ namespace molilian.core
|
|
|
foreach (var member in indexMembers)
|
|
|
{
|
|
|
var parts = member.Split('|');
|
|
|
- if (parts.Length != 4) continue;
|
|
|
+ if (parts.Length != 2) continue;
|
|
|
|
|
|
string eventType = NormalizeEventType(parts[0]);
|
|
|
- string platform = Normalize(parts[1]);
|
|
|
- string scene = NormalizeOrAll(parts[2]);
|
|
|
- string uniqueId = NormalizeOrAll(parts[3]);
|
|
|
if (!SupportEventTypes.Contains(eventType)) continue;
|
|
|
+ if (!int.TryParse(parts[1], out var trackId) || trackId <= 0) continue;
|
|
|
|
|
|
- string countKey = $"{RedisPrefix}:daily:{eventType}:{platform}:{scene}:{uniqueId}:{dateStr}";
|
|
|
+ string countKey = $"{RedisPrefix}:daily:{eventType}:{trackId}:{dateStr}";
|
|
|
int total = await RedisHelper.GetAsync<int>(countKey);
|
|
|
if (total <= 0) continue;
|
|
|
|
|
|
- int linkId = GetTrackLinkId(conn, eventType, platform, scene, uniqueId);
|
|
|
var exist = new DBContext.Table(conn, "track_daily_report")
|
|
|
.Fields("id")
|
|
|
- .Get<dynamic>("report_date=@report_date AND event_type=@event_type AND platform=@platform AND scene=@scene AND unique_id=@unique_id",
|
|
|
- new { report_date = targetDate, event_type = eventType, platform, scene, unique_id = uniqueId });
|
|
|
+ .Get<dynamic>("report_date=@report_date AND event_type=@event_type AND track_link_id=@trackId",
|
|
|
+ new { report_date = targetDate, event_type = eventType, trackId });
|
|
|
|
|
|
var update = new DBContext.Table(conn, "track_daily_report")
|
|
|
.Add("event_count", total)
|
|
|
- .Add("track_link_id", linkId)
|
|
|
+ .Add("track_link_id", trackId)
|
|
|
+ .Add("platform", string.Empty)
|
|
|
+ .Add("scene", string.Empty)
|
|
|
+ .Add("unique_id", string.Empty)
|
|
|
.Add("update_time", DateTime.Now);
|
|
|
|
|
|
if (exist == null)
|
|
|
{
|
|
|
update.Add("report_date", targetDate)
|
|
|
.Add("event_type", eventType)
|
|
|
- .Add("platform", platform)
|
|
|
- .Add("scene", scene)
|
|
|
- .Add("unique_id", uniqueId)
|
|
|
.Add("create_time", DateTime.Now)
|
|
|
.Create();
|
|
|
}
|
|
|
@@ -244,12 +213,28 @@ namespace molilian.core
|
|
|
}
|
|
|
return rows;
|
|
|
}
|
|
|
+ public static string BuildPath(int trackId, TkDataDTO result)
|
|
|
+ {
|
|
|
+ if (result == null || result.parse_type != "dp") return string.Empty;
|
|
|
+ string unique_id = $"1|{result.itemId}_{result.mktId}";
|
|
|
+ return BuildPath(trackId, unique_id);
|
|
|
+ }
|
|
|
+ public static string BuildPath(int trackId, JdDataDTO result)
|
|
|
+ {
|
|
|
+ if (result == null || result.parse_type != "dp") return string.Empty;
|
|
|
+ string unique_id = $"13|{result.shortLinkurl.UrlEncode()}";
|
|
|
+ return BuildPath(trackId, unique_id);
|
|
|
+ }
|
|
|
+ public static string BuildPath(int trackId, PddDataDTO result)
|
|
|
+ {
|
|
|
+ if (result == null || result.parse_type != "dp") return string.Empty;
|
|
|
+ string unique_id = $"9|{result.shortLinkurl.UrlEncode()}";
|
|
|
+ return BuildPath(trackId, unique_id);
|
|
|
+ }
|
|
|
|
|
|
- public static string BuildPath(int trackId, string eventType)
|
|
|
+ public static string BuildPath(int trackId, string unique_id = "")
|
|
|
{
|
|
|
- eventType = NormalizeEventType(eventType);
|
|
|
- string safeEventType = WebUtility.UrlEncode(eventType);
|
|
|
- return $"https://api.molilian.com/tracks/track?track_id={trackId}&eventType={safeEventType}";
|
|
|
+ return $"https://api.molilian.com/tracks/track?track_id={trackId}&unique_id={unique_id}";
|
|
|
}
|
|
|
|
|
|
public static string GetLinkCacheKey(string eventType, string typename, string scene, string uniqueId)
|
|
|
@@ -292,6 +277,58 @@ namespace molilian.core
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ public static Task<bool> LogTrackRequestAsync(TrackRequestLogDTO dto)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ dto.event_type = NormalizeEventType(dto.event_type);
|
|
|
+ dto.platform = Normalize(dto.platform);
|
|
|
+ dto.typename = Normalize(dto.typename);
|
|
|
+ dto.scene = NormalizeOrAll(dto.scene);
|
|
|
+ dto.unique_id = NormalizeOrAll(dto.unique_id);
|
|
|
+ dto.ip = Normalize(dto.ip);
|
|
|
+ dto.user_agent = Normalize(dto.user_agent);
|
|
|
+ dto.referer = Normalize(dto.referer);
|
|
|
+ dto.create_time = DateTime.Now;
|
|
|
+
|
|
|
+ RedisHelper.RPush(TrackRequestLogKey, dto);
|
|
|
+ return Task.FromResult(true);
|
|
|
+ }
|
|
|
+ catch
|
|
|
+ {
|
|
|
+ return Task.FromResult(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static async Task<int> InsertTrackRequestLogAsync(int limit, YunhuiKit.RedisClient redis)
|
|
|
+ {
|
|
|
+ int count = 0;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ using var conn = DBContext.GetOpenConnection();
|
|
|
+ for (int i = 0; i < limit; i++)
|
|
|
+ {
|
|
|
+ var entity = await redis.LPopAsync<TrackRequestLogDTO>(TrackRequestLogKey);
|
|
|
+ if (entity == null) break;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ if (entity.create_time == default) entity.create_time = DateTime.Now;
|
|
|
+ conn.Insert(entity);
|
|
|
+ count++;
|
|
|
+ }
|
|
|
+ catch
|
|
|
+ {
|
|
|
+ // ignore malformed item
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch
|
|
|
+ {
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+
|
|
|
private static string Normalize(string value)
|
|
|
{
|
|
|
return (value ?? string.Empty).Trim();
|