dodo hold 6 ماه پیش
والد
کامیت
7a5e68f47c

+ 0 - 68
molilian.api/Controllers/admin/TRACKS_API.md

@@ -1,68 +0,0 @@
-# 监测链接接口说明(含 admin 鉴权)
-
-## 业务场景
-- 为第三方提供优惠券/活动链接,对曝光(`expose`)与点击(`click`)进行监测。
-- 监测数据以多维度(平台 / 场景 / 场景+唯一ID)写入 Redis,并定时落库到 `track_daily_report`,可与 `push_data_report` 汇总分析。
-- 触发接口高频,默认只触达 Redis;首次访问会通过缓存判断并自动补全 `track_links` 记录。
-
-## 名词与存储
-- `track_links`:监测链接配置表(event_type, typename(平台,来源 platformOptions), scene, unique_id, path, description)。
-- `track_daily_report`:日聚合表(event_count, track_link_id 等)。
-- Redis 缓存键:
-  - 链接缓存:`:tracks:link:{eventType}:{platform}:{scene}:{uniqueId}`(30 天)。
-  - 事件计数(日维):`:tracks:daily:{eventType}:{platform}:{scene}:{uniqueId}:{yyyyMMdd}`。
-  - 事件计数(时维):`:tracks:hour:{eventType}:{platform}:{scene}:{uniqueId}:{yyyyMMddHH}`。
-  - 索引集(枚举有数据的维度):`:tracks:daily:index:{yyyyMMdd}:unique` 等。
-- 维度缺省值:`scene`/`uniqueId` 为空时使用空字符串。
-- 路径模板(交付方自行补域名):`/Tracks/Track?track_id={id}&eventType={eventType}`。
-
-## 公共接口(无需鉴权)
-
-### 触发监测
-- `GET /Tracks/Track`
-- 用途:记录曝光/点击,快速返回(fire-and-forget)。
-- 参数:
-  - `track_id` *(必填)*:监测链接 ID
-  - `eventType` *(必填)*:`expose` 或 `click`
-- 响应:`{ success: true, message: "ok" }`
-- 说明:接口不会自动创建链接,必须先调用生成接口拿到 `track_id`。
-
-### 生成监测链接
-- `POST /Tracks/Create`
-- 参数:`eventType` *(必填)*,`typename`、`scene`、`uniqueId`、`description` *(可空,默认空字符串)*
-- 响应:`{ success: true, message: "ok", url: "/Tracks/Track?track_id={id}&eventType=..." }`
-- 说明:如果已存在则复用;内部写入 `track_links` 与缓存。
-
-### 日报落库
-- `GET /Tracks/Daily`
-- 参数:
-  - `daysAgo`:默认 1(统计昨天)
-  - `reportDate`:`YYYY-MM-DD`,优先于 daysAgo
-- 响应:`{ success: true, message: "ok", rows: <写入条数> }`
-- 说明:读取 Redis 的日索引与计数,落库 `track_daily_report`。
-
-## 管理端接口(需 admin 鉴权,路径前缀 `/api/TracksAdmin/*`)
-支持查询/查看/新增/删除/报表查询。
-
-### 列表
-- `POST /api/TracksAdmin/list`
-- Body:`{ "current":1, "pageSize":10, "getTotal":true, "sort":"id", "order":"DESC|ASC|descending|ascending", "keyword":"tb" }`
-- 说明:按 event_type/typename/scene/unique_id 模糊搜索。
-
-### 详情
-- `GET /api/TracksAdmin/info?id=123`
-
-### 新增
-- `POST /api/TracksAdmin/create`
-- Body:`{ "event_type":"expose", "typename":1, "scene":"coupon", "unique_id":"u1", "description":"xxx" }`
-- 说明:`typename` 取值来自前端 `platformOptions`(pushReport.vue 引用的 `platformList`),后台创建始终生成新链接(不复用)。
-
-### 删除
-- `POST /api/TracksAdmin/delete`
-- Body:`{ "id":123 }`
-- 说明:删除 DB 记录并清理缓存 `:tracks:link:{eventType}:{platform}:{scene}:{uniqueId}`。
-
-### 报表列表
-- `POST /api/TracksAdmin/report`
-- Body:`{ "current":1, "pageSize":10, "getTotal":true, "track_link_id":123, "event_type":"expose", "platform":"tb", "scene":"coupon", "unique_id":"u1", "start":"2024-01-01", "end":"2024-01-31" }`
-- 说明:按链接/事件/平台/场景/唯一ID及日期范围筛选 `track_daily_report`,分页返回。

+ 18 - 4
molilian.api/Controllers/public/TracksController.cs

@@ -19,9 +19,9 @@ namespace molilian.api.Controllers
         }
 
         [HttpGet]
-        public async Task<ActionResult> Track([FromQuery] string eventType, [FromQuery] int track_id = 0)
+        public async Task<ActionResult> Track([FromQuery] int track_id, [FromQuery] string unique_id)
         {
-            if (track_id <= 0 || string.IsNullOrEmpty(eventType))
+            if (track_id <= 0)
             {
                 return new APIResult(new { success = false, message = "miss" });
             }
@@ -32,13 +32,27 @@ namespace molilian.api.Controllers
                 return new APIResult(new { success = false, message = "not found" });
             }
 
-            _ = TracksCore.TrackAsync(eventType, link.platform, link.scene, link.unique_id);
+            var dto = new TrackRequestLogDTO
+            {
+                track_id = track_id,
+                event_type = link.event_type,
+                platform = link.platform,
+                typename = link.platform,
+                scene = link.scene,
+                unique_id = unique_id,
+                ip = _accessor.HttpContext.GetUserIp(),
+                user_agent = Request.Headers.UserAgent.ToString(),
+                referer = Request.Headers.Referer.ToString()
+            };
+            _ = TracksCore.LogTrackRequestAsync(dto);
+            _ = TracksCore.TrackAsync(link);
             return new APIResult(new { success = true, message = "ok" });
         }
 
         [HttpPost]
-        public async Task<ActionResult> Create([FromQuery] string eventType, [FromQuery] string typename = "", [FromQuery] string scene = "", [FromQuery] string uniqueId = "", [FromQuery] string description = "")
+        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 (link == null)
             {

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
molilian.api/Properties/PublishProfiles/latest.pubxml.user


+ 91 - 54
molilian.core/Core/TracksCore.cs

@@ -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();

+ 3 - 1
molilian.core/Core/aliyun/AliyunCore.cs

@@ -86,7 +86,9 @@ namespace molilian.core
                     couponEffectiveStartTime = item.CouponStartTime,
                     couponEffectiveEndTime = item.CouponEndTime,
                     sellCountStr = $"{item.Volume}",
-                    provcity = item.Provcity
+                    provcity = item.Provcity,
+                    exposeTracks = $"https://api.molilian.com/tracks/track?track_id=7&unique_id={item.ItemId}",
+                    clickTracks = $"https://api.molilian.com/tracks/track?track_id=8&unique_id={item.ItemId}"
                 };
 
                 if (newItem.h5_url.StartsWith("//")) newItem.h5_url = "https:" + newItem.h5_url;

+ 1 - 0
molilian.core/Core/log/base.cs

@@ -86,6 +86,7 @@ namespace molilian.core
                     RunTaskWithLoggingAsync(() => InsertCouponLogAsync(limit, redis), "task_insert_parse_coupon_logs"),
                     RunTaskWithLoggingAsync(() => InsertActivityLogAsync(limit, redis), "task_insert_activity_logs"),
                     RunTaskWithLoggingAsync(() => InsertCpsLogAsync(limit, redis), "task_insert_parse_cps_logs"),
+                    RunTaskWithLoggingAsync(() => TracksCore.InsertTrackRequestLogAsync(limit, redis), "task_insert_track_request_logs"),
 
 
 

+ 8 - 2
molilian.core/Core/taoke/TaokeOpenCore.cs

@@ -144,7 +144,9 @@ namespace molilian.core
                                 result.success,
                                 result.message,
                                 result.reason,
-                                promotionImg = result.PromotionImg
+                                promotionImg = result.PromotionImg,
+                                exposeTracks = "https://api.molilian.com/tracks/track?track_id=7&unique_id=",
+                                clickTracks = "https://api.molilian.com/tracks/track?track_id=8&unique_id=",
                             }, APIResultCodeEnum.OK, new JsonSerializerOptions
                             {
                                 Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
@@ -190,7 +192,9 @@ namespace molilian.core
                                 result.success,
                                 result.message,
                                 result.reason,
-                                promotionImg = result.PromotionImg
+                                promotionImg = result.PromotionImg,
+                                exposeTracks = $"https://api.molilian.com/tracks/track?track_id=7&unique_id=",
+                                clickTracks = $"https://api.molilian.com/tracks/track?track_id=8&unique_id="
                             }, APIResultCodeEnum.OK, new JsonSerializerOptions
                             {
                                 Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
@@ -336,6 +340,8 @@ namespace molilian.core
                     similarPromotion = result.SimilarPromotion,
                     promotionImg = result.PromotionImg,
                     result.url,
+                    exposeTracks = "https://api.molilian.com/tracks/track?track_id=7&unique_id=",
+                    clickTracks = "https://api.molilian.com/tracks/track?track_id=8&unique_id=",
                 }, APIResultCodeEnum.OK, new JsonSerializerOptions
                 {
                     Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping

+ 3 - 1
molilian.core/Core/taoke/TkActivityCore.cs

@@ -139,7 +139,9 @@ namespace molilian.core
                 result.shortTitle,
                 result.priceAfterCoupon,
                 result.commissionRate,
-                result.couponAmount
+                result.couponAmount,
+                exposeTracks = $"https://api.molilian.com/tracks/track?track_id=11&unique_id={item_id}",
+                clickTracks = $"https://api.molilian.com/tracks/track?track_id=12&unique_id={item_id}"
             });
         }
     }

+ 2 - 0
molilian.core/Core/taoke/UnionCouponCore.cs

@@ -38,6 +38,8 @@ namespace molilian.core
                             result.shortLinkurl,
                             result.token,
                             result.channel,
+                            exposeTracks =$"https://api.molilian.com/tracks/track?track_id=9&unique_id={result.channel}",
+                            clickTracks = $"https://api.molilian.com/tracks/track?track_id=10&unique_id={result.channel}",
                         });
                     }
                     else

+ 11 - 1
molilian.core/Core/taoke/UnionParseCore/UnionParseCore.cs

@@ -155,7 +155,7 @@ namespace molilian.core
                 result.itemName,
             }, !string.IsNullOrEmpty(result.deeplink_url) ? APIResultCodeEnum.OK : APIResultCodeEnum.NotAcceptable);
         }
-        
+
         private static bool ValidateDeeplinkIfRequired(bool success, string deeplink_url)
         {
             //根据 deeplink 是否为空返回 success 值
@@ -301,6 +301,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),
                 result.with_middle_page,
                 result.message,
                 sub_message = result.reason,
@@ -376,6 +378,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),
                         result.with_middle_page,
                         result.message,
                         sub_message = result.reason,
@@ -700,6 +704,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),
                 result.with_middle_page,
                 result.message,
                 sub_message = result.reason,
@@ -733,6 +739,8 @@ namespace molilian.core
                 //result.success,
                 result.message,
                 result.commercial,
+                exposeTracks = TracksCore.BuildPath(TracksCore.ExposeTrackId, result),
+                clickTracks = TracksCore.BuildPath(TracksCore.ClickTrackId, result),
                 link_type = result.link_type.ToString(),
                 channel = result?.channel.ToString(),
                 result.channel_type,
@@ -1462,6 +1470,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),
                 sub_message = result.reason,
                 home_page = "pinduoduo://com.xunmeng.pinduoduo/".Equals(result.deeplink_url),
                 link_type = result.link_type.ToString(),

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

@@ -47,6 +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),
                         result.with_middle_page,
                         result.message,
                         sub_message = result.reason,
@@ -492,6 +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),
                 result.with_middle_page,
                 result.message,
                 sub_message = result.reason,

+ 17 - 0
molilian.core/DTO/TracksDTO.cs

@@ -38,4 +38,21 @@ namespace molilian.core
         public DateTime create_time { get; set; } = DateTime.Now;
         public DateTime update_time { get; set; } = DateTime.Now;
     }
+
+    [Table("track_request_logs")]
+    public class TrackRequestLogDTO
+    {
+        [Key]
+        public long id { get; set; }
+        public int track_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 string ip { get; set; } = string.Empty;
+        public string user_agent { get; set; } = string.Empty;
+        public string referer { get; set; } = string.Empty;
+        public DateTime create_time { get; set; } = DateTime.Now;
+    }
 }

+ 2 - 0
molilian.core/DTO/alimama/PromotionQueryDTO.cs

@@ -53,6 +53,8 @@ namespace molilian.core
         public string couponEffectiveEndTime { get; set; } = string.Empty;
         public string sellCountStr { get; set; } = string.Empty;
         public string provcity { get; set; } = string.Empty;
+        public string exposeTracks { get; set; } = string.Empty;
+        public string clickTracks { get; set; } = string.Empty;
 
     }
 }

+ 4 - 1
molilian.core/Plus/OtherApiPlus.cs

@@ -123,6 +123,9 @@ namespace molilian.core
                 newItem.couponEffectiveEndTime = item.Read<string>("CouponEndTime", string.Empty);
                 newItem.sellCountStr = item.Read<string>("sellCountStr", string.Empty);
                 newItem.provcity = item.Read<string>("Provcity", string.Empty);
+                newItem.exposeTracks = $"https://api.molilian.com/tracks/track?track_id=7&unique_id={itemId}";
+                newItem.clickTracks = $"https://api.molilian.com/tracks/track?track_id=8&unique_id={itemId}";
+
                 arr.Add(newItem);
             }
 
@@ -270,7 +273,7 @@ namespace molilian.core
             }
             else
             {
-                result.message= "转链失败";
+                result.message = "转链失败";
                 result.reason = message;
             }
             return result;

+ 16 - 0
track_request_logs.sql

@@ -0,0 +1,16 @@
+CREATE TABLE `track_request_logs` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `track_id` int NOT NULL DEFAULT 0,
+  `event_type` varchar(32) NOT NULL DEFAULT '',
+  `platform` varchar(64) NOT NULL DEFAULT '',
+  `typename` varchar(64) NOT NULL DEFAULT '',
+  `scene` varchar(128) NOT NULL DEFAULT '',
+  `unique_id` varchar(128) NOT NULL DEFAULT '',
+  `ip` varchar(64) NOT NULL DEFAULT '',
+  `user_agent` varchar(512) NOT NULL DEFAULT '',
+  `referer` varchar(512) NOT NULL DEFAULT '',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  KEY `idx_track_event` (`track_id`,`event_type`),
+  KEY `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است