浏览代码

🌈 style(代理、账号池): 优化展示样式

dodo hold 3 月之前
父节点
当前提交
96dbb45917

+ 23 - 16
molilian.api/Controllers/admin/JdUnionController.cs

@@ -284,21 +284,28 @@ namespace molilian.api.Controllers
                 };
             }
 
-            using var conn = DBContext.GetOpenConnection();
-            var result = new DBContext.Table(conn, "jd_pool")
-                .Where(filter, new { keyword, status, stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<JdPoolDTO>(getTotal);
-
-            foreach (var item in result.List)
-            {
-                item.app_secret = string.Empty;
-                item.cookies = string.Empty;
-                item.riskCookie = RiskControlCore.GetRiskCookie($"jd:{item.id}");
-            }
-            return new APIResult(new { data = result });
-        }
+            using var conn = DBContext.GetOpenConnection();
+            var result = new DBContext.Table(conn, "jd_pool")
+                .Where(filter, new { keyword, status, stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<JdPoolDTO>(getTotal);
+
+            var proxyNodes = (ProxyNodesCore.AllList() ?? Array.Empty<ProxyNodesDTO>())
+                .ToList();
+
+            foreach (var item in result.List)
+            {
+                item.app_secret = string.Empty;
+                item.cookies = string.Empty;
+                item.riskCookie = RiskControlCore.GetRiskCookie($"jd:{item.id}");
+                item.proxyStatus = proxyNodes
+                    .FirstOrDefault(proxyNode =>
+                        ProxyNodesCore.IsMatchNode(proxyNode, item.nodeName))
+                    ?.status;
+            }
+            return new APIResult(new { data = result });
+        }
 
         [HttpPost]
         public async Task<ActionResult> update([FromBody] JsonElement form)
@@ -393,4 +400,4 @@ namespace molilian.api.Controllers
             });
         }
     }
-}
+}

+ 23 - 16
molilian.api/Controllers/admin/PddUnionController.cs

@@ -112,21 +112,28 @@ namespace molilian.api.Controllers
                 };
             }
 
-            using var conn = DBContext.GetOpenConnection();
-            var result = new DBContext.Table(conn, "pdd_pool")
-                .Where(filter, new { keyword, status, stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<PddPoolDTO>(getTotal);
-
-            foreach (var item in result.List)
-            {
-                item.app_secret = string.Empty;
-                item.cookies = string.Empty;
-                item.riskCookie = RiskControlCore.GetRiskCookie($"pdd:{item.id}");
-            }
-            return new APIResult(new { data = result });
-        }
+            using var conn = DBContext.GetOpenConnection();
+            var result = new DBContext.Table(conn, "pdd_pool")
+                .Where(filter, new { keyword, status, stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<PddPoolDTO>(getTotal);
+
+            var proxyNodes = (ProxyNodesCore.AllList() ?? Array.Empty<ProxyNodesDTO>())
+                .ToList();
+
+            foreach (var item in result.List)
+            {
+                item.app_secret = string.Empty;
+                item.cookies = string.Empty;
+                item.riskCookie = RiskControlCore.GetRiskCookie($"pdd:{item.id}");
+                item.proxyStatus = proxyNodes
+                    .FirstOrDefault(proxyNode =>
+                        ProxyNodesCore.IsMatchNode(proxyNode, item.nodeName))
+                    ?.status;
+            }
+            return new APIResult(new { data = result });
+        }
 
         [HttpPost]
         public async Task<ActionResult> update([FromBody] JsonElement form)
@@ -300,4 +307,4 @@ namespace molilian.api.Controllers
         }
 
     }
-}
+}

+ 580 - 104
molilian.api/Controllers/admin/ProxyController.cs

@@ -1,104 +1,580 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using Org.BouncyCastle.Ocsp;
-using System.Text.Json;
-using System.Runtime.InteropServices;
-using TencentCloud.Scf.V20180416.Models;
-using Quartz.Impl.AdoJobStore.Common;
-using TencentCloud.Gaap.V20180529.Models;
-using System.Net;
-using TencentCloud.Ie.V20200304.Models;
-using TencentCloud.Soe.V20180724.Models;
-
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [MyAuthorize("admin")]
-    [Route("api/[controller]/[action]")]
-    public class ProxyController : ControllerBase
-    {
-        readonly IAuthorizationProvider provider = new AdminProvider();
-        protected IHttpContextAccessor _accessor;
-        public ProxyController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-        [HttpGet]
-        public ActionResult get()
-        {
-            var list = new DBContext.Table("proxy_nodes")
-                .Where("status=@status", new { status = 1 })
-                .Select<ProxyNodesDTO>();
-
-            // 参考数据
-            //61	1	6	品阅245	品阅辅助网卡	bj	http	http://172.21.127.245:33128	molilian	Bhquvmkn8zLO36lGjuqk	2024-04-09 14:25:53	2024-04-09 14:25:56
-            //59  1   6   品阅243 品阅辅助网卡  bj http    http://172.21.127.243:33128	molilian	Bhquvmkn8zLO36lGjuqk	2024-04-09 14:25:53	2024-04-09 14:25:56
-
-            foreach (var proxy in list)
-            {
-                //通过 curl myip.ipip.net检测proxy可访问行
-            }
-
-
-            var tasks = new List<Task<int>>
-                {
-                Task.Run(()=>{ })
-             };
-
-            // 等待所有任务完成
-            Task.WhenAll(tasks).Wait();
-
-
-
-            return new APIResult(new { list });
-        }
-
-
-        [HttpGet]
-        public async Task<ActionResult> ChangePublicIpByName(string nodeName)
-        {
-
-            var proxy_node = new DBContext.Table("proxy_nodes").Get<dynamic>("name=@nodeName", new { nodeName });
-            if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" });
-
-            int aliyun_id = proxy_node.aliyun_id;
-            string proxy_server = proxy_node.server;
-
-            var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyun_id);
-            if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" });
-
-
-            var uri = new Uri(proxy_server);
-            string privateIp = uri.Host;
-            AliyunCore core = new AliyunCore(account);
-            var success = await core.ChangePublicIpAsync(privateIp);
-            return new APIResult(new { success = "ok" });
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> ChangePublicIp(int id)
-        {
-            id = id >= 20000 ? id - 20000 : id;
-
-            var proxy_node = new DBContext.Table("proxy_nodes").Get<dynamic>(id);
-            if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" });
-
-            int aliyun_id = proxy_node.aliyun_id;
-            string proxy_server = proxy_node.server;
-
-            var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyun_id);
-            if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" });
-
-
-
-            var uri = new Uri(proxy_server);
-            string privateIp = uri.Host;
-            AliyunCore core = new AliyunCore(account);
-            var success = await core.ChangePublicIpAsync(privateIp);
-            return new APIResult(new { success = "ok" });
-        }
-
-    }
-}
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using molilian.core;
+using System.Net;
+using System.Text.Json;
+
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [MyAuthorize("admin")]
+    [Route("api/[controller]/[action]")]
+    public class ProxyController : ControllerBase
+    {
+        private const string ProxyCheckRequestUrl = "http://cip.cc";
+        private const string ProxyCheckUserAgent = "curl/8.7.1";
+
+        readonly IAuthorizationProvider provider = new AdminProvider();
+        protected IHttpContextAccessor _accessor;
+
+        public ProxyController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+        [HttpGet]
+        public ActionResult get()
+        {
+            return new APIResult(new
+            {
+                data = BuildProxyNodeListResult(1, 20, true, string.Empty, string.Empty)
+            });
+        }
+
+        [HttpPost]
+        public ActionResult list([FromBody] JsonElement form)
+        {
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 20);
+            bool getTotal = form.Read("getTotal", true);
+            string keyword = form.Read("keyword", string.Empty);
+            string status = form.Read("status", string.Empty);
+
+            return new APIResult(new
+            {
+                data = BuildProxyNodeListResult(page, size, getTotal, keyword, status)
+            });
+        }
+
+        private static dynamic BuildProxyNodeListResult(
+            int page,
+            int size,
+            bool getTotal,
+            string keyword,
+            string status
+        )
+        {
+            page = page <= 0 ? 1 : page;
+            size = size <= 0 ? 20 : size;
+
+            string filter = string.Empty;
+            string likeKeyword = string.Empty;
+            IEnumerable<string> matchedNodeNames = Array.Empty<string>();
+            bool? statusValue = null;
+
+            if (!string.IsNullOrWhiteSpace(status))
+            {
+                status = status.Trim().ToLowerInvariant();
+                if (status == "enabled")
+                {
+                    statusValue = true;
+                    filter += " AND status=@statusValue";
+                }
+                else if (status == "disabled")
+                {
+                    statusValue = false;
+                    filter += " AND status=@statusValue";
+                }
+            }
+
+            if (!string.IsNullOrWhiteSpace(keyword))
+            {
+                likeKeyword = $"%{keyword.Trim()}%";
+                matchedNodeNames = FindMatchedNodeNames(likeKeyword).ToArray();
+
+                filter += " AND (name LIKE @likeKeyword OR nodeName LIKE @likeKeyword OR description LIKE @likeKeyword OR server LIKE @likeKeyword OR end_point LIKE @likeKeyword OR type LIKE @likeKeyword";
+                if (matchedNodeNames.Any())
+                {
+                    filter += " OR name IN @matchedNodeNames OR nodeName IN @matchedNodeNames";
+                }
+                filter += ")";
+            }
+
+            filter = filter.StringTrimStart(" AND ");
+
+            var result = new DBContext.Table("proxy_nodes")
+                .Where(filter, new { statusValue, likeKeyword, matchedNodeNames })
+                .Page(size, page)
+                .Order("status DESC, name ASC")
+                .PageList<ProxyNodeListItemDTO>(getTotal);
+
+            List<ProxyNodeAccountSourceDTO> tkAccounts = LoadProxyNodeAccountSources("tk_pool");
+            List<ProxyNodeAccountSourceDTO> jdAccounts = LoadProxyNodeAccountSources("jd_pool");
+            List<ProxyNodeAccountSourceDTO> pddAccounts = LoadProxyNodeAccountSources("pdd_pool");
+
+            foreach (var node in result.List)
+            {
+                var accounts = BuildUsageAccounts(node, "tk", tkAccounts)
+                    .Concat(BuildUsageAccounts(node, "jd", jdAccounts))
+                    .Concat(BuildUsageAccounts(node, "pdd", pddAccounts))
+                    .OrderByDescending(item => item.status)
+                    .ThenBy(item => item.platform)
+                    .ThenBy(item => item.name)
+                    .ToList();
+
+                node.accountCount = accounts.Count;
+                node.onlineAccountCount = accounts.Count(item => item.status);
+                node.offlineAccountCount = accounts.Count(item => !item.status);
+                node.accounts = accounts;
+            }
+
+            return result;
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> update([FromBody] JsonElement form)
+        {
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            var token = provider.Get(_accessor.HttpContext);
+
+            int id = form.Read<int>("id", 0);
+            string name = form.Read<string>("name", string.Empty);
+            string val = form.Read<string>("val", string.Empty);
+
+            if (id == 0)
+            {
+                return new APIResult(new
+                {
+                    data = new { success = false, msg = "更新失败,请检查输入参数" }
+                });
+            }
+
+            int result;
+            switch (name)
+            {
+                case "status":
+                    bool bVal = "true".Equals(val, StringComparison.OrdinalIgnoreCase) || "1".Equals(val);
+                    result = new DBContext.Table("proxy_nodes")
+                        .Add(name, bVal)
+                        .Add("last_time", DateTime.Now)
+                        .Where("id=@id", new { id })
+                        .Update();
+                    break;
+                default:
+                    return new APIResult(new
+                    {
+                        data = new { success = false, msg = "更新失败,未授权操作" }
+                    });
+            }
+
+            bool success = result > 0;
+            if (success)
+            {
+                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"proxy_nodes:{id}:{name}", string.Empty, val);
+                ProxyNodesCore.Refresh();
+                await EndPointCore.NotifyReload(true);
+            }
+
+            return new APIResult(new
+            {
+                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" }
+            });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> check(int id, string target = "internal")
+        {
+            var proxyNode = new DBContext.Table("proxy_nodes").Get<ProxyNodesDTO>("id=@id", new { id });
+            if (proxyNode == null)
+            {
+                return new APIResult(new
+                {
+                    data = new { success = false, msg = "没有匹配的 proxy_nodes 记录" }
+                });
+            }
+
+            target = string.IsNullOrWhiteSpace(target) ? "internal" : target.Trim().ToLowerInvariant();
+            if (target is not ("internal" or "external"))
+            {
+                return new APIResult(new
+                {
+                    data = new { success = false, msg = "target 仅支持 internal 或 external" }
+                });
+            }
+
+            string externalProxyAddress = BuildExternalProxyAddress(proxyNode.id);
+
+            var data = new ProxyNodeCheckResponseDTO
+            {
+                success = false,
+                msg = "未执行检测",
+            };
+
+            if (target == "internal")
+            {
+                var internalResult = await CheckProxyAsync(
+                    proxyNode.server,
+                    proxyNode.username,
+                    proxyNode.password
+                );
+                data.success = internalResult.success;
+                data.msg = internalResult.success ? "内网代理检测成功" : "内网代理检测失败";
+                data.check = new ProxyCheckChannelDTO
+                {
+                    address = proxyNode.server,
+                    success = internalResult.success,
+                    result = internalResult.result,
+                };
+            }
+            else
+            {
+                var externalResult = await CheckProxyAsync(
+                    BuildProxyUri(externalProxyAddress, proxyNode.type),
+                    proxyNode.username,
+                    proxyNode.password
+                );
+                data.success = externalResult.success;
+                data.msg = externalResult.success ? "外网代理检测成功" : "外网代理检测失败";
+                data.check = new ProxyCheckChannelDTO
+                {
+                    address = externalProxyAddress,
+                    success = externalResult.success,
+                    result = externalResult.result,
+                };
+            }
+
+            return new APIResult(new
+            {
+                data
+            });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> ChangePublicIpByName(string nodeName)
+        {
+            var proxyNode = new DBContext.Table("proxy_nodes")
+                .Get<dynamic>("(nodeName=@nodeName OR name=@nodeName)", new { nodeName });
+            if (proxyNode == null)
+            {
+                return new APIResult(new
+                {
+                    data = new { success = false, msg = "没有匹配的 proxy_nodes 记录" }
+                });
+            }
+
+            int aliyunId = proxyNode.aliyun_id;
+            string proxyServer = proxyNode.server;
+
+            var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyunId);
+            if (account == null)
+            {
+                return new APIResult(new
+                {
+                    data = new { success = false, msg = "没有匹配的 aliyun_pool 记录" }
+                });
+            }
+
+            var uri = new Uri(proxyServer);
+            string privateIp = uri.Host;
+            AliyunCore core = new(account);
+            var (success, oldIp, newIp) = await core.ChangePublicIpAsync(privateIp);
+
+            return new APIResult(new
+            {
+                data = new
+                {
+                    success,
+                    msg = success ? $"更换公网 IP 成功:{oldIp} -> {newIp}" : "更换公网 IP 失败"
+                }
+            });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> ChangePublicIp(int id)
+        {
+            id = id >= 20000 ? id - 20000 : id;
+
+            var proxyNode = new DBContext.Table("proxy_nodes").Get<dynamic>(id);
+            if (proxyNode == null)
+            {
+                return new APIResult(new
+                {
+                    data = new { success = false, msg = "没有匹配的 proxy_nodes 记录" }
+                });
+            }
+
+            int aliyunId = proxyNode.aliyun_id;
+            string proxyServer = proxyNode.server;
+
+            var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyunId);
+            if (account == null)
+            {
+                return new APIResult(new
+                {
+                    data = new { success = false, msg = "没有匹配的 aliyun_pool 记录" }
+                });
+            }
+
+            var uri = new Uri(proxyServer);
+            string privateIp = uri.Host;
+            AliyunCore core = new(account);
+            var (success, oldIp, newIp) = await core.ChangePublicIpAsync(privateIp);
+
+            return new APIResult(new
+            {
+                data = new
+                {
+                    success,
+                    msg = success ? $"更换公网 IP 成功:{oldIp} -> {newIp}" : "更换公网 IP 失败"
+                }
+            });
+        }
+
+        private static IEnumerable<ProxyNodeUsageAccountDTO> BuildUsageAccounts(
+            ProxyNodeListItemDTO node,
+            string platform,
+            IEnumerable<ProxyNodeAccountSourceDTO> accounts
+        )
+        {
+            var nodeNames = GetProxyNodeMatchNames(node).ToArray();
+            if (!nodeNames.Any())
+            {
+                return Enumerable.Empty<ProxyNodeUsageAccountDTO>();
+            }
+
+            return accounts
+                .Where(account => MatchNodeName(account.nodeName, nodeNames))
+                .Select(account => new ProxyNodeUsageAccountDTO
+                {
+                    id = account.id,
+                    platform = platform,
+                    name = account.name,
+                    company = account.company,
+                    nodeName = account.nodeName,
+                    status = account.status,
+                });
+        }
+
+        private static bool MatchNodeName(
+            string accountNodes,
+            IEnumerable<string> nodeNames
+        )
+        {
+            var nodeNameSet = nodeNames
+                .Where(item => !string.IsNullOrWhiteSpace(item))
+                .Select(item => item.Trim())
+                .ToHashSet(StringComparer.OrdinalIgnoreCase);
+
+            if (nodeNameSet.Count == 0)
+            {
+                return false;
+            }
+
+            return SplitNodeNames(accountNodes)
+                .Any(item => nodeNameSet.Contains(item));
+        }
+
+        private static IEnumerable<string> GetProxyNodeMatchNames(ProxyNodeListItemDTO node)
+        {
+            return new[] { node.nodeName, node.name }
+                .Where(item => !string.IsNullOrWhiteSpace(item))
+                .Select(item => item.Trim())
+                .Distinct(StringComparer.OrdinalIgnoreCase);
+        }
+
+        private static IEnumerable<string> FindMatchedNodeNames(string keyword)
+        {
+            if (string.IsNullOrWhiteSpace(keyword))
+            {
+                return Array.Empty<string>();
+            }
+
+            const string accountFilter = "name LIKE @keyword OR company LIKE @keyword OR nodename LIKE @keyword";
+
+            List<ProxyNodeNameSourceDTO> allNodes = new();
+            allNodes.AddRange(LoadProxyNodeNameSources("tk_pool", accountFilter, keyword));
+            allNodes.AddRange(LoadProxyNodeNameSources("jd_pool", accountFilter, keyword));
+            allNodes.AddRange(LoadProxyNodeNameSources("pdd_pool", accountFilter, keyword));
+
+            return allNodes
+                .SelectMany(item => SplitNodeNames(item.nodeName))
+                .Distinct(StringComparer.OrdinalIgnoreCase)
+                .ToList();
+        }
+
+        private static List<ProxyNodeAccountSourceDTO> LoadProxyNodeAccountSources(string tableName)
+        {
+            return new DBContext.Table(tableName)
+                .Fields("id, name, company, status, nodename AS nodeName")
+                .Select<ProxyNodeAccountSourceDTO>()?
+                .ToList() ?? new List<ProxyNodeAccountSourceDTO>();
+        }
+
+        private static List<ProxyNodeNameSourceDTO> LoadProxyNodeNameSources(
+            string tableName,
+            string filter,
+            string keyword
+        )
+        {
+            return new DBContext.Table(tableName)
+                .Fields("nodename AS nodeName")
+                .Where(filter, new { keyword })
+                .Select<ProxyNodeNameSourceDTO>()?
+                .ToList() ?? new List<ProxyNodeNameSourceDTO>();
+        }
+
+        private static IEnumerable<string> SplitNodeNames(string accountNodes)
+        {
+            if (string.IsNullOrWhiteSpace(accountNodes))
+            {
+                return Array.Empty<string>();
+            }
+
+            return accountNodes
+                .Replace(',', ',')
+                .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+        }
+
+        private static string BuildExternalProxyAddress(int id)
+        {
+            return $"bjapi.molilian.com:20{id:D3}";
+        }
+
+        private static string BuildProxyUri(string address, string type)
+        {
+            if (string.IsNullOrWhiteSpace(address))
+            {
+                return string.Empty;
+            }
+
+            address = address.Trim();
+            if (address.Contains("://", StringComparison.Ordinal) &&
+                Uri.TryCreate(address, UriKind.Absolute, out var absoluteUri))
+            {
+                return absoluteUri.AbsoluteUri;
+            }
+
+            string scheme = string.IsNullOrWhiteSpace(type) ? "http" : type.Trim().ToLowerInvariant();
+            if (scheme is not ("http" or "https" or "socks4" or "socks4a" or "socks5"))
+            {
+                scheme = "http";
+            }
+            return $"{scheme}://{address}";
+        }
+
+        private static async Task<(bool success, string result)> CheckProxyAsync(
+            string proxyAddress,
+            string username,
+            string password
+        )
+        {
+            try
+            {
+                string proxyUri = BuildProxyUri(proxyAddress, "http");
+                WebClientUtility client = new()
+                {
+                    Timeout = TimeSpan.FromSeconds(3),
+                    UserAgent = ProxyCheckUserAgent,
+                };
+
+                if (string.IsNullOrWhiteSpace(proxyUri))
+                {
+                    return (false, "代理地址为空");
+                }
+
+                client.Proxy = new WebProxy
+                {
+                    Address = new Uri(proxyUri),
+                    Credentials = string.IsNullOrWhiteSpace(username) && string.IsNullOrWhiteSpace(password)
+                        ? null
+                        : new NetworkCredential(username, password)
+                };
+
+                var response = await client.RequestAsync(ProxyCheckRequestUrl);
+                string body = response.Body()?.Trim() ?? string.Empty;
+
+                if (response.Successed)
+                {
+                    return (true, string.IsNullOrWhiteSpace(body) ? "代理可用,但目标站点无返回内容" : body);
+                }
+
+                if (response.ResponseException != null)
+                {
+                    return (false, response.ResponseException.GetBaseException().Message);
+                }
+
+                if (response.ResponseMessage != null)
+                {
+                    string message = $"HTTP {(int)response.ResponseMessage.StatusCode}";
+                    if (!string.IsNullOrWhiteSpace(body))
+                    {
+                        message += $" - {body}";
+                    }
+                    return (false, message);
+                }
+
+                if (!string.IsNullOrWhiteSpace(body))
+                {
+                    return (false, body);
+                }
+
+                return (false, "请求失败,但未返回异常或响应内容");
+            }
+            catch (TaskCanceledException)
+            {
+                return (false, "请求超时(3s)");
+            }
+            catch (Exception ex)
+            {
+                return (false, ex.GetBaseException().Message);
+            }
+        }
+    }
+
+    public class ProxyNodeAccountSourceDTO
+    {
+        public int id { get; set; }
+        public string name { get; set; } = string.Empty;
+        public string company { get; set; } = string.Empty;
+        public bool status { get; set; }
+        public string nodeName { get; set; } = string.Empty;
+    }
+
+    public class ProxyNodeNameSourceDTO
+    {
+        public string nodeName { get; set; } = string.Empty;
+    }
+
+    public class ProxyNodeUsageAccountDTO
+    {
+        public int id { get; set; }
+        public string platform { get; set; } = string.Empty;
+        public string name { get; set; } = string.Empty;
+        public string company { get; set; } = string.Empty;
+        public string nodeName { get; set; } = string.Empty;
+        public bool status { get; set; }
+    }
+
+    public class ProxyNodeListItemDTO
+    {
+        public int id { get; set; }
+        public bool status { get; set; }
+        public string name { get; set; } = string.Empty;
+        public string nodeName { get; set; } = string.Empty;
+        public string description { get; set; } = string.Empty;
+        public string end_point { get; set; } = string.Empty;
+        public string type { get; set; } = string.Empty;
+        public string server { get; set; } = string.Empty;
+        public string username { get; set; } = string.Empty;
+        public DateTime create_time { get; set; }
+        public DateTime last_time { get; set; }
+        public int accountCount { get; set; }
+        public int onlineAccountCount { get; set; }
+        public int offlineAccountCount { get; set; }
+        public List<ProxyNodeUsageAccountDTO> accounts { get; set; } = new();
+    }
+
+    public class ProxyCheckChannelDTO
+    {
+        public string address { get; set; } = string.Empty;
+        public bool success { get; set; }
+        public string result { get; set; } = string.Empty;
+    }
+
+    public class ProxyNodeCheckResponseDTO
+    {
+        public bool success { get; set; }
+        public string msg { get; set; } = string.Empty;
+        public ProxyCheckChannelDTO? check { get; set; }
+    }
+}

+ 24 - 17
molilian.api/Controllers/admin/TaobaoController.cs

@@ -190,22 +190,29 @@ namespace molilian.api.Controllers
                 };
             }
 
-            using var conn = DBContext.GetOpenConnection();
-            var result = new DBContext.Table(conn, "tk_pool")
-                .Where(filter, new { keyword, status, stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<TkPoolDTO>(getTotal);
-
-            foreach (var item in result.List)
-            {
-                item.current_amt = Math.Round(RiskControlCore.GetIncomeAmt(TkChannelEnum.tb, $"{item.id}"), 2);
-                item.cookies = string.Empty;
-                //await RiskControlCore.SetRiskCookie($"tb:{item.id}", "TEST");
-                item.riskCookie = RiskControlCore.GetRiskCookie($"tb:{item.id}");
-            }
-            return new APIResult(new { data = result });
-        }
+            using var conn = DBContext.GetOpenConnection();
+            var result = new DBContext.Table(conn, "tk_pool")
+                .Where(filter, new { keyword, status, stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<TkPoolDTO>(getTotal);
+
+            var proxyNodes = (ProxyNodesCore.AllList() ?? Array.Empty<ProxyNodesDTO>())
+                .ToList();
+
+            foreach (var item in result.List)
+            {
+                item.current_amt = Math.Round(RiskControlCore.GetIncomeAmt(TkChannelEnum.tb, $"{item.id}"), 2);
+                item.cookies = string.Empty;
+                //await RiskControlCore.SetRiskCookie($"tb:{item.id}", "TEST");
+                item.riskCookie = RiskControlCore.GetRiskCookie($"tb:{item.id}");
+                item.proxyStatus = proxyNodes
+                    .FirstOrDefault(proxyNode =>
+                        ProxyNodesCore.IsMatchNode(proxyNode, item.nodeName))
+                    ?.status;
+            }
+            return new APIResult(new { data = result });
+        }
 
 
         [HttpPost]
@@ -991,4 +998,4 @@ namespace molilian.api.Controllers
         }
 
     }
-}
+}

文件差异内容过多而无法显示
+ 0 - 0
molilian.api/Properties/PublishProfiles/latest.pubxml.user


+ 142 - 100
molilian.core/Core/ProxyNodesCore.cs

@@ -18,54 +18,44 @@ namespace molilian.core
         private static readonly object _lockObj = new();
         private static IEnumerable<ProxyNodesDTO> _cached;
         private static IEnumerable<ProxyNodesDTO> _all_cached;
-        public static string GetSelectOne(string node, bool all_node = false)
-        {
-            var list = all_node ? AllList() : List();
-            if (!list.Any()) return null;
-            if (!node.Contains(",")) return node;
-
-            // 处理包含逗号的节点参数
-            string selectedNode = node;
-            var nodes = node.Split(',', StringSplitOptions.RemoveEmptyEntries)
-                           .Select(n => n.Trim())
-                           .Where(n => !string.IsNullOrEmpty(n))
-                           .ToArray();
-
-            if (nodes.Length > 0)
-            {
-                // 随机选择一个节点
-                var random = new Random();
-                selectedNode = nodes[random.Next(nodes.Length)];
-            }
-            return selectedNode;
-        }
+        public static string GetSelectOne(string node, bool all_node = false)
+        {
+            var list = all_node ? AllList() : List();
+            if (!list.Any()) return null;
+
+            var nodes = SplitNodeNames(node).ToArray();
+            if (nodes.Length == 0) return node?.Trim();
+            if (nodes.Length == 1) return nodes[0];
+
+            // 处理包含逗号的节点参数
+            string selectedNode = node;
+            var random = new Random();
+            selectedNode = nodes[random.Next(nodes.Length)];
+            return selectedNode;
+        }
 
 
         public static WebProxy? GetOne(string node, bool all_node = false)
         {
-            var list = all_node ? AllList() : List();
-            if (!list.Any()) return null;
-
-            // 处理包含逗号的节点参数
-            string selectedNode = node;
-            if (node.Contains(","))
-            {
-                var nodes = node.Split(',', StringSplitOptions.RemoveEmptyEntries)
-                               .Select(n => n.Trim())
-                               .Where(n => !string.IsNullOrEmpty(n))
-                               .ToArray();
-
-                if (nodes.Length > 0)
-                {
-                    // 随机选择一个节点
-                    var random = new Random();
-                    selectedNode = nodes[random.Next(nodes.Length)];
-                }
-            }
-
-            var ProxyNodesDTO = list.Where(e => SelectEndPointUseProxy(e, selectedNode))
-                                   .OrderBy(l => Guid.NewGuid())
-                                   .FirstOrDefault();
+            var list = all_node ? AllList() : List();
+            if (!list.Any()) return null;
+
+            // 处理包含逗号的节点参数
+            string selectedNode = node;
+            var nodes = SplitNodeNames(node).ToArray();
+            if (nodes.Length > 1)
+            {
+                var random = new Random();
+                selectedNode = nodes[random.Next(nodes.Length)];
+            }
+            else if (nodes.Length == 1)
+            {
+                selectedNode = nodes[0];
+            }
+
+            var ProxyNodesDTO = list.Where(e => SelectEndPointUseProxy(e, selectedNode))
+                                   .OrderBy(l => Guid.NewGuid())
+                                   .FirstOrDefault();
             if (ProxyNodesDTO == null) return null;
 
 
@@ -96,22 +86,22 @@ namespace molilian.core
         }
 
 
-        private static bool SelectEndPointUseProxy(ProxyNodesDTO item, string node = "")
-        {
-            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
-            {
-                if (item.end_point != _end_point) return false;
-            }
-            return string.IsNullOrEmpty(node) || item.name == node;
-        }
-        private static bool SelectEndPointUseProxy(ProxyNodesDTO item, int nodeid)
-        {
-            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
-            {
-                if (item.end_point != _end_point) return false;
-            }
-            return nodeid > 0 || item.id == nodeid;
-        }
+        private static bool SelectEndPointUseProxy(ProxyNodesDTO item, string node = "")
+        {
+            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
+            {
+                if (item.end_point != _end_point) return false;
+            }
+            return string.IsNullOrEmpty(node) || IsMatchNode(item, node);
+        }
+        private static bool SelectEndPointUseProxy(ProxyNodesDTO item, int nodeid)
+        {
+            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
+            {
+                if (item.end_point != _end_point) return false;
+            }
+            return nodeid <= 0 || item.id == nodeid;
+        }
 
         public static WebProxy? RandomOne(string excludeNode = "")
         {
@@ -130,26 +120,63 @@ namespace molilian.core
                 Credentials = new NetworkCredential(ProxyNodesDTO.username, ProxyNodesDTO.password)
             };
         }
-        private static bool SelectEndPointExcludeNodeUseProxy(ProxyNodesDTO item, string excludeNode = "")
-        {
-            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
-            {
-                if (item.end_point != _end_point) return false;
-            }
-            return string.IsNullOrEmpty(excludeNode) || item.name != excludeNode;
-        }
-
-        public static IEnumerable<ProxyNodesDTO> List(bool force = false)
-        {
-            if (!force && _cached != null) return _cached;
-
-            string cache_key = $"cache:proxy_nodes";
-            var list = RedisHelper.Get<IEnumerable<ProxyNodesDTO>>(cache_key);
-            if (force || list == null)
-            {
-                lock (_lockObj)
-                {
-                    list = new DBContext.Table("proxy_nodes")
+        private static bool SelectEndPointExcludeNodeUseProxy(ProxyNodesDTO item, string excludeNode = "")
+        {
+            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
+            {
+                if (item.end_point != _end_point) return false;
+            }
+            return string.IsNullOrEmpty(excludeNode) || !IsMatchNode(item, excludeNode);
+        }
+
+        public static IEnumerable<string> SplitNodeNames(string nodeNames)
+        {
+            if (string.IsNullOrWhiteSpace(nodeNames))
+            {
+                return Array.Empty<string>();
+            }
+
+            return nodeNames
+                .Replace(',', ',')
+                .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
+                .Where(item => !string.IsNullOrWhiteSpace(item));
+        }
+
+        public static bool IsMatchNode(ProxyNodesDTO item, string nodeName)
+        {
+            if (string.IsNullOrWhiteSpace(nodeName))
+            {
+                return false;
+            }
+
+            var configuredNodes = SplitNodeNames(nodeName)
+                .ToHashSet(StringComparer.OrdinalIgnoreCase);
+            if (configuredNodes.Count == 0)
+            {
+                return false;
+            }
+
+            return new[] { item.nodeName, item.name }
+                .Where(value => !string.IsNullOrWhiteSpace(value))
+                .Select(value => value.Trim())
+                .Any(value => configuredNodes.Contains(value));
+        }
+
+        public static IEnumerable<ProxyNodesDTO> List(bool force = false)
+        {
+            if (!force && _cached != null) return _cached;
+
+            string cache_key = $"cache:proxy_nodes";
+            var list = RedisHelper.Get<IEnumerable<ProxyNodesDTO>>(cache_key);
+            if (!force && NeedReloadNodeNameCache(list))
+            {
+                list = null;
+            }
+            if (force || list == null)
+            {
+                lock (_lockObj)
+                {
+                    list = new DBContext.Table("proxy_nodes")
                         .Where("status=@status", new { status = 1 })
                         .Select<ProxyNodesDTO>();
                     if (list == null) return default;
@@ -160,17 +187,21 @@ namespace molilian.core
             return list;
         }
 
-        public static IEnumerable<ProxyNodesDTO> AllList(bool force = false)
-        {
-            if (!force && _all_cached != null) return _all_cached;
-
-            string cache_key = $"cache:all_proxy_nodes";
-            var list = RedisHelper.Get<IEnumerable<ProxyNodesDTO>>(cache_key);
-            if (force || list == null)
-            {
-                lock (_lockObj)
-                {
-                    list = new DBContext.Table("proxy_nodes")
+        public static IEnumerable<ProxyNodesDTO> AllList(bool force = false)
+        {
+            if (!force && _all_cached != null) return _all_cached;
+
+            string cache_key = $"cache:all_proxy_nodes";
+            var list = RedisHelper.Get<IEnumerable<ProxyNodesDTO>>(cache_key);
+            if (!force && NeedReloadNodeNameCache(list))
+            {
+                list = null;
+            }
+            if (force || list == null)
+            {
+                lock (_lockObj)
+                {
+                    list = new DBContext.Table("proxy_nodes")
                         .Where("", new { status = 1 })
                         .Select<ProxyNodesDTO>();
                     if (list == null) return default;
@@ -180,12 +211,23 @@ namespace molilian.core
             _all_cached = list;
             return list;
         }
-        public static void Refresh()
-        {
-            _ = List(true);
-            _ = AllList(true);
-        }
-
-    }
-
-}
+        public static void Refresh()
+        {
+            _ = List(true);
+            _ = AllList(true);
+        }
+
+        private static bool NeedReloadNodeNameCache(IEnumerable<ProxyNodesDTO> list)
+        {
+            if (list == null)
+            {
+                return false;
+            }
+
+            var items = list.ToArray();
+            return items.Length > 0 && items.All(item => string.IsNullOrWhiteSpace(item.nodeName));
+        }
+
+    }
+
+}

+ 8 - 7
molilian.core/DTO/ProxyNodesDTO.cs

@@ -3,13 +3,14 @@ using TencentCloud.Ame.V20190916.Models;
 
 namespace molilian.core
 {
-    public class ProxyNodesDTO
-    {
-        public int id { get; set; }
-        public bool status { get; set; }
-        public string name { get; set; } = string.Empty;
-        public string description { get; set; } = string.Empty;
-        public string end_point { get; set; } = string.Empty;
+    public class ProxyNodesDTO
+    {
+        public int id { get; set; }
+        public bool status { get; set; }
+        public string name { get; set; } = string.Empty;
+        public string nodeName { get; set; } = string.Empty;
+        public string description { get; set; } = string.Empty;
+        public string end_point { get; set; } = string.Empty;
 
         public string type { get; set; } = string.Empty;
         public string server { get; set; } = string.Empty;

+ 4 - 3
molilian.core/DTO/alimama/TkPoolDTO.cs

@@ -25,8 +25,9 @@
         public DateTime login_time { get; set; } = DateTime.Now;
         public bool status { get; set; }
         public string suspended_endpoint { get; set; } = string.Empty;
-        public string nodeName { get; set; } = string.Empty;
-        public decimal current_amt { get; set; } = 0;
+        public string nodeName { get; set; } = string.Empty;
+        public bool? proxyStatus { get; set; }
+        public decimal current_amt { get; set; } = 0;
         public decimal daily_income_limit { get; set; } = 0;
 
 
@@ -75,4 +76,4 @@
         public string riskCookie { get; set; } = string.Empty;
 
     }
-}
+}

+ 3 - 2
molilian.core/DTO/jd/JdPoolDTO.cs

@@ -35,8 +35,9 @@ namespace molilian.core
         public string h5st { get; set; } = string.Empty;
         public string envStr { get; set; } = string.Empty;
 
-        public string nodeName { get; set; } = string.Empty;
-        public string end_point { get; set; } = string.Empty;
+        public string nodeName { get; set; } = string.Empty;
+        public bool? proxyStatus { get; set; }
+        public string end_point { get; set; } = string.Empty;
         public decimal current_amt { get; set; } = 0;
         public int current_daily_calls { get; set; } = 0;
         public int current_hourly_calls { get; set; } = 0;

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

@@ -15,8 +15,9 @@ namespace molilian.core
         public int id { get; set; }
         public int duoId { get; set; }
         public string name { get; set; } = string.Empty;
-        public string nodeName { get; set; } = string.Empty;
-        public string end_point { get; set; } = string.Empty;
+        public string nodeName { get; set; } = string.Empty;
+        public bool? proxyStatus { get; set; }
+        public string end_point { get; set; } = string.Empty;
         public string app_key { get; set; } = string.Empty;
         public string app_secret { get; set; } = string.Empty;
         public string pid { get; set; } = string.Empty;

部分文件因为文件数量过多而无法显示