Ver código fonte

增加“厂商智能助手转链”

dodo hold 4 meses atrás
pai
commit
8ba4eeeb2a

+ 86 - 0
AGENTS.md

@@ -0,0 +1,86 @@
+# AGENTS.md
+
+This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
+
+## Project Overview
+
+This is a .NET 8 ASP.NET Core e-commerce affiliate tracking API server called "molilian" that handles multiple affiliate platforms including Taobao, JD.com, PDD (拼多多), and others. The system processes coupon tracking, link generation, order tracking, and data reporting for affiliate marketing operations.
+
+## Architecture
+
+- **molilian.api**: ASP.NET Core Web API project with controllers for admin and public endpoints
+- **molilian.core**: Core business logic library containing all platform integrations and data processing
+- **taobao-sdk-netcore-auto**: Third-party Taobao SDK for API integration
+- **YunhuiKit**: External dependency for additional utilities
+
+The system follows a layered architecture:
+- Controllers handle HTTP requests/responses
+- Core classes contain business logic for different platforms (taobao, jd, pdd, etc.)
+- Plus classes provide additional utilities and managers
+- DTO classes define data transfer objects
+
+## Development Commands
+
+### Build
+```bash
+dotnet build molilian.sln
+```
+
+### Run API Server
+```bash
+cd molilian.api
+dotnet run
+```
+The API will be available at http://localhost:8186 with Swagger UI at /swagger
+
+### Run with Docker
+```bash
+cd molilian.api
+docker-compose up
+```
+
+### Restore Dependencies
+```bash
+dotnet restore molilian.sln
+```
+
+## Key Platform Integrations
+
+The system integrates with multiple Chinese e-commerce platforms:
+
+- **Taobao/Tmall**: `molilian.core/Core/taoke/` - Handles Taobao affiliate operations
+- **JD.com**: `molilian.core/Core/jd/` - JD affiliate platform integration  
+- **PDD (拼多多)**: `molilian.core/Core/pdd/` - Pinduoduo affiliate operations
+- **Kuaishou**: `molilian.core/Core/ks/` - Short video platform integration
+- **Douyin**: `molilian.core/Core/douyin/` - TikTok China platform
+- **CPS Platforms**: `molilian.core/Core/cps/` - Various CPS affiliate networks
+
+## Database Configuration
+
+The application uses MySQL with Redis for caching. Configuration is handled through environment variables:
+- `DBConfig`: MySQL connection string
+- `RedisConfig`: Redis connection string  
+- `EndPoint`: Deployment endpoint identifier
+
+Multiple environment profiles are configured in launchSettings.json for different deployment scenarios.
+
+## API Structure
+
+- **Admin Controllers** (`Controllers/admin/`): Management endpoints for platform configurations, user management, and reporting
+- **Public Controllers** (`Controllers/public/`): Public-facing APIs for link generation, task processing, and affiliate operations
+
+## Core Components
+
+- **Risk Control**: `molilian.core/Core/taoke/RiskControlCore.cs` - Handles fraud detection and risk management
+- **Order Tracking**: `molilian.core/Core/taoke/OrderTrackingCore.cs` - Tracks affiliate order conversions
+- **Union Parse**: `molilian.core/Core/taoke/UnionParseCore/` - Parses and converts affiliate links
+- **Proxy Management**: `molilian.core/Plus/ProxyManager.cs` - Manages proxy rotation for API calls
+- **Task Workers**: `molilian.core/Worker/` - Background job processing
+
+## Logging
+
+Extensive logging is implemented across all platforms in `molilian.core/Core/log/` with separate log handlers for each affiliate platform.
+
+## Docker Support
+
+The project includes Docker support with Dockerfile and docker-compose.yml for containerized deployment.

+ 33 - 2
molilian.api/Controllers/public/ApiController.cs

@@ -125,6 +125,8 @@ namespace molilian.api.Controllers
         {
         {
             var img = form.Read("img", string.Empty);
             var img = form.Read("img", string.Empty);
             var url = form.Read("url", string.Empty);
             var url = form.Read("url", string.Empty);
+            var tkl = form.Read("tkl", string.Empty);
+            var itemid = form.Read("itemid", string.Empty);
             var ip = form.Read("ip", string.Empty);
             var ip = form.Read("ip", string.Empty);
             var oaid = form.Read("oaid", string.Empty);
             var oaid = form.Read("oaid", string.Empty);
             var scene = form.Read("scene", string.Empty);
             var scene = form.Read("scene", string.Empty);
@@ -154,10 +156,39 @@ namespace molilian.api.Controllers
                 }
                 }
                 return new APIResult(new { msg = "ok" });
                 return new APIResult(new { msg = "ok" });
             }
             }
-            return await core.PromotionQueryAsync(img, oaid, ip, scene, recommand, debug, aid);
-        }
 
 
+            // 从口令解析商品图 临时需求
+            if (!string.IsNullOrEmpty(tkl))
+            {
+                string goods_url = AlimamaPlus.GetLink(tkl);
+                (bool successed, string itemUrl) = AlimamaPlus.GetItemUrl(goods_url);
+                if (successed && !string.IsNullOrEmpty(itemUrl))
+                {
+                    itemid = AlimamaPlus.ExtractItemId(itemUrl);
+                    (APIResult target, PromotionQueryDTO data) = await core.PromotionQueryByItemIdAsync(itemid, oaid, ip, scene, recommand, debug, aid);
+                     
+
+                    if (data != null)
+                    {
+                        url = data.PromotionImg[0].pic;
+                        if (!string.IsNullOrEmpty(url))
+                        {
+                            var bytes = new WebClientUtility().Request(url).ResponseBody;
+                            img = Convert.ToBase64String(bytes);
+                        }
+                    }
+                    return await core.PromotionQueryAsync(img, oaid, ip, scene, recommand, debug, aid);
+                }
 
 
+                if (!string.IsNullOrEmpty(itemid))
+                {
+                    (APIResult result, PromotionQueryDTO data) = await core.PromotionQueryByItemIdAsync(itemid, oaid, ip, scene, recommand, debug, aid);
+                    return result;
+                }
+            }
+            return await core.PromotionQueryAsync(img, oaid, ip, scene, recommand, debug, aid);
+
+        }
 
 
     }
     }
 }
 }

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
molilian.api/Properties/PublishProfiles/latest.pubxml.user


+ 13 - 3
molilian.api/Properties/launchSettings.json

@@ -4,7 +4,7 @@
       "commandName": "Project",
       "commandName": "Project",
       "launchBrowser": true,
       "launchBrowser": true,
       "launchUrl": "swagger",
       "launchUrl": "swagger",
-      "environmentVariables": {
+      "environmentVariables2": {
         "ASPNETCORE_ENVIRONMENT": "Development",
         "ASPNETCORE_ENVIRONMENT": "Development",
         "EndPoint": "coupon",
         "EndPoint": "coupon",
         "NtfyServer": "https://ntfy.yunhui800.com/vozQub8a78ABLqqY",
         "NtfyServer": "https://ntfy.yunhui800.com/vozQub8a78ABLqqY",
@@ -16,7 +16,17 @@
         "CenterDB": "",
         "CenterDB": "",
         "CenterRedis": ""
         "CenterRedis": ""
       },
       },
-      "environmentVariables2": {
+      "environmentVariables_similar": {
+        "ASPNETCORE_ENVIRONMENT": "Development",
+        "EndPoint": "sh1",
+        "NtfyServer": "https://ntfy.yunhui800.com/similar",
+        "ANPush": "",
+        "DBType": "MySQL",
+        "DBConfig": "Server=rm-uf65lh1i98d1c2fi70o.rwlb.rds.aliyuncs.com; Port=3306; Database=similar; Uid=similar; Pwd=tNE5BZxEsdkFaDek;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60;",
+        "RedisConfig": "47.102.204.251:6379,password=pKBiS4ka2IpXayIdcx00,defaultDDB_REDISatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=similar",
+        "DB_REDIS": "47.102.204.251:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=similar"
+      },
+      "environmentVariables": {
         "ASPNETCORE_ENVIRONMENT": "Development",
         "ASPNETCORE_ENVIRONMENT": "Development",
         "EndPoint": "bj",
         "EndPoint": "bj",
         "NtfyServer": "https://ntfy.yunhui800.com/5Sq9BytXXM5WDY3G",
         "NtfyServer": "https://ntfy.yunhui800.com/5Sq9BytXXM5WDY3G",
@@ -27,7 +37,7 @@
         "RedisConfig": "101.200.152.61:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook",
         "RedisConfig": "101.200.152.61:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook",
         "CenterDB": "",
         "CenterDB": "",
         "CenterRedis": ""
         "CenterRedis": ""
-      }, 
+      },
       "environmentVariables1": {
       "environmentVariables1": {
         "ASPNETCORE_ENVIRONMENT": "Development",
         "ASPNETCORE_ENVIRONMENT": "Development",
         "EndPoint": "cadmin",
         "EndPoint": "cadmin",

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

@@ -102,6 +102,61 @@ namespace molilian.core
             return result;
             return result;
         }
         }
 
 
+        public async Task<PromotionQueryDTO> itemSimilaritemAsync(string itemids, string pid, PromotionQueryDTO result)
+        {
+            var response = await plus.GetProductInfoByIds(itemids, pid);
+            if (response == null)
+            {
+                result.message = "调用失败";
+                result.reason = "内部错误";
+                return result;
+            }
+
+            result.success = (bool)response.Body.Success;
+            if (!result.success)
+            {
+                result.message = "调用失败";
+                result.reason = $"{response.Body.Code}:{response.Body.Message}";
+                return result;
+            }
+
+
+            List<PromotionQueryItemDTO> arr = [];
+            foreach (var e in response.Body.Data.Auctions)
+            {
+                var item = e.Result;
+
+                PromotionQueryItemDTO newItem = new()
+                {
+                    itemId = item.ItemId,
+                    h5_url = string.IsNullOrEmpty(item.CouponShareUrl) ? item.Url : item.CouponShareUrl,
+                    deeplink_url = string.IsNullOrEmpty(item.DeeplinkCouponShareUrl) ? item.DeeplinkUrl : item.DeeplinkCouponShareUrl,
+                    userType = $"{item.UserType}",
+                    shopTitle = item.ShopTitle,
+                    itemName = item.Title,
+                    pic = item.PicUrl,
+                    price = item.ReservePrice,
+                    promotionPrice = item.PriceAfterCoupon,
+                    couponAmount = $"{item.CouponAmount}",
+                    couponEffectiveStartTime = item.CouponStartTime,
+                    couponEffectiveEndTime = item.CouponEndTime,
+                    sellCountStr = $"{item.Volume}",
+                    provcity = item.Provcity,
+                    exposeTracks = $"https://api.molilian.com/tracks/track?track_id=7&unique_id={item.ItemId}",
+                    clickTracks = $"https://api.molilian.com/tracks/track?track_id=8&unique_id={item.ItemId}"
+                };
+
+                if (newItem.h5_url.StartsWith("//")) newItem.h5_url = "https:" + newItem.h5_url;
+                if (newItem.pic.StartsWith("//")) newItem.pic = "https:" + newItem.pic;
+                arr.Add(newItem);
+            }
+
+            result.message = "OK";
+            result.reason = string.Empty;
+            result.PromotionImg = arr;
+            return result;
+        }
+
 
 
 
 
         public QueryAccountBalanceResponse QueryAccountBalance()
         public QueryAccountBalanceResponse QueryAccountBalance()

+ 160 - 0
molilian.core/Core/taoke/TaokeOpenCore.cs

@@ -24,6 +24,166 @@ namespace molilian.core
             _config = TkConfigCore.Get();
             _config = TkConfigCore.Get();
         }
         }
 
 
+        public async Task<(APIResult, PromotionQueryDTO)> PromotionQueryByItemIdAsync(string itemid, string oaid = "", string ip = "", string scene = "", bool recommand = true, bool debug = false, int aid = 0)
+        {
+            PromotionQueryDTO result = new()
+            {
+                end_point = _end_point,
+                ip = ip,
+                oaid = oaid,
+                scene = scene,
+                itemid = itemid
+            };
+            if (string.IsNullOrEmpty(oaid))
+            {
+                result.success = false;
+                result.message = "调用失败";
+                result.reason = "无效OAID";
+                _ = TkLogCore.PromotionImgLogAsync(result);
+                return (new APIResult(new
+                {
+                    result.success,
+                    result.message,
+                    result.reason,
+                }, APIResultCodeEnum.OK, new JsonSerializerOptions
+                {
+                    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                }), null);
+            }
+
+            if (string.IsNullOrEmpty(itemid))
+            {
+                result.success = false;
+                result.message = "调用失败";
+                result.reason = "无效商品ID";
+                _ = TkLogCore.PromotionImgLogAsync(result);
+                return (new APIResult(new
+                {
+                    result.success,
+                    result.message,
+                    result.reason,
+                }, APIResultCodeEnum.OK, new JsonSerializerOptions
+                {
+                    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                }), null);
+            }
+            try
+            {
+                TkPoolDTO account;
+                if (aid == 0)
+                {
+                    account = await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.promotionQuery, false, string.Empty, 0);
+                }
+                else
+                {
+                    account = await TkPoolCore.GetOneAsync(aid);
+                }
+                if (account == null)
+                {
+                    result.success = false;
+                    result.message = "调用失败";
+                    result.reason = "没有匹配账号";
+                    _ = TkLogCore.PromotionImgLogAsync(result);
+                    return (new APIResult(new
+                    {
+                        result.success,
+                        result.message,
+                        result.reason,
+                    }, APIResultCodeEnum.OK, new JsonSerializerOptions
+                    {
+                        Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                    }), result);
+                }
+
+                string nodeName = ProxyNodesCore.GetSelectOne(account.nodeName);
+                var proxy = ProxyNodesCore.GetOne(nodeName);
+                TaobaoOpenPlus taobao = new(account.appkey, account.appSecret, account.open_pid, debug, proxy);
+                int timeout = _config.similar_timeout;
+#if DEBUG
+                timeout = 50000;
+#endif
+
+                result.accountId = account.id;
+                result.accountName = account.name;
+
+                result.proxy_nodename = nodeName;
+                result.proxy_node = proxy?.Address?.Host;
+
+                using var cts = new CancellationTokenSource();
+                cts.CancelAfter(timeout);
+
+                DateTime stime = DateTime.Now;
+
+#if DEBUG
+                account.api_id = 17;
+                account.api = "aliyun";
+                account.refpid = "mm_6695915495_3161300068_115818650332";
+#endif
+
+                result.proxy_nodename = null;
+                result.proxy_node = null;
+
+                AliyunCore api;
+                if (account.api_id == 0)
+                {
+                    api = new AliyunCore(account.appkey, account.appSecret);
+                }
+                else
+                {
+                    api = new AliyunCore(account.api_id);
+                }
+
+                var aliDelayTask = Task.Delay(1500, cts.Token);
+                var aliRequestTask = api.itemSimilaritemAsync(itemid, account.open_pid, result);
+
+                var aliCompletedTask = await Task.WhenAny(aliDelayTask, aliRequestTask);
+                if (aliCompletedTask == aliRequestTask)
+                {
+                    result = await aliRequestTask;
+                }
+                else
+                {
+                    result.success = false;
+                    result.message = "调用失败";
+                    result.reason = "请求超时";
+                }
+
+                var ts2 = DateTime.Now - stime;
+                result.elapsedTime2 = (int)ts2.TotalMilliseconds;
+
+                _ = TkLogCore.PromotionImgLogAsync(result);
+                return (new APIResult(new
+                {
+                    result.success,
+                    result.message,
+                    result.reason,
+                    promotionImg = result.PromotionImg,
+                    exposeTracks = $"https://api.molilian.com/tracks/track?track_id=7&unique_id=",
+                    clickTracks = $"https://api.molilian.com/tracks/track?track_id=8&unique_id="
+                }, APIResultCodeEnum.OK, new JsonSerializerOptions
+                {
+                    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                }), result);
+            }
+            catch (Exception ex)
+            {
+                result.success = false;
+                result.message = "调用失败";
+                result.reason = ex.Message;
+                _ = TkLogCore.PromotionImgLogAsync(result);
+                return (new APIResult(new
+                {
+                    result.success,
+                    result.message,
+                    result.reason,
+                }, APIResultCodeEnum.OK, new JsonSerializerOptions
+                {
+                    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                }), result);
+            }
+
+        }
+
 
 
         public async Task<APIResult> PromotionQueryAsync(string img, string oaid = "", string ip = "", string scene = "", bool recommand = true, bool debug = false, int aid = 0)
         public async Task<APIResult> PromotionQueryAsync(string img, string oaid = "", string ip = "", string scene = "", bool recommand = true, bool debug = false, int aid = 0)
         {
         {

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

@@ -49,7 +49,7 @@ namespace molilian.core
             }
             }
 
 
             var config = TkConfigCore.Get();
             var config = TkConfigCore.Get();
-            if (config.webook_xhs_zlong == 1 && !string.IsNullOrEmpty(request.Oaid))
+            if ("xhs".Equals(request.Channel) && config.webook_xhs_zlong == 1 && !string.IsNullOrEmpty(request.Oaid))
             {
             {
                 _ = XhsZlongForwardCore.ForwardAsync(request.Oaid);
                 _ = XhsZlongForwardCore.ForwardAsync(request.Oaid);
             }
             }
@@ -600,6 +600,10 @@ namespace molilian.core
                 (bool isTaobaoUrl, string url) = AlimamaPlus.IsTaobaoUrl(content);
                 (bool isTaobaoUrl, string url) = AlimamaPlus.IsTaobaoUrl(content);
 
 
                 //============================== 放弃转链-没有匹配账号 ==============================
                 //============================== 放弃转链-没有匹配账号 ==============================
+#if DEBUG
+                request.AccountId = 133;
+#endif
+
                 TkPoolDTO? account = request.AccountId > 0 ?
                 TkPoolDTO? account = request.AccountId > 0 ?
                     await TkPoolCore.GetOneAsync(request.AccountId) :
                     await TkPoolCore.GetOneAsync(request.AccountId) :
                     await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.parse, isTaobaoUrl, request.RiskStrategy, request.LaunchScene);
                     await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.parse, isTaobaoUrl, request.RiskStrategy, request.LaunchScene);

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

@@ -13,6 +13,7 @@ namespace molilian.core
         public string proxy_node { get; set; } = string.Empty;
         public string proxy_node { get; set; } = string.Empty;
         public string proxy_nodename { get; set; } = string.Empty;
         public string proxy_nodename { get; set; } = string.Empty;
         public string img { get; set; } = string.Empty;
         public string img { get; set; } = string.Empty;
+        public string itemid { get; set; } = string.Empty;
         public string ip { get; set; } = string.Empty;
         public string ip { get; set; } = string.Empty;
         public string oaid { get; set; } = string.Empty;
         public string oaid { get; set; } = string.Empty;
         public bool success { get; set; } = false;
         public bool success { get; set; } = false;

+ 3 - 0
molilian.core/Plus/Alimama/parse.cs

@@ -179,6 +179,9 @@ namespace molilian.core
                     case "aliyun":
                     case "aliyun":
                         result = await endpoint_aliyun(result.content, result, cancellationToken);
                         result = await endpoint_aliyun(result.content, result, cancellationToken);
                         break;
                         break;
+                    case "tbk_dg":
+                        result = await endpoint_tbk_dg(result.content, result, cancellationToken);
+                        break;
                     case "veapi":
                     case "veapi":
                     case "ddx":
                     case "ddx":
                     case "other_api":
                     case "other_api":

+ 71 - 18
molilian.core/Plus/Alimama/parse_2.cs

@@ -37,18 +37,18 @@ namespace molilian.core
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// 提取url里面的id参数
-        /// </summary>
-        /// <param name="url"></param>
-        /// <returns></returns>
-        public static string? ExtractItemId(string url)
-        {
-            Match match = Regex.Match(url, @"(?:\?|&)id=(\d+)");
-            if (match.Success)
-            {
-                return match.Groups[1].Value;
-            }
-            return null;
+        /// 提取url里面的商品id参数
+        /// </summary>
+        /// <param name="url"></param>
+        /// <returns></returns>
+        public static string? ExtractItemId(string url)
+        {
+            Match match = Regex.Match(url, @"(?:\?|&)(?:id|shareDetailItemId|itemIds)=(\d+)");
+            if (match.Success)
+            {
+                return match.Groups[1].Value;
+            }
+            return null;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -71,11 +71,12 @@ namespace molilian.core
             }
             }
             //https://pages-g.m.taobao.com/wow/z/app/detail-next/item/index?x-ssr=true&id=753169018016
             //https://pages-g.m.taobao.com/wow/z/app/detail-next/item/index?x-ssr=true&id=753169018016
 
 
-            if (url.Contains("//item.taobao.com/item.htm?") ||
-                url.Contains("//pages-g.m.taobao.com/wow/z/app/detail-next/item/index?") ||
-                url.Contains("//main.m.taobao.com/detail/index.html?") ||
-                url.Contains("//main.m.taobao.com/security-h5-detail/home?") ||
-                url.Contains("//h5.m.taobao.com/awp/core/detail.htm?") ||
+            if (url.Contains("//item.taobao.com/item.htm?") ||
+                url.Contains("//pages-g.m.taobao.com/wow/z/app/detail-next/item/index?") ||
+                (url.Contains("//pages-fast.m.taobao.com/wow/bz/jingmi/") && url.Contains("shareDetailItemId=")) ||
+                url.Contains("//main.m.taobao.com/detail/index.html?") ||
+                url.Contains("//main.m.taobao.com/security-h5-detail/home?") ||
+                url.Contains("//h5.m.taobao.com/awp/core/detail.htm?") ||
                 url.Contains("//new.m.taobao.com/detail.htm?") ||
                 url.Contains("//new.m.taobao.com/detail.htm?") ||
                 url.Contains("//outfliggys.m.taobao.com/app/trip/rx-travel-detail") ||
                 url.Contains("//outfliggys.m.taobao.com/app/trip/rx-travel-detail") ||
                 url.Contains("//detail.m.tmall.com/item.htm?") ||
                 url.Contains("//detail.m.tmall.com/item.htm?") ||
@@ -245,7 +246,7 @@ namespace molilian.core
         /// </summary>
         /// </summary>
         /// <param name="url"></param>
         /// <param name="url"></param>
         /// <returns></returns>
         /// <returns></returns>
-        (bool, string) GetDesiredUrl(string url)
+        public (bool, string) GetDesiredUrl(string url)
         {
         {
             string result;
             string result;
             try
             try
@@ -299,6 +300,58 @@ namespace molilian.core
             return (false, result);
             return (false, result);
         }
         }
 
 
+        public static (bool, string) GetItemUrl(string url, WebProxy proxy = null)
+        {
+            string result;
+            try
+            {
+                if (!IsShortLink(url)) return (false, "不是淘宝短网址");
+
+                string desiredUrlPattern = "var url = '(.*?)'";
+                WebClientUtility client = new()
+                {
+                    Proxy = proxy,
+                    UserAgent = Sayaka.Common.ProviderFakeUserAgent.RandomComputer
+                };
+#if DEBUG
+                client.Proxy = null;
+#endif
+                var response = client.Request(url);
+
+                var responseBody = response.Body();
+                result = responseBody;
+                if (response.Successed)
+                {
+
+                    if (response.ResponseMessage.IsSuccessStatusCode)
+                    {
+                        Match match = Regex.Match(responseBody, desiredUrlPattern);
+                        if (match.Success)
+                        {
+                            result = match.Groups[1].Value; // 返回匹配的 URL
+
+                            if (string.IsNullOrEmpty(result)) return (false, "没有匹配的URL");
+                            return (true, result);
+                        }
+                    }
+                    else
+                    {
+                        result = $"{response.ResponseMessage.StatusCode}\n{responseBody}";
+                    }
+                }
+                else
+                {
+                    throw response.ResponseException;
+
+                }
+            }
+            catch (Exception ex)
+            {
+                result = $"{ex.Message}\n{ex.StackTrace}";
+            }
+            return (false, result);
+        }
+
 
 
         private void InternalTextProcessing(string content, ref TkDataDTO result)
         private void InternalTextProcessing(string content, ref TkDataDTO result)
         {
         {

+ 73 - 0
molilian.core/Plus/Alimama/parse_endpoint/endpoint_tbk_dg.cs

@@ -0,0 +1,73 @@
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using StackExchange.Redis;
+using System.Diagnostics;
+using System.Text.Json;
+using TencentCloud.Tcm.V20210413.Models;
+
+
+namespace molilian.core
+{
+    public partial class AlimamaPlus
+    {
+        public async Task<TkDataDTO> endpoint_tbk_dg(string content, TkDataDTO result, CancellationToken cancellationToken = default)
+        {
+            string item_id = content.GetContentPart("item.htm?id=", "");
+            if (string.IsNullOrEmpty(item_id))
+            {
+                _ = new LoggerLibrary("endpoint", "tbk_dg")
+                    .Info(content, "response null")
+                    .SaveAsync();
+                result.channel = TkChannelEnum.tb;
+                result.link_type = LinkTypeEnum.unknown;
+                result.success = false;
+                result.message = "放弃转链";
+                result.reason = "无效商品id";
+                return result;
+            }
+
+            string oaid = result.oaid;
+            if (string.IsNullOrEmpty(oaid)) { oaid = DateTime.Now.Convert2UnixTimestamp().ToString().MD5(); }
+            TaobaoOpenPlus taobao = new(_account.appkey, _account.appSecret, _account.open_pid, false, null);
+            var response = taobao.SmartAssistantLinkConvertRequest(content, oaid);
+
+            if (response == null)
+            {
+                _ = new LoggerLibrary("endpoint", "tbk_dg")
+                    .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 = !response.IsError;
+            if (!result.success)
+            {
+                result.message = "转链失败";
+                result.reason = $"{response.SubErrMsg}";
+
+                _ = new LoggerLibrary("endpoint", "tbk_dg")
+                    .Info(content, result.reason)
+                    .SaveAsync();
+
+                result.channel = TkChannelEnum.tb;
+                result.link_type = LinkTypeEnum.unknown;
+                result.success = false;
+                return result;
+            }
+
+
+            string deeplink = response?.Data?.PageDeeplink;
+
+            result.deeplink_url = deeplink;
+            string h5Url = deeplink.GetContentPart("h5Url=", "&").UrlDecode();
+            result.shortLinkurl = h5Url;
+            return result;
+        }
+
+    }
+
+}

+ 4 - 4
molilian.core/Plus/JDUnion/H5stHack.cs

@@ -13,14 +13,14 @@ namespace molilian.core
             string useragent = _account.user_agent;
             string useragent = _account.user_agent;
             if (string.IsNullOrEmpty(useragent)) useragent = ProviderFakeUserAgent.RandomComputer;
             if (string.IsNullOrEmpty(useragent)) useragent = ProviderFakeUserAgent.RandomComputer;
 #if DEBUG
 #if DEBUG
-            //_config.jd_h5st_sign_version = "5.0.6";
+            _config.jd_h5st_sign_version = "5.3.2";
 #endif
 #endif
             var args = new
             var args = new
             {
             {
                 version = _config.jd_h5st_sign_version,
                 version = _config.jd_h5st_sign_version,
                 body = querystring,
                 body = querystring,
                 pin = _account.pin,
                 pin = _account.pin,
-                envStr = _account.envStr,
+                //envStr = _account.envStr,
                 ua = _account.user_agent,
                 ua = _account.user_agent,
                 //h5st = _account.h5st,
                 //h5st = _account.h5st,
                 appId = "586ae",
                 appId = "586ae",
@@ -33,8 +33,8 @@ namespace molilian.core
                 if (string.IsNullOrEmpty(url)) url = "http://localhost:3001/h5st";
                 if (string.IsNullOrEmpty(url)) url = "http://localhost:3001/h5st";
 
 
 #if DEBUG
 #if DEBUG
-                url = "http://bjapi.molilian.com:3001/h5st";
-                //url = "http://127.0.0.1:3002/h5st";
+                //url = "http://bjapi.molilian.com:3001/h5st";
+                url = "http://127.0.0.1:3001/h5st";
 #endif
 #endif
 
 
                 WebClientUtility client = new WebClientUtility();
                 WebClientUtility client = new WebClientUtility();

+ 64 - 13
molilian.core/Plus/TaobaoOpen/TaobaoOpenPlus.cs

@@ -57,11 +57,11 @@ namespace molilian.core
 
 
 
 
 
 
-        public TbkThorMfrsPromotionQueryResponse PromotionQueryRequest(string base64, string oaid)
-        {
-            var client = new DefaultTopClient(_server_url, _app_key, _app_secret, "json");
-            client.SetProxy(_proxy);
-            TbkThorMfrsPromotionQueryRequest req = new();
+        public TbkThorMfrsPromotionQueryResponse PromotionQueryRequest(string base64, string oaid)
+        {
+            var client = new DefaultTopClient(_server_url, _app_key, _app_secret, "json");
+            client.SetProxy(_proxy);
+            TbkThorMfrsPromotionQueryRequest req = new();
             TbkThorMfrsPromotionQueryRequest.ManufacturerLaunchOptionDomain obj1 = new()
             TbkThorMfrsPromotionQueryRequest.ManufacturerLaunchOptionDomain obj1 = new()
             {
             {
                 Img = base64,
                 Img = base64,
@@ -79,14 +79,65 @@ namespace molilian.core
                 var _req = req.Convert2Json();
                 var _req = req.Convert2Json();
                 var _rsp = rsp.Convert2Json();
                 var _rsp = rsp.Convert2Json();
                 new LoggerLibrary("debug", oaid).Info(_req, _rsp).Save();
                 new LoggerLibrary("debug", oaid).Info(_req, _rsp).Save();
-            }
-            return rsp;
-        }
-
-
-
-        public async Task<(List<PromotionQueryItemDTO>, string, int)> GetRecommendDataWithRetry(
-            RecommendRequestType type, string oaid,
+            }
+            return rsp;
+        }
+
+
+        public TbkDgSmartAssistantLinkConvertResponse SmartAssistantLinkConvertRequest(
+            string originalItemUrl,
+            string oaid,
+            TbkDgSmartAssistantLinkConvertRequest.TargetItemDtoDomain? targetItem = null,
+            TbkDgSmartAssistantLinkConvertRequest.ItemFeedDtoDomain? itemFeed = null)
+        {
+            if (string.IsNullOrWhiteSpace(this._pid))
+            {
+                throw new ArgumentException("pid is required", nameof(this._pid));
+            }
+
+            string[] pidParts = this._pid.Split('_', StringSplitOptions.RemoveEmptyEntries);
+            if (pidParts.Length < 4 || !long.TryParse(pidParts[^1], out long adzoneId))
+            {
+                throw new ArgumentException("pid format is invalid", nameof(this._pid));
+            }
+
+            var client = new DefaultTopClient(_server_url, _app_key, _app_secret, "json");
+            client.SetProxy(_proxy);
+
+            TbkDgSmartAssistantLinkConvertRequest req = new()
+            {
+                AdzoneId = adzoneId,
+                SubPid = this._pid,
+                TargetItemDto = "{}",
+                ItemFeedDto = "{}",
+                OriginalItemUrl = originalItemUrl,
+                DeviceDto_ = new TbkDgSmartAssistantLinkConvertRequest.DeviceDtoDomain() { DeviceId = oaid, DeviceType = "OAID_MD5" }
+            };
+
+            if (targetItem != null)
+            {
+                req.TargetItemDto_ = targetItem;
+            }
+
+            if (itemFeed != null)
+            {
+                req.ItemFeedDto_ = itemFeed;
+            }
+
+            TbkDgSmartAssistantLinkConvertResponse rsp = client.Execute(req);
+            if (_debug)
+            {
+                var _req = req.Convert2Json();
+                var _rsp = rsp.Convert2Json();
+                new LoggerLibrary("debug", this._pid).Info(_req, _rsp).Save();
+            }
+            return rsp;
+        }
+
+
+
+        public async Task<(List<PromotionQueryItemDTO>, string, int)> GetRecommendDataWithRetry(
+            RecommendRequestType type, string oaid,
             string cookies,
             string cookies,
             CancellationToken cancellationToken = default)
             CancellationToken cancellationToken = default)
         {
         {

+ 170 - 0
molilian.core/Plus/TaobaoOpen/TbkDgSmartAssistantLinkConvert.cs

@@ -0,0 +1,170 @@
+using System;
+using System.Collections.Generic;
+using System.Xml.Serialization;
+using Top.Api;
+using Top.Api.Util;
+
+namespace Top.Api.Request
+{
+    /// <summary>
+    /// TOP API: taobao.tbk.dg.smart.assistant.link.convert
+    /// </summary>
+    public class TbkDgSmartAssistantLinkConvertRequest : BaseTopRequest<Top.Api.Response.TbkDgSmartAssistantLinkConvertResponse>
+    {
+        /// <summary>
+        /// 推广位id,mm_xx_xx_xx pid三段式中的第三段
+        /// </summary>
+        public long? AdzoneId { get; set; }
+
+        /// <summary>
+        /// 设备信息
+        /// </summary>
+        public string DeviceDto { get; set; } = string.Empty;
+
+        public DeviceDtoDomain DeviceDto_ { set { DeviceDto = TopUtils.ObjectToJson(value); } }
+
+        /// <summary>
+        /// 导购商品流
+        /// </summary>
+        public string ItemFeedDto { get; set; } = "{}";
+
+        public ItemFeedDtoDomain ItemFeedDto_ { set { ItemFeedDto = TopUtils.ObjectToJson(value); } }
+
+        /// <summary>
+        /// 原始商品链接
+        /// </summary>
+        public string OriginalItemUrl { get; set; } = string.Empty;
+
+        /// <summary>
+        /// 三方pid,满足mm_xxx_xxx_xxx格式
+        /// </summary>
+        public string SubPid { get; set; } = string.Empty;
+
+        /// <summary>
+        /// 同款商品
+        /// </summary>
+        public string TargetItemDto { get; set; } = "{}";
+
+        public TargetItemDtoDomain TargetItemDto_ { set { TargetItemDto = TopUtils.ObjectToJson(value); } }
+
+        public override string GetApiName()
+        {
+            return "taobao.tbk.dg.smart.assistant.link.convert";
+        }
+
+        public override IDictionary<string, string> GetParameters()
+        {
+            TopDictionary parameters = new();
+            parameters.Add("adzone_id", AdzoneId?.ToString());
+            parameters.Add("device_dto", DeviceDto);
+            parameters.Add("item_feed_dto", ItemFeedDto);
+            parameters.Add("original_item_url", OriginalItemUrl);
+            parameters.Add("sub_pid", SubPid);
+            parameters.Add("target_item_dto", TargetItemDto);
+
+            return parameters;
+        }
+
+        public override void Validate()
+        {
+            RequestValidator.ValidateRequired("adzone_id", AdzoneId);
+            RequestValidator.ValidateRequired("device_dto", DeviceDto);
+            RequestValidator.ValidateRequired("item_feed_dto", ItemFeedDto);
+            RequestValidator.ValidateRequired("original_item_url", OriginalItemUrl);
+            RequestValidator.ValidateRequired("sub_pid", SubPid);
+            RequestValidator.ValidateRequired("target_item_dto", TargetItemDto);
+        }
+
+        [Serializable]
+        public class DeviceDtoDomain : TopObject
+        {
+            /// <summary>
+            /// 设备ID MD5值
+            /// </summary>
+            [XmlElement("device_id")]
+            public string DeviceId { get; set; } = string.Empty;
+
+            /// <summary>
+            /// 支持OAID_MD5,IDFA_MD5
+            /// </summary>
+            [XmlElement("device_type")]
+            public string DeviceType { get; set; } = string.Empty;
+        }
+
+        [Serializable]
+        public class ItemFeedDtoDomain : TopObject
+        {
+            /// <summary>
+            /// 商品ID列表,最多10个
+            /// </summary>
+            [XmlArray("item_feed_list")]
+            [XmlArrayItem("string")]
+            public List<string>? ItemFeedList { get; set; }
+
+            /// <summary>
+            /// 转链类型,1-单品 2-会场定坑
+            /// </summary>
+            [XmlElement("link_transfer_type")]
+            public long? LinkTransferType { get; set; }
+
+            /// <summary>
+            /// 定坑会场ID
+            /// </summary>
+            [XmlElement("page_id")]
+            public string? PageId { get; set; }
+        }
+
+        [Serializable]
+        public class TargetItemDtoDomain : TopObject
+        {
+            /// <summary>
+            /// 商品ID,未入参联盟补齐
+            /// </summary>
+            [XmlElement("item_id")]
+            public string? ItemId { get; set; }
+
+            /// <summary>
+            /// 转链类型,1-单品 2-会场定坑
+            /// </summary>
+            [XmlElement("link_transfer_type")]
+            public long? LinkTransferType { get; set; }
+
+            /// <summary>
+            /// 定坑会场ID
+            /// </summary>
+            [XmlElement("page_id")]
+            public string? PageId { get; set; }
+        }
+    }
+}
+
+namespace Top.Api.Response
+{
+    /// <summary>
+    /// TbkDgSmartAssistantLinkConvertResponse.
+    /// </summary>
+    public class TbkDgSmartAssistantLinkConvertResponse : TopResponse
+    {
+        /// <summary>
+        /// 转链结果
+        /// </summary>
+        [XmlElement("data")]
+        public SmartAssertLinkResultDomain? Data { get; set; }
+
+        [Serializable]
+        public class SmartAssertLinkResultDomain : TopObject
+        {
+            /// <summary>
+            /// 不返回导购会场原因
+            /// </summary>
+            [XmlElement("msg")]
+            public string Msg { get; set; } = string.Empty;
+
+            /// <summary>
+            /// 进行校验后输出的导购会场deeplink
+            /// </summary>
+            [XmlElement("page_deeplink")]
+            public string PageDeeplink { get; set; } = string.Empty;
+        }
+    }
+}

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff