Explorar o código

调整pddprecheck

dodo hold hai 3 meses
pai
achega
1af2348efd

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
molilian.api/Properties/PublishProfiles/latest.pubxml.user


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

@@ -771,6 +771,39 @@ namespace molilian.core
                     return PddParseOutput(result);
                 }
 
+                var precheck = await PddUnionPlus.PrecheckGoodsIdAsync(result.shortLinkurl, cancellationToken);
+                switch (precheck)
+                {
+                    case RrecheckGoodsIdResult.InvalidLink:
+                        result.message = "放弃转链";
+                        result.reason = "无效链接";
+                        break;
+                    case RrecheckGoodsIdResult.Empty:
+                        result.message = "放弃转链";
+                        result.reason = $"无效的商品precheck";
+                        break;
+
+                    case RrecheckGoodsIdResult.Exception:
+                        result.message = "转链失败";
+                        result.reason = $"商品预检异常";
+                        break;
+                }
+
+                if (precheck != RrecheckGoodsIdResult.Success)
+                {
+                    result.success = false;
+                    result.link_type = LinkTypeEnum.unknown;
+                    result.channel_type = ChannelTypeEnum.pdd;
+                    result.accountId = 0;
+                    result.accountName = string.Empty;
+                    result.content = content;
+                    result.deeplink_url = PddUnionPlus.GetDeeplink(result.shortLinkurl);
+                    _ = TkLogCore.ParseLogAsync(result);
+                    return PddParseOutput(result);
+                }
+
+
+
                 PddPoolDTO account = null;
                 // 直接调用GetOne获取账号(parse_type="dp")
                 // 新的调度策略已经在内部处理了:

+ 254 - 20
molilian.core/Plus/pdd/PddUnionPlus.cs

@@ -1,17 +1,30 @@
 using dodohold.core;
-using Sayaka.Common;
-using System.Diagnostics;
-using System.Text.RegularExpressions;
-using System.Threading.Channels;
-using System.Web;
+using Sayaka.Common;
+using System.Diagnostics;
+using System.Text.RegularExpressions;
+using System.Threading.Channels;
+using System.Web;
 
 namespace molilian.core
 {
-    public partial class PddUnionPlus
+    public enum RrecheckGoodsIdResult
     {
-        public static string _url_pattern = @"https?://(?:[\w-]+\.)*(?:yangkeduo\.com|pinduoduo\.com)(?:/[^\s]*)?";
-        public static string GetLink(string content)
-        {
+        Success,
+        Empty,
+        Exception,
+        InvalidLink,
+        OtherAff,
+    }
+
+    public partial class PddUnionPlus
+    {
+        private static readonly object _precheckCircuitLock = new();
+        private static bool _precheckDisabled = false;
+        private static int _precheckConsecutiveExceptionCount = 0;
+        private static int _precheckDisableErrorThreshold = 10;
+        public static string _url_pattern = @"https?://(?:[\w-]+\.)*(?:yangkeduo\.com|pinduoduo\.com)(?:/[^\s]*)?";
+        public static string GetLink(string content)
+        {
             if (string.IsNullOrEmpty(content)) return content;
             string result = content;
             //
@@ -27,16 +40,107 @@ namespace molilian.core
         }
 
 
-        public static string GetDeeplink(string url)
-        {
-            string result = $"pinduoduo://com.xunmeng.pinduoduo/{url}";
-            return result;
-        }
-
-
-
-        private string ExtractGoodsIdUrl(string url)
-        {
+        public static string GetDeeplink(string url)
+        {
+            string result = $"pinduoduo://com.xunmeng.pinduoduo/{url}";
+            return result;
+        }
+
+        private static bool IsGoodsDetailPath(string path)
+        {
+            if (string.IsNullOrWhiteSpace(path)) return false;
+            return Regex.IsMatch(path, @"^/goods\d*\.html$", RegexOptions.IgnoreCase);
+        }
+
+        private static bool IsPddShortLinkHost(string host)
+        {
+            return "p.pinduoduo.com".Equals(host, StringComparison.OrdinalIgnoreCase);
+        }
+
+        private static bool TryGetPrecheckPsGoodsUrl(string url, out string normalizedUrl)
+        {
+            normalizedUrl = string.Empty;
+            if (string.IsNullOrWhiteSpace(url)) return false;
+            if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)) return false;
+
+            if (IsPddShortLinkHost(uri.Host) && !string.IsNullOrWhiteSpace(uri.AbsolutePath) && uri.AbsolutePath != "/")
+            {
+                normalizedUrl = $"{uri.Scheme}://{uri.Host}{uri.PathAndQuery}";
+                return true;
+            }
+
+            if (!IsGoodsDetailPath(uri.AbsolutePath)) return false;
+
+            var queryParams = HttpUtility.ParseQueryString(uri.Query);
+            string goodsId = queryParams["goods_id"];
+            if (!string.IsNullOrWhiteSpace(goodsId) && long.TryParse(goodsId, out _))
+            {
+                return false;
+            }
+
+            string ps = queryParams["ps"];
+            if (string.IsNullOrWhiteSpace(ps)) return false;
+
+            normalizedUrl = $"{uri.Scheme}://{uri.Host}{uri.AbsolutePath}?ps={HttpUtility.UrlEncode(ps)}";
+            return true;
+        }
+
+        private static bool IsPrecheckDisabled()
+        {
+            lock (_precheckCircuitLock)
+            {
+                return _precheckDisabled;
+            }
+        }
+
+        private static void ResetPrecheckExceptionState()
+        {
+            lock (_precheckCircuitLock)
+            {
+                _precheckConsecutiveExceptionCount = 0;
+            }
+        }
+
+        private static (int count, bool disabledNow) RegisterPrecheckException()
+        {
+            lock (_precheckCircuitLock)
+            {
+                _precheckConsecutiveExceptionCount++;
+                bool disabledNow = false;
+                if (!_precheckDisabled && _precheckConsecutiveExceptionCount >= _precheckDisableErrorThreshold)
+                {
+                    _precheckDisabled = true;
+                    disabledNow = true;
+                }
+                return (_precheckConsecutiveExceptionCount, disabledNow);
+            }
+        }
+
+        private static void NotifyPrecheckException(string goodsUrl, string stage, string detail, int count, bool disabledNow)
+        {
+            string message = $"【PDD商品Precheck异常】{_end_point}\n" +
+                $"url:{goodsUrl}\n" +
+                $"stage:{stage}\n" +
+                $"连续异常:{count}/{_precheckDisableErrorThreshold}\n" +
+                $"{detail}";
+
+            if (disabledNow)
+            {
+                message += "\n状态: 已临时禁用Precheck,后续默认返回Success";
+            }
+
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = message,
+                priority = NifyMessagePriority.high,
+                tags = ["red_circle"]
+            });
+        }
+
+
+
+        private string ExtractGoodsIdUrl(string url)
+        {
             var uri = new Uri(url);
             var queryParams = HttpUtility.ParseQueryString(uri.Query);
             string goodsId = queryParams["goods_id"];
@@ -145,6 +249,132 @@ namespace molilian.core
             };
         }
 
+
+        public static async Task<RrecheckGoodsIdResult> PrecheckGoodsIdAsync(string content, CancellationToken cancellationToken = default)
+        {
+            string url = GetLink(content);
+            if (string.IsNullOrEmpty(url))
+            {
+                return RrecheckGoodsIdResult.InvalidLink;
+            }
+
+            if (!TryGetPrecheckPsGoodsUrl(url, out string goods_url))
+            {
+                return RrecheckGoodsIdResult.Success;
+            }
+
+
+
+            if (IsPrecheckDisabled()) return RrecheckGoodsIdResult.Success;
+            if (string.IsNullOrWhiteSpace(goods_url)) return RrecheckGoodsIdResult.Empty;
+            try
+            {
+                const string precheckClientId = "c8823690b47842649e4fee054317e0c1";
+                const string precheckClientSecret = "0b5eeab761a6b61df59f11296387b95523a26b09";
+                const string precheckPid = "13585632_187155840";
+                var precheckAccount = new PddPoolDTO
+                {
+                    app_key = precheckClientId,
+                    app_secret = precheckClientSecret
+                };
+                var ts = DateTime.Now.Convert2UnixTimestamp(true);
+                var args = new Dictionary<string, string>
+                {
+                    { "type", "pdd.ddk.goods.search" },
+                    { "data_type", "JSON" },
+                    { "client_id", precheckClientId },
+                    { "keyword", goods_url.UrlDecode() },
+                    { "pid", precheckPid },
+                    { "timestamp", ts.ToString() }
+                };
+                args = GenerateSignature(precheckAccount, args);
+
+                var query = HttpUtility.ParseQueryString(string.Empty);
+                foreach (var kvp in args) query[kvp.Key] = kvp.Value;
+                string urlWithQuery = $"{_baseUrl}?{query}";
+
+                WebClientUtility client = new();
+                client.SetContentType("application/json");
+                var response = await client.RequestAsync(urlWithQuery, "GET", cancellationToken);
+                string body = response.Body();
+
+                if (!response.Successed)
+                {
+                    _ = new LoggerLibrary("PddUnion", "PrecheckGoodsIdAsync").Info(urlWithQuery, body).SaveAsync();
+                    var status = RegisterPrecheckException();
+                    NotifyPrecheckException(goods_url, "RequestAsync", $"response.Successed=false\nbody:{body}", status.count, status.disabledNow);
+                    return status.disabledNow ? RrecheckGoodsIdResult.Success : RrecheckGoodsIdResult.Exception;
+                }
+
+                var root = body.Convert2Object<GoodsSearchDTO>();
+                if (root?.error_response != null)
+                {
+                    _ = new LoggerLibrary("PddUnion", "PrecheckGoodsIdAsync.Empty").Info(urlWithQuery, body).SaveAsync();
+                    ResetPrecheckExceptionState();
+                    return RrecheckGoodsIdResult.Empty;
+                }
+
+                var goodsList = root?.goods_search_response?.goods_list;
+                if (goodsList == null)
+                {
+                    _ = new LoggerLibrary("PddUnion", "PrecheckGoodsIdAsync").Info(urlWithQuery, body).SaveAsync();
+                    var status = RegisterPrecheckException();
+                    NotifyPrecheckException(goods_url, "GoodsSearchResponse", $"goods_list is null\nbody:{body}", status.count, status.disabledNow);
+                    return status.disabledNow ? RrecheckGoodsIdResult.Success : RrecheckGoodsIdResult.Exception;
+                }
+
+                bool isValidGoods = goodsList.Any(item => item.goods_id > 0 && !string.IsNullOrWhiteSpace(item.goods_name));
+                if (!isValidGoods && goodsList.Count > 0)
+                {
+                    _ = new LoggerLibrary("PddUnion", "PrecheckGoodsIdAsync.Empty").Info(urlWithQuery, body).SaveAsync();
+                }
+
+                ResetPrecheckExceptionState();
+                return isValidGoods ? RrecheckGoodsIdResult.Success : RrecheckGoodsIdResult.Empty;
+            }
+            catch (Exception ex)
+            {
+                _ = new LoggerLibrary("PddUnion", "PrecheckGoodsIdAsync").Info(goods_url, ex.ToString()).SaveAsync();
+                var status = RegisterPrecheckException();
+                NotifyPrecheckException(goods_url, "Exception", ex.ToString(), status.count, status.disabledNow);
+                return status.disabledNow ? RrecheckGoodsIdResult.Success : RrecheckGoodsIdResult.Exception;
+            }
+
+
+            //            client_id:c8823690b47842649e4fee054317e0c1
+            //client_secret:0b5eeab761a6b61df59f11296387b95523a26b09
+
+
+            //https://gw-api.pinduoduo.com/api/router
+
+            //{
+            //                "type": "pdd.ddk.goods.search",
+            //    "data_type": "JSON",
+            //    "client_id": "c8823690b47842649e4fee054317e0c1",
+            //    "keyword": "https://mobile.yangkeduo.com/goods.html?ps=ROyjHRQJ7d",
+            //    "pid": "13585632_187155840",
+            //    "timestamp": 1779396871,
+            //    "sign": "3DE407843BECA8BA480E8BA4C51E6049"
+            //}
+
+            //            这样可以用字符串链接去查询是否多多商品。返回goods_list里面有内容就是可以去转链的。
+
+            //下面这个就是不能转链的
+            //{
+            //                "goods_search_response": {
+            //                    "goods_list": [{
+            //                        "subsidy_goods_type": 0,
+            //            "subsidy_list": [],
+            //            "platform_discount_list": [],
+            //            "goods_id": 922492775451,
+            //            "goods_sign": "E9L2dKKUUo9LeKDhwePAPXLcFjD6hB3MKw_JGaDyX4DX"
+            //                    }],
+            //        "total_count": 1,
+            //        "request_id": "17793969978830062"
+            //                }
+            //            }
+        }
+
         public async Task<PddDataDTO> PddParseAsync(string content, int commerceType, PddDataDTO result, CancellationToken cancellationToken = default)
         {
             string message = "OK";
@@ -214,10 +444,13 @@ namespace molilian.core
             stopwatch.Start();
             var redirectedUrl = await GetRedirectedUrlAsync(url, cancellationToken);
             stopwatch.Stop();
+            bool redirected = !string.IsNullOrWhiteSpace(redirectedUrl) &&
+                !string.Equals(redirectedUrl, url, StringComparison.OrdinalIgnoreCase);
 
 
-            if (redirectedUrl != url)
+            if (redirected)
             {
+                // 字符串ID转数字ID
                 result.rawContent2 = redirectedUrl;
                 url = redirectedUrl;
                 result.elapsedTime2 = (int)stopwatch.ElapsedMilliseconds;
@@ -253,6 +486,7 @@ namespace molilian.core
             }
 
 
+
             stopwatch2.Stop();
             result.elapsedTime3 = (int)stopwatch2.ElapsedMilliseconds;
             return result;

+ 20 - 0
molilian.core/Plus/pdd/open.cs

@@ -42,6 +42,26 @@ namespace molilian.core
             public ErrorResponse error_response { get; set; }
         }
 
+        public class GoodsSearchItem
+        {
+            public long goods_id { get; set; }
+            public string goods_sign { get; set; } = string.Empty;
+            public string goods_name { get; set; } = string.Empty;
+        }
+
+        public class GoodsSearchResponse
+        {
+            public List<GoodsSearchItem> goods_list { get; set; } = [];
+            public int total_count { get; set; }
+            public string request_id { get; set; } = string.Empty;
+        }
+
+        public class GoodsSearchDTO
+        {
+            public GoodsSearchResponse? goods_search_response { get; set; }
+            public ErrorResponse? error_response { get; set; }
+        }
+
 
         public static Dictionary<string, string> GenerateSignature(PddPoolDTO account, Dictionary<string, string> args)
         {

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio