CouponCenterCore.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. using dodohold.core;
  2. namespace molilian.core
  3. {
  4. public partial class CouponCenterCore
  5. {
  6. public const string Version = "0.0.1";
  7. private const string CacheKey = "cache:coupon_center:activities:v1";
  8. private const int CacheExpireSeconds = 600;
  9. private const int EmptyCacheExpireSeconds = 60;
  10. private static readonly object _lockObj = new();
  11. private static CouponCenterActivitiesResponseDTO? _cached;
  12. private static DateTime _cachedExpireAt = DateTime.MinValue;
  13. public static CouponCenterActivitiesResponseDTO GetActivities(string channel = "", bool force = false)
  14. {
  15. var snapshot = GetSnapshot(force);
  16. var platformId = NormalizePlatformId(channel);
  17. return BuildResponse(snapshot, platformId);
  18. }
  19. public static void Refresh()
  20. {
  21. _ = GetSnapshot(true);
  22. }
  23. private static CouponCenterActivitiesResponseDTO GetSnapshot(bool force = false)
  24. {
  25. if (!force && _cached != null && _cachedExpireAt > DateTime.Now)
  26. {
  27. return _cached;
  28. }
  29. if (!force)
  30. {
  31. try
  32. {
  33. var cached = RedisHelper.Get<CouponCenterActivitiesResponseDTO>(CacheKey);
  34. if (cached != null)
  35. {
  36. SetLocalCache(cached, CacheExpireSeconds);
  37. return cached;
  38. }
  39. }
  40. catch
  41. {
  42. }
  43. }
  44. lock (_lockObj)
  45. {
  46. if (!force && _cached != null && _cachedExpireAt > DateTime.Now)
  47. {
  48. return _cached;
  49. }
  50. var snapshot = LoadFromDb();
  51. var cacheSeconds = HasData(snapshot) ? CacheExpireSeconds : EmptyCacheExpireSeconds;
  52. SetLocalCache(snapshot, cacheSeconds);
  53. if (HasData(snapshot))
  54. {
  55. try
  56. {
  57. RedisHelper.Set(CacheKey, snapshot, CacheExpireSeconds);
  58. }
  59. catch
  60. {
  61. }
  62. }
  63. return snapshot;
  64. }
  65. }
  66. private static CouponCenterActivitiesResponseDTO LoadFromDb()
  67. {
  68. try
  69. {
  70. var platformEntities = new DBContext.Table("coupon_center_platforms")
  71. .Where("status=@status", new { status = 1 })
  72. .Select<CouponCenterPlatformEntityDTO>()?
  73. .OrderBy(item => item.sort)
  74. .ThenBy(item => item.id)
  75. .ToList() ?? [];
  76. var platformIds = platformEntities
  77. .Where(item => !string.IsNullOrWhiteSpace(item.id))
  78. .Select(item => item.id)
  79. .ToHashSet(StringComparer.OrdinalIgnoreCase);
  80. var activityEntities = new DBContext.Table("coupon_center_activities")
  81. .Where("status=@status", new { status = 1 })
  82. .Select<CouponCenterActivityEntityDTO>()?
  83. .Where(item => !string.IsNullOrWhiteSpace(item.id) && platformIds.Contains(item.platform_id))
  84. .OrderBy(item => item.sort)
  85. .ThenBy(item => item.id)
  86. .ToList() ?? [];
  87. var activityIds = activityEntities
  88. .Select(item => item.id)
  89. .ToHashSet(StringComparer.OrdinalIgnoreCase);
  90. var couponLookup = (new DBContext.Table("coupon_center_coupons")
  91. .Where("status=@status", new { status = 1 })
  92. .Select<CouponCenterCouponEntityDTO>()?
  93. .Where(item => !string.IsNullOrWhiteSpace(item.activity_id) && activityIds.Contains(item.activity_id))
  94. .OrderBy(item => item.sort)
  95. .ThenBy(item => item.id)
  96. .GroupBy(item => item.activity_id, StringComparer.OrdinalIgnoreCase)
  97. .ToDictionary(
  98. group => group.Key,
  99. group => group.Select(MapCoupon).ToList(),
  100. StringComparer.OrdinalIgnoreCase)) ?? new Dictionary<string, List<CouponCenterCouponDTO>>(StringComparer.OrdinalIgnoreCase);
  101. return new CouponCenterActivitiesResponseDTO
  102. {
  103. version = Version,
  104. platforms = platformEntities.Select(MapPlatform).ToList(),
  105. activities = activityEntities.Select(item => MapActivity(item, couponLookup)).ToList(),
  106. pagination = new CouponCenterPaginationDTO()
  107. };
  108. }
  109. catch (Exception ex)
  110. {
  111. _ = new LoggerLibrary("CouponCenterCore", "LoadFromDb")
  112. .Info(ex.Message, ex.StackTrace)
  113. .SaveAsync();
  114. return new CouponCenterActivitiesResponseDTO
  115. {
  116. version = Version,
  117. pagination = new CouponCenterPaginationDTO()
  118. };
  119. }
  120. }
  121. private static CouponCenterActivitiesResponseDTO BuildResponse(CouponCenterActivitiesResponseDTO snapshot, string platformId)
  122. {
  123. if (snapshot == null)
  124. {
  125. return new CouponCenterActivitiesResponseDTO
  126. {
  127. version = Version,
  128. pagination = new CouponCenterPaginationDTO()
  129. };
  130. }
  131. if (string.IsNullOrWhiteSpace(platformId))
  132. {
  133. return CloneResponse(snapshot);
  134. }
  135. var platforms = snapshot.platforms ?? [];
  136. var activities = snapshot.activities ?? [];
  137. return new CouponCenterActivitiesResponseDTO
  138. {
  139. version = snapshot.version,
  140. platforms = platforms
  141. .Where(item => item.id.Equals(platformId, StringComparison.OrdinalIgnoreCase))
  142. .Select(ClonePlatform)
  143. .ToList(),
  144. activities = activities
  145. .Where(item => item.platformId.Equals(platformId, StringComparison.OrdinalIgnoreCase))
  146. .Select(CloneActivity)
  147. .ToList(),
  148. pagination = new CouponCenterPaginationDTO()
  149. };
  150. }
  151. private static CouponCenterActivitiesResponseDTO CloneResponse(CouponCenterActivitiesResponseDTO source)
  152. {
  153. var platforms = source.platforms ?? [];
  154. var activities = source.activities ?? [];
  155. var pagination = source.pagination ?? new CouponCenterPaginationDTO();
  156. return new CouponCenterActivitiesResponseDTO
  157. {
  158. version = source.version,
  159. platforms = platforms.Select(ClonePlatform).ToList(),
  160. activities = activities.Select(CloneActivity).ToList(),
  161. pagination = new CouponCenterPaginationDTO
  162. {
  163. hasMore = pagination.hasMore,
  164. nextCursor = pagination.nextCursor
  165. }
  166. };
  167. }
  168. private static CouponCenterPlatformDTO ClonePlatform(CouponCenterPlatformDTO item)
  169. {
  170. return new CouponCenterPlatformDTO
  171. {
  172. id = item.id,
  173. name = item.name,
  174. iconUrl = item.iconUrl
  175. };
  176. }
  177. private static CouponCenterActivityDTO CloneActivity(CouponCenterActivityDTO item)
  178. {
  179. var coupons = item.coupons ?? [];
  180. return new CouponCenterActivityDTO
  181. {
  182. id = item.id,
  183. platformId = item.platformId,
  184. name = item.name,
  185. shortDesc = item.shortDesc,
  186. deeplink = item.deeplink,
  187. coupons = coupons.Select(CloneCoupon).ToList()
  188. };
  189. }
  190. private static CouponCenterCouponDTO CloneCoupon(CouponCenterCouponDTO item)
  191. {
  192. return new CouponCenterCouponDTO
  193. {
  194. id = item.id,
  195. value = item.value,
  196. expireTime = item.expireTime,
  197. category = item.category,
  198. threshold = item.threshold,
  199. status = item.status
  200. };
  201. }
  202. private static CouponCenterPlatformDTO MapPlatform(CouponCenterPlatformEntityDTO item)
  203. {
  204. return new CouponCenterPlatformDTO
  205. {
  206. id = item.id,
  207. name = item.name,
  208. iconUrl = item.icon_url
  209. };
  210. }
  211. private static CouponCenterActivityDTO MapActivity(CouponCenterActivityEntityDTO item, IReadOnlyDictionary<string, List<CouponCenterCouponDTO>> couponLookup)
  212. {
  213. return new CouponCenterActivityDTO
  214. {
  215. id = item.id,
  216. platformId = item.platform_id,
  217. name = item.name,
  218. shortDesc = item.short_desc,
  219. deeplink = string.IsNullOrWhiteSpace(item.deeplink)
  220. ? $"magicchain://coupon-center/activity?activityId={item.id}"
  221. : item.deeplink,
  222. coupons = couponLookup.TryGetValue(item.id, out var coupons)
  223. ? coupons.Select(CloneCoupon).ToList()
  224. : []
  225. };
  226. }
  227. private static CouponCenterCouponDTO MapCoupon(CouponCenterCouponEntityDTO item)
  228. {
  229. return new CouponCenterCouponDTO
  230. {
  231. id = item.id,
  232. value = item.value,
  233. expireTime = item.expire_time,
  234. category = item.category,
  235. threshold = item.threshold,
  236. status = string.IsNullOrWhiteSpace(item.coupon_status) ? "available" : item.coupon_status
  237. };
  238. }
  239. private static bool HasData(CouponCenterActivitiesResponseDTO snapshot)
  240. {
  241. if (snapshot == null)
  242. {
  243. return false;
  244. }
  245. return (snapshot.platforms?.Count ?? 0) > 0 || (snapshot.activities?.Count ?? 0) > 0;
  246. }
  247. private static void SetLocalCache(CouponCenterActivitiesResponseDTO snapshot, int cacheSeconds)
  248. {
  249. _cached = snapshot;
  250. _cachedExpireAt = DateTime.Now.AddSeconds(cacheSeconds);
  251. }
  252. private static string NormalizePlatformId(string channel)
  253. {
  254. channel = channel?.Trim().ToLowerInvariant() ?? string.Empty;
  255. return channel switch
  256. {
  257. "taobao" => "tb",
  258. "vip" => "vipshop",
  259. "tbflash" => "tb_flash",
  260. "taobaoflash" => "tb_flash",
  261. "淘宝闪购" => "tb_flash",
  262. _ => channel,
  263. };
  264. }
  265. }
  266. }