Parcourir la source

增加账号异常提醒接口

dodo hold il y a 1 an
Parent
commit
a3f6efed6f

+ 86 - 0
CLAUDE.md

@@ -0,0 +1,86 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.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.

+ 136 - 0
molilian.api/Controllers/admin/MessageController.cs

@@ -0,0 +1,136 @@
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using System.Text.Json;
+using static molilian.core.ChartDataCore;
+using Org.BouncyCastle.Asn1;
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [MyAuthorize("admin")]
+    [Route("api/[controller]/[action]")]
+    public class MessageController : ControllerBase
+    {
+        readonly IAuthorizationProvider provider = new AdminProvider();
+        protected IHttpContextAccessor _accessor;
+        public MessageController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+        [HttpGet]
+        public async Task<ActionResult> AccountWarning()
+        {
+            IEnumerable<TkPoolDTO> tb_list = await TkPoolCore.ListAsync();
+
+            List<string> typeKeys = new List<string>();
+            List<string> riskStrategyKeys = new List<string>();
+
+            Dictionary<string, List<int>> total = new();
+            foreach (TkPoolDTO tb in tb_list)
+            {
+                if (!string.IsNullOrEmpty(tb.parse_type) && !typeKeys.Contains(tb.parse_type)) typeKeys.Add(tb.parse_type);
+                if (!string.IsNullOrEmpty(tb.riskStrategy))
+                {
+                    string key = $"{tb.riskStrategy}-{tb.launchScene}";
+                    if (!riskStrategyKeys.Contains(key)) riskStrategyKeys.Add(key);
+                }
+            }
+            // 统计 typeKeys 的在线/离线状态
+            foreach (string typeKey in typeKeys)
+            {
+                var typeAccounts = tb_list.Where(tb => tb.parse_type == typeKey);
+                int totalCount = typeAccounts.Count();
+                int onlineCount = typeAccounts.Count(tb => tb.status);
+
+                total[typeKey] = new List<int> { totalCount, onlineCount };
+            }
+
+            // 统计 riskStrategyKeys 的在线/离线状态
+            foreach (string riskStrategyKey in riskStrategyKeys)
+            {
+                // riskStrategyKey 格式为 "riskStrategy-launchScene"
+                string[] parts = riskStrategyKey.Split('-');
+                if (parts.Length == 2)
+                {
+                    string riskStrategy = parts[0];
+                    if (int.TryParse(parts[1], out int launchScene))
+                    {
+                        var riskAccounts = tb_list.Where(tb =>
+                            tb.riskStrategy == riskStrategy &&
+                            tb.launchScene == launchScene);
+
+                        int totalCount = riskAccounts.Count();
+                        int onlineCount = riskAccounts.Count(tb => tb.status);
+
+                        total[riskStrategyKey] = [totalCount, onlineCount];
+                    }
+                }
+            }
+
+            IEnumerable<JdPoolDTO> jd_list = await JdPoolCore.ListAsync();
+
+            List<string> jdTypeKeys = new List<string>();
+            List<string> jdRiskStrategyKeys = new List<string>();
+
+            Dictionary<string, List<int>> jdTotal = new();
+
+            // 收集京东的 typeKeys 和 riskStrategyKeys
+            foreach (JdPoolDTO jd in jd_list)
+            {
+                if (!string.IsNullOrEmpty(jd.parse_type) && !jdTypeKeys.Contains(jd.parse_type)) jdTypeKeys.Add(jd.parse_type);
+                if (!string.IsNullOrEmpty(jd.riskStrategy))
+                {
+                    string key = $"{jd.riskStrategy}-{jd.launchScene}";
+                    if (!jdRiskStrategyKeys.Contains(key)) jdRiskStrategyKeys.Add(key);
+                }
+            }
+
+            // 统计京东 typeKeys 的在线/离线状态
+            foreach (string typeKey in jdTypeKeys)
+            {
+                var typeAccounts = jd_list.Where(jd => jd.parse_type == typeKey);
+                int totalCount = typeAccounts.Count();
+                int onlineCount = typeAccounts.Count(jd => jd.status);
+
+                jdTotal[typeKey] = new List<int> { totalCount, onlineCount };
+            }
+
+            // 统计京东 riskStrategyKeys 的在线/离线状态
+            foreach (string riskStrategyKey in jdRiskStrategyKeys)
+            {
+                // riskStrategyKey 格式为 "riskStrategy-launchScene"
+                string[] parts = riskStrategyKey.Split('-');
+                if (parts.Length == 2)
+                {
+                    string riskStrategy = parts[0];
+                    if (int.TryParse(parts[1], out int launchScene))
+                    {
+                        var riskAccounts = jd_list.Where(jd =>
+                            jd.riskStrategy == riskStrategy &&
+                            jd.launchScene == launchScene);
+
+                        int totalCount = riskAccounts.Count();
+                        int onlineCount = riskAccounts.Count(jd => jd.status);
+                        jdTotal[riskStrategyKey] = [totalCount, onlineCount];
+                    }
+                }
+            }
+
+
+            return new APIResult(new
+            {
+                data = new
+                {
+                    tb = total,
+                    jd = jdTotal
+                }
+            });
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> list([FromBody] JsonElement form)
+        {
+            return new APIResult(new { data = string.Empty });
+        }
+    }
+}

+ 4 - 1
molilian.api/Controllers/admin/PddUnionController.cs

@@ -32,10 +32,11 @@ namespace molilian.api.Controllers
 
 
             string cookie = form.Read<string>("cookie", string.Empty);
+            int id = form.Read<int>("id", 0);
             cookie = cookie.Replace("\r", "").Replace("\n", "").Trim();
             if (!cookie.EndsWith(";")) cookie += ";";
 
-            var accountId = await PddPoolCore.UpdateCookies(cookie, user_agent);
+            var accountId = await PddPoolCore.UpdateCookies(cookie, user_agent, id);
             bool success = accountId > 0;
             if (success)
             {
@@ -144,6 +145,8 @@ namespace molilian.api.Controllers
             switch (name)
             {
                 case "enable_parse":
+                case "enable_sync_revenue":
+                case "enable_sync_order":
                 case "enable_coupon":
                 case "cookie_status":
                 case "status":

+ 3 - 2
molilian.api/Controllers/public/TaskController.cs

@@ -676,7 +676,7 @@ namespace molilian.api.Controllers
 
                     string lockKey = $"lock:exception:sleep:jd_{account.id}";
                     if (!string.IsNullOrEmpty(RedisHelper.Get(lockKey))) continue;
-                     
+
                     if (changed) JdPoolCore.Update(account);
                 }
                 if (any_jd_changed) JdPoolCore.Refresh();
@@ -758,7 +758,7 @@ namespace molilian.api.Controllers
         {
             string message = string.Empty;
 
-             
+
             string filter = "";
 #if DEBUG
             filter = "id IN (37, 15)";
@@ -917,6 +917,7 @@ namespace molilian.api.Controllers
                 foreach (var account in pdd_list)
                 {
                     if (account.is_hide) continue;
+                    if (!account.enable_sync_revenue) continue;
 
                     if (!string.IsNullOrEmpty(account.cookies))
                     {

+ 3 - 3
molilian.api/Controllers/public/TkController.cs

@@ -52,7 +52,7 @@ namespace molilian.api.Controllers
             var clickId = form.Read("clickId", string.Empty);
             var commerceType = form.Read<int>("commerceType", 0);
             var riskStrategy = form.Read("riskStrategy", string.Empty);
-            var launchScene = form.Read<int>("launchScene", 0);
+            var launchScene = form.Read<int>("launchScene", -1);
 
 
 
@@ -93,7 +93,7 @@ namespace molilian.api.Controllers
             int accountid = form.Read("aid", 0);
             var commerceType = form.Read<int>("commerceType", 0);
             var riskStrategy = form.Read("riskStrategy", string.Empty);
-            var launchScene = form.Read<int>("launchScene", 0);
+            var launchScene = form.Read<int>("launchScene", -1);
 
 #if DEBUG
             //ip = "127.0.0.1";
@@ -117,7 +117,7 @@ namespace molilian.api.Controllers
             var commerceType = form.Read<int>("commerceType", 0);
             //入参参数。brw-浏览器,qapp-快应用
             var riskStrategy = form.Read("riskStrategy", string.Empty);
-            var launchScene = form.Read<int>("launchScene", 0);
+            var launchScene = form.Read<int>("launchScene", -1);
 
 #if DEBUG
             //ip = "127.0.0.1";

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
molilian.api/Properties/PublishProfiles/latest.pubxml.user


+ 3 - 3
molilian.core/Core/jd/JdPoolCore.cs

@@ -83,14 +83,14 @@ namespace molilian.core
             }
             else
             {
-                if ("brw".Equals(riskStrategy) && launchScene != 0)
+                if (!string.IsNullOrEmpty(riskStrategy) && launchScene != -1)
                 {
-                    list = list.Where(item => string.IsNullOrEmpty(item.parse_type) && "brw".Equals(item.riskStrategy) && item.launchScene == launchScene).ToList();
+                    list = list.Where(item => string.IsNullOrEmpty(item.parse_type) && riskStrategy.Equals(item.riskStrategy) && item.launchScene == launchScene).ToList();
                     if (!list.Any()) return null;
                 }
                 else
                 {
-                    list = list.Where(item => string.IsNullOrEmpty(item.parse_type) && !"brw".Equals(item.riskStrategy)).ToList();
+                    list = list.Where(item => string.IsNullOrEmpty(item.parse_type) && string.IsNullOrEmpty(item.riskStrategy)).ToList();
                     if (!list.Any()) return null;
                 }
 

+ 2 - 1
molilian.core/Core/log/pdd.cs

@@ -135,7 +135,7 @@ namespace molilian.core
                     await RiskControlCore.CallsIncrByAsync(response.channel, response.accountId);
                     await saveClientRequestTotalAsync(response.channel, response.ip, response.oaid);
                 }
-
+                //30007:检测到您推广行为异常,链接生成失败
                 if (response.reason.Equals("您的调用次数过高"))
                 {
                     PddPoolCore.TempSuspend(response.accountId);
@@ -143,6 +143,7 @@ namespace molilian.core
 
                 if (!response.success && ("nologin".Equals(response.reason) ||
                     "方法不存在".Equals(response.reason) ||
+                    "43001:会话已过期".Equals(response.reason) ||
                     "未登录".Equals(response.reason)))
                 {
                     PddPoolCore.Disabled(response.accountId, response.accountName, $"{response.rawContent}\n{response.rawContent2}");

+ 91 - 10
molilian.core/Core/pdd/PddPoolCore.cs

@@ -14,6 +14,7 @@ using TencentCloud.Tcm.V20210413.Models;
 using System.Security.Cryptography;
 using System.Collections.Concurrent;
 using YunhuiKit;
+using Microsoft.AspNetCore.Components.RenderTree;
 
 
 namespace molilian.core
@@ -27,6 +28,11 @@ namespace molilian.core
         private static string _end_point;
         private static Dictionary<string, decimal> _incomeAmt = new();
         private static ConcurrentDictionary<int, DateTime> _suspend = new();
+        
+        // 添加本地内存统计 - 线程安全
+        private static readonly ConcurrentDictionary<string, int> _dailyUsage = new();
+        private static volatile string _currentDay = DateTime.Now.ToString("yyyyMMdd");
+        private static readonly object _dayResetLock = new object(); // 专用于日期重置的锁
 
         private static SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
 
@@ -51,19 +57,86 @@ namespace molilian.core
 
             }
 
-            return list.Where(e => IsMatch(e, mode, accountid)).OrderBy(l => Guid.NewGuid()).FirstOrDefault();
+            return list.Where(e => IsMatch(e, mode, accountid))
+                .OrderBy(account => GetCurrentDayUsage(account.id))
+                .ThenBy(account => account.id) // 相同使用次数时按ID排序,确保稳定性
+                .FirstOrDefault();
         }
 
+        private static int GetCurrentDayUsage(int accountId)
+        {
+            CheckAndResetDailyStats();
+            string currentDay = _currentDay; // 获取当前快照,避免在构建key过程中被修改
+            string key = $"{accountId}_{currentDay}";
+            return _dailyUsage.GetOrAdd(key, 0);
+        }
+
+        private static void CheckAndResetDailyStats()
+        {
+            var today = DateTime.Now.ToString("yyyyMMdd");
+            
+            // 使用volatile读取,如果日期相同则直接返回,避免锁开销
+            if (today == _currentDay) return;
+            
+            // 只有在日期不同时才尝试获取锁
+            lock (_dayResetLock)
+            {
+                // 双重检查:再次验证日期是否需要重置
+                if (today != _currentDay)
+                {
+                    // 清空所有统计数据
+                    _dailyUsage.Clear();
+                    
+                    // 原子性更新当前日期(volatile写入)
+                    _currentDay = today;
+                    
+                    // 可选:记录日期切换日志
+                    Console.WriteLine($"Daily stats reset for date: {today}");
+                }
+            }
+        }
+
+
+        internal static void UpdateAccountUsage(int accountId)
+        {
+            CheckAndResetDailyStats();
+            string currentDay = _currentDay; // 获取当前快照,确保一致性
+            string key = $"{accountId}_{currentDay}";
+            
+            // 使用AddOrUpdate确保原子性递增
+            _dailyUsage.AddOrUpdate(key, 1, (k, oldValue) => oldValue + 1);
+        }
+
+        /// <summary>
+        /// 获取当前统计信息(用于监控和调试)
+        /// </summary>
+        internal static Dictionary<string, int> GetCurrentUsageSnapshot()
+        {
+            CheckAndResetDailyStats();
+            return new Dictionary<string, int>(_dailyUsage);
+        }
+
+        /// <summary>
+        /// 强制清理统计数据(仅用于测试或紧急情况)
+        /// </summary>
+        internal static void ForceClearStats()
+        {
+            lock (_dayResetLock)
+            {
+                _dailyUsage.Clear();
+                Console.WriteLine("Force cleared daily usage stats");
+            }
+        }
 
         internal static void TempSuspend(int accountId)
         {
             _suspend.AddOrUpdate(accountId, DateTime.Now, (key, oldValue) => DateTime.Now);
         }
 
-
         private static bool IsMatch(PddPoolDTO item, PddUnionWorkMode mode, int accountid)
         {
             if (accountid != 0 && accountid != item.id) return false;
+            if (!item.enable_parse) return false;
             if (mode != PddUnionWorkMode.All && mode != item.work_mode) return false;
             if (item.work_mode == PddUnionWorkMode.Crawler && !item.cookie_status) return false;
 
@@ -81,6 +154,17 @@ namespace molilian.core
                 if (ts.TotalSeconds < 70) return false;
             }
 
+            if (item.cis_limit > 0)
+            {
+                string lockKey = $"pdd_cis_limit_{accountid}";
+                int cis_num = RedisHelper.Get<int>(lockKey);
+                if (cis_num > 0) return false;
+            }
+            if (item.rpm_limit > 0)
+            {
+                int rpm_num = RiskControlCore.GetCalls(TkChannelEnum.pdd, item.id, DateTime.Now.ToString("yyyyMMddHHmm"));
+                if (rpm_num >= item.rpm_limit) return false;
+            }
             if (item.daily_calls_limit > 0)
             {
                 int daily_num = RiskControlCore.GetCalls(TkChannelEnum.pdd, item.id, DateTime.Now.ToString("yyyyMMdd"));
@@ -248,10 +332,7 @@ namespace molilian.core
             return result;
         }
 
-
-
-
-        public static async Task<int> UpdateCookies(string cookies, string user_agent)
+        public static async Task<int> UpdateCookies(string cookies, string user_agent, int id = 0)
         {
             if (string.IsNullOrEmpty(cookies)) return 0;
             var userinfo = await GetUserInfo(cookies);
@@ -262,18 +343,18 @@ namespace molilian.core
             string lastPid = userinfo.PathRead<string>("result.lastPid", string.Empty);
 
             int accountId = 0;
-            if (duoId == 0) return 0;
+            if (duoId == 0 && id == 0) return 0;
 
 
-            var exist = new DBContext.Table("pdd_pool").Get<JdPoolDTO>("duoId=@duoId", new { duoId });
+            string filter = id != 0 ? "id=@id" : "duoId=@duoId";
+            var exist = new DBContext.Table("pdd_pool").Get<PddPoolDTO>(filter, new { id, duoId });
             if (exist != null)
             {
                 var status = exist.status;
                 var work_mode = exist.work_mode;
                 accountId = exist.id;
-                if (work_mode == JdUnionWorkMode.Crawler) status = true;
+                if (work_mode == PddUnionWorkMode.Crawler) status = true;
                 new DBContext.Table("pdd_pool")
-                    .Add("duoId", duoId)
                     .Add("cookies", cookies)
                     .Add("user_agent", user_agent)
                     .Add("status", status)

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

@@ -27,11 +27,12 @@ namespace molilian.core
         }
         internal static async Task CallsIncrByAsync(TkChannelEnum channel, int accountId)
         {
+            await CallsIncrByAsync(channel, accountId, DateTime.Now.ToString("yyyyMMddHHmm"), 300);
+            await CallsIncrByAsync(channel, accountId, DateTime.Now.ToString("yyyyMMddHH"), 14400);
             await CallsIncrByAsync(channel, accountId, DateTime.Now.ToString("yyyyMMdd"));
-            await CallsIncrByAsync(channel, accountId, DateTime.Now.ToString("yyyyMMddHH"));
         }
 
-        internal static async Task CallsIncrByAsync(TkChannelEnum channel, int accountId, string flag)
+        internal static async Task CallsIncrByAsync(TkChannelEnum channel, int accountId, string flag, int expire = 259200)
         {
             string key = $"{channel}:{accountId}:{flag}";
             if (_calls.ContainsKey(key))
@@ -44,17 +45,10 @@ namespace molilian.core
             }
             string cache_key = $"RiskControl:{key}:calls:{flag}";
             await RedisKit.IncrByAsync(cache_key);
-            await RedisKit.ExpireAsync(cache_key, 3 * 86400);
+            await RedisKit.ExpireAsync(cache_key, expire);
         }
 
-
-        internal static void CallsIncrBy(TkChannelEnum channel, int accountId)
-        {
-            CallsIncrBy(channel, accountId, DateTime.Now.ToString("yyyyMMdd"));
-            CallsIncrBy(channel, accountId, DateTime.Now.ToString("yyyyMMddHH"));
-        }
-
-        internal static void CallsIncrBy(TkChannelEnum channel, int accountId, string flag)
+        internal static async Task CallsIncrByAsync(TkChannelEnum channel, int accountId, string flag, double expire)
         {
             string key = $"{channel}:{accountId}:{flag}";
             if (_calls.ContainsKey(key))
@@ -66,10 +60,12 @@ namespace molilian.core
                 _calls[key] = 1;
             }
             string cache_key = $"RiskControl:{key}:calls:{flag}";
-            RedisHelper.IncrBy(cache_key);
-            RedisHelper.Expire(cache_key, 3 * 86400);
+            await RedisKit.IncrByAsync(cache_key);
+            await RedisKit.ExpireAsync(cache_key, expire);
         }
 
+
+
         internal static async Task SetCallsAsync(TkChannelEnum channel, int accountId, string flag, int val)
         {
             string key = $"{channel}:{accountId}:{flag}";
@@ -136,7 +132,7 @@ namespace molilian.core
             await RedisHelper.ExpireAsync(cache_key, 3 * 86400);
         }
 
-         
+
         public static async Task<int> GetAllNodesTkEndpointCallsAsync(int accountId, int ep_id, string flag)
         {
             string key = $"tk_endpoint:{accountId}:{ep_id}:{flag}";

+ 3 - 3
molilian.core/Core/taoke/TkPoolCore.cs

@@ -66,14 +66,14 @@ namespace molilian.core
             }
             else
             {
-                if ("brw".Equals(riskStrategy) && launchScene != 0)
+                if (!string.IsNullOrEmpty(riskStrategy) && launchScene != -1)
                 {
-                    list = list.Where(item => string.IsNullOrEmpty(item.parse_type) && "brw".Equals(item.riskStrategy) && item.launchScene == launchScene).ToList();
+                    list = list.Where(item => string.IsNullOrEmpty(item.parse_type) && riskStrategy.Equals(item.riskStrategy) && item.launchScene == launchScene).ToList();
                     if (!list.Any()) return null;
                 }
                 else
                 {
-                    list = list.Where(item => string.IsNullOrEmpty(item.parse_type) && !"brw".Equals(item.riskStrategy)).ToList();
+                    list = list.Where(item => string.IsNullOrEmpty(item.parse_type) && string.IsNullOrEmpty(item.riskStrategy)).ToList();
                     if (!list.Any()) return null;
                 }
             }

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

@@ -2,6 +2,7 @@
 using Microsoft.AspNetCore.Mvc;
 using System.Diagnostics;
 using System.Text.RegularExpressions;
+using YunhuiKit;
 
 
 namespace molilian.core
@@ -133,7 +134,7 @@ namespace molilian.core
 
 
         public static async Task<APIResult> TaobaoParseAsync(string content, int commerceType,
-            string ip = "", string oaid = "", string riskStrategy = "", int launchScene = 0,
+            string ip = "", string oaid = "", string riskStrategy = "", int launchScene = -1,
             int accountid = 0)
         {
             bool other_aff = false;
@@ -507,7 +508,7 @@ namespace molilian.core
         }
 
         public static async Task<APIResult> JdParseAsync(string content, int commerceType, string ip, string oaid,
-            string riskStrategy = "", int launchScene = 0,
+            string riskStrategy = "", int launchScene = -1,
             int accountid = 0, string clickId = "", CancellationToken cancellationToken = default)
         {
             var config = TkConfigCore.Get();
@@ -776,8 +777,6 @@ namespace molilian.core
                         return PddParseOutput(result);
                     }
                 }
-
-
                 //============================== 放弃转链-地区过滤 ==============================
                 if (accountid == 0 && PddUnionPlus.ShouldIgnoreRequest(config, ip, oaid, out string reason))
                 {
@@ -788,7 +787,23 @@ namespace molilian.core
                     _ = TkLogCore.ParseLogAsync(result);
                     return PddParseOutput(result);
                 }
-                var account = PddPoolCore.GetOne(PddUnionWorkMode.All, accountid);
+                PddPoolDTO account = null;
+                string lockKey;
+                long cis_num = 0;
+
+                for (int i = 0; i < 3; i++)
+                {
+                    account = PddPoolCore.GetOne(PddUnionWorkMode.All, accountid);
+                    if (account == null) continue;
+
+                    if (account.cis_limit > 0)
+                    {
+                        lockKey = $"pdd_cis_limit_{result.accountId}";
+                        cis_num = RedisHelper.Get<long>(lockKey);
+                        if (cis_num > 0) continue;
+                    }
+                    break;
+                }
                 if (account == null)
                 {
                     result.success = false;
@@ -800,9 +815,27 @@ namespace molilian.core
                 }
                 result.accountId = account.id;
                 result.accountName = account.name;
+                result.proxy_node = account.nodeName;
 
-                var plus = new PddUnionPlus(account);
+                if (account.cis_limit > 0)
+                {
+                    // 更新当前分钟的使用统计
+                    PddPoolCore.UpdateAccountUsage(account.id);
 
+                    lockKey = $"pdd_cis_limit_{result.accountId}";
+                    cis_num = RedisHelper.IncrBy(lockKey);
+                    if (cis_num > 1)
+                    {
+                        result.success = false;
+                        result.message = "放弃转链";
+                        result.reason = $"没有匹配账号{cis_num}";
+                        result.deeplink_url = PddUnionPlus.GetDeeplink(result.rawContent);
+                        _ = TkLogCore.ParseLogAsync(result);
+                        return PddParseOutput(result);
+                    }
+                    RedisHelper.Expire(lockKey, TimeSpan.FromMilliseconds(account.cis_limit));
+                }
+                var plus = new PddUnionPlus(account);
                 result = await plus.PddParseAsync(content, commerceType, result, cancellationToken);
             }
             catch (Exception ex)

+ 115 - 32
molilian.core/Core/taoke/UnionParseCore/dp2dp.cs

@@ -16,7 +16,7 @@ namespace molilian.core
     public partial class UnionParseCore
     {
         public static async Task<APIResult> DeeplinkTaobaoParseAsync(string content, int commerceType,
-            string ip = "", string oaid = "", string riskStrategy = "", int launchScene = 0,
+            string ip = "", string oaid = "", string riskStrategy = "", int launchScene = -1,
             int accountid = 0)
         {
             bool other_aff = false;
@@ -61,37 +61,45 @@ namespace molilian.core
                     });
                 }
 
-
-                (bool isTaobaUrl, string shortLinkurl) = AlimamaPlus.IsTaobaoUrl(parseResult.url);
-                if (!isTaobaUrl)
+                if (!AlimamaPlus.IsShortLink(parseResult.url))
                 {
-                    result.success = false;
-                    result.channel = TkChannelEnum.tb;
-                    result.link_type = LinkTypeEnum.unknown;
-                    result.success = false;
-                    result.message = "放弃转链";
-                    result.reason = "无效商品id";
-
-                    if (!result.success)
+                    (bool isTaobaUrl, string shortLinkurl) = AlimamaPlus.IsTaobaoUrl(parseResult.url);
+                    if (!isTaobaUrl)
                     {
-                        result.deeplink_url = string.Empty;
+                        result.success = false;
+                        result.channel = TkChannelEnum.tb;
+                        result.link_type = LinkTypeEnum.unknown;
+                        result.success = false;
+                        result.message = "放弃转链";
+                        result.reason = "无效商品id";
+
+                        if (!result.success)
+                        {
+                            result.deeplink_url = string.Empty;
+                        }
+                        _ = TkLogCore.ParseLogAsync(result);
+                        return TaobaoParseOutput(result, swData, new
+                        {
+                            result.success,
+                            result.commercial,
+                            result.message,
+                            result.content,
+                            result.itemName,
+                            other_aff,
+                            result.deeplink_url,
+                        });
                     }
-                    _ = TkLogCore.ParseLogAsync(result);
-                    return TaobaoParseOutput(result, swData, new
-                    {
-                        result.success,
-                        result.commercial,
-                        result.message,
-                        result.content,
-                        result.itemName,
-                        other_aff,
-                        result.deeplink_url,
-                    });
+                    result.shortLinkurl = shortLinkurl;
+                    result.content = shortLinkurl;
+                    content = shortLinkurl;
+                }
+                else
+                {
+                    result.content = parseResult.url;
+                    result.shortLinkurl = parseResult.url;
+                    content = parseResult.url;
                 }
-                result.shortLinkurl = shortLinkurl;
                 result.deeplink_url = string.Empty;
-                result.content = shortLinkurl;
-                content = shortLinkurl;
 
                 string reason = string.Empty;
 
@@ -454,7 +462,7 @@ namespace molilian.core
 
 
         public static async Task<APIResult> DeeplinkJdParseAsync(string content, int commerceType, string ip, string oaid,
-            string riskStrategy = "", int launchScene = 0,
+            string riskStrategy = "", int launchScene = -1,
             int accountid = 0, string clickId = "", CancellationToken cancellationToken = default)
         {
             var config = TkConfigCore.Get();
@@ -662,7 +670,23 @@ namespace molilian.core
                     _ = TkLogCore.ParseLogAsync(result);
                     return PddParseOutput(result);
                 }
-                var account = PddPoolCore.GetOne(PddUnionWorkMode.All, accountid, "dp");
+                PddPoolDTO account = null;
+                string lockKey;
+                long cis_num = 0;
+
+                for (int i = 0; i < 3; i++)
+                {
+                    account = PddPoolCore.GetOne(PddUnionWorkMode.All, accountid, "dp");
+                    if (account == null) continue;
+
+                    if (account.cis_limit > 0)
+                    {
+                        lockKey = $"pdd_cis_limit_{result.accountId}";
+                        cis_num = RedisHelper.Get<long>(lockKey);
+                        if (cis_num > 0) continue;
+                    }
+                    break;
+                }
                 if (account == null)
                 {
                     result.success = false;
@@ -674,6 +698,27 @@ namespace molilian.core
                 }
                 result.accountId = account.id;
                 result.accountName = account.name;
+                result.proxy_node = account.nodeName;
+
+                if (account.cis_limit > 0)
+                {
+                    // 更新当前分钟的使用统计
+                    PddPoolCore.UpdateAccountUsage(account.id);
+
+                    lockKey = $"pdd_cis_limit_{result.accountId}";
+                    cis_num = RedisHelper.IncrBy(lockKey);
+                    if (cis_num > 1)
+                    {
+                        result.success = false;
+                        result.message = "放弃转链";
+                        result.reason = $"没有匹配账号{cis_num}";
+                        result.deeplink_url = PddUnionPlus.GetDeeplink(result.rawContent);
+                        _ = TkLogCore.ParseLogAsync(result);
+                        return PddParseOutput(result);
+                    }
+                    RedisHelper.Expire(lockKey, TimeSpan.FromMilliseconds(account.cis_limit));
+
+                }
 
                 var plus = new PddUnionPlus(account);
 
@@ -721,7 +766,12 @@ namespace molilian.core
             }
             try
             {
-                string param = content.GetContentPart("params=", "");
+                string param = content.GetContentPart("params=", "&");
+                if (string.IsNullOrEmpty(param))
+                {
+                    param = content.GetContentPart("params=", "");
+                }
+
                 param = param.UrlDecode();
                 var root = param.Convert2JsonElement();
                 string category = root.Read("category", string.Empty);
@@ -801,7 +851,14 @@ namespace molilian.core
                 return resust;
             }
 
-            string itemid_pattern = "(?:pc_detail\\.itemId\\.\\d+&id=|detail\\.tmall\\.com\\/item\\.htm\\?id=)(\\d+)";
+            string itemid_pattern = "(?:pc_detail\\.itemId\\.\\d+&id=|(?:detail|item).*?[?&]id=)(\\d+)";
+            //https://h5.m.taobao.com/awp/core/detail.htm?id=951950415328
+            //taobao://item.taobao.com/item.htm?id=950190032209
+            //https://main.m.taobao.com/detail/index.html?id=908945171430&price=148.9&sourceType=item&suid=9a9921f6-5610-497c-9bb3-94427da5fbc0&ut_sk=1.aFUBza6VAN8DAOC0tQdyzNek_21646297_1753594384153.Copy.ShareGlobalNavigation_1&un=8ad28c612754fb28d9e5b32ae03d1ada&share_crt_v=1&un_site=0&spm=a2159r.13376460.0.0&tbSocialPopKey=shareItem&sp_tk=MnluVjQ0N2tDUXU%3D&cpp=1&shareurl=true&short_name=h.hOFywxY0R9ZuBR4&bxsign=scduUbJTmvx63UiuFdNYy6FDvPx0iaKQu9uyc1zfURgLpCZqHIO1Aqj8fgZ6_ZUy-rJeS1ZH3PiAGcemxyFqpeN5BTKnDbDuU77d6sqWD2TVok-8K3nspP6YVfAZBvzfx7WLVAMu3X6119Ru_1ooSt9ug&tk=2ynV447kCQu&app=chrome&x-ssr=true&slk_gid=gid_er_sidebar_0&action=ali.open.nav&module=h5&bootImage=0&slk_sid=2nf7IO8XhWICAd9KM5hOtK8k_1753783603680&slk_t=1753783605292&slk_gid=gid_er_sidebar_0&afcPromotionOpen=false&bc_fl_src=h5_huanduan&source=slk_dp
+            //https://main.m.taobao.com/detail/index.html?id=937709627060&price=208.9&sourceType=item&suid=48a4d791-127c-45d7-b580-a63097897697&ut_sk=1.aFUBza6VAN8DAOC0tQdyzNek_21646297_1753594384153.Copy.ShareGlobalNavigation_1&un=8ad28c612754fb28d9e5b32ae03d1ada&share_crt_v=1&un_site=0&spm=a2159r.13376460.0.0&tbSocialPopKey=shareItem&sp_tk=SG42aTQ0NzFMc3Y%3D&cpp=1&shareurl=true&short_name=h.hOCuiAd32dv9wBK&bxsign=scd0KcwU22FkrzJ77bVvMLnP0u4_5wpLz1ERfC-eCQE1wk21BYNMuT-w0I07gisSMdabKmlWh9F6DwW5DXLagikYvHjSD7Eh5n2n6kmyw1aF-dZWuqzGkI9Z-AHw-rffwjrPx1cXEsQ35HqOwmmkMEiYw&tk=Hn6i4471Lsv&app=chrome&x-ssr=true&slk_gid=gid_er_sidebar_0&action=ali.open.nav&module=h5&bootImage=0&slk_sid=580NIcIpZVQBASQOANYD2H43_1753783601070&slk_t=1753783605251&slk_gid=gid_er_sidebar_0&afcPromotionOpen=false&bc_fl_src=h5_huanduan&source=slk_dp
+            //https://main.m.taobao.com/detail/index.html?spm=a224e.7913437.1.1849716&id=672205270263&sku_properties=210158085%3A170720673&_bind=true&bc_fl_src=tmall_market_llb_1_1849716&llbPlatform=pc&agentId=1685089&llbIsd=1&bxsign=llbirWpI8yJlJLibTbWOXuYbuykv0WCTORO2Hj7JqVtdGau62UJiz2xO%2FZSIjdwbOudIUUsdN51H41YisrOLI53ZMhblPS9ZMyVhFDisOySHmk%3D&ntfMsId=65df6cbb682a0779f23141a7efd9c391&llbOsd=1&mm_unid=1_11192113_560501015e6d5754040c0255026f5353086656040b075e&jlogid=0718172753b3bfbd&x-ssr=true&slk_gid=gid_er_sidebar_0&bc_fl_src=tmall_market_llb_1_1849716&action=ali.open.nav&module=h5&bootImage=0&slk_sid=rnd07d9b9_1753784332640&slk_t=1753784332860&slk_gid=gid_er_sidebar_0&afcPromotionOpen=false&source=slk_dp
+
+
 
             var regex = new Regex(itemid_pattern);
             var match = regex.Match(content);
@@ -839,13 +896,39 @@ tbopen://m.taobao.com/tbopen/index.html?h5Url=https%3A%2F%2Fku.m.taobao.com%2Fmi
                 return resust;
             }
 
-            if (url.Contains("s.click.taobao.com"))
+            if (AlimamaPlus.IsAffLink(url))
             {
                 resust.success = false;
                 resust.reason = "其他推广链接";
                 return resust;
             }
 
+            if (url.Contains("ali_trackid="))
+            {
+                resust.success = false;
+                resust.reason = "其他推广链接";
+                return resust;
+            }
+
+            if (AlimamaPlus.IsShortLink(url))
+            {
+                resust.success = true;
+                resust.url = url;
+                return resust;
+            }
+
+            regex = new Regex(itemid_pattern);
+            match = regex.Match(url);
+            if (match.Success)
+            {
+                string itemId = match.Groups[1].Value;
+                string item_url = $"https://item.taobao.com/item.htm?id={itemId}";
+
+                resust.success = true;
+                resust.url = item_url;
+                return resust;
+            }
+
             resust.success = false;
             resust.reason = "未知链接";
             return resust;

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

@@ -120,7 +120,7 @@ namespace molilian.core
         public string end_point { get; set; } = string.Empty;
         public TkSubCodeEnum subCode { get; set; } = TkSubCodeEnum.Other;
         public string riskStrategy { get; set; } = string.Empty;
-        public int launchScene { get; set; } = 0;
+        public int launchScene { get; set; } = -1;
         public string parse_type { get; set; } = string.Empty;
         public string item_url { get; set; } = string.Empty;
         public string item_deeplink_url { get; set; } = string.Empty;

+ 1 - 1
molilian.core/DTO/alimama/TkPoolDTO.cs

@@ -65,7 +65,7 @@
         /// brw-浏览器,qapp-快应用
         /// </summary>
         public string riskStrategy { get; set; } = string.Empty;
-        public int launchScene { get; set; } = 0;
+        public int launchScene { get; set; } = -1;
         public bool useCouponLinkFirst { get; set; } = true;
         public string parse_type { get; set; } = string.Empty;
 

+ 1 - 1
molilian.core/DTO/jd/JdDataDTO.cs

@@ -33,7 +33,7 @@
         public int subCode { get; set; } = 0;
         public string riskStrategy { get; set; } = string.Empty;
         public string parse_type { get; set; } = string.Empty;
-        public int launchScene { get; set; } = 0;
+        public int launchScene { get; set; } = -1;
     }
 
 }

+ 1 - 1
molilian.core/DTO/jd/JdPoolDTO.cs

@@ -59,7 +59,7 @@
         public bool other_api_non_numeric_required { get; set; } = true;
         public string riskStrategy { get; set; } = string.Empty;
 
-        public int launchScene { get; set; } = 0;
+        public int launchScene { get; set; } = -1;
         public string parse_type { get; set; } = string.Empty;
 
 

+ 3 - 0
molilian.core/DTO/pdd/PddPoolDTO.cs

@@ -40,10 +40,13 @@ namespace molilian.core
         public int hourly_calls_limit { get; set; } = 0;
         public int current_daily_calls { get; set; } = 0;
         public int time_range { get; set; } = 0;
+        public int rpm_limit { get; set; } = 0;
+        public int cis_limit { get; set; } = 0;
 
         public decimal draw_balance { get; set; } = 0;
         public bool enable_parse { get; set; } = false;
         public bool enable_coupon { get; set; } = false;
+        public bool enable_sync_revenue { get; set; } = false;
         public bool enable_sync_order { get; set; } = false;
         public bool is_hide { get; set; } = true;
         public bool enable_call_stats { get; set; } = true;

+ 2 - 2
molilian.core/Plus/Alimama/parse.cs

@@ -534,7 +534,7 @@ namespace molilian.core
         {
             reason = string.Empty;
             //2025-05-11 brw不做风控
-            if ("brw".Equals(riskStrategy) && launchScene != 0) return false;
+            if (!string.IsNullOrEmpty(riskStrategy) && launchScene != -1) return false;
 
             try
             {
@@ -586,7 +586,7 @@ namespace molilian.core
         {
             reason = string.Empty;
             //2025-05-11 brw不做风控
-            if ("brw".Equals(riskStrategy) && launchScene != 0) return false;
+            if (!string.IsNullOrEmpty(riskStrategy) && launchScene != -1) return false;
 
             if (!TestParseCore.InWhitelist(ip, oaid)) return false;
 

+ 16 - 9
molilian.core/Plus/Alimama/parse_2.cs

@@ -111,7 +111,19 @@ namespace molilian.core
             {
                 return true;
             }
-
+            return false;
+        }
+        public static bool IsShortLink(string url)
+        {
+            if (!url.StartsWith("https://") || !url.StartsWith("https://")) url = "https://" + url;
+            Uri uri = new(url);
+            if (uri.Host.Equals("e.tb.cn", StringComparison.OrdinalIgnoreCase) ||
+                uri.Host.Equals("m.tb.cn", StringComparison.OrdinalIgnoreCase) ||
+                uri.Host.Equals("u.tb.cn", StringComparison.OrdinalIgnoreCase) ||
+                uri.Host.Equals("s.tb.cn", StringComparison.OrdinalIgnoreCase))
+            {
+                return true;
+            }
             return false;
         }
 
@@ -162,11 +174,7 @@ namespace molilian.core
             string result;
             try
             {
-                if (!url.Contains("m.tb.cn") &&
-                    !url.Contains("u.tb.cn") &&
-                    !url.Contains("e.tb.cn") &&
-                    !url.Contains("s.tb.cn"))
-                    return (false, "不是淘宝短网址");
+                if (!IsShortLink(url)) return (false, "不是淘宝短网址");
 
                 if (!_account.enable_deep_url) return (true, url);
 
@@ -243,8 +251,7 @@ namespace molilian.core
             string result;
             try
             {
-                if (!url.Contains("m.tb.cn") && !url.Contains("e.tb.cn") && !url.Contains("s.tb.cn"))
-                    return (false, "不是淘宝短网址");
+                if (!IsShortLink(url)) return (false, "不是淘宝短网址");
 
                 string desiredUrlPattern = "var url = '(.*?)'";
                 WebClientUtility client = new()
@@ -572,7 +579,7 @@ namespace molilian.core
         {
             if (string.IsNullOrEmpty(url)) return LinkTypeEnum.unknown;
 
-            if (url.Contains("m.tb.cn") || url.Contains("s.tb.cn") || url.Contains("e.tb.cn")) return LinkTypeEnum.baseDomain;
+            if (IsShortLink(url)) return LinkTypeEnum.baseDomain;
 
             if (url.StartsWith("https://huodong.m.taobao.com/act/talent/live.html")) return LinkTypeEnum.live;
             if (url.StartsWith("https://web.m.taobao.com/app/tnode/web/index")) return LinkTypeEnum.video;

+ 3 - 2
molilian.core/Plus/JDUnion/base.cs

@@ -111,7 +111,7 @@ namespace molilian.core
         {
             reason = string.Empty;
             //2025-06-24 brw不做风控
-            if ("brw".Equals(riskStrategy) && launchScene != 0) return false;
+            if (!string.IsNullOrEmpty(riskStrategy) && launchScene != -1) return false;
 
             try
             {
@@ -161,7 +161,8 @@ namespace molilian.core
         public static bool FlowControlIgnoreRequest(TkConfigDTO config, string riskStrategy, int launchScene, out string reason)
         {
             reason = string.Empty;
-            if ("brw".Equals(riskStrategy) && launchScene != 0) return false;
+            if (!string.IsNullOrEmpty(riskStrategy) && launchScene != -1) return false;
+
             try
             {
                 // IP和流量控制

+ 7 - 0
molilian.core/Plus/pdd/Crawler.cs

@@ -43,6 +43,10 @@ namespace molilian.core
             var ts = DateTime.Now.Convert2UnixTimestamp(true);
             string cookies = _account.cookies.Trim();
 
+#if DEBUG
+            cookies = "DDJB_PASS_ID=bedd7aa97aff1edfe392ad8d2c8f1dc5; DDJB_LOGIN_SCENE=0";
+#endif
+
             string useragent = ProviderFakeUserAgent.RandomComputer;
             string url = "https://jinbao.pinduoduo.com/network/api/promotion/transferUrl";
 
@@ -69,6 +73,9 @@ namespace molilian.core
             var response = await client.RequestAsync(url, "POST", cancellationToken);
 
             string body = response.Body();
+            //{"success":false,"errorCode":43001,"errorMsg":"会话已过期","result":null}
+
+
             var root = body.Convert2Object<PddTransferUrlResponse>();
             string message = string.Empty;
 

+ 1 - 0
molilian.core/Plus/pdd/PddUnionPlus.cs

@@ -2,6 +2,7 @@
 using Sayaka.Common;
 using System.Diagnostics;
 using System.Text.RegularExpressions;
+using System.Threading.Channels;
 using System.Web;
 
 namespace molilian.core

+ 1 - 1
molilian.core/Plus/pdd/base.cs

@@ -30,7 +30,7 @@ namespace molilian.core
         private WebProxy? _proxy = null;
 
         public TkConfigDTO _config;
-        private static PddPoolDTO _account;
+        private PddPoolDTO _account;
         private static string _end_point;
 
         static PddUnionPlus()

+ 1 - 1
molilian.core/molilian.core.csproj

@@ -25,7 +25,7 @@
     <PackageReference Include="IP2Region.Net" Version="2.0.2" />
     <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
     <PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
-    <PackageReference Include="YunhuiKit" Version="0.0.38" />
+    <PackageReference Include="YunhuiKit" Version="0.0.42" />
   </ItemGroup>
 
   <ItemGroup>

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff