Quellcode durchsuchen

veapi改成otherapi;
补点击的batchId 从订单支付时间改成入库时间;
isTaobaoUrl 处理指定淘宝数字链接的规则;

dodo hold vor 1 Jahr
Ursprung
Commit
904bf5794c

+ 6 - 6
molilian.api/Controllers/public/TaskController.cs

@@ -62,7 +62,7 @@ namespace molilian.api.Controllers
                 int accountId = account.id;
 
                 DateTime create_time = DateTime.Now.AddDays(-2);
-                string filter = "accountId=@accountId AND create_time>@create_time AND itemId IS NULL";
+                string filter = "accountId=@accountId AND create_time>@create_time AND (itemId IS NULL OR itemId = '')";
                 var order_list = new DBContext.Table("tk_order_details")
                       .Where(filter, new { accountId, create_time })
                       .Order("create_time")
@@ -193,9 +193,6 @@ namespace molilian.api.Controllers
             int total = 0;
             foreach (var account in list)
             {
-#if DEBUG
-                if (account.id != 17) continue;
-#endif
                 if (!account.enable_sync_order) continue;
 
                 try
@@ -924,7 +921,10 @@ namespace molilian.api.Controllers
             EndPointCore.Refresh();
             ProxyNodesCore.Refresh();
             TkPoolCore.Refresh();
-            VeapiPoolCore.Refresh();
+            OtherApiPoolCore.Refresh();
+            TkEndpointCore.Refresh();
+            TkEndpointManager.Refresh();
+
 
             ApiAccountCore.Refresh();
             DataokeCore.Refresh();
@@ -952,7 +952,7 @@ namespace molilian.api.Controllers
             EndPointCore.Refresh();
             ProxyNodesCore.Refresh();
             TkPoolCore.Refresh();
-            VeapiPoolCore.Refresh();
+            OtherApiPoolCore.Refresh();
             JdPoolCore.Refresh();
             PddPoolCore.Refresh();
 

+ 1 - 1
molilian.api/Controllers/public/TestController.cs

@@ -42,7 +42,7 @@ namespace molilian.api.Controllers
                 list
             });
         }
-
+         
 
         [HttpGet]
         public async Task<ActionResult> testReconnectionRedis()

+ 8 - 126
molilian.api/Controllers/public/TkController.cs

@@ -50,6 +50,7 @@ namespace molilian.api.Controllers
             var oaid = form.Read("oaid", string.Empty);
             var clickId = form.Read("clickId", string.Empty);
             var commerceType = form.Read<int>("commerceType", 0);
+            var riskStrategy = form.Read("riskStrategy", string.Empty);
 
 
             //验证签名
@@ -74,7 +75,7 @@ namespace molilian.api.Controllers
             {
                 return new APIResult(new { success = false, message = "验证错误4" }, APIResultCodeEnum.Unauthorized);
             }
-            return await UnionParseCore.UnionParseAsync(content, channel, commerceType, ip, oaid, 0, clickId);
+            return await UnionParseCore.UnionParseAsync(content, channel, commerceType, ip, oaid, riskStrategy, 0, clickId);
         }
 
         [HttpPost]
@@ -87,12 +88,13 @@ namespace molilian.api.Controllers
             var clickId = form.Read("clickId", string.Empty);
             int accountid = form.Read("aid", 0);
             var commerceType = form.Read<int>("commerceType", 0);
+            var riskStrategy = form.Read("riskStrategy", string.Empty);
 
 #if DEBUG
             //ip = "127.0.0.1";
             //oaid = "test-oaid";
 #endif
-            return await UnionParseCore.UnionParseAsync(content, channel, commerceType, ip, oaid, accountid, clickId);
+            return await UnionParseCore.UnionParseAsync(content, channel, commerceType, ip, oaid, riskStrategy, accountid, clickId);
         }
 
 
@@ -107,6 +109,8 @@ namespace molilian.api.Controllers
             int accountid = form.Read("aid", 0);
             int count = form.Read("count", 0);
             var commerceType = form.Read<int>("commerceType", 0);
+            //入参参数。brw-浏览器,qapp-快应用
+            var riskStrategy = form.Read("riskStrategy", string.Empty);
 
 #if DEBUG
             //ip = "127.0.0.1";
@@ -114,7 +118,7 @@ namespace molilian.api.Controllers
 #endif
             for (int i = 0; i < count; i++)
             {
-                _ = await UnionParseCore.UnionParseAsync(content[i], channel, commerceType, ip, oaid, accountid, clickId);
+                _ = await UnionParseCore.UnionParseAsync(content[i], channel, commerceType, ip, oaid, riskStrategy, accountid, clickId);
             }
             return new APIResult(new { msg = "ok" });
         }
@@ -174,128 +178,6 @@ namespace molilian.api.Controllers
             return await UnionCouponCore.UnionCouponAsync(content, dplink, channel, ip, oaid, clickId);
         }
 
-        /// <summary>
-        /// 第三方调用
-        /// </summary>
-        /// <param name="s"></param>
-        /// <param name="s2"></param>
-        /// <returns></returns>
-        [HttpGet]
-        public async Task<ActionResult> parse(string s, string s2 = "", string ip = "", string oaid = "")
-        {
-            AlimamaPlus alimama = null;
-            ip = AlimamaPlus.OppoDecode(ip);
-            oaid = AlimamaPlus.OppoDecode(oaid);
-            var result = AlimamaPlus.GetFormattedObject(s, ip, oaid);
-            string body = "";
-            string content = s.UrlDecode();
-            string content2 = s2.UrlDecode();
-            string account_name = string.Empty;
-            result.rawContent = content;
-            result.rawContent2 = content2;
-
-            try
-            {
-                if (AlimamaPlus.ShouldIgnoreRequest(ip, oaid, out string reason))
-                {
-                    result.success = false;
-                    result.message = "放弃转链";
-                    result.reason = reason;
-                    _ = TkLogCore.LogAsync(result);
-                    return new APIResult(new
-                    {
-                        result.success,
-                        result.message,
-                        result.content,
-                        result.couponAmount,
-                        result.taoToken,
-                        result.shortLinkurl,
-                        result.deeplink_url,
-                    });
-                }
-                var account = await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.parse);
-                if (account == null)
-                {
-                    result.success = false;
-                    result.message = "放弃转链";
-                    result.reason = "没有匹配账号";
-                    _ = TkLogCore.LogAsync(result);
-                    return new APIResult(new
-                    {
-                        result.success,
-                        result.message,
-                        result.content,
-                        result.couponAmount,
-                        result.taoToken,
-                        result.shortLinkurl,
-                        result.deeplink_url,
-                    });
-                }
-
-                result.accountId = account.id;
-                result.accountName = account.company;
-                account_name = account.name;
-                alimama = new AlimamaPlus(account);
-
-                if (alimama.ShouldIgnoreOtherAff(s2))
-                {
-                    result.success = false;
-                    result.message = "放弃转链";
-                    result.reason = "其他推广链接";
-                    _ = TkLogCore.LogAsync(result);
-                    return new APIResult(new
-                    {
-                        result.success,
-                        result.message,
-                        result.content,
-                        result.couponAmount,
-                        result.taoToken,
-                        result.shortLinkurl,
-                        result.deeplink_url,
-                    });
-                }
-
-                result = await alimama.UnionParseAsync(ip, oaid, content, 0, result);
-            }
-            catch (Exception ex)
-            {
-                if (ex.Message.Contains("was canceled"))
-                {
-                    result.success = false;
-                    result.message = "放弃转链";
-                    result.reason = "请求超时";
-                }
-                else
-                {
-                    //new LoggerLibrary("api_error", "parse")
-                    //.Info(s, s2)
-                    //.Info($"【taobao】{account_name}", body)
-                    //.Info(ex.Message, ex.StackTrace)
-                    //.SaveAsync();
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = $"【转链接口异常】{result.accountName}\n{content}\n{content2}\n{body}\n\n{ex.Message}\n{ex.StackTrace}",
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                    result.success = false;
-                    result.message = "放弃转链";
-                    result.reason = "转链接口异常";
-                }
-            }
-
-            _ = TkLogCore.LogAsync(result, alimama);
-            return new APIResult(new
-            {
-                result.success,
-                result.message,
-                result.content,
-                result.couponAmount,
-                result.taoToken,
-                result.shortLinkurl,
-                result.deeplink_url,
-            });
-        }
 
         [HttpGet]
         public async Task<ActionResult> jd_parse(string s, string s2 = "", string ip = "", string oaid = "")
@@ -306,7 +188,7 @@ namespace molilian.api.Controllers
             string content2 = s2.UrlDecode();
             try
             {
-                if (AlimamaPlus.ShouldIgnoreRequest(ip, oaid, out string reason))
+                if (AlimamaPlus.ShouldIgnoreRequest(ip, oaid, string.Empty, out string reason))
                 {
                     result.success = false;
                     result.message = "放弃转链";

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
molilian.api/Properties/PublishProfiles/latest.pubxml.user


+ 2 - 0
molilian.core/Core/aliyun/AliyunCore.cs

@@ -12,6 +12,7 @@ using static AlibabaCloud.SDK.Ecs20140526.Models.DescribeNetworkInterfacesRespon
 using static AlibabaCloud.SDK.Vpc20160428.Models.DescribeEipAddressesResponseBody.DescribeEipAddressesResponseBodyEipAddresses;
 using AlibabaCloud.SDK.ImageSearch20210501.Models;
 using static AlibabaCloud.SDK.Ecs20140526.Models.DescribeNetworkInterfacesResponseBody.DescribeNetworkInterfacesResponseBodyNetworkInterfaceSets;
+using TencentCloud.Cam.V20190116.Models;
 
 namespace molilian.core
 {
@@ -98,6 +99,7 @@ namespace molilian.core
         }
 
 
+
         public QueryAccountBalanceResponse QueryAccountBalance()
         {
             return plus.QueryAccountBalance();

+ 2 - 2
molilian.core/Core/cps/UnionCpsCore.cs

@@ -248,7 +248,7 @@ namespace molilian.core
             {
                 string reason;
                 if (AlimamaPlus.FilterRiskLink(ip, oaid, content, out reason) ||
-                    AlimamaPlus.ShouldIgnoreRequest(ip, oaid, out reason))
+                    AlimamaPlus.ShouldIgnoreRequest(ip, oaid, string.Empty, out reason))
                 {
                     result.success = false;
                     result.message = "放弃转链";
@@ -263,7 +263,7 @@ namespace molilian.core
                     });
                 }
 
-                var account = await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.parse);
+                var account = await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.parse, false, string.Empty);
                 if (account == null)
                 {
                     result.success = false;

+ 1 - 1
molilian.core/Core/log/taobao.cs

@@ -248,8 +248,8 @@ namespace molilian.core
                 .Add("subCode", data.subCode)
                 .Add("ip", data.ip)
                 .Add("oaid", data.oaid)
+                .Add("riskStrategy", data.riskStrategy)
                 .Add("create_time", data.create_time)
-
                 .Create(DBContext.InsertType.NORMAL, transaction);
         }
 

+ 14 - 10
molilian.core/Core/taoke/OrderTrackingCore.cs

@@ -95,7 +95,7 @@ namespace molilian.core
                             // 按优先级依次查询不同的缓存key
                             TkDataDTO result = null;
 
-           
+
                             // 遍历所有关联账户ID
                             foreach (var relatedId in relatedAccountIds)
                             {
@@ -198,7 +198,8 @@ namespace molilian.core
 
         public static async Task<bool> MatchOrder(TkPoolDTO account, TkOrderDetailDTO item)
         {
-            if (item.tbPaidTime < DateTime.Now.AddDays(-1)) return false;
+            DateTime now = DateTime.Now;
+            if (item.tbPaidTime < now.AddDays(-24)) return false;
 
             var summary = await GetLinkSummaryAsync(account, item.itemId, item.mktId, item.itemTitle);
             if (summary == null) return false;
@@ -213,11 +214,10 @@ namespace molilian.core
 
             DateTime paidTime = item.tbPaidTime;
             if (paidTime < summary.create_time) return false;
+            if (paidTime < now.AddDays(-24)) return false;
 
-            // todo 改成入库时间
-
-            int minutes = paidTime.Minute / 10 * 10;
-            DateTime rounded = new(paidTime.Year, paidTime.Month, paidTime.Day, paidTime.Hour, minutes, 0);
+            int minutes = now.Minute / 10 * 10;
+            DateTime rounded = new(now.Year, now.Month, now.Day, now.Hour, minutes, 0);
             string batchId = rounded.ToString("yyyyMMddHHmm");
 
             string accountName = item.accountName;
@@ -234,7 +234,9 @@ namespace molilian.core
                     break;
             }
 
-            DateTime expTime = item.tbPaidTime.Hour >= 21 ? item.tbPaidTime.AddHours(3) : item.tbPaidTime.Date.AddDays(1);
+            var exist = new DBContext.Table("tk_order_tracking").Fields("id").Get<dynamic>("tradeId=@tradeId", new { item.tradeId });
+
+            DateTime expTime = now.Hour >= 21 ? now.AddHours(3) : now.Date.AddDays(1);
             var data = new TkOrderTrackingDTO()
             {
                 channel = summary.channel,
@@ -260,9 +262,11 @@ namespace molilian.core
                 exp_time = expTime,
                 click_num = click_num,
             };
-
-            using var conn = DBContext.GetOpenConnection();
-            conn.Replace(data);
+            if (exist == null)
+            {
+                using var conn = DBContext.GetOpenConnection();
+                conn.Insert(data);
+            }
             //RemoveLinkSummary(item.accountId, item.itemId, item.mktId, item.itemTitle);
             await RemoveLinkSummaryAsync(item.accountId, item.itemId, item.mktId, item.itemTitle);
             return true;

+ 5 - 3
molilian.core/Core/taoke/TaokeOpenCore.cs

@@ -73,7 +73,7 @@ namespace molilian.core
                 TkPoolDTO account;
                 if (aid == 0)
                 {
-                    account = await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.promotionQuery);
+                    account = await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.promotionQuery, false, string.Empty);
                 }
                 else
                 {
@@ -124,13 +124,15 @@ namespace molilian.core
                 switch (account.api)
                 {
                     case "veapi":
+                    case "ddx":
+                    case "other_api":
                         if (account.api_id > 0)
                         {
                             result.proxy_nodename = null;
                             result.proxy_node = null;
 
-                            var api = VeapiPoolCore.Get(account.api_id);
-                            var core = new VeapiPlus(account.name, api.key, api.sessionKey);
+                            var api = OtherApiPoolCore.Get(account.api_id);
+                            var core = new OtherApiPlus(account.name, api.key, api.sessionKey, account.api, api.api_url);
                             core.picSimilaritem(img, ref result);
 
                             var ts2 = DateTime.Now - stime;

+ 34 - 7
molilian.core/Core/taoke/TkEndpointCore.cs

@@ -1,5 +1,6 @@
 using dodohold.core;
 using molilian.core;
+using Org.BouncyCastle.Crypto;
 using System;
 using System.Collections.Concurrent;
 using System.Linq;
@@ -13,7 +14,7 @@ public partial class TkEndpointCore
     private static IEnumerable<TkEndpointConfigDTO> _cached;
 
     // 添加到 TkEndpointCore 类中
-    private static readonly ConcurrentDictionary<int, List<TkEndpointConfigDTO>> _accountEndpointCache = new();
+    private static readonly ConcurrentDictionary<(int, bool?), List<TkEndpointConfigDTO>> _accountEndpointCache = new();
 
 
     private static async Task<IEnumerable<TkEndpointConfigDTO>> ListAsync(bool force = false)
@@ -73,10 +74,11 @@ public partial class TkEndpointCore
     }
 
 
-    public static async Task<List<TkEndpointConfigDTO>> GetEndpointsByAccountAsync(int accountId)
+    public static async Task<List<TkEndpointConfigDTO>> GetEndpointsByAccountAsync(int accountId, bool? isTaobaoUrl = null, string parseEndpoints = null)
     {
-        // 1. 从缓存读取(直接返回副本)
-        if (_accountEndpointCache.TryGetValue(accountId, out var cached))
+        var cacheKey = (accountId, isTaobaoUrl);
+
+        if (_accountEndpointCache.TryGetValue(cacheKey, out var cached))
             return cached.Select(x => x.DeepCopy()).ToList();
 
         // 2. 获取所有端点(包含全局和专属)
@@ -99,9 +101,32 @@ public partial class TkEndpointCore
             .Where(e => e != null)
             .ToList();
 
-        // 4. 存入缓存(存储深拷贝后的组合配置)
-        _accountEndpointCache.TryAdd(accountId, mergedEndpoints.Select(x => x.DeepCopy()).ToList());
+        // 4. 根据isTaobaoUrl过滤
+        if (isTaobaoUrl.HasValue)
+        {
+            mergedEndpoints = mergedEndpoints.Where(e =>
+            {
+                if (e.taobao_id_handling == 0) return true;
+                if (e.taobao_id_handling == 1 && isTaobaoUrl.Value) return true;
+                if (e.taobao_id_handling == 2 && !isTaobaoUrl.Value) return true;
+                return false;
+            }).ToList();
+        }
+
+        // 5. 排除指定的端点
+        if (!string.IsNullOrEmpty(parseEndpoints))
+        {
+            var parseEndpointIds = parseEndpoints.Split(',')
+                .Select(idStr => int.TryParse(idStr.Trim(), out var id) ? id : (int?)null)
+                .Where(id => id.HasValue)
+                .Select(id => id.Value)
+                .ToHashSet();
+
+            mergedEndpoints = mergedEndpoints.Where(e => parseEndpointIds.Contains(e.ep_id)).ToList();
+        }
 
+        // 6. 存入缓存
+        _accountEndpointCache.TryAdd(cacheKey, mergedEndpoints.Select(x => x.DeepCopy()).ToList());
         return mergedEndpoints;
     }
 
@@ -110,7 +135,9 @@ public partial class TkEndpointCore
     // 添加缓存清理方法
     public static void ClearEndpointCache(int accountId)
     {
-        _accountEndpointCache.TryRemove(accountId, out _);
+        _accountEndpointCache.TryRemove((accountId, true), out _);
+        _accountEndpointCache.TryRemove((accountId, false), out _);
+        _accountEndpointCache.TryRemove((accountId, null), out _);
     }
 
 

+ 53 - 159
molilian.core/Core/taoke/TkEndpointManager.cs

@@ -7,44 +7,33 @@ using System.Linq;
 
 public partial class TkEndpointManager
 {
-    // 存储每个账号的端点状态(Key: accountId)
+    // 只保留轮询索引
     private static readonly ConcurrentDictionary<int, AccountEndpointState> accountStates = new();
 
     // 记录每个账号最后一次挂起操作的时间
     private static readonly ConcurrentDictionary<int, DateTime> lastSuspendTimes = new();
 
-    // 每个账号的挂起锁对象
-    private static readonly ConcurrentDictionary<int, object> accountLocks = new();
-
-    // 挂起冷却时间(3秒)
-    private static readonly TimeSpan SuspendCooldown = TimeSpan.FromSeconds(3);
-
-    // 替换原来的 ConcurrentDictionary<int, object>
-    private static readonly ConcurrentDictionary<int, SemaphoreSlim> accountSemaphores = new();
-
 
     public static void Refresh()
     {
         accountStates.Clear();
         lastSuspendTimes.Clear();
-        accountLocks.Clear();
-        accountSemaphores.Clear();
     }
 
 
     // 从数据库获取所有可用的API端点
-    private static async Task<Dictionary<int, string>> GetAllAvailableApisAsync(int accountId)
+    private static async Task<Dictionary<int, string>> GetAllAvailableApisAsync(int accountId, bool? isTaobaoUrl = null, string parseEndpoint = null)
     {
-        var endpoints = await TkEndpointCore.GetEndpointsByAccountAsync(accountId);
+        var endpoints = await TkEndpointCore.GetEndpointsByAccountAsync(accountId, isTaobaoUrl, parseEndpoint);
         return endpoints?
             .Where(e => e.status)
             .ToDictionary(e => e.ep_id, e => e.endpoint) ?? new Dictionary<int, string>();
     }
 
     // 从数据库获取端点的挂起时间配置
-    private static async Task<ConcurrentDictionary<string, int>> GetEndpointHoldMinutesAsync(int accountId)
+    private static async Task<ConcurrentDictionary<string, int>> GetEndpointHoldMinutesAsync(int accountId, bool? isTaobaoUrl = null, string parseEndpoint = null)
     {
-        var endpoints = await TkEndpointCore.GetEndpointsByAccountAsync(accountId);
+        var endpoints = await TkEndpointCore.GetEndpointsByAccountAsync(accountId, isTaobaoUrl, parseEndpoint);
         var dict = new ConcurrentDictionary<string, int>();
 
         if (endpoints != null)
@@ -59,23 +48,9 @@ public partial class TkEndpointManager
     }
     public static bool IsEndpointSuspended(int accountId, string endpoint)
     {
-        if (!accountStates.TryGetValue(accountId, out var state))
-            return false;
-
-        return state.IsEndpointSuspended(endpoint);
+        return RedisHelper.Exists($"tk_suspend:{accountId}:{endpoint}");
     }
 
-    // 新增:直接接收端点对象的版本
-    public static bool IsEndpointSuspended(TkEndpointConfigDTO endpoint)
-    {
-        if (endpoint == null) return true;
-        if (!accountStates.TryGetValue(endpoint.tk_pool_id, out var state))
-            return false;
-
-        return state.IsEndpointSuspended(endpoint.endpoint);
-    }
-
-
     /// <summary>
     /// 检查指定账号是否至少拥有一个可用的endpoint
     /// </summary>
@@ -91,7 +66,7 @@ public partial class TkEndpointManager
             return true; // 如果账号没有状态记录,所有节点都是正常的
         }
 
-        return availableApis.Any(api => !state.IsEndpointSuspended(api.Value));
+        return availableApis.Any(api => !IsEndpointSuspended(accountId, api.Value));
     }
 
     /// <summary>
@@ -133,11 +108,10 @@ public partial class TkEndpointManager
 
         foreach ((var api_id, var api) in availableApis)
         {
-            if (state.IsEndpointSuspended(api))
+            if (IsEndpointSuspended(accountId, api))
             {
-                var suspendUntil = state.GetSuspendTime(api);
-                var remainingTime = suspendUntil - DateTime.Now;
-                status[api] = $"挂起 (剩余时间: {remainingTime:mm\\:ss})";
+                // Redis没有挂起到期时间,展示"挂起"即可
+                status[api] = $"挂起";
             }
             else
             {
@@ -147,43 +121,25 @@ public partial class TkEndpointManager
         return status;
     }
 
-    public static async Task<(int, string)> GetConvertApiAsync(int accountId, string parseEndpoint)
+    public static async Task<(int, string)> GetConvertApiAsync(int accountId, bool isTaobaoUrl, string parseEndpoint)
     {
-        var endpoints = await TkEndpointCore.GetEndpointsByAccountAsync(accountId);
+        var endpoints = await TkEndpointCore.GetEndpointsByAccountAsync(accountId, isTaobaoUrl, parseEndpoint);
         if (endpoints == null || !endpoints.Any(e => e.status)) return (0, string.Empty);
 
         // 1. 过滤可用端点(状态正常)
         var availableApis = endpoints.Where(e => e.status);
 
-        // 2. 如果指定了 parseEndpoint,按 ID 进一步过滤
-        if (!string.IsNullOrEmpty(parseEndpoint))
-        {
-            var endpointIds = parseEndpoint.Split(',')
-                .Select(idStr => int.TryParse(idStr.Trim(), out var id) ? id : (int?)null)
-                .Where(id => id.HasValue)
-                .Select(id => id.Value)
-                .ToHashSet();
-
-            // 添加日志,看看过滤前后的端点
-            var beforeCount = availableApis.Count();
-            availableApis = availableApis.Where(e => endpointIds.Contains(e.ep_id));
-            var afterCount = availableApis.Count();
-
-            if (!availableApis.Any())
-                return (0, string.Empty);
-        }
-
         var state = accountStates.GetOrAdd(accountId, _ => new AccountEndpointState());
         var now = DateTime.Now;
 
-        // 3. 先找出所有未被挂起、未达限制的端点
+        // 2. 先找出所有未被挂起、未达限制的端点
         var availableEndpoints = availableApis
-            .Where(e => !state.IsEndpointSuspended(e.endpoint))
+            .Where(e => !IsEndpointSuspended(accountId, e.endpoint))
             .Where(e => e.hourly_calls_limit <= 0 || e.current_hourly_calls < e.hourly_calls_limit)
             .Where(e => e.daily_calls_limit <= 0 || e.current_daily_calls < e.daily_calls_limit)
             .ToArray();
 
-        // 4. 如果存在可用端点,直接返回
+        // 3. 如果存在可用端点,直接返回
         if (availableEndpoints.Length > 0)
         {
             // 生成唯一的轮询key,确保每种可用端点组合都有独立索引
@@ -198,9 +154,9 @@ public partial class TkEndpointManager
             return (selectedEndpoint.ep_id, selectedEndpoint.endpoint);
         }
 
-        // 5. 检查是否仅因时间间隔限制
+        // 4. 检查是否仅因时间间隔限制
         var intervalLimitedEndpoints = availableApis
-            .Where(e => !state.IsEndpointSuspended(e.endpoint))
+            .Where(e => !IsEndpointSuspended(accountId, e.endpoint))
             .Where(e => e.hourly_calls_limit <= 0 || e.current_hourly_calls < e.hourly_calls_limit)
             .Where(e => e.daily_calls_limit <= 0 || e.current_daily_calls < e.daily_calls_limit)
             .Where(e => e.interval_seconds > 0 &&
@@ -214,9 +170,9 @@ public partial class TkEndpointManager
             return (0, string.Empty);
         }
 
-        // 6. 检查所有端点的状态
-        var allEndpointsSuspended = availableApis.All(e => state.IsEndpointSuspended(e.endpoint));
-        var allEndpointsLimited = availableApis.All(e => 
+        // 5. 检查所有端点的状态
+        var allEndpointsSuspended = availableApis.All(e => IsEndpointSuspended(accountId, e.endpoint));
+        var allEndpointsLimited = availableApis.All(e =>
             (e.hourly_calls_limit > 0 && e.current_hourly_calls >= e.hourly_calls_limit) ||
             (e.daily_calls_limit > 0 && e.current_daily_calls >= e.daily_calls_limit));
 
@@ -226,7 +182,7 @@ public partial class TkEndpointManager
             return (0, "ALL");
         }
 
-        // 7. 其他情况(理论上不应该到达这里)
+        // 6. 其他情况(理论上不应该到达这里)
         return (0, string.Empty);
     }
 
@@ -258,51 +214,26 @@ public partial class TkEndpointManager
 
     public static async Task SuspendAsync(int accountId, string endpoint, SuspendReason reason = SuspendReason.Passive, int? customHoldMinutes = null)
     {
-        // 获取或创建账号特定的信号量
-        var semaphore = accountSemaphores.GetOrAdd(accountId, _ => new SemaphoreSlim(1, 1));
-
-        await semaphore.WaitAsync();
-        try
+        var holdMinutes = await GetEndpointHoldMinutesAsync(accountId);
+        TimeSpan suspendDuration;
+        if (customHoldMinutes.HasValue)
         {
-            // 在锁外部获取所需数据(避免在锁内await)
-            var holdMinutes = await GetEndpointHoldMinutesAsync(accountId);
-
-            // 如果是被动暂停,检查冷却时间
-            if (lastSuspendTimes.TryGetValue(accountId, out var lastSuspendTime) &&
-                DateTime.UtcNow - lastSuspendTime < SuspendCooldown)
-            {
-                return;
-            }
-
-            // 确定挂起时长
-            TimeSpan suspendDuration;
-            if (customHoldMinutes.HasValue)
-            {
-                suspendDuration = TimeSpan.FromMinutes(customHoldMinutes.Value);
-            }
-            else
-            {
-                suspendDuration = reason switch
-                {
-                    SuspendReason.HourlyLimit => CalculateHourlySuspendDuration(),
-                    SuspendReason.DailyLimit => CalculateDailySuspendDuration(),
-                    _ => TimeSpan.FromMinutes(holdMinutes.GetValueOrDefault(endpoint, 240))
-                };
-            }
-
-            // 执行挂起操作
-            var state = accountStates.GetOrAdd(accountId, _ => new AccountEndpointState());
-            state.SuspendEndpoint(endpoint, suspendDuration);
-
-            lastSuspendTimes[accountId] = DateTime.UtcNow;
-
-            _ = new LoggerLibrary("转链接口风控", $"{accountId}").Info($"{accountId}_{endpoint}", reason.ToString()).SaveAsync();
-            _ = NotifyCore.NotifyAsync($"【转链接口风控】{accountId}_{endpoint} ({reason})");
+            suspendDuration = TimeSpan.FromMinutes(customHoldMinutes.Value);
         }
-        finally
+        else
         {
-            semaphore.Release();
+            suspendDuration = reason switch
+            {
+                SuspendReason.HourlyLimit => CalculateHourlySuspendDuration(),
+                SuspendReason.DailyLimit => CalculateDailySuspendDuration(),
+                _ => TimeSpan.FromMinutes(holdMinutes.GetValueOrDefault(endpoint, 240))
+            };
         }
+
+        RedisHelper.Set($"tk_suspend:{accountId}:{endpoint}", 1, (int)suspendDuration.TotalSeconds);
+
+        _ = new LoggerLibrary("转链接口风控", accountId.ToString()).Info($"{accountId}_{endpoint}", reason.ToString()).SaveAsync();
+        _ = NotifyCore.NotifyAsync($"【转链接口风控】{accountId}_{endpoint} ({reason})");
     }
 
 
@@ -313,43 +244,36 @@ public partial class TkEndpointManager
     /// <param name="endpoint">要释放的端点名称,如果为null则释放所有端点的挂起状态</param>
     public static void ReleaseSuspend(int accountId, string endpoint = null)
     {
-        // 获取或创建账号特定的锁对象
-        var accountLock = accountLocks.GetOrAdd(accountId, _ => new object());
-
-        lock (accountLock)
+        if (endpoint == null)
         {
-            if (accountStates.TryGetValue(accountId, out var state))
+            var pattern = $"tk_suspend:{accountId}:*";
+            var keys = RedisHelper.Keys(pattern);
+            foreach (var key in keys)
             {
-                if (endpoint == null)
-                {
-                    // 释放所有端点的挂起状态
-                    state.suspendedEndpoints.Clear();
-                }
-                else
-                {
-                    // 释放指定端点的挂起状态
-                    state.suspendedEndpoints.TryRemove(endpoint, out _);
-                }
-
-                // 记录日志
-                var action = endpoint == null ? "释放所有挂起" : $"释放挂起({endpoint})";
-                _ = new LoggerLibrary("转链接口风控", $"{accountId}").Info($"{accountId}", action).SaveAsync();
-                _ = NotifyCore.NotifyAsync($"【转链接口风控】{accountId} {action}");
+                RedisHelper.Del(key);
             }
         }
+        else
+        {
+            RedisHelper.Del($"tk_suspend:{accountId}:{endpoint}");
+        }
+
+        var action = endpoint == null ? "释放所有挂起" : $"释放挂起({endpoint})";
+        _ = new LoggerLibrary("转链接口风控", accountId.ToString()).Info($"{accountId}", action).SaveAsync();
+        _ = NotifyCore.NotifyAsync($"【转链接口风控】{accountId} {action}");
     }
 
 
     private static TimeSpan CalculateHourlySuspendDuration()
     {
-        var now = DateTime.UtcNow;
+        var now = DateTime.Now;
         var nextHour = now.AddHours(1).Date.AddHours(now.Hour + 1); // 下一个整点(如 14:30 → 15:00)
         return nextHour - now;
     }
 
     private static TimeSpan CalculateDailySuspendDuration()
     {
-        var now = DateTime.UtcNow;
+        var now = DateTime.Now;
         var tomorrow = now.Date.AddDays(1); // 次日零点
         return tomorrow - now;
     }
@@ -379,38 +303,8 @@ public partial class TkEndpointManager
 
     private class AccountEndpointState
     {
-        internal readonly ConcurrentDictionary<string, DateTime> suspendedEndpoints = new();
-
-        // 为每个账号和 parseEndpoint 组合维护独立的索引
+        // 只保留轮询索引
         private readonly ConcurrentDictionary<string, int> roundRobinIndices = new();
-
-        public bool IsEndpointSuspended(string endpoint)
-        {
-            if (!suspendedEndpoints.TryGetValue(endpoint, out var suspendUntil))
-                return false;
-
-            if (DateTime.Now >= suspendUntil)
-            {
-                suspendedEndpoints.TryRemove(endpoint, out _);
-                return false;
-            }
-            return true;
-        }
-
-        public DateTime? GetSuspendTime(string endpoint)
-        {
-            if (suspendedEndpoints.TryGetValue(endpoint, out var suspendUntil))
-            {
-                return suspendUntil;
-            }
-            return null;
-        }
-
-        public void SuspendEndpoint(string endpoint, TimeSpan duration)
-        {
-            suspendedEndpoints[endpoint] = DateTime.Now.Add(duration);
-        }
-
         public int GetOrAddAccountIndex(string key) => roundRobinIndices.GetOrAdd(key, -1);
         public void UpdateAccountIndex(string key, int newIndex) => roundRobinIndices[key] = newIndex;
     }

+ 34 - 5
molilian.core/Core/taoke/TkPoolCore.cs

@@ -48,20 +48,49 @@ namespace molilian.core
             }
             return null;
         }
-        public static async Task<TkPoolDTO> GetOneAsync(TkAction action)
+        public static async Task<TkPoolDTO> GetOneAsync(TkAction action, bool isTaobaoUrl, string riskStrategy)
         {
             var list = await ListAsync().ConfigureAwait(false);
             if (!list.Any())
                 return null;
 
+            if ("brw".Equals(riskStrategy))
+            {
+                list = list.Where(item => "brw".Equals(item.riskStrategy)).ToList();
+                if (!list.Any()) return null;
+            }
+            else
+            {
+                list = list.Where(item => !"brw".Equals(item.riskStrategy)).ToList();
+                if (!list.Any()) return null;
+            }
+
+            var filteredList = new List<TkPoolDTO>();
+            foreach (var item in list)
+            {
+                // 检查是否有账号关联了当前账号
+                var ownerAccount = list.FirstOrDefault(other =>
+                    other.id != item.id &&
+                    !string.IsNullOrEmpty(other.related_account_ids) &&
+                    other.related_account_ids.Split(',').Contains(item.id.ToString()));
+
+                // 如果没有账号关联它,或者关联它的账号在线,则可以使用
+                if (ownerAccount == null || list.Any(a => a.id == ownerAccount.id))
+                {
+                    filteredList.Add(item);
+                }
+            }
+
+            if (filteredList.Count == 0) return null;
+
             // 1. 筛选出有可用端点的账号池,并计算权重
             var weightedAccounts = new List<(TkPoolDTO account, int weight)>();
             var random = new Random(Guid.NewGuid().GetHashCode()); // 避免重复种子问题
 
-            foreach (var item in list)
+            foreach (var item in filteredList)
             {
                 // 获取账号关联的所有端点配置
-                var endpoints = await TkEndpointCore.GetEndpointsByAccountAsync(item.id);
+                var endpoints = await TkEndpointCore.GetEndpointsByAccountAsync(item.id, isTaobaoUrl, item.parseEndpoint);
                 if (endpoints == null || endpoints.Count == 0)
                     continue;
 
@@ -70,7 +99,7 @@ namespace molilian.core
                     .Where(e => e.status)
                     .Where(e => e.hourly_calls_limit <= 0 || e.current_hourly_calls < e.hourly_calls_limit)
                     .Where(e => e.daily_calls_limit <= 0 || e.current_daily_calls < e.daily_calls_limit)
-                    .Where(e => !TkEndpointManager.IsEndpointSuspended(e)) // 直接检查端点对象
+                    .Where(e => !TkEndpointManager.IsEndpointSuspended(item.id, e.endpoint)) // 直接检查端点对象
                     .ToList();
 
                 if (availableEndpoints.Count == 0) continue;
@@ -82,7 +111,7 @@ namespace molilian.core
                 weightedAccounts.Add((item, weight));
             }
 
-            if (weightedAccounts.Count == 0)  return null;
+            if (weightedAccounts.Count == 0) return null;
 
             // 2. 加权随机选择
             int totalWeight = weightedAccounts.Sum(x => x.weight);

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

@@ -89,7 +89,7 @@ namespace molilian.core
             {
                 string reason;
                 if (AlimamaPlus.FilterRiskLink(ip, oaid, content, out reason) ||
-                    AlimamaPlus.ShouldIgnoreRequest(ip, oaid, out reason))
+                    AlimamaPlus.ShouldIgnoreRequest(ip, oaid, string.Empty, out reason))
                 {
                     result.success = false;
                     result.message = "放弃转链";
@@ -104,7 +104,7 @@ namespace molilian.core
                     });
                 }
 
-                var account = await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.coupon);
+                var account = await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.coupon, false, string.Empty);
                 if (account == null)
                 {
                     result.success = false;

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

@@ -8,7 +8,7 @@ namespace molilian.core
 {
     public partial class UnionParseCore
     {
-        public static async Task<ActionResult> UnionParseAsync(string content, string channel, int commerceType, string ip, string oaid, int accountid = 0, string clickId = "")
+        public static async Task<ActionResult> UnionParseAsync(string content, string channel, int commerceType, string ip, string oaid, string riskStrategy, int accountid = 0, string clickId = "")
         {
 
             content = content.Trim();
@@ -20,7 +20,7 @@ namespace molilian.core
                 "ks" => await KsParseAsync(content, commerceType, ip, oaid, accountid),
                 "dy" => await DyParseAsync(content, commerceType, ip, oaid),
                 "pdd" => await PddParseAsync(content, commerceType, ip, oaid, accountid),
-                "tb" => await TaobaoParseAsync(content, commerceType, ip, oaid, accountid),
+                "tb" => await TaobaoParseAsync(content, commerceType, ip, oaid, riskStrategy, accountid),
                 _ => await DeeplinkParseCore.ParseAsync(content, channel, ip, oaid),
             };
         }
@@ -121,7 +121,7 @@ namespace molilian.core
         }
 
         public static async Task<APIResult> TaobaoParseAsync(string content, int commerceType,
-            string ip = "", string oaid = "",
+            string ip = "", string oaid = "", string riskStrategy = "",
             int accountid = 0)
         {
             bool other_aff = false;
@@ -129,6 +129,7 @@ namespace molilian.core
             //content = content.UrlDecode();
             content = content[..Math.Min(1000, content.Length)];
             var result = AlimamaPlus.GetFormattedObject(content, ip, oaid);
+            result.riskStrategy = riskStrategy;
 
             Dictionary<string, long> swData = new();
             swData.Add("StartNew", 0);
@@ -313,7 +314,8 @@ namespace molilian.core
 
                 }
                 //============================== 放弃转链-地区过滤 ==============================
-                bool is_ignore2 = AlimamaPlus.ShouldIgnoreRequest(ip, oaid, out reason);
+
+                bool is_ignore2 = AlimamaPlus.ShouldIgnoreRequest(ip, oaid, riskStrategy, out reason);
 
                 if (is_ignore2)
                 {
@@ -335,10 +337,12 @@ namespace molilian.core
                     });
                 }
 
+                (bool isTaobaoUrl, string url) = AlimamaPlus.IsTaobaoUrl(content);
+
                 //============================== 放弃转链-没有匹配账号 ==============================
                 TkPoolDTO? account = accountid > 0 ?
                     await TkPoolCore.GetOneAsync(accountid) :
-                    await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.parse);
+                    await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.parse, isTaobaoUrl, riskStrategy);
                 if (account == null)
                 {
                     result.success = false;
@@ -373,7 +377,7 @@ namespace molilian.core
                 using var cts = new CancellationTokenSource();
                 cts.CancelAfter(timeout);
 
-                var requestTask = Task.Run(() => alimama.UnionParseAsync(ip, oaid, content, commerceType, result, cts.Token, swData), cts.Token);
+                var requestTask = Task.Run(() => alimama.UnionParseAsync(ip, oaid, content, commerceType, result, isTaobaoUrl, riskStrategy, cts.Token, swData), cts.Token);
                 var delayTask = Task.Delay(timeout, cts.Token);
                 var completedTask = await Task.WhenAny(requestTask, delayTask);
                 if (completedTask == requestTask)
@@ -454,6 +458,7 @@ namespace molilian.core
                 result?.couponEffectiveEndTime,
                 result.shortLinkurl,
                 result.deeplink_url,
+                result.riskStrategy,
                 result.content,
                 result.taoToken,
                 other_aff,

+ 12 - 12
molilian.core/Core/taoke/VeapiPoolCore.cs

@@ -10,35 +10,35 @@ using dodohold.core;
 
 namespace molilian.core
 {
-    public partial class VeapiPoolCore
+    public partial class OtherApiPoolCore
     {
         private static readonly object _lockObj = new();
-        private static IEnumerable<VeapiPoolDTO> _cached;
-        public static VeapiPoolDTO? GetOne()
+        private static IEnumerable<OtherApiPoolDTO> _cached;
+        public static OtherApiPoolDTO? GetOne()
         {
             var list = List();
             if (!list.Any()) return null;
             return list.OrderBy(l => Guid.NewGuid()).FirstOrDefault();
         }
-        public static VeapiPoolDTO? Get(int id)
+        public static OtherApiPoolDTO? Get(int id)
         {
             var list = List();
             if (!list.Any()) return null;
             return list.Where(o => o.id == id).FirstOrDefault();
         }
-        public static IEnumerable<VeapiPoolDTO> List(bool force = false)
+        public static IEnumerable<OtherApiPoolDTO> List(bool force = false)
         {
             if (!force && _cached != null) return _cached;
 
-            string cache_key = $"cache:veapi_pool";
-            var list = RedisHelper.Get<IEnumerable<VeapiPoolDTO>>(cache_key);
+            string cache_key = $"cache:other_api_pool";
+            var list = RedisHelper.Get<IEnumerable<OtherApiPoolDTO>>(cache_key);
             if (force || list == null)
             {
                 lock (_lockObj)
                 {
-                    list = new DBContext.Table("veapi_pool")
+                    list = new DBContext.Table("other_api_pool")
                         .Where("status=@status", new { status = 1 })
-                        .Select<VeapiPoolDTO>();
+                        .Select<OtherApiPoolDTO>();
                     if (list == null) return default;
                     RedisHelper.Set(cache_key, list, 30 * 86400);
                 }
@@ -53,13 +53,13 @@ namespace molilian.core
 
         public static void Disabled(string name)
         {
-            string cache_key = $"cache:veapi_pool:{name}:disabled";
+            string cache_key = $"cache:other_api_pool:{name}:disabled";
             long count = RedisHelper.IncrBy(cache_key);
             if (count > 0) return;
 
             RedisHelper.Expire(cache_key, 10);
 
-            new DBContext.Table("veapi_pool")
+            new DBContext.Table("other_api_pool")
                 .Add("status", 0)
                 .Where("name=@name", new { name })
                 .Update();
@@ -71,7 +71,7 @@ namespace molilian.core
                 priority = NifyMessagePriority.high,
                 tags = ["red_circle"]
             });
-            NotifyCore.AnPushNotify("掉线", $"【veapi:{name}】cookie 掉线");
+            NotifyCore.AnPushNotify("掉线", $"【第三方接口:{name}】cookie 掉线");
         }
 
     }

+ 2 - 1
molilian.core/DTO/VeapiPoolDTO.cs → molilian.core/DTO/OtherApiPoolDTO.cs

@@ -1,6 +1,6 @@
 namespace molilian.core
 {
-    public class VeapiPoolDTO
+    public class OtherApiPoolDTO
     {
         public int id { get; set; }
         public string name { get; set; } = string.Empty;
@@ -10,6 +10,7 @@
         public DateTime last_time { get; set; }
         public bool status { get; set; }
         public string refpid { get; set; } = string.Empty;
+        public string api_url { get; set; } = string.Empty;
 
     }
 }

+ 1 - 0
molilian.core/DTO/alimama/TkDataDTO.cs

@@ -119,6 +119,7 @@ namespace molilian.core
         public string couponEffectiveEndTime { get; set; } = string.Empty;
         public string end_point { get; set; } = string.Empty;
         public TkSubCodeEnum subCode { get; set; } = TkSubCodeEnum.Other;
+        public string riskStrategy { get; set; } = string.Empty;
 
     }
 

+ 3 - 0
molilian.core/DTO/alimama/TkEndpointConfigDTO.cs

@@ -31,5 +31,8 @@ namespace molilian.core
         public int suspend_duration { get; set; } = 0;
 
         public DateTime last_call_time { get; set; } = DateTime.MinValue;
+
+        //0无强制要求;1只处理淘宝数字id的文本;2不处理淘宝数字id的文本
+        public int taobao_id_handling { get; set; } = 0;
     }
 }

+ 6 - 0
molilian.core/DTO/alimama/TkPoolDTO.cs

@@ -60,5 +60,11 @@
         public bool enable_call_stats { get; set; } = true;
         public string parseEndpoint { get; set; } = string.Empty;
         public string related_account_ids { get; set; } = string.Empty;
+
+        /// <summary>
+        /// brw-浏览器,qapp-快应用
+        /// </summary>
+        public string riskStrategy { get; set; } = string.Empty;
+
     }
 }

+ 75 - 2
molilian.core/Plus/Alimama/orders.cs

@@ -543,7 +543,7 @@ SET  order_ord_num = order_ord_num_3 + order_ord_num_12 + order_ord_num_13 + ord
                         }
 
                         //进行订单和转链匹配
-                        //(int state, item.itemId) = GetRawItemId(item.mktId);
+                        (int state, item.itemId) = GetRawItemId(item.mktId);
                         if (item.tkStatus == 3 && item.tbPaidTime != DateTime.MinValue) orderTimes.Add(item.tbPaidTime.Date);
                         using var conn = DBContext.GetOpenConnection();
                         int? id = conn.Replace(item);
@@ -568,9 +568,82 @@ SET  order_ord_num = order_ord_num_3 + order_ord_num_12 + order_ord_num_13 + ord
             }
             return (total, max_modifiedTime, orderTimes);
         }
+        public (int, string) GetRawItemId(string mktId)
+        {
+            ////
+            ///
+            string body = string.Empty, location = string.Empty;
+            var proxy = ProxyNodesCore.RandomOne();
+            try
+            {
+                string cacheKey = $":cache:mkt2itemId:{mktId}";
 
+                string itemId = RedisHelper.Get(cacheKey);
+                if (itemId != null) return (1, itemId);
 
-        public (int, string) GetRawItemId(string mktId)
+                string mktItemLink = $"https://uland.taobao.com/item/edetail?id={mktId}";
+                ////uland.taobao.com/item/edetail?id=98t7UOZmNWX4dh2eJSQt6-b7Y8yZVuBQ5ywoz7TDA
+                ////uland.taobao.com/item/edetail?id=98t7UOZmNWX4dh2eJSQt6-b7Y8yZVuBQ5ywoz7TD
+                ///
+                var client = new WebClientUtility();
+                client.Proxy = proxy;
+                client.AddHeaders("accept-language", "zh-CN,zh;q=0.9");
+                client.AllowAutoRedirect = false;
+                var response = client.Request(mktItemLink);
+                if (!response.Successed)
+                {
+                    throw response.ResponseException;
+                }
+
+                location = response.ResponseMessage?.Headers?.Location?.ToString();
+                string message;
+                if (!string.IsNullOrEmpty(location) && location.Contains("err.taobao.com/error1.html"))
+                {
+                    message = $"【GetRawItemId】{_accountId}:{_accountName}\t{proxy?.Address}\n" +
+                       $"{mktId}\n{location}";
+
+                    //NotifyCore.Notify(new NifyMessage
+                    //{
+                    //    message = message,
+                    //    priority = NifyMessagePriority.high,
+                    //    tags = ["red_circle"]
+                    //});
+                    return (0, null);
+                }
+
+                if (!string.IsNullOrEmpty(location) && location.Contains("item.htm?id="))
+                {
+                    itemId = location.GetContentPart("?id=", "");
+                }
+
+                if (!string.IsNullOrEmpty(itemId))
+                {
+                    RedisHelper.Set(cacheKey, itemId, 86400 * 7);
+                }
+                else
+                {
+                }
+                return (1, itemId);
+            }
+            catch (Exception ex)
+            {
+                string message = $"【GetRawItemId】{_accountId}:{_accountName}\t{proxy?.Address}\n" +
+                    $"{mktId}\n{location}\n{body}\n{ex.Message}";
+
+                NotifyCore.Notify(new NifyMessage
+                {
+                    message = message,
+                    priority = NifyMessagePriority.high,
+                    tags = ["red_circle"]
+                });
+            }
+            return (0, null);
+        }
+
+
+
+
+        public (int, string) GetRawItemId22(string mktId)
         {
             string body = string.Empty, location = string.Empty;
             try

+ 39 - 36
molilian.core/Plus/Alimama/parse.cs

@@ -7,6 +7,9 @@ using System.Security.Cryptography;
 using TencentCloud.Ecm.V20190719.Models;
 using System.Diagnostics;
 using static dodohold.core.ZTOExpress.CreateOrderArgs;
+using System.Text.Json;
+using TencentCloud.Tcm.V20210413.Models;
+using TencentCloud.Tione.V20211111.Models;
 
 namespace molilian.core
 {
@@ -70,6 +73,7 @@ namespace molilian.core
         }
 
         public async Task<TkDataDTO> UnionParseAsync(string ip, string oaid, string content, int commerceType, TkDataDTO result,
+          bool isTaobaoUrl, string riskStrategy,
             CancellationToken cancellationToken = default,
             Dictionary<string, long> swData = null)
         {
@@ -127,7 +131,7 @@ namespace molilian.core
             }
 
             //============================== 放弃转链-地区过滤 ==============================
-            bool is_ignore2 = FlowControlIgnoreRequest(ip, oaid, out string reason);
+            bool is_ignore2 = FlowControlIgnoreRequest(ip, oaid, riskStrategy, out string reason);
             if (is_ignore2)
             {
                 result.success = false;
@@ -158,28 +162,8 @@ namespace molilian.core
             {
                 if (try_count >= 3) break;
 
-                //if (!string.IsNullOrEmpty(_api) && _api_id > 0)
-                //{
-                //    switch (_api)
-                //    {
-                //        case "veapi":
-                //            var account = VeapiPoolCore.Get(_api_id);
-                //            if (account != null)
-                //            {
-                //                var core = new VeapiPlus(account.name, account.key, account.sessionKey);
-                //                result = await core.UnionParseAsync(content, _refpid, result, cancellationToken);
-                //                result.tkEndpoint = 4;
-                //                return result;
-                //            }
-                //            break;
-                //        case "dataoke":
-                //            break;
-                //        default:
-                //            break;
-                //    }
-                //}
 
-                (int ep_id, string tkEndpoint) = await TkEndpointManager.GetConvertApiAsync(_accountId, _account.parseEndpoint);
+                (int ep_id, string tkEndpoint) = await TkEndpointManager.GetConvertApiAsync(_accountId, isTaobaoUrl, _account.parseEndpoint);
                 result.tkEndpoint = ep_id;
                 switch (tkEndpoint)
                 {
@@ -192,16 +176,20 @@ namespace molilian.core
                     case "tool_links":
                         result = await endpoint_tool_links(result.content, result, cancellationToken);
                         break;
+                    case "aliyun":
+                        result = await endpoint_aliyun(result.content, result, cancellationToken);
+                        break;
                     case "veapi":
+                    case "ddx":
+                    case "other_api":
                         if (!string.IsNullOrEmpty(_api) && _api_id > 0)
                         {
-                            var account = VeapiPoolCore.Get(_api_id);
+                            var account = OtherApiPoolCore.Get(_api_id);
                             if (account != null)
                             {
-                                var core = new VeapiPlus(account.name, account.key, account.sessionKey);
+                                var core = new OtherApiPlus(account.name, account.key, account.sessionKey, tkEndpoint, account.api_url);
                                 string refpid = string.IsNullOrEmpty(account.refpid) ? _refpid : account.refpid;
-                                result = await core.UnionParseAsync(content, refpid, result, cancellationToken);
-                                result.tkEndpoint = 4;
+                                result = await core.UnionParseAsync(result.content, refpid, result, cancellationToken);
                                 return result;
                             }
                         }
@@ -215,14 +203,14 @@ namespace molilian.core
                             return result;
                         }
                         break;
-                    case "ALL":
-                        //没有可以用的接口了
-                        result.link_type = LinkTypeEnum.unknown;
-                        result.subCode = TkSubCodeEnum.ParseDeny;
-                        result.success = false;
-                        result.message = "放弃转链";
-                        result.reason = "接口风控ALL";
-                        return result;
+                    //case "ALL":
+                    //    //没有可以用的接口了
+                    //    result.link_type = LinkTypeEnum.unknown;
+                    //    result.subCode = TkSubCodeEnum.ParseDeny;
+                    //    result.success = false;
+                    //    result.message = "放弃转链";
+                    //    result.reason = "接口风控ALL";
+                    //    return result;
                     default:
                         result.link_type = LinkTypeEnum.unknown;
                         result.subCode = TkSubCodeEnum.ParseDeny;
@@ -249,6 +237,15 @@ namespace molilian.core
             return result;
         }
 
+        public static bool ContainsTaobaoId(string content, string rawContent)
+        {
+            var match = Regex.Match(content, @"(?<=[?&]id=)\d+(?=&|$)");
+            if (!match.Success) return false;
+
+            string id = match.Value;
+            return Regex.IsMatch(rawContent, $@"(^|\D){id}($|\D)");
+        }
+
         public static string ReplaceUrls(string content, string shortLink)
         {
             if (string.IsNullOrEmpty(content) || string.IsNullOrEmpty(shortLink)) return content;
@@ -535,9 +532,12 @@ namespace molilian.core
             return false;
         }
 
-        public static bool ShouldIgnoreRequest(string ip, string oaid, out string reason)
+        public static bool ShouldIgnoreRequest(string ip, string oaid, string riskStrategy, out string reason)
         {
             reason = string.Empty;
+            //2025-05-11 brw不做风控
+            if ("brw".Equals(riskStrategy)) return false;
+
             try
             {
                 // IP和流量控制
@@ -584,9 +584,12 @@ namespace molilian.core
             return false;
         }
 
-        public static bool FlowControlIgnoreRequest(string ip, string oaid, out string reason)
+        public static bool FlowControlIgnoreRequest(string ip, string oaid, string riskStrategy, out string reason)
         {
             reason = string.Empty;
+            //2025-05-11 brw不做风控
+            if ("brw".Equals(riskStrategy)) return false;
+
             if (ip.StartsWith("127.0.0")) return false;
 
             try

+ 1 - 1
molilian.core/Plus/Alimama/parse_2.cs

@@ -57,7 +57,7 @@ namespace molilian.core
         /// </summary>
         /// <param name="url"></param>
         /// <returns></returns>
-        private static (bool, string) IsTaobaoUrl(string url)
+        public static (bool, string) IsTaobaoUrl(string url)
         {
             if (url.Contains("//a.m.taobao.com/i") ||
                 url.Contains("//internal.tt.detail.taobao.com/i"))

+ 101 - 0
molilian.core/Plus/Alimama/parse_endpoint/aliyun.cs

@@ -0,0 +1,101 @@
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using System.Diagnostics;
+using System.Text.Json;
+using TencentCloud.Tcm.V20210413.Models;
+
+
+namespace molilian.core
+{
+    public partial class AlimamaPlus
+    {
+        public async Task<TkDataDTO> endpoint_aliyun(string content, TkDataDTO result, CancellationToken cancellationToken = default)
+        {
+            string item_id = content.GetContentPart("item.htm?id=", "");
+            if (string.IsNullOrEmpty(item_id))
+            {
+                _ = new LoggerLibrary("endpoint", "aliyun")
+                    .Info(content, "response null")
+                    .SaveAsync();
+                result.channel = TkChannelEnum.tb;
+                result.link_type = LinkTypeEnum.unknown;
+                result.success = false;
+                result.message = "放弃转链";
+                result.reason = "无效商品id";
+                return result;
+            }
+
+
+
+            var plus = new AliyunPlus(_account.appkey, _account.appSecret);
+            var response = await plus.GetProductInfoByIds(item_id, _account.open_pid);
+            if (response == null)
+            {
+                _ = new LoggerLibrary("endpoint", "aliyun")
+                    .Info(content, "response null")
+                    .SaveAsync();
+                result.channel = TkChannelEnum.tb;
+                result.link_type = LinkTypeEnum.unknown;
+                result.success = false;
+                result.message = "fail";
+                result.reason = "接口异常";
+                return result;
+            }
+
+            result.success = (bool)response.Body.Success;
+            if (!result.success)
+            {
+                result.message = "fail";
+                result.reason = $"{response.Body.Code}:{response.Body.Message}";
+
+                _ = new LoggerLibrary("endpoint", "aliyun")
+                    .Info(content, result.reason)
+                    .SaveAsync();
+
+                result.channel = TkChannelEnum.tb;
+                result.link_type = LinkTypeEnum.unknown;
+                result.success = false;
+                return result;
+            }
+
+            if (response.Body.Data.Auctions.Count == 0)
+            {
+                _ = new LoggerLibrary("endpoint", "aliyun")
+                    .Info(content, "response null")
+                    .SaveAsync();
+                result.channel = TkChannelEnum.tb;
+                result.link_type = LinkTypeEnum.unknown;
+                result.success = false;
+                result.message = "result empty";
+                result.reason = string.Empty;
+                result.subCode = TkSubCodeEnum.NoConvert;
+                return result;
+            }
+            var data = response.Body.Data.Auctions[0].Result;
+            result.itemId = data.InputItemId;
+            result.couponAmount = (decimal)data.CouponAmount;
+            result.couponEffectiveStartTime = data.CouponStartTime;
+            result.couponEffectiveEndTime = data.CouponEndTime;
+            result.taoToken = string.Empty;
+            string url = data.Url;
+            if (url.StartsWith("//")) url = $"https:{url}";
+            result.shortLinkurl = url;
+            result.deeplink_url = AlimamaPlus.GetDeeplink(url);
+            result.mktId = data.ItemId;
+            result.itemName = data.Title;
+            if (decimal.TryParse(data.PriceAfterCoupon, out decimal promotionPrice))
+            {
+                result.promotionPrice = promotionPrice;
+            }
+            result.sellerNickName = data.Nick;
+            result.shopTitle = data.ShopTitle;
+            string picUrl = data.PicUrl;
+            if (picUrl.StartsWith("//")) picUrl = $"https:{picUrl}";
+            result.pic = picUrl;
+
+            return result;
+        }
+
+    }
+
+}

+ 77 - 2
molilian.core/Plus/Aliyun/ImageSearch.cs

@@ -12,7 +12,7 @@ namespace molilian.core
     public partial class AliyunPlus
     {
         private static string _end_point;
-        private static int _timeout = 1500;
+        private static int _timeout = 5000;
         static AliyunPlus()
         {
             _end_point = Environment.GetEnvironmentVariable("IMG_EndPoint");
@@ -21,7 +21,8 @@ namespace molilian.core
 #if DEBUG
                 _end_point = "imagesearch.cn-shanghai.aliyuncs.com";
 #else
-                _end_point = "imagesearch-vpc.cn-shanghai.aliyuncs.com";
+                //_end_point = "imagesearch-vpc.cn-shanghai.aliyuncs.com";
+                _end_point = "imagesearch.cn-shanghai.aliyuncs.com";
 #endif
             }
 
@@ -125,6 +126,80 @@ namespace molilian.core
         }
 
 
+        public async Task<GetProductInfoByIdsResponse> GetProductInfoByIds(string itemIds, string pid)
+        {
+            if (string.IsNullOrEmpty(itemIds)) return null;
+            if (string.IsNullOrEmpty(pid)) return null;
+
+            try
+            {
+
+                AlibabaCloud.TeaUtil.Models.RuntimeOptions runtime = new();
+
+                Config config = new()
+                {
+                    AccessKeyId = _app_key,
+                    AccessKeySecret = _app_secret,
+                    Endpoint = _end_point,
+                    ConnectTimeout = _timeout,
+                    RegionId = "cn-shanghai"
+                };
+                Client client = new(config);
+                GetProductInfoByIdsRequest request = new()
+                {
+                    ItemIds = itemIds,
+                    Pid = pid,
+                    Fields = "itemId,Title,Nick,SellerId,ShortTitle,SubTitle,PicUrl,ReservePrice,ZkFinalPrice,PriceAfterCoupon,CouponTotalCount,CouponRemainCount,CouponStartTime,CouponEndTime,CouponStartFee,CouponAmount,CouponInfo,CommissionRate,CouponShareUrl,DeeplinkCouponShareUrl,Url,DeeplinkUrl"
+                };
+
+                var response = await client.GetProductInfoByIdsWithOptionsAsync(request, runtime);
+                return response;
+            }
+            catch (TeaException error)
+            {
+                // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
+                // 错误 message
+                Console.WriteLine(error.Message);
+                // 诊断地址
+                Console.WriteLine(error.Data["Recommend"]);
+                AlibabaCloud.TeaUtil.Common.AssertAsString(error.Message);
+
+
+                int.TryParse($"{error.DataResult["statusCode"]}", out int statusCode);
+                GetProductInfoByIdsResponse response = new();
+                response.Body = new();
+                response.Body.Success = false;
+                response.Body.Code = statusCode;
+                response.Body.Message = $"{error.DataResult["Code"]},{error.DataResult["Message"]}";
+
+
+                LoggerLibrary log = new("GetProductInfoByIds", "tea");
+                log.Info(error.Message);
+                _ = log.SaveAsync();
+                return response;
+            }
+            catch (Exception _error)
+            {
+                TeaException error = new TeaException(new Dictionary<string, object>
+                {
+                    { "message", _error.Message }
+                });
+                // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
+                // 错误 message
+                Console.WriteLine(error.Message);
+                // 诊断地址
+                Console.WriteLine(error.Data["Recommend"]);
+                AlibabaCloud.TeaUtil.Common.AssertAsString(error.Message);
+
+
+                LoggerLibrary log = new("GetProductInfoByIds", "error");
+                log.Info(error.Message);
+                log.Info(error.Data["Recommend"].Convert2Json());
+                _ = log.SaveAsync();
+            }
+            return null;
+        }
+
     }
 
 }

+ 126 - 8
molilian.core/Plus/VeapiPlus.cs → molilian.core/Plus/OtherApiPlus.cs

@@ -5,18 +5,21 @@ using System.Text.RegularExpressions;
 
 namespace molilian.core
 {
-    public partial class VeapiPlus
+    public partial class OtherApiPlus
     {
 
         private string _accountName;
         private string _key;
         private string _sessionKey;
-        private string _refpid;
-        public VeapiPlus(string accountName, string key, string sessionKey)
+        private string _style;
+        private string _api_url;
+        public OtherApiPlus(string accountName, string key, string sessionKey, string style, string api_url)
         {
             _accountName = accountName;
             _key = key;
             _sessionKey = sessionKey;
+            _style = style;
+            _api_url = api_url;
         }
 
         public TkDataDTO UnionParse(string content, ref TkDataDTO result)
@@ -199,11 +202,29 @@ namespace molilian.core
             return result;
 
         }
-
         public async Task<TkDataDTO> UnionParseAsync(string content, string pid, TkDataDTO result, CancellationToken cancellationToken)
+        {
+            switch (_style)
+            {
+                case "ddx":
+                    return await DdxParseAsync(content, pid, result, cancellationToken);
+                case "veapi":
+                default:
+                    return await VeapiParseAsync(content, pid, result, cancellationToken);
+            }
+        }
+        private async Task<TkDataDTO> VeapiParseAsync(string content, string pid, TkDataDTO result, CancellationToken cancellationToken)
         {
             string para = content.UrlEncode();
             string url = $"http://api.veapi.cn/tbk/generalconvert?vekey={_key}&para={para}&pid={pid}";
+            if (!string.IsNullOrEmpty(_api_url))
+            {
+                url = _api_url;
+                url = url.Replace("{apikey}", _key);
+                url = url.Replace("{pid}", pid);
+                url = url.Replace("{content}", para);
+            }
+
             var response = await new WebClientUtility().RequestAsync(url, "GET", cancellationToken);
 
             var body = response.Body();
@@ -212,7 +233,6 @@ namespace molilian.core
             int error = root.Read<int>("error", 0);
             bool success = error == 0;
             string message = root.Read("msg", string.Empty);
-
             var data = root.ElementRead("data");
             result.channel = TkChannelEnum.tb;
             result.accountName = _accountName;
@@ -220,6 +240,7 @@ namespace molilian.core
             result.success = success;
             result.message = message;
 
+
             if (success)
             {
                 result.content = content;
@@ -234,8 +255,8 @@ namespace molilian.core
                 // veapi 缺少商品pic 和 promotionPrice
 
                 result.taoToken = data.Read<string>("cps_full_tpwd", string.Empty);
-                string coupon_full_tpwd = data.Read<string>("coupon_full_tpwd", string.Empty);
-                if (!string.IsNullOrEmpty(coupon_full_tpwd)) result.taoToken = coupon_full_tpwd;
+                //string coupon_full_tpwd = data.Read<string>("coupon_full_tpwd", string.Empty);
+                //if (!string.IsNullOrEmpty(coupon_full_tpwd)) result.taoToken = coupon_full_tpwd;
 
                 result.shortLinkurl = AlimamaPlus.GetLink(result.taoToken);
                 result.itemName = result.taoToken.GetContentPart(result.shortLinkurl, "").Trim();
@@ -244,11 +265,108 @@ namespace molilian.core
                 result.deeplink_url = AlimamaPlus.GetDeeplink(result.shortLinkurl);
                 return result;
             }
-
+            else
+            {
+                result.message= "转链失败";
+                result.reason = message;
+            }
             return result;
 
         }
 
+        private async Task<TkDataDTO> DdxParseAsync(string content, string pid, TkDataDTO result, CancellationToken cancellationToken)
+        {
+            string para = result.rawContent.UrlEncode();
+            string url = $"https://api.tbk.dingdanxia.com/tbk/tkl_privilege?apikey={_key}&pid={pid}&relation_id=3197256735&activityId=&extspk=&tb_auth_id=&tkl=XXX+{para}";
+            if (!string.IsNullOrEmpty(_api_url))
+            {
+                url = _api_url;
+                url = url.Replace("{apikey}", _key);
+                url = url.Replace("{pid}", pid);
+                url = url.Replace("{content}", para);
+            }
+
+            var client = new WebClientUtility();
+
+#if DEBUG
+            var response = await client.RequestAsync(url, "GET");
+#else
+            var response = await client.RequestAsync(url, "GET", cancellationToken);
+#endif
+
+            var body = response.Body();
+            var root = body.Convert2JsonElement();
+
+            int code = root.Read<int>("code", 0);
+            bool success = code == 200;
+            string message = root.Read("msg", string.Empty);
+            if (message.StartsWith("数据返回失败【"))
+            {
+                message = message.GetContentPart("数据返回失败【", "】");
+            }
+
+            var data = root.ElementRead("data");
+            result.channel = TkChannelEnum.tb;
+            result.accountName = _accountName;
+
+            result.success = success;
+            result.message = message;
+
+            if (success)
+            {
+                result.content = content;
+                result.message = string.Empty;
+                result.commercial = true;
+
+                result.couponAmount = data.Read<decimal>("coupon", 0);
+                result.couponEffectiveStartTime = data.Read<string>("coupon_start_time", string.Empty);
+                result.couponEffectiveEndTime = data.Read<string>("coupon_end_time", string.Empty);
+
+                result.mktId = data.Read<string>("item_id", string.Empty);
+                result.itemId = content.GetContentPart("item.htm?id=", "");
+
+
+                result.pic = data.PathRead<string>("itemInfo.pict_url", string.Empty);
+                result.sellerNickName = data.PathRead<string>("itemInfo.nick", string.Empty);
+                result.promotionPrice = data.PathRead<decimal>("itemInfo.qh_final_price", 0);
+                if (result.promotionPrice == 0)
+                {
+                    result.promotionPrice = data.PathRead<decimal>("itemInfo.zk_final_price", 0);
+                }
+
+                result.taoToken = data.Read<string>("long_item_tpwd", string.Empty);
+                //string long_coupon_tpwd = data.Read<string>("long_coupon_tpwd", string.Empty);
+                //if (!string.IsNullOrEmpty(long_coupon_tpwd)) result.taoToken = long_coupon_tpwd;
+
+                result.shortLinkurl = AlimamaPlus.GetLink(result.taoToken);
+                result.itemName = result.taoToken.GetContentPart(result.shortLinkurl, "").Trim();
+
+
+                result.deeplink_url = AlimamaPlus.GetDeeplink(result.shortLinkurl);
+                return result;
+            }
+            else
+            {
+                result.content = result.rawContent;
+                switch (message)
+                {
+                    case "该口令无效,请检查后重新提交":
+                    case "该商品已下架":
+                        result.message = "转链失败";
+                        result.reason = message;
+                        result.subCode = TkSubCodeEnum.NoConvert;
+                        break;
+                    default:
+                        result.message = "转链失败";
+                        result.reason = message;
+                        result.subCode = TkSubCodeEnum.NetError;
+                        break;
+                }
+                return result;
+            }
+
+        }
+
     }
 
 }

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.