dodo hold 5 meses atrás
pai
commit
abf890fa65

+ 27 - 9
molilian.api/Controllers/admin/ConfigController.cs

@@ -33,9 +33,9 @@ namespace molilian.api.Controllers
         }
 
 
-        [HttpPost]
-        public async Task<ActionResult> Update([FromBody] TkConfigDTO form)
-        {
+        [HttpPost]
+        public async Task<ActionResult> Update([FromBody] TkConfigDTO form)
+        {
             var clientIp = _accessor.HttpContext.GetUserIp();
             var token = provider.Get(_accessor.HttpContext);
 
@@ -70,9 +70,27 @@ namespace molilian.api.Controllers
 
             string desc = ObjectComparer.PrintCompareToString(old, form).Trim();
             OperationLogCore.LogOperation(token.AccessKey, clientIp, "config", old.Convert2Json(), desc);
-            EndPointCore.NotifyReload();
-            return new APIResult(new { msg = "更新成功", success = result > 0 });
-        }
-
-    }
-}
+            EndPointCore.NotifyReload();
+            return new APIResult(new { msg = "更新成功", success = result > 0 });
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> xhsZlongForwardReport([FromBody] JsonElement form)
+        {
+            _ = provider.Get(_accessor.HttpContext);
+
+            int current = form.Read("current", 1);
+            int pageSize = form.Read("pageSize", 20);
+            string bucketType = form.Read("bucket_type", "day");
+            string start = form.Read("start", string.Empty);
+            string end = form.Read("end", string.Empty);
+
+            DateTime? startTime = DateTime.TryParse(start, out var parsedStart) ? parsedStart : null;
+            DateTime? endTime = DateTime.TryParse(end, out var parsedEnd) ? parsedEnd : null;
+
+            var data = await XhsZlongForwardCore.QueryReportAsync(bucketType, startTime, endTime, current, pageSize);
+            return new APIResult(new { data });
+        }
+
+    }
+}

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


+ 18 - 12
molilian.core/Core/taoke/UnionParseCore/UnionParseCore.cs

@@ -11,14 +11,14 @@ using TencentCloud.Cwp.V20180228.Models;
 using YunhuiKit;
 
 
-namespace molilian.core
-{
-    public partial class UnionParseCore
-    {
-        /// <summary>
-        /// 联盟转链统一入口
-        /// </summary>
-        public static async Task<ActionResult> UnionParseAsync(UnionParseRequest request)
+namespace molilian.core
+{
+    public partial class UnionParseCore
+    {
+        /// <summary>
+        /// 联盟转链统一入口
+        /// </summary>
+        public static async Task<ActionResult> UnionParseAsync(UnionParseRequest request)
         {
             if (request.SpecialText == 1)
             {
@@ -48,6 +48,11 @@ namespace molilian.core
                     };
             }
 
+            var config = TkConfigCore.Get();
+            if (config.webook_xhs_zlong == 1 && !string.IsNullOrEmpty(request.Oaid))
+            {
+                _ = XhsZlongForwardCore.ForwardAsync(request.Oaid);
+            }
 
             return request.Channel switch
             {
@@ -58,10 +63,11 @@ namespace molilian.core
                 "dy" => await DyParseAsync(request),
                 "pdd" => await PddParseAsync(request),
                 "tb" => await TaobaoParseAsync(request),
-                _ => await DeeplinkParseCore.ParseAsync(request),
-            };
-        }
-        //return await DeeplinkParseCore.ParseAsync(content, channel, ip, oaid);
+                _ => await DeeplinkParseCore.ParseAsync(request),
+            };
+        }
+
+        //return await DeeplinkParseCore.ParseAsync(content, channel, ip, oaid);
 
         public static async Task<APIResult> WemeetParseAsync(string content, string ip, string oaid)
         {

+ 315 - 0
molilian.core/Core/taoke/UnionParseCore/XhsZlongForwardCore.cs

@@ -0,0 +1,315 @@
+using dodohold.core;
+using System.Net;
+using System.Globalization;
+using YunhuiKit;
+
+namespace molilian.core
+{
+    public static class XhsZlongForwardCore
+    {
+        public const string RedisPrefix = ":union_parse:xhs_zlong_forward";
+        public const string ExceptionRedisPrefix = $"{RedisPrefix}:exception";
+        private const string ExceptionAggregateType = "all";
+        private const string ForwardUrl = "https://tracker.z-long.cn/ad/tracker/special/liutiyun/xhs";
+        private static readonly HttpClient HttpClient = new()
+        {
+            Timeout = TimeSpan.FromSeconds(10)
+        };
+
+        public static async Task ForwardAsync(string oaid)
+        {
+            string requestUrl = string.Empty;
+            try
+            {
+                long unixTimeMilliseconds = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+                requestUrl = $"{ForwardUrl}?oaid={WebUtility.UrlEncode(oaid ?? string.Empty)}&datetime={unixTimeMilliseconds}";
+            }
+            catch (Exception ex)
+            {
+                _ = new LoggerLibrary("xhs_zlong_forward")
+                    .Info(requestUrl)
+                    .Info(ex.Message, ex.StackTrace)
+                    .SaveAsync();
+                return;
+            }
+
+            try
+            {
+                SaveCount();
+            }
+            catch (Exception ex)
+            {
+                _ = new LoggerLibrary("xhs_zlong_forward")
+                    .Info(requestUrl)
+                    .Info(ex.Message, ex.StackTrace)
+                    .SaveAsync();
+            }
+
+            try
+            {
+                using var response = await HttpClient.GetAsync(requestUrl, HttpCompletionOption.ResponseHeadersRead);
+                if (response.IsSuccessStatusCode) return;
+
+                SaveExceptionCount($"status_{(int)response.StatusCode}");
+            }
+            catch (TaskCanceledException)
+            {
+                SaveExceptionCount("timeout");
+            }
+            catch (Exception)
+            {
+                SaveExceptionCount("request_error");
+            }
+        }
+
+        private static void SaveCount()
+        {
+            DateTime now = DateTime.Now;
+            var metrics = GetBucketMetrics(now);
+
+            foreach (var metric in metrics)
+            {
+                string key = GetCountKey(metric.bucketType, metric.bucketValue);
+                SaveMetricCount(key, GetIndexKey(metric.bucketType), metric.expireSeconds);
+            }
+        }
+
+        public static async Task<XhsZlongForwardReportResult> QueryReportAsync(string bucketType, DateTime? start, DateTime? end, int page, int size)
+        {
+            string normalizedBucketType = NormalizeBucketType(bucketType);
+            page = Math.Max(page, 1);
+            size = size <= 0 ? 20 : size;
+
+            var list = new List<XhsZlongForwardReportItem>();
+            string[] keys = await TkLogCore.GetTotalKeysAsync(GetIndexKey(normalizedBucketType));
+
+            DateTime? startTime = start?.Date;
+            DateTime? endTime = end?.Date.AddDays(1).AddTicks(-1);
+
+            foreach (var key in keys)
+            {
+                if (!TryParseKey(key, out var item)) continue;
+                if (!string.Equals(item.bucket_type, normalizedBucketType, StringComparison.OrdinalIgnoreCase)) continue;
+                if (startTime.HasValue && item.report_time < startTime.Value) continue;
+                if (endTime.HasValue && item.report_time > endTime.Value) continue;
+
+                list.Add(item);
+            }
+
+            var totalTasks = list.Select(async item =>
+            {
+                var totalTask = TkLogCore.GetTotalAsync(item.redis_key);
+                var exceptionTask = TkLogCore.GetTotalAsync(GetExceptionCountKey(ExceptionAggregateType, item.bucket_type, item.bucket_value));
+                await Task.WhenAll(totalTask, exceptionTask);
+
+                item.total = totalTask.Result;
+                item.exception_count = exceptionTask.Result;
+                return item;
+            });
+
+            list = (await Task.WhenAll(totalTasks))
+                .Where(x => x.total > 0)
+                .ToList();
+
+            list = list
+                .OrderByDescending(x => x.report_time)
+                .ThenByDescending(x => x.bucket_value)
+                .ToList();
+
+            int count = list.Count;
+            int skip = (page - 1) * size;
+            int pageCount = count == 0 ? 0 : (int)Math.Ceiling(count / (double)size);
+
+            return new XhsZlongForwardReportResult
+            {
+                list = list.Skip(skip).Take(size).ToList(),
+                page = page,
+                size = size,
+                count = count,
+                pageCount = pageCount,
+                summary = await GetSummaryAsync(),
+            };
+        }
+
+        public static async Task<XhsZlongForwardReportSummary> GetSummaryAsync()
+        {
+            DateTime now = DateTime.Now;
+            var hourTask = TkLogCore.GetTotalAsync(GetCountKey("hour", now.ToString("yyyyMMddHH")));
+            var dayTask = TkLogCore.GetTotalAsync(GetCountKey("day", now.ToString("yyyyMMdd")));
+            var monthTask = TkLogCore.GetTotalAsync(GetCountKey("month", now.ToString("yyyyMM")));
+            var yearTask = TkLogCore.GetTotalAsync(GetCountKey("year", now.ToString("yyyy")));
+
+            var hourExceptionTask = TkLogCore.GetTotalAsync(GetExceptionCountKey(ExceptionAggregateType, "hour", now.ToString("yyyyMMddHH")));
+            var dayExceptionTask = TkLogCore.GetTotalAsync(GetExceptionCountKey(ExceptionAggregateType, "day", now.ToString("yyyyMMdd")));
+            var monthExceptionTask = TkLogCore.GetTotalAsync(GetExceptionCountKey(ExceptionAggregateType, "month", now.ToString("yyyyMM")));
+            var yearExceptionTask = TkLogCore.GetTotalAsync(GetExceptionCountKey(ExceptionAggregateType, "year", now.ToString("yyyy")));
+
+            await Task.WhenAll(hourTask, dayTask, monthTask, yearTask, hourExceptionTask, dayExceptionTask, monthExceptionTask, yearExceptionTask);
+
+            return new XhsZlongForwardReportSummary
+            {
+                hour = hourTask.Result,
+                day = dayTask.Result,
+                month = monthTask.Result,
+                year = yearTask.Result,
+                hour_exception = hourExceptionTask.Result,
+                day_exception = dayExceptionTask.Result,
+                month_exception = monthExceptionTask.Result,
+                year_exception = yearExceptionTask.Result,
+            };
+        }
+
+        private static string NormalizeBucketType(string bucketType)
+        {
+            return bucketType?.Trim().ToLowerInvariant() switch
+            {
+                "hour" => "hour",
+                "month" => "month",
+                "year" => "year",
+                _ => "day",
+            };
+        }
+
+        private static string GetIndexKey(string bucketType)
+        {
+            return $"{RedisPrefix}:index:{bucketType}";
+        }
+
+        private static string GetCountKey(string bucketType, string bucketValue)
+        {
+            return $"{RedisPrefix}:{bucketType}:{bucketValue}";
+        }
+
+        private static string GetExceptionCountKey(string exceptionType, string bucketType, string bucketValue)
+        {
+            return $"{ExceptionRedisPrefix}:{exceptionType}:{bucketType}:{bucketValue}";
+        }
+
+        private static string GetExceptionIndexKey(string exceptionType, string bucketType)
+        {
+            return $"{ExceptionRedisPrefix}:index:{exceptionType}:{bucketType}";
+        }
+
+        private static (string bucketType, string bucketValue, int expireSeconds)[] GetBucketMetrics(DateTime now)
+        {
+            return
+            [
+                ("hour", now.ToString("yyyyMMddHH"), 86400 * 90),
+                ("day", now.ToString("yyyyMMdd"), 86400 * 366),
+                ("month", now.ToString("yyyyMM"), 86400 * 3660),
+                ("year", now.ToString("yyyy"), 86400 * 3660),
+            ];
+        }
+
+        private static void SaveMetricCount(string key, string indexKey, int expireSeconds)
+        {
+            RedisHelper.IncrBy(key);
+            RedisHelper.Expire(key, expireSeconds);
+            RedisHelper.SAdd(indexKey, key);
+            RedisHelper.Expire(indexKey, expireSeconds);
+        }
+
+        private static void SaveExceptionCount(string exceptionType)
+        {
+            DateTime now = DateTime.Now;
+            var metrics = GetBucketMetrics(now);
+
+            foreach (var metric in metrics)
+            {
+                string key = GetExceptionCountKey(exceptionType, metric.bucketType, metric.bucketValue);
+                SaveMetricCount(key, GetExceptionIndexKey(exceptionType, metric.bucketType), metric.expireSeconds);
+
+                string aggregateKey = GetExceptionCountKey(ExceptionAggregateType, metric.bucketType, metric.bucketValue);
+                SaveMetricCount(aggregateKey, GetExceptionIndexKey(ExceptionAggregateType, metric.bucketType), metric.expireSeconds);
+            }
+        }
+
+        private static bool TryParseKey(string key, out XhsZlongForwardReportItem item)
+        {
+            item = new XhsZlongForwardReportItem();
+            if (string.IsNullOrWhiteSpace(key)) return false;
+
+            var parts = key.Split(':', StringSplitOptions.RemoveEmptyEntries);
+            if (parts.Length < 4) return false;
+
+            string bucketType = parts[^2];
+            string bucketValue = parts[^1];
+            if (!TryParseBucket(bucketType, bucketValue, out var reportTime, out var displayTime)) return false;
+
+            item.bucket_type = bucketType;
+            item.bucket_value = bucketValue;
+            item.report_time = reportTime;
+            item.display_time = displayTime;
+            item.redis_key = key;
+            return true;
+        }
+
+        private static bool TryParseBucket(string bucketType, string bucketValue, out DateTime reportTime, out string displayTime)
+        {
+            reportTime = DateTime.MinValue;
+            displayTime = string.Empty;
+
+            string format = bucketType switch
+            {
+                "hour" => "yyyyMMddHH",
+                "day" => "yyyyMMdd",
+                "month" => "yyyyMM",
+                "year" => "yyyy",
+                _ => string.Empty,
+            };
+            if (string.IsNullOrEmpty(format)) return false;
+
+            bool success = DateTime.TryParseExact(
+                bucketValue,
+                format,
+                CultureInfo.InvariantCulture,
+                DateTimeStyles.None,
+                out reportTime);
+
+            if (!success) return false;
+
+            displayTime = bucketType switch
+            {
+                "hour" => reportTime.ToString("yyyy-MM-dd HH:00"),
+                "day" => reportTime.ToString("yyyy-MM-dd"),
+                "month" => reportTime.ToString("yyyy-MM"),
+                "year" => reportTime.ToString("yyyy"),
+                _ => bucketValue,
+            };
+            return true;
+        }
+    }
+
+    public class XhsZlongForwardReportSummary
+    {
+        public long hour { get; set; }
+        public long day { get; set; }
+        public long month { get; set; }
+        public long year { get; set; }
+        public long hour_exception { get; set; }
+        public long day_exception { get; set; }
+        public long month_exception { get; set; }
+        public long year_exception { get; set; }
+    }
+
+    public class XhsZlongForwardReportItem
+    {
+        public string bucket_type { get; set; } = string.Empty;
+        public string bucket_value { get; set; } = string.Empty;
+        public DateTime report_time { get; set; } = DateTime.MinValue;
+        public string display_time { get; set; } = string.Empty;
+        public long total { get; set; }
+        public long exception_count { get; set; }
+        public string redis_key { get; set; } = string.Empty;
+    }
+
+    public class XhsZlongForwardReportResult
+    {
+        public int page { get; set; }
+        public int pageCount { get; set; }
+        public int size { get; set; }
+        public int count { get; set; }
+        public List<XhsZlongForwardReportItem> list { get; set; } = [];
+        public XhsZlongForwardReportSummary summary { get; set; } = new();
+    }
+}

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

@@ -64,6 +64,7 @@ namespace molilian.core
         public string dyIgnorePercentageCity { get; set; } = string.Empty;
         public int dy_limit_per_ip_24h { get; set; } = 0;
         public int dy_limit_per_oaid_24h { get; set; } = 0;
+        public int webook_xhs_zlong { get; set; } = 0;
 
 
 

+ 3 - 2
molilian.core/Plus/DeepleaperPlus.cs

@@ -124,7 +124,7 @@ namespace molilian.core
 
 
             int error = root.Read<int>("errorCode", 0);
-            bool success = error == 0;
+            bool success = error == 0 || error == 200;
             string message = root.Read("errorMessage", string.Empty);
             result.success = false;
             //if (message.Contains("该url暂不支持解析"))
@@ -136,7 +136,7 @@ namespace molilian.core
 
             result.reason = message;
             result.message = "转链失败";
-            if (error != 0) return result;
+            if (!success) return result;
             try
             {
                 var shoppingInfoItems = root.ElementRead("reply").ElementRead("commands")[0].ElementRead("body").ElementRead("templateContent").ElementRead("items")[0].ElementRead("shoppingInfoItems");
@@ -148,6 +148,7 @@ namespace molilian.core
                 result.shortLinkurl = shoppingInfoItems[0].PathRead<string>("detailUrl.webURL");
                 result.deeplink_url = shoppingInfoItems[0].PathRead<string>("detailUrl.deepLink.url");
                 result.with_middle_page = shoppingInfoItems[0].PathRead<int>("detailUrl.type");
+                result.reason = shoppingInfoItems[0].PathRead<string>("detailUrl.subMsg");
                 if (result.with_middle_page == 1) { result.commercial = true; }
 
 

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