| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580 |
- 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; }
- }
- }
|