Explorar el Código

更新追踪链接业务

dodo hold hace 3 meses
padre
commit
c8bd026e11

+ 29 - 4
molilian.api/Controllers/admin/TracksAdminController.cs

@@ -36,7 +36,7 @@ namespace molilian.api.Controllers
             string filter = string.Empty;
             if (!string.IsNullOrEmpty(keyword))
             {
-                filter += " AND (event_type LIKE @keyword OR platform LIKE @keyword OR scene LIKE @keyword OR unique_id LIKE @keyword)";
+                filter += " AND (event_type LIKE @keyword OR platform LIKE @keyword OR typename LIKE @keyword OR scene LIKE @keyword OR unique_id LIKE @keyword)";
                 keyword = $"%{keyword}%";
             }
             filter = filter.StringTrimStart(" AND ");
@@ -72,6 +72,7 @@ namespace molilian.api.Controllers
             int linkId = form.Read("track_link_id", 0);
             string eventType = form.Read("event_type", string.Empty);
             string platform = form.Read("platform", string.Empty);
+            string typename = form.Read("typename", string.Empty);
             string scene = form.Read("scene", string.Empty);
             string uniqueId = form.Read("unique_id", string.Empty);
             DateTime? start = form.Read<DateTime?>("start", null);
@@ -81,6 +82,7 @@ namespace molilian.api.Controllers
             if (linkId > 0) filter += " AND track_link_id=@linkId";
             if (!string.IsNullOrWhiteSpace(eventType)) filter += " AND event_type=@eventType";
             if (!string.IsNullOrWhiteSpace(platform)) filter += " AND platform=@platform";
+            if (!string.IsNullOrWhiteSpace(typename)) filter += " AND typename=@typename";
             if (!string.IsNullOrWhiteSpace(scene)) filter += " AND scene=@scene";
             if (!string.IsNullOrWhiteSpace(uniqueId)) filter += " AND unique_id=@uniqueId";
             if (start.HasValue) filter += " AND report_date>=@start";
@@ -90,7 +92,7 @@ namespace molilian.api.Controllers
             string orderBy = "report_date DESC";
 
             var result = new DBContext.Table("track_daily_report")
-                .Where(filter, new { linkId, eventType, platform, scene, uniqueId, start, end })
+                .Where(filter, new { linkId, eventType, platform, typename, scene, uniqueId, start, end })
                 .Page(size, page)
                 .Order(orderBy)
                 .PageList<TrackDailyReportDTO>(getTotal);
@@ -98,11 +100,32 @@ namespace molilian.api.Controllers
             return new APIResult(new { data = result });
         }
 
+        [HttpPost]
+        public async Task<ActionResult> hourly([FromBody] JsonElement form)
+        {
+            var token = provider.Get(_accessor.HttpContext);
+            int linkId = form.Read("track_link_id", 0);
+            string dateText = form.Read("date", string.Empty);
+            DateTime date = DateTime.TryParse(dateText, out var parsed) ? parsed.Date : DateTime.Now.Date;
+
+            var list = await TracksCore.GetHourlyReportAsync(linkId, date);
+            return new APIResult(new
+            {
+                data = new
+                {
+                    list,
+                    count = list.Count
+                }
+            });
+        }
+
         [HttpPost]
         public async Task<ActionResult> create([FromBody] TrackLinkDTO data)
         {
             var token = provider.Get(_accessor.HttpContext);
-            var link = await TracksCore.CreateLinkAsync(data.event_type, data.typename, data.scene, data.unique_id, data.description, true);
+            if (string.IsNullOrWhiteSpace(data.typename)) data.typename = data.platform;
+            if (string.IsNullOrWhiteSpace(data.platform)) data.platform = data.typename;
+            var link = await TracksCore.CreateLinkAsync(data.event_type, data.platform, data.typename, data.scene, data.unique_id, data.description, true);
             bool success = link != null && link.id > 0;
             return new APIResult(new
             {
@@ -127,11 +150,13 @@ namespace molilian.api.Controllers
             {
                 string eventType = (item.event_type ?? string.Empty).Trim().ToLowerInvariant();
                 string platform = (item.platform ?? string.Empty).Trim();
+                string typename = (item.typename ?? string.Empty).Trim();
                 string scene = string.IsNullOrWhiteSpace(item.scene) ? string.Empty : item.scene.Trim();
                 string uniqueId = string.IsNullOrWhiteSpace(item.unique_id) ? string.Empty : item.unique_id.Trim();
 
-                string cacheKey = TracksCore.GetLinkCacheKey(eventType, platform, scene, uniqueId);
+                string cacheKey = TracksCore.GetLinkCacheKey(eventType, platform, typename, scene, uniqueId);
                 RedisHelper.Del(cacheKey);
+                RedisHelper.Del(TracksCore.GetLinkIdCacheKey(item.id));
             }
             return new APIResult(new
             {

+ 38 - 20
molilian.api/Controllers/public/TracksController.cs

@@ -37,7 +37,7 @@ namespace molilian.api.Controllers
                 track_id = track_id,
                 event_type = link.event_type,
                 platform = link.platform,
-                typename = link.platform,
+                typename = link.typename,
                 scene = link.scene,
                 unique_id = unique_id,
                 ip = _accessor.HttpContext.GetUserIp(),
@@ -53,7 +53,8 @@ namespace molilian.api.Controllers
         public async Task<ActionResult> Create([FromQuery] string eventType, [FromQuery] string platform = "", [FromQuery] string typename = "", [FromQuery] string scene = "", [FromQuery] string uniqueId = "", [FromQuery] string description = "")
         {
             if (string.IsNullOrWhiteSpace(typename)) typename = platform;
-            var link = await TracksCore.CreateLinkAsync(eventType, typename, scene, uniqueId, description);
+            if (string.IsNullOrWhiteSpace(platform)) platform = typename;
+            var link = await TracksCore.CreateLinkAsync(eventType, platform, typename, scene, uniqueId, description);
             if (link == null)
             {
                 return new APIResult(new { success = false, message = "invalid params or eventType" });
@@ -64,23 +65,40 @@ namespace molilian.api.Controllers
         }
 
         [HttpGet]
-        public async Task<ActionResult> Daily([FromQuery] int daysAgo = 1, [FromQuery] string reportDate = "")
-        {
-            DateTime date;
-            if (!string.IsNullOrWhiteSpace(reportDate) && DateTime.TryParse(reportDate, out var parsed))
-            {
-                date = parsed.Date;
-            }
-            else
-            {
-                if (daysAgo <= 0) daysAgo = 1;
-                date = DateTime.Now.AddDays(-daysAgo).Date;
-            }
-
-            int rows = await TracksCore.FlushDailyAsync(DateTime.Now.Date);
-            rows += await TracksCore.FlushDailyAsync(date);
-            return new APIResult(new { success = true, message = "ok", rows });
-
-        }
+        public async Task<ActionResult> Daily([FromQuery] int daysAgo = 0, [FromQuery] string reportDate = "", [FromQuery] bool includeToday = false, [FromQuery] bool includeYesterday = true)
+        {
+            var dates = new List<DateTime>();
+            DateTime today = DateTime.Now.Date;
+
+            if (!string.IsNullOrWhiteSpace(reportDate) && DateTime.TryParse(reportDate, out var parsed))
+            {
+                dates.Add(parsed.Date);
+                if (includeToday && parsed.Date != today) dates.Add(today);
+            }
+            else
+            {
+                if (daysAgo > 0)
+                {
+                    var date = today.AddDays(-daysAgo);
+                    dates.Add(date);
+                    if (includeToday && date != today) dates.Add(today);
+                }
+                else
+                {
+                    dates.Add(today);
+                    if (includeYesterday) dates.Add(today.AddDays(-1));
+                }
+            }
+
+            int rows = 0;
+            var reportDates = new List<string>();
+            foreach (var date in dates.Distinct())
+            {
+                rows += await TracksCore.FlushDailyAsync(date);
+                reportDates.Add(date.ToString("yyyy-MM-dd"));
+            }
+            return new APIResult(new { success = true, message = "ok", rows, reportDates });
+
+        }
     }
 }

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
molilian.api/Properties/PublishProfiles/latest.pubxml.user


+ 116 - 39
molilian.core/Core/TracksCore.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Data;
 using System.Net;
 using System.Threading.Tasks;
+using Dapper;
 using CSRedis;
 using dodohold.core;
 using System.Text.Json;
@@ -10,6 +11,12 @@ using YunhuiKit;
 
 namespace molilian.core
 {
+    public enum TrackType
+    {
+        Expose,
+        Click
+    }
+
     public partial class TracksCore
     {
         private const string RedisPrefix = ":tracks_v123";
@@ -18,16 +25,24 @@ namespace molilian.core
         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;
+        private static readonly HashSet<string> SupportEventTypes = new(StringComparer.OrdinalIgnoreCase) { "expose", "click" };
+
 
-        /// <summary>
-        /// 生成一个监测链接(仅返回 path,不包含域名),同时落地到 track_links
-        /// </summary>
+        /// <summary>
+        /// 生成一个监测链接(仅返回 path,不包含域名),同时落地到 track_links
+        /// </summary>
         public static Task<TrackLinkDTO> CreateLinkAsync(string eventType, string typename, string scene, string uniqueId, string description = "", bool forceNew = false)
+        {
+            return CreateLinkAsync(eventType, string.Empty, typename, scene, uniqueId, description, forceNew);
+        }
+
+        /// <summary>
+        /// 生成一个监测链接(仅返回 path,不包含域名),同时落地到 track_links
+        /// </summary>
+        public static Task<TrackLinkDTO> CreateLinkAsync(string eventType, string platform, string typename, string scene, string uniqueId, string description = "", bool forceNew = false)
         {
             eventType = NormalizeEventType(eventType);
+            platform = Normalize(platform);
             typename = Normalize(typename);
             scene = NormalizeOrAll(scene);
             uniqueId = NormalizeOrAll(uniqueId);
@@ -38,7 +53,7 @@ namespace molilian.core
                 return Task.FromResult<TrackLinkDTO>(null);
             }
 
-            string cacheKey = $"{RedisPrefix}:link:{eventType}:{typename}:{scene}:{uniqueId}";
+            string cacheKey = GetLinkCacheKey(eventType, platform, typename, scene, uniqueId);
             if (!forceNew)
             {
                 try
@@ -63,8 +78,8 @@ namespace molilian.core
                 if (!forceNew)
                 {
                     exist = new DBContext.Table(conn, "track_links")
-                        .Get<TrackLinkDTO>("event_type=@event_type AND platform=@platform AND scene=@scene AND unique_id=@unique_id",
-                            new { event_type = eventType, platform = typename, scene, unique_id = uniqueId });
+                        .Get<TrackLinkDTO>("event_type=@event_type AND platform=@platform AND typename=@typename AND scene=@scene AND unique_id=@unique_id",
+                            new { event_type = eventType, platform, typename, scene, unique_id = uniqueId });
                 }
 
                 if (exist != null)
@@ -76,6 +91,8 @@ namespace molilian.core
                         exist.path = path;
                         new DBContext.Table(conn, "track_links")
                             .Add("path", path)
+                            .Add("platform", platform)
+                            .Add("typename", typename)
                             .Add("description", description)
                             .Add("update_time", DateTime.Now)
                             .Where("id=@id", new { exist.id })
@@ -89,7 +106,8 @@ namespace molilian.core
                 var item = new TrackLinkDTO
                 {
                     event_type = eventType,
-                    platform = typename,
+                    platform = platform,
+                    typename = typename,
                     scene = scene,
                     unique_id = uniqueId,
                     path = string.Empty,
@@ -160,7 +178,7 @@ namespace molilian.core
         }
 
         /// <summary>
-        /// 日报:拉取昨日 Redis 计数并落地到 track_daily_report(默认统计昨天,可传 reportDate)
+        /// 日报:拉取指定日期 Redis 计数并落地到 track_daily_report,可高频重复执行。
         /// </summary>
         public static async Task<int> FlushDailyAsync(DateTime targetDate)
         {
@@ -172,6 +190,7 @@ namespace molilian.core
 
             int rows = 0;
             using var conn = DBContext.GetOpenConnection();
+            var linkCache = new Dictionary<int, TrackLinkDTO>();
             foreach (var member in indexMembers)
             {
                 var parts = member.Split('|');
@@ -185,48 +204,100 @@ namespace molilian.core
                 int total = await RedisHelper.GetAsync<int>(countKey);
                 if (total <= 0) continue;
 
-                var exist = new DBContext.Table(conn, "track_daily_report")
-                    .Fields("id")
-                    .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", 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("create_time", DateTime.Now)
-                          .Create();
-                }
-                else
+                if (!linkCache.TryGetValue(trackId, out var link))
                 {
-                    update.Where("id=@id", new { exist.id }).Update();
+                    link = new DBContext.Table(conn, "track_links")
+                        .Get<TrackLinkDTO>("id=@id", new { id = trackId });
+                    if (link != null) linkCache[trackId] = link;
                 }
+
+                await UpsertDailyReportAsync(conn, targetDate.Date, eventType, trackId, link, total);
                 rows++;
             }
             return rows;
-        }
-        public static string BuildPath(int trackId, TkDataDTO result)
-        {
+        }
+
+        private static Task<int> UpsertDailyReportAsync(IDbConnection conn, DateTime reportDate, string eventType, int trackId, TrackLinkDTO link, int total)
+        {
+            const string sql = @"
+INSERT INTO track_daily_report
+    (report_date, event_type, track_link_id, platform, typename, scene, unique_id, event_count, create_time, update_time)
+VALUES
+    (@reportDate, @eventType, @trackId, @platform, @typename, @scene, @uniqueId, @eventCount, @now, @now)
+ON DUPLICATE KEY UPDATE
+    event_count = VALUES(event_count),
+    platform = VALUES(platform),
+    typename = VALUES(typename),
+    scene = VALUES(scene),
+    unique_id = VALUES(unique_id),
+    update_time = VALUES(update_time);";
+
+            return conn.ExecuteAsync(sql, new
+            {
+                reportDate,
+                eventType,
+                trackId,
+                platform = link?.platform ?? string.Empty,
+                typename = link?.typename ?? string.Empty,
+                scene = link?.scene ?? string.Empty,
+                uniqueId = link?.unique_id ?? string.Empty,
+                eventCount = total,
+                now = DateTime.Now
+            });
+        }
+
+        public static async Task<List<TrackHourlyReportDTO>> GetHourlyReportAsync(int trackId, DateTime targetDate)
+        {
+            var result = new List<TrackHourlyReportDTO>();
+            if (trackId <= 0) return result;
+
+            var link = await GetLinkByIdAsync(trackId);
+            if (link == null) return result;
+
+            string eventType = NormalizeEventType(link.event_type);
+            if (!SupportEventTypes.Contains(eventType)) return result;
+
+            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);
+
+                result.Add(new TrackHourlyReportDTO
+                {
+                    track_link_id = trackId,
+                    event_type = eventType,
+                    platform = link.platform ?? string.Empty,
+                    typename = link.typename ?? string.Empty,
+                    scene = link.scene ?? string.Empty,
+                    unique_id = link.unique_id ?? string.Empty,
+                    report_date = targetDate.Date,
+                    hour = hour,
+                    event_count = total
+                });
+            }
+
+            return result;
+        }
+
+        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);
         }
-        public static string BuildPath(int trackId, JdDataDTO result)
+        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);
         }
-        public static string BuildPath(int trackId, PddDataDTO result)
+        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);
@@ -238,12 +309,18 @@ namespace molilian.core
         }
 
         public static string GetLinkCacheKey(string eventType, string typename, string scene, string uniqueId)
+        {
+            return GetLinkCacheKey(eventType, string.Empty, typename, scene, uniqueId);
+        }
+
+        public static string GetLinkCacheKey(string eventType, string platform, string typename, string scene, string uniqueId)
         {
             eventType = NormalizeEventType(eventType);
+            platform = Normalize(platform);
             typename = Normalize(typename);
             scene = NormalizeOrAll(scene);
             uniqueId = NormalizeOrAll(uniqueId);
-            return $"{RedisPrefix}:link:{eventType}:{typename}:{scene}:{uniqueId}";
+            return $"{RedisPrefix}:link:{eventType}:{platform}:{typename}:{scene}:{uniqueId}";
         }
 
         public static string GetLinkIdCacheKey(int trackId)

+ 78 - 12
molilian.core/Core/taoke/UnionParseCore/UnionParseCore.cs

@@ -331,8 +331,8 @@ namespace molilian.core
             {
                 success = ValidateDeeplinkIfRequired(result.success, result.deeplink_url),
                 result.commercial,
-                exposeTracks = TracksCore.BuildPath(TracksCore.ExposeTrackId, result),
-                clickTracks = TracksCore.BuildPath(TracksCore.ClickTrackId, result),
+                exposeTracks = TracksCore.BuildPath(TrackType.Expose, result),
+                clickTracks = TracksCore.BuildPath(TrackType.Click, result),
                 result.with_middle_page,
                 result.message,
                 sub_message = result.reason,
@@ -409,8 +409,8 @@ namespace molilian.core
                     {
                         success = ValidateDeeplinkIfRequired(result.success, result.deeplink_url),
                         result.commercial,
-                        exposeTracks = TracksCore.BuildPath(TracksCore.ExposeTrackId, result),
-                        clickTracks = TracksCore.BuildPath(TracksCore.ClickTrackId, result),
+                        exposeTracks = TracksCore.BuildPath(TrackType.Expose, result),
+                        clickTracks = TracksCore.BuildPath(TrackType.Click, result),
                         result.with_middle_page,
                         result.message,
                         sub_message = result.reason,
@@ -741,8 +741,8 @@ namespace molilian.core
                 {
                     success = ValidateDeeplinkIfRequired(result.success, result.deeplink_url),
                     result.commercial,
-                    exposeTracks = TracksCore.BuildPath(TracksCore.ExposeTrackId, result),
-                    clickTracks = TracksCore.BuildPath(TracksCore.ClickTrackId, result),
+                    exposeTracks = TracksCore.BuildPath(TrackType.Expose, result),
+                    clickTracks = TracksCore.BuildPath(TrackType.Click, result),
                     result.with_middle_page,
                     result.message,
                     sub_message = result.reason,
@@ -771,8 +771,8 @@ namespace molilian.core
             {
                 success = ValidateDeeplinkIfRequired(result.success, result.deeplink_url),
                 result.commercial,
-                exposeTracks = TracksCore.BuildPath(TracksCore.ExposeTrackId, result),
-                clickTracks = TracksCore.BuildPath(TracksCore.ClickTrackId, result),
+                exposeTracks = TracksCore.BuildPath(TrackType.Expose, result),
+                clickTracks = TracksCore.BuildPath(TrackType.Click, result),
                 result.with_middle_page,
                 result.message,
                 sub_message = result.reason,
@@ -807,8 +807,8 @@ namespace molilian.core
                 //result.success,
                 result.message,
                 result.commercial,
-                exposeTracks = TracksCore.BuildPath(TracksCore.ExposeTrackId, result),
-                clickTracks = TracksCore.BuildPath(TracksCore.ClickTrackId, result),
+                exposeTracks = TracksCore.BuildPath(TrackType.Expose, result),
+                clickTracks = TracksCore.BuildPath(TrackType.Click, result),
                 link_type = result.link_type.ToString(),
                 channel = result?.channel.ToString(),
                 result.channel_type,
@@ -1342,6 +1342,39 @@ namespace molilian.core
                     return PddParseOutput(result);
                 }
 
+                var precheck = await PddUnionPlus.PrecheckGoodsIdAsync(result.shortLinkurl, cancellationToken);
+                switch (precheck)
+                {
+                    case RrecheckGoodsIdResult.InvalidLink:
+                        result.message = "放弃转链";
+                        result.reason = "无效链接";
+                        break;
+                    case RrecheckGoodsIdResult.Empty:
+                        result.message = "放弃转链";
+                        result.reason = $"无效的商品precheck";
+                        break;
+
+                    case RrecheckGoodsIdResult.Exception:
+                        result.message = "转链失败";
+                        result.reason = $"商品预检异常";
+                        break;
+                }
+
+                if (precheck != RrecheckGoodsIdResult.Success)
+                {
+                    result.success = false;
+                    result.link_type = LinkTypeEnum.unknown;
+                    result.channel_type = ChannelTypeEnum.pdd;
+                    result.accountId = 0;
+                    result.accountName = string.Empty;
+                    result.content = content;
+                    result.deeplink_url = PddUnionPlus.GetDeeplink(result.shortLinkurl);
+                    _ = TkLogCore.ParseLogAsync(result);
+                    return PddParseOutput(result);
+                }
+
+
+
 
                 PddPoolDTO account = null;
                 // 直接调用GetOne获取账号
@@ -1498,6 +1531,39 @@ namespace molilian.core
                     return PddParseOutput(result);
                 }
 
+                var precheck = await PddUnionPlus.PrecheckGoodsIdAsync(result.shortLinkurl, cancellationToken);
+                switch (precheck)
+                {
+                    case RrecheckGoodsIdResult.InvalidLink:
+                        result.message = "放弃转链";
+                        result.reason = "无效链接";
+                        break;
+                    case RrecheckGoodsIdResult.Empty:
+                        result.message = "放弃转链";
+                        result.reason = $"无效的商品precheck";
+                        break;
+
+                    case RrecheckGoodsIdResult.Exception:
+                        result.message = "转链失败";
+                        result.reason = $"商品预检异常";
+                        break;
+                }
+
+                if (precheck != RrecheckGoodsIdResult.Success)
+                {
+                    result.success = false;
+                    result.link_type = LinkTypeEnum.unknown;
+                    result.channel_type = ChannelTypeEnum.pdd;
+                    result.accountId = 0;
+                    result.accountName = string.Empty;
+                    result.content = content;
+                    result.deeplink_url = PddUnionPlus.GetDeeplink(result.shortLinkurl);
+                    _ = TkLogCore.ParseLogAsync(result);
+                    return PddParseOutput(result);
+                }
+
+
+
                 PddPoolDTO account = null;
                 // 直接调用GetOne获取账号
                 // 新的调度策略已经在内部处理了:
@@ -1569,8 +1635,8 @@ namespace molilian.core
                 success = ValidateDeeplinkIfRequired(result.success, result.deeplink_url),
                 result.message,
                 result.commercial,
-                exposeTracks = TracksCore.BuildPath(TracksCore.ExposeTrackId, result),
-                clickTracks = TracksCore.BuildPath(TracksCore.ClickTrackId, result),
+                exposeTracks = TracksCore.BuildPath(TrackType.Expose, result),
+                clickTracks = TracksCore.BuildPath(TrackType.Click, result),
                 sub_message = result.reason,
                 home_page = "pinduoduo://com.xunmeng.pinduoduo/".Equals(result.deeplink_url),
                 link_type = result.link_type.ToString(),

+ 4 - 4
molilian.core/Core/taoke/UnionParseCore/dp2dp.cs

@@ -47,8 +47,8 @@ namespace molilian.core
                     {
                         success = ValidateDeeplinkIfRequired(result.success, result.deeplink_url),
                         result.commercial,
-                        exposeTracks = TracksCore.BuildPath(TracksCore.ExposeTrackId, result),
-                        clickTracks = TracksCore.BuildPath(TracksCore.ClickTrackId, result),
+                        exposeTracks = TracksCore.BuildPath(TrackType.Expose, result),
+                        clickTracks = TracksCore.BuildPath(TrackType.Click, result),
                         result.with_middle_page,
                         result.message,
                         sub_message = result.reason,
@@ -494,8 +494,8 @@ namespace molilian.core
                 //result.success,
                 success = ValidateDeeplinkIfRequired(result.success, result.deeplink_url),
                 result.commercial,
-                exposeTracks = TracksCore.BuildPath(TracksCore.ExposeTrackId, result),
-                clickTracks = TracksCore.BuildPath(TracksCore.ClickTrackId, result),
+                exposeTracks = TracksCore.BuildPath(TrackType.Expose, result),
+                clickTracks = TracksCore.BuildPath(TrackType.Click, result),
                 result.with_middle_page,
                 result.message,
                 sub_message = result.reason,

+ 16 - 5
molilian.core/DTO/TracksDTO.cs

@@ -10,11 +10,7 @@ namespace molilian.core
         public int id { get; set; }
         public string event_type { get; set; } = string.Empty;
         public string platform { get; set; } = string.Empty;
-        public string typename
-        {
-            get => platform;
-            set => platform = value;
-        }
+        public string typename { get; set; } = string.Empty;
         public string scene { get; set; } = string.Empty;
         public string unique_id { get; set; } = string.Empty;
         public string path { get; set; } = string.Empty;
@@ -31,6 +27,7 @@ namespace molilian.core
         public DateTime report_date { get; set; } = DateTime.Now.Date;
         public string event_type { get; set; } = string.Empty;
         public string platform { get; set; } = string.Empty;
+        public string typename { get; set; } = string.Empty;
         public string scene { get; set; } = string.Empty;
         public string unique_id { get; set; } = string.Empty;
         public int track_link_id { get; set; } = 0;
@@ -39,6 +36,20 @@ namespace molilian.core
         public DateTime update_time { get; set; } = DateTime.Now;
     }
 
+    public class TrackHourlyReportDTO
+    {
+        public int track_link_id { get; set; } = 0;
+        public string event_type { get; set; } = string.Empty;
+        public string platform { get; set; } = string.Empty;
+        public string typename { get; set; } = string.Empty;
+        public string scene { get; set; } = string.Empty;
+        public string unique_id { get; set; } = string.Empty;
+        public DateTime report_date { get; set; } = DateTime.Now.Date;
+        public int hour { get; set; } = 0;
+        public string hour_label => $"{hour:00}:00";
+        public int event_count { get; set; } = 0;
+    }
+
     [Table("track_request_logs")]
     public class TrackRequestLogDTO
     {

+ 10 - 3
molilian.core/Plus/pdd/PddUnionPlus.cs

@@ -309,9 +309,16 @@ namespace molilian.core
                 var root = body.Convert2Object<GoodsSearchDTO>();
                 if (root?.error_response != null)
                 {
-                    _ = new LoggerLibrary("PddUnion", "PrecheckGoodsIdAsync.Empty").Info(urlWithQuery, body).SaveAsync();
-                    ResetPrecheckExceptionState();
-                    return RrecheckGoodsIdResult.Empty;
+                    _ = new LoggerLibrary("PddUnion", "PrecheckGoodsIdAsync").Info(urlWithQuery, body).SaveAsync();
+                    var status = RegisterPrecheckException();
+                    var error = root.error_response;
+                    NotifyPrecheckException(
+                        goods_url,
+                        "ErrorResponse",
+                        $"error_code:{error.error_code}\nsub_code:{error.sub_code}\nerror_msg:{error.error_msg}\nsub_msg:{error.sub_msg}\nbody:{body}",
+                        status.count,
+                        status.disabledNow);
+                    return status.disabledNow ? RrecheckGoodsIdResult.Success : RrecheckGoodsIdResult.Exception;
                 }
 
                 var goodsList = root?.goods_search_response?.goods_list;

+ 102 - 0
track_report_schema_fix.sql

@@ -0,0 +1,102 @@
+-- Fix track report dimensions:
+-- 1. track_links stores platform and typename separately.
+-- 2. track_daily_report uniqueness is based on the monitored link, not display dimensions.
+
+SET @sql = (
+  SELECT IF(
+    COUNT(*) = 0,
+    'ALTER TABLE `track_links` ADD COLUMN `typename` varchar(64) NOT NULL DEFAULT '''' AFTER `platform`',
+    'SELECT 1'
+  )
+  FROM information_schema.COLUMNS
+  WHERE TABLE_SCHEMA = DATABASE()
+    AND TABLE_NAME = 'track_links'
+    AND COLUMN_NAME = 'typename'
+);
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+SET @sql = (
+  SELECT IF(
+    COUNT(*) = 0,
+    'ALTER TABLE `track_daily_report` ADD COLUMN `typename` varchar(64) NOT NULL DEFAULT '''' AFTER `platform`',
+    'SELECT 1'
+  )
+  FROM information_schema.COLUMNS
+  WHERE TABLE_SCHEMA = DATABASE()
+    AND TABLE_NAME = 'track_daily_report'
+    AND COLUMN_NAME = 'typename'
+);
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+UPDATE `track_links`
+SET `typename` = CASE
+  WHEN `typename` <> '' THEN `typename`
+  WHEN `platform` IN ('淘宝', '京东', '拼多多', 'tb', 'jd', 'pdd', '1', '9', '13') THEN ''
+  ELSE `platform`
+END;
+
+UPDATE `track_links` SET `platform` = '淘宝' WHERE `id` IN (1, 2);
+UPDATE `track_links` SET `platform` = '京东' WHERE `id` IN (19, 20);
+UPDATE `track_links` SET `platform` = '拼多多' WHERE `id` IN (21, 22);
+
+SET @sql = (
+  SELECT IF(
+    COUNT(*) > 0,
+    'ALTER TABLE `track_daily_report` DROP INDEX `uk_track_daily`',
+    'SELECT 1'
+  )
+  FROM information_schema.STATISTICS
+  WHERE TABLE_SCHEMA = DATABASE()
+    AND TABLE_NAME = 'track_daily_report'
+    AND INDEX_NAME = 'uk_track_daily'
+);
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+UPDATE `track_daily_report` d
+JOIN `track_links` l ON d.`track_link_id` = l.`id`
+SET d.`platform` = l.`platform`,
+    d.`typename` = l.`typename`,
+    d.`scene` = l.`scene`,
+    d.`unique_id` = l.`unique_id`;
+
+DROP TEMPORARY TABLE IF EXISTS `tmp_track_daily_keep`;
+DROP TEMPORARY TABLE IF EXISTS `tmp_track_daily_merge`;
+
+CREATE TEMPORARY TABLE `tmp_track_daily_keep` AS
+SELECT MIN(`id`) AS `id`
+FROM `track_daily_report`
+GROUP BY `report_date`, `event_type`, `track_link_id`;
+
+CREATE TEMPORARY TABLE `tmp_track_daily_merge` AS
+  SELECT
+    `report_date`,
+    `event_type`,
+    `track_link_id`,
+    MAX(`event_count`) AS `event_count`
+  FROM `track_daily_report`
+  GROUP BY `report_date`, `event_type`, `track_link_id`;
+
+UPDATE `track_daily_report` d
+JOIN `tmp_track_daily_merge` m
+  ON d.`report_date` = m.`report_date`
+   AND d.`event_type` = m.`event_type`
+   AND d.`track_link_id` = m.`track_link_id`
+SET d.`event_count` = m.`event_count`
+WHERE d.`id` IN (SELECT `id` FROM `tmp_track_daily_keep`);
+
+DELETE d
+FROM `track_daily_report` d
+LEFT JOIN `tmp_track_daily_keep` k ON d.`id` = k.`id`
+WHERE k.`id` IS NULL;
+
+DROP TEMPORARY TABLE `tmp_track_daily_keep`;
+DROP TEMPORARY TABLE `tmp_track_daily_merge`;
+
+ALTER TABLE `track_daily_report`
+  ADD UNIQUE KEY `uk_track_daily` (`report_date`, `event_type`, `track_link_id`);

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio