TracksCore.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Net;
  5. using System.Threading.Tasks;
  6. using Dapper;
  7. using CSRedis;
  8. using dodohold.core;
  9. using System.Text.Json;
  10. using YunhuiKit;
  11. namespace molilian.core
  12. {
  13. public enum TrackType
  14. {
  15. Expose,
  16. Click
  17. }
  18. public partial class TracksCore
  19. {
  20. private const string RedisPrefix = ":tracks_v123";
  21. private const int DailyExpireSeconds = 40 * 86400;
  22. private const int HourlyExpireSeconds = 7 * 86400; // keep a week of hourly buckets
  23. private const string DefaultDimensionValue = "";
  24. private const int LinkCacheExpireSeconds = 30 * 86400;
  25. private const string LinkCacheVersion = "v2";
  26. private const string TrackRequestLogKey = ":tracks:request:logs";
  27. private static readonly HashSet<string> SupportEventTypes = new(StringComparer.OrdinalIgnoreCase) { "expose", "click" };
  28. /// <summary>
  29. /// 生成一个监测链接(仅返回 path,不包含域名),同时落地到 track_links
  30. /// </summary>
  31. public static Task<TrackLinkDTO> CreateLinkAsync(string eventType, string typename, string scene, string uniqueId, string description = "", bool forceNew = false)
  32. {
  33. return CreateLinkAsync(eventType, string.Empty, typename, scene, uniqueId, description, forceNew);
  34. }
  35. /// <summary>
  36. /// 生成一个监测链接(仅返回 path,不包含域名),同时落地到 track_links
  37. /// </summary>
  38. public static Task<TrackLinkDTO> CreateLinkAsync(string eventType, string platform, string typename, string scene, string uniqueId, string description = "", bool forceNew = false)
  39. {
  40. eventType = NormalizeEventType(eventType);
  41. platform = Normalize(platform);
  42. typename = Normalize(typename);
  43. scene = NormalizeOrAll(scene);
  44. uniqueId = NormalizeOrAll(uniqueId);
  45. description = Normalize(description);
  46. if (!SupportEventTypes.Contains(eventType))
  47. {
  48. return Task.FromResult<TrackLinkDTO>(null);
  49. }
  50. string cacheKey = GetLinkCacheKey(eventType, platform, typename, scene, uniqueId);
  51. if (!forceNew)
  52. {
  53. try
  54. {
  55. int cachedId = RedisHelper.Get<int>(cacheKey);
  56. if (cachedId > 0)
  57. {
  58. var cachedLink = RedisHelper.Get<TrackLinkDTO>(GetLinkIdCacheKey(cachedId));
  59. if (cachedLink != null) return Task.FromResult(cachedLink);
  60. }
  61. }
  62. catch
  63. {
  64. // ignore cache errors
  65. }
  66. }
  67. try
  68. {
  69. using var conn = DBContext.GetOpenConnection();
  70. TrackLinkDTO exist = null;
  71. if (!forceNew)
  72. {
  73. exist = new DBContext.Table(conn, "track_links")
  74. .Get<TrackLinkDTO>("event_type=@event_type AND platform=@platform AND typename=@typename AND scene=@scene AND unique_id=@unique_id",
  75. new { event_type = eventType, platform, typename, scene, unique_id = uniqueId });
  76. }
  77. if (exist != null)
  78. {
  79. exist.description = description;
  80. string path = BuildPath(exist.id, uniqueId);
  81. if (string.IsNullOrEmpty(exist.path) || !exist.path.Contains("track_id"))
  82. {
  83. exist.path = path;
  84. new DBContext.Table(conn, "track_links")
  85. .Add("path", path)
  86. .Add("platform", platform)
  87. .Add("typename", typename)
  88. .Add("description", description)
  89. .Add("update_time", DateTime.Now)
  90. .Where("id=@id", new { exist.id })
  91. .Update();
  92. }
  93. _ = RedisHelper.Set(cacheKey, exist.id, LinkCacheExpireSeconds);
  94. _ = RedisHelper.Set(GetLinkIdCacheKey(exist.id), exist, LinkCacheExpireSeconds);
  95. return Task.FromResult(exist);
  96. }
  97. var item = new TrackLinkDTO
  98. {
  99. event_type = eventType,
  100. platform = platform,
  101. typename = typename,
  102. scene = scene,
  103. unique_id = uniqueId,
  104. path = string.Empty,
  105. description = description,
  106. create_time = DateTime.Now,
  107. update_time = DateTime.Now
  108. };
  109. var id = conn.Insert(item);
  110. if (id != null && int.TryParse(id.ToString(), out var linkId))
  111. {
  112. item.id = linkId;
  113. item.path = BuildPath(linkId, uniqueId);
  114. new DBContext.Table(conn, "track_links")
  115. .Add("path", item.path)
  116. .Add("description", description)
  117. .Add("update_time", DateTime.Now)
  118. .Where("id=@id", new { item.id })
  119. .Update();
  120. }
  121. _ = RedisHelper.Set(cacheKey, item.id, LinkCacheExpireSeconds);
  122. _ = RedisHelper.Set(GetLinkIdCacheKey(item.id), item, LinkCacheExpireSeconds);
  123. return Task.FromResult(item);
  124. }
  125. catch (Exception ex)
  126. {
  127. _ = new LoggerLibrary("TracksCore", "CreateLink")
  128. .Info(ex.Message, ex.StackTrace)
  129. .SaveAsync();
  130. return Task.FromResult<TrackLinkDTO>(null);
  131. }
  132. }
  133. /// <summary>
  134. /// 曝光/点击触发:计入 Redis,失败不影响返回
  135. /// </summary>
  136. public static Task<bool> TrackAsync(TrackLinkDTO link, string scene = "", int accountId = 0)
  137. {
  138. string dateStr = DateTime.Now.ToString("yyyyMMdd");
  139. string hourStr = DateTime.Now.ToString("yyyyMMddHH");
  140. string metricScene = ResolveMetricScene(link, scene);
  141. accountId = Math.Max(accountId, 0);
  142. string indexValue = BuildMetricIndexValue(link.event_type, link.id, metricScene);
  143. var metrics = new List<(string key, string indexKey, string indexValue, int expire)>
  144. {
  145. (BuildDailyCountKey(link.event_type, link.id, dateStr, metricScene), $"{RedisPrefix}:daily:index:{dateStr}:track", indexValue, DailyExpireSeconds),
  146. (BuildHourlyCountKey(link.event_type, link.id, hourStr, metricScene), $"{RedisPrefix}:hour:index:{hourStr}:track", indexValue, HourlyExpireSeconds),
  147. };
  148. if (accountId > 0)
  149. {
  150. string accountIndexValue = BuildMetricIndexValue(link.event_type, link.id, metricScene, accountId);
  151. metrics.Add((BuildDailyCountKey(link.event_type, link.id, dateStr, metricScene, accountId), $"{RedisPrefix}:daily:index:{dateStr}:track", accountIndexValue, DailyExpireSeconds));
  152. metrics.Add((BuildHourlyCountKey(link.event_type, link.id, hourStr, metricScene, accountId), $"{RedisPrefix}:hour:index:{hourStr}:track", accountIndexValue, HourlyExpireSeconds));
  153. }
  154. try
  155. {
  156. foreach (var metric in metrics)
  157. {
  158. RedisHelper.IncrBy(metric.key);
  159. RedisHelper.Expire(metric.key, metric.expire);
  160. RedisHelper.SAdd(metric.indexKey, metric.indexValue);
  161. RedisHelper.Expire(metric.indexKey, metric.expire);
  162. }
  163. return Task.FromResult(true);
  164. }
  165. catch (Exception ex)
  166. {
  167. _ = new LoggerLibrary("TracksCore", "TrackAsync")
  168. .Info(ex.Message, ex.StackTrace)
  169. .SaveAsync();
  170. return Task.FromResult(false);
  171. }
  172. }
  173. /// <summary>
  174. /// 日报:拉取指定日期 Redis 计数并落地到 track_daily_report,可高频重复执行。
  175. /// </summary>
  176. public static async Task<int> FlushDailyAsync(DateTime targetDate)
  177. {
  178. string dateStr = targetDate.ToString("yyyyMMdd");
  179. string indexKey = $"{RedisPrefix}:daily:index:{dateStr}:track";
  180. var indexMembers = await RedisHelper.SMembersAsync<string>(indexKey) ?? [];
  181. if (indexMembers == null || indexMembers.Length == 0) return 0;
  182. var buckets = new Dictionary<string, DailyReportBucket>();
  183. using var conn = DBContext.GetOpenConnection();
  184. var linkCache = new Dictionary<int, TrackLinkDTO>();
  185. foreach (var member in indexMembers)
  186. {
  187. var parts = member.Split('|');
  188. if (parts.Length < 2 || parts.Length > 4) continue;
  189. string eventType = NormalizeEventType(parts[0]);
  190. if (!SupportEventTypes.Contains(eventType)) continue;
  191. if (!int.TryParse(parts[1], out var trackId) || trackId <= 0) continue;
  192. bool hasScenePart = parts.Length >= 3;
  193. string memberScene = hasScenePart ? DecodeIndexPart(parts[2]) : string.Empty;
  194. int accountId = 0;
  195. if (parts.Length == 4 && (!int.TryParse(parts[3], out accountId) || accountId <= 0)) continue;
  196. string countKey = BuildDailyCountKey(eventType, trackId, dateStr, hasScenePart ? memberScene : string.Empty, accountId);
  197. int total = await RedisHelper.GetAsync<int>(countKey);
  198. if (total <= 0) continue;
  199. if (!linkCache.TryGetValue(trackId, out var link))
  200. {
  201. link = new DBContext.Table(conn, "track_links")
  202. .Get<TrackLinkDTO>("id=@id", new { id = trackId });
  203. if (link != null) linkCache[trackId] = link;
  204. }
  205. if (link != null)
  206. {
  207. string linkEventType = NormalizeEventType(link.event_type);
  208. if (!SupportEventTypes.Contains(linkEventType)) continue;
  209. if (!string.Equals(eventType, linkEventType, StringComparison.OrdinalIgnoreCase))
  210. {
  211. _ = new LoggerLibrary("TracksCore", "FlushDaily")
  212. .Info($"skip stale track bucket: member={member}, link_event_type={linkEventType}", string.Empty)
  213. .SaveAsync();
  214. continue;
  215. }
  216. eventType = linkEventType;
  217. }
  218. string reportScene = ResolveMetricScene(link, memberScene);
  219. string bucketKey = BuildMetricIndexValue(eventType, trackId, reportScene, accountId);
  220. if (!buckets.TryGetValue(bucketKey, out var bucket))
  221. {
  222. bucket = new DailyReportBucket
  223. {
  224. EventType = eventType,
  225. TrackId = trackId,
  226. Scene = reportScene,
  227. AccountId = accountId,
  228. Link = link
  229. };
  230. buckets[bucketKey] = bucket;
  231. }
  232. bucket.Total += total;
  233. }
  234. int rows = 0;
  235. foreach (var bucket in buckets.Values)
  236. {
  237. await UpsertDailyReportAsync(conn, targetDate.Date, bucket.EventType, bucket.TrackId, bucket.Scene, bucket.AccountId, bucket.Link, bucket.Total);
  238. rows++;
  239. }
  240. return rows;
  241. }
  242. private static Task<int> UpsertDailyReportAsync(IDbConnection conn, DateTime reportDate, string eventType, int trackId, string scene, int accountId, TrackLinkDTO? link, int total)
  243. {
  244. const string sql = @"
  245. INSERT INTO track_daily_report
  246. (report_date, event_type, track_link_id, account_id, platform, typename, scene, unique_id, event_count, create_time, update_time)
  247. VALUES
  248. (@reportDate, @eventType, @trackId, @accountId, @platform, @typename, @scene, @uniqueId, @eventCount, @now, @now)
  249. ON DUPLICATE KEY UPDATE
  250. event_count = VALUES(event_count),
  251. account_id = VALUES(account_id),
  252. platform = VALUES(platform),
  253. typename = VALUES(typename),
  254. scene = VALUES(scene),
  255. unique_id = VALUES(unique_id),
  256. update_time = VALUES(update_time);";
  257. return conn.ExecuteAsync(sql, new
  258. {
  259. reportDate,
  260. eventType,
  261. trackId,
  262. accountId,
  263. platform = link?.platform ?? string.Empty,
  264. typename = link?.typename ?? string.Empty,
  265. scene,
  266. uniqueId = link?.unique_id ?? string.Empty,
  267. eventCount = total,
  268. now = DateTime.Now
  269. });
  270. }
  271. public static async Task<List<TrackHourlyReportDTO>> GetHourlyReportAsync(int trackId, DateTime targetDate, string scene = "", int accountId = 0)
  272. {
  273. var result = new List<TrackHourlyReportDTO>();
  274. if (trackId <= 0) return result;
  275. var link = await GetLinkByIdAsync(trackId);
  276. if (link == null) return result;
  277. string eventType = NormalizeEventType(link.event_type);
  278. if (!SupportEventTypes.Contains(eventType)) return result;
  279. string metricScene = ResolveMetricScene(link, scene);
  280. string linkScene = ResolveMetricScene(link, string.Empty);
  281. string dateStr = targetDate.ToString("yyyyMMdd");
  282. accountId = Math.Max(accountId, 0);
  283. for (int hour = 0; hour < 24; hour++)
  284. {
  285. string hourStr = $"{dateStr}{hour:00}";
  286. int total = await RedisHelper.GetAsync<int>(BuildHourlyCountKey(eventType, trackId, hourStr, metricScene, accountId));
  287. if (accountId == 0 && !string.IsNullOrEmpty(metricScene) && metricScene == linkScene)
  288. {
  289. total += await RedisHelper.GetAsync<int>(BuildHourlyCountKey(eventType, trackId, hourStr, string.Empty));
  290. }
  291. result.Add(new TrackHourlyReportDTO
  292. {
  293. track_link_id = trackId,
  294. event_type = eventType,
  295. platform = link.platform ?? string.Empty,
  296. typename = link.typename ?? string.Empty,
  297. scene = metricScene,
  298. unique_id = link.unique_id ?? string.Empty,
  299. account_id = accountId,
  300. report_date = targetDate.Date,
  301. hour = hour,
  302. event_count = total
  303. });
  304. }
  305. return result;
  306. }
  307. public static string BuildPath(TrackType type, TkDataDTO result)
  308. {
  309. int trackId = type == TrackType.Click ? 1 : 2;
  310. if (result == null) return string.Empty;
  311. string unique_id = $"1|{result.itemId}_{result.mktId}";
  312. return BuildPath(trackId, unique_id, GetTrackScene(result.parse_type, result.riskStrategy), result.accountId);
  313. }
  314. public static string BuildPath(TrackType type, JdDataDTO result)
  315. {
  316. int trackId = type == TrackType.Click ? 19 : 20;
  317. if (result == null) return string.Empty;
  318. string unique_id = $"13|{result.shortLinkurl.UrlEncode()}";
  319. return BuildPath(trackId, unique_id, GetTrackScene(result.parse_type, result.riskStrategy), result.accountId);
  320. }
  321. public static string BuildPath(TrackType type, PddDataDTO result)
  322. {
  323. int trackId = type == TrackType.Click ? 21 : 22;
  324. if (result == null) return string.Empty;
  325. string unique_id = $"9|{result.shortLinkurl.UrlEncode()}";
  326. return BuildPath(trackId, unique_id, GetTrackScene(result.parse_type, result.riskStrategy), result.accountId);
  327. }
  328. public static string BuildBrwSimilarPath(TrackType type, TkDataDTO result, PromotionQueryItemDTO similar_goods = null, int index = 0)
  329. {
  330. int trackId = type == TrackType.Click ? 23 : 24;
  331. if (result == null) return string.Empty;
  332. string unique_id = $"1|{result.itemId}_{result.mktId}";
  333. string scene = string.Empty;
  334. if (similar_goods != null)
  335. {
  336. unique_id = $"1|{result.itemId}_{result.mktId}_{index}";
  337. scene = "similar";
  338. }
  339. return BuildPath(trackId, unique_id, scene, result.accountId);
  340. }
  341. public static string BuildPath(int trackId, string unique_id = "", string scene = "", int accountId = 0)
  342. {
  343. scene = NormalizeReportScene(scene);
  344. string url = $"https://api.molilian.com/tracks/track?track_id={trackId}&unique_id={unique_id}";
  345. if (!string.IsNullOrEmpty(scene)) url += $"&scene={scene.UrlEncode()}";
  346. if (accountId > 0) url += $"&account_id={accountId}";
  347. return url;
  348. }
  349. public static string ResolveMetricScene(TrackLinkDTO? link, string scene = "")
  350. {
  351. scene = NormalizeReportScene(scene);
  352. if (!string.IsNullOrEmpty(scene)) return scene;
  353. return NormalizeOrAll(link?.scene ?? string.Empty);
  354. }
  355. public static string GetLinkCacheKey(string eventType, string typename, string scene, string uniqueId)
  356. {
  357. return GetLinkCacheKey(eventType, string.Empty, typename, scene, uniqueId);
  358. }
  359. public static string GetLinkCacheKey(string eventType, string platform, string typename, string scene, string uniqueId)
  360. {
  361. eventType = NormalizeEventType(eventType);
  362. platform = Normalize(platform);
  363. typename = Normalize(typename);
  364. scene = NormalizeOrAll(scene);
  365. uniqueId = NormalizeOrAll(uniqueId);
  366. return $"{RedisPrefix}:cache:{LinkCacheVersion}:link:{eventType}:{platform}:{typename}:{scene}:{uniqueId}";
  367. }
  368. public static string GetLinkIdCacheKey(int trackId)
  369. {
  370. return $"{RedisPrefix}:cache:{LinkCacheVersion}:linkid:{trackId}";
  371. }
  372. public static async Task<TrackLinkDTO> GetLinkByIdAsync(int trackId)
  373. {
  374. if (trackId <= 0) return null;
  375. string cacheKey = GetLinkIdCacheKey(trackId);
  376. try
  377. {
  378. var cached = RedisHelper.Get<TrackLinkDTO>(cacheKey);
  379. if (cached != null) return cached;
  380. }
  381. catch { }
  382. try
  383. {
  384. var item = new DBContext.Table("track_links").Get<TrackLinkDTO>("id=@id", new { id = trackId });
  385. if (item != null)
  386. {
  387. _ = RedisHelper.Set(cacheKey, item, LinkCacheExpireSeconds);
  388. }
  389. return item;
  390. }
  391. catch
  392. {
  393. return null;
  394. }
  395. }
  396. public static Task<bool> LogTrackRequestAsync(TrackRequestLogDTO dto)
  397. {
  398. try
  399. {
  400. dto.event_type = NormalizeEventType(dto.event_type);
  401. dto.platform = Normalize(dto.platform);
  402. dto.typename = Normalize(dto.typename);
  403. dto.scene = NormalizeOrAll(dto.scene);
  404. dto.unique_id = NormalizeOrAll(dto.unique_id);
  405. dto.ip = Normalize(dto.ip);
  406. dto.user_agent = Normalize(dto.user_agent);
  407. dto.referer = Normalize(dto.referer);
  408. dto.create_time = DateTime.Now;
  409. RedisHelper.RPush(TrackRequestLogKey, dto);
  410. return Task.FromResult(true);
  411. }
  412. catch
  413. {
  414. return Task.FromResult(false);
  415. }
  416. }
  417. public static async Task<int> InsertTrackRequestLogAsync(int limit, YunhuiKit.RedisClient redis)
  418. {
  419. int count = 0;
  420. try
  421. {
  422. using var conn = DBContext.GetOpenConnection();
  423. for (int i = 0; i < limit; i++)
  424. {
  425. var entity = await redis.LPopAsync<TrackRequestLogDTO>(TrackRequestLogKey);
  426. if (entity == null) break;
  427. try
  428. {
  429. if (entity.create_time == default) entity.create_time = DateTime.Now;
  430. conn.Insert(entity);
  431. count++;
  432. }
  433. catch
  434. {
  435. // ignore malformed item
  436. }
  437. }
  438. }
  439. catch
  440. {
  441. return count;
  442. }
  443. return count;
  444. }
  445. private static string Normalize(string value)
  446. {
  447. return (value ?? string.Empty).Trim();
  448. }
  449. private static string NormalizeReportScene(string value)
  450. {
  451. var scene = Normalize(value);
  452. return scene == "默认场景" ? string.Empty : scene;
  453. }
  454. private static string NormalizeOrAll(string value)
  455. {
  456. var result = Normalize(value);
  457. return string.IsNullOrEmpty(result) ? DefaultDimensionValue : result;
  458. }
  459. private static string NormalizeEventType(string value)
  460. {
  461. return Normalize(value).ToLowerInvariant();
  462. }
  463. private static string GetTrackScene(string parseType, string riskStrategy)
  464. {
  465. parseType = NormalizeReportScene(parseType);
  466. if (!string.IsNullOrEmpty(parseType)) return parseType;
  467. riskStrategy = NormalizeReportScene(riskStrategy);
  468. if (!string.IsNullOrEmpty(riskStrategy)) return riskStrategy;
  469. return DefaultDimensionValue;
  470. }
  471. private static string BuildDailyCountKey(string eventType, int trackId, string dateStr, string scene, int accountId = 0)
  472. {
  473. eventType = NormalizeEventType(eventType);
  474. scene = NormalizeReportScene(scene);
  475. string key = $"{RedisPrefix}:daily:{eventType}:{trackId}:{dateStr}";
  476. if (!string.IsNullOrEmpty(scene)) key += $":{EncodeIndexPart(scene)}";
  477. if (accountId > 0) key += $":account:{accountId}";
  478. return key;
  479. }
  480. private static string BuildHourlyCountKey(string eventType, int trackId, string hourStr, string scene, int accountId = 0)
  481. {
  482. eventType = NormalizeEventType(eventType);
  483. scene = NormalizeReportScene(scene);
  484. string key = $"{RedisPrefix}:hour:{eventType}:{trackId}:{hourStr}";
  485. if (!string.IsNullOrEmpty(scene)) key += $":{EncodeIndexPart(scene)}";
  486. if (accountId > 0) key += $":account:{accountId}";
  487. return key;
  488. }
  489. private static string BuildMetricIndexValue(string eventType, int trackId, string scene, int accountId = 0)
  490. {
  491. eventType = NormalizeEventType(eventType);
  492. scene = NormalizeReportScene(scene);
  493. if (accountId > 0) return $"{eventType}|{trackId}|{EncodeIndexPart(scene)}|{accountId}";
  494. if (string.IsNullOrEmpty(scene)) return $"{eventType}|{trackId}";
  495. return $"{eventType}|{trackId}|{EncodeIndexPart(scene)}";
  496. }
  497. private static string EncodeIndexPart(string value)
  498. {
  499. return Normalize(value).UrlEncode();
  500. }
  501. private static string DecodeIndexPart(string value)
  502. {
  503. try
  504. {
  505. return Normalize(value).UrlDecode();
  506. }
  507. catch
  508. {
  509. return Normalize(value);
  510. }
  511. }
  512. private sealed class DailyReportBucket
  513. {
  514. public string EventType { get; set; } = string.Empty;
  515. public int TrackId { get; set; }
  516. public string Scene { get; set; } = string.Empty;
  517. public int AccountId { get; set; }
  518. public TrackLinkDTO? Link { get; set; }
  519. public int Total { get; set; }
  520. }
  521. private static int GetTrackLinkId(IDbConnection conn, string eventType, string platform, string scene, string uniqueId)
  522. {
  523. try
  524. {
  525. var record = new DBContext.Table(conn, "track_links")
  526. .Fields("id")
  527. .Get<TrackLinkDTO>("event_type=@event_type AND platform=@platform AND scene=@scene AND unique_id=@unique_id",
  528. new { event_type = eventType, platform, scene, unique_id = uniqueId });
  529. if (record == null) return 0;
  530. return record.id;
  531. }
  532. catch
  533. {
  534. return 0;
  535. }
  536. }
  537. }
  538. }