فهرست منبع

🧪 test(京东风控): 增加京东风控api

dodo hold 1 ماه پیش
والد
کامیت
9977e37525
2فایلهای تغییر یافته به همراه465 افزوده شده و 14 حذف شده
  1. 465 14
      molilian.api/Controllers/public/TestController.cs
  2. 0 0
      molilian.api/Properties/PublishProfiles/latest.pubxml.user

+ 465 - 14
molilian.api/Controllers/public/TestController.cs

@@ -3,6 +3,8 @@ using dodohold.core;
 using Dapper;
 using Microsoft.AspNetCore.Mvc;
 using Org.BouncyCastle.Ocsp;
+using System.Collections.Concurrent;
+using System.Diagnostics;
 using System.Text.Json;
 using System.Runtime.InteropServices;
 using System.Net;
@@ -23,20 +25,71 @@ namespace molilian.api.Controllers
 {
     [ApiController]
     [Route("[controller]/[action]")]
-    public class TestController : ControllerBase
-    {
-
-        protected IHttpContextAccessor _accessor;
-        public TestController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-
-        [HttpGet]
-        public async Task<ActionResult> ecs_list_test()
-        {
-            var list = AliyunPoolCore.EcsList();
+    public class TestController : ControllerBase
+    {
+        private const string JdStressDeeplinkContent = "openapp.jdmobile://virtual?params=%7B%22category%22%3A%22jump%22%2C%22des%22%3A%22productDetail%22%2C%22skuId%22%3A%2210139003162781%22%2C%22sourceType%22%3A%22Item%22%2C%22sourceValue%22%3A%22view-ware%22%7D";
+
+        protected IHttpContextAccessor _accessor;
+        public TestController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> jd_unsafe_parse_stress([FromQuery] int t = 5, [FromQuery] int time = 60, [FromQuery] string ip = "127.0.0.1", [FromQuery] string oaid = "", [FromQuery] bool wait = false)
+        {
+            t = t <= 0 ? 5 : t;
+            time = time <= 0 ? 60 : time;
+
+            string runId = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff");
+            string logName = $"run_{runId}_t{t}_time{time}";
+
+            string startLogStatus = await SaveJdStressStartLogAsync(runId, logName, t, time, ip, oaid, wait);
+
+            if (wait)
+            {
+                var result = await RunJdStressAsync(runId, logName, t, time, ip, oaid, startLogStatus, CancellationToken.None);
+                return new APIResult(result);
+            }
+
+            _ = Task.Run(async () =>
+            {
+                try
+                {
+                    await RunJdStressAsync(runId, logName, t, time, ip, oaid, startLogStatus, CancellationToken.None);
+                }
+                catch (Exception ex)
+                {
+                    _ = new LoggerLibrary("jd_api_stress", $"{logName}_runner_error")
+                        .Info(ex.Message, ex.StackTrace)
+                        .SaveAsync();
+                }
+            });
+
+            return new APIResult(new
+            {
+                success = true,
+                message = "后台压测已启动",
+                runId,
+                requestPerSecond = t,
+                timeSeconds = time,
+                expectedRequests = (long)t * time,
+                wait,
+                log = new
+                {
+                    type = "LoggerLibrary",
+                    dir = "jd_api_stress",
+                    name = logName,
+                    status = startLogStatus
+                }
+            });
+        }
+
+
+        [HttpGet]
+        public async Task<ActionResult> ecs_list_test()
+        {
+            var list = AliyunPoolCore.EcsList();
             return new APIResult(new
             {
                 success = true,
@@ -927,6 +980,404 @@ VALUES
             public string end_point { get; set; } = string.Empty;
         }
 
+        private static async Task<string> SaveJdStressStartLogAsync(string runId, string logName, int t, int time, string ip, string oaid, bool wait)
+        {
+            try
+            {
+                await new LoggerLibrary("jd_api_stress", logName)
+                    .AppendLine($"status=started")
+                    .AppendLine($"runId={runId}")
+                    .AppendLine($"startedAt={DateTime.Now:O}")
+                    .AppendLine($"t={t}")
+                    .AppendLine($"timeSeconds={time}")
+                    .AppendLine($"ip={ip}")
+                    .AppendLine($"oaid={oaid}")
+                    .AppendLine($"wait={wait}")
+                    .AppendLine($"content={JdStressDeeplinkContent}")
+                    .SaveAsync();
+                return "started";
+            }
+            catch (Exception ex)
+            {
+                return ex.Message;
+            }
+        }
+
+        private static async Task<JdStressRunResult> RunJdStressAsync(string runId, string logName, int t, int time, string ip, string oaid, string startLogStatus, CancellationToken cancellationToken)
+        {
+            var startedAt = DateTime.Now;
+            var stopwatch = Stopwatch.StartNew();
+
+            long scheduledCount = 0;
+            long completedCount = 0;
+            long successCount = 0;
+            long failCount = 0;
+            long exceptionCount = 0;
+            long riskControlCount = 0;
+            long totalElapsedMs = 0;
+            int activeCount = 0;
+            int peakActiveCount = 0;
+
+            var responseLines = new ConcurrentQueue<string>();
+            var messageCounts = new ConcurrentDictionary<string, int>(StringComparer.Ordinal);
+            var codeCounts = new ConcurrentDictionary<string, int>(StringComparer.Ordinal);
+            int initialTaskCapacity = (int)Math.Min((long)t * Math.Min(time, 60), 4096L);
+            var runningTasks = new List<Task>(initialTaskCapacity);
+            string message = "ok";
+
+            try
+            {
+                for (int second = 0; second < time && !cancellationToken.IsCancellationRequested; second++)
+                {
+                    for (int i = 0; i < t; i++)
+                    {
+                        long sequence = Interlocked.Increment(ref scheduledCount);
+                        runningTasks.Add(Task.Run(async () =>
+                        {
+                            int currentActive = Interlocked.Increment(ref activeCount);
+                            UpdateMax(ref peakActiveCount, currentActive);
+
+                            try
+                            {
+                                var item = await ExecuteJdStressRequestAsync(sequence, ip, oaid, cancellationToken);
+                                Interlocked.Increment(ref completedCount);
+                                Interlocked.Add(ref totalElapsedMs, item.ElapsedMs);
+
+                                if (item.Success)
+                                {
+                                    Interlocked.Increment(ref successCount);
+                                }
+                                else
+                                {
+                                    Interlocked.Increment(ref failCount);
+                                }
+
+                                if (item.IsException)
+                                {
+                                    Interlocked.Increment(ref exceptionCount);
+                                }
+
+                                if (IsRiskControlResult(item))
+                                {
+                                    Interlocked.Increment(ref riskControlCount);
+                                }
+
+                                string messageKey = string.IsNullOrWhiteSpace(item.SubMessage)
+                                    ? item.Message
+                                    : $"{item.Message}:{item.SubMessage}";
+                                if (string.IsNullOrWhiteSpace(messageKey)) messageKey = "empty";
+                                messageCounts.AddOrUpdate(messageKey, 1, (_, value) => value + 1);
+                                codeCounts.AddOrUpdate(item.Code.ToString(), 1, (_, value) => value + 1);
+
+                                responseLines.Enqueue(
+                                    $"{item.FinishedAt:O}\tseq={item.Sequence}\telapsedMs={item.ElapsedMs}\tcode={item.Code}\tsuccess={item.Success}\tmessage={NormalizeLogValue(item.Message)}\tsub_message={NormalizeLogValue(item.SubMessage)}\tresponse={NormalizeLogValue(item.Response)}");
+                            }
+                            finally
+                            {
+                                Interlocked.Decrement(ref activeCount);
+                            }
+                        }));
+                    }
+
+                    await ObserveCompletedTasksAsync(runningTasks);
+
+                    var nextTick = TimeSpan.FromSeconds(second + 1);
+                    var delay = nextTick - stopwatch.Elapsed;
+                    if (delay > TimeSpan.Zero)
+                    {
+                        await Task.Delay(delay, cancellationToken);
+                    }
+                }
+            }
+            catch (OperationCanceledException ex)
+            {
+                message = $"canceled:{ex.Message}";
+                responseLines.Enqueue($"{DateTime.Now:O}\tmessage={NormalizeLogValue(message)}");
+            }
+            catch (Exception ex)
+            {
+                message = $"runner error:{ex.Message}";
+                responseLines.Enqueue($"{DateTime.Now:O}\tmessage={NormalizeLogValue(message)}\tstack={NormalizeLogValue(ex.StackTrace ?? string.Empty)}");
+            }
+
+            try
+            {
+                await Task.WhenAll(runningTasks);
+            }
+            catch (Exception ex)
+            {
+                message = $"task wait error:{ex.Message}";
+                responseLines.Enqueue($"{DateTime.Now:O}\tmessage={NormalizeLogValue(message)}\tstack={NormalizeLogValue(ex.StackTrace ?? string.Empty)}");
+            }
+
+            stopwatch.Stop();
+
+            string logStatus = "saved";
+            try
+            {
+                var log = new LoggerLibrary("jd_api_stress", logName);
+                log.AppendLine($"status=finished");
+                log.AppendLine($"startLogStatus={startLogStatus}");
+                log.AppendLine($"runId={runId}");
+                log.AppendLine($"startedAt={startedAt:O}");
+                log.AppendLine($"finishedAt={DateTime.Now:O}");
+                log.AppendLine($"t={t}");
+                log.AppendLine($"timeSeconds={time}");
+                log.AppendLine($"ip={ip}");
+                log.AppendLine($"oaid={oaid}");
+                log.AppendLine($"content={JdStressDeeplinkContent}");
+                log.AppendLine($"scheduled={scheduledCount}");
+                log.AppendLine($"completed={completedCount}");
+                log.AppendLine($"success={successCount}");
+                log.AppendLine($"fail={failCount}");
+                log.AppendLine($"exception={exceptionCount}");
+                log.AppendLine($"riskControl={riskControlCount}");
+                log.AppendLine($"peakActive={peakActiveCount}");
+                log.AppendLine("messageCounts=");
+                foreach (var item in messageCounts.OrderByDescending(item => item.Value))
+                {
+                    log.AppendLine($"{item.Key}\t{item.Value}");
+                }
+                log.AppendLine("codeCounts=");
+                foreach (var item in codeCounts.OrderByDescending(item => item.Value))
+                {
+                    log.AppendLine($"{item.Key}\t{item.Value}");
+                }
+                log.AppendLine("responses=");
+                while (responseLines.TryDequeue(out string? line))
+                {
+                    log.AppendLine(line);
+                }
+                await log.SaveAsync();
+            }
+            catch (Exception ex)
+            {
+                logStatus = ex.Message;
+            }
+
+            return new JdStressRunResult
+            {
+                success = true,
+                message = message,
+                runId = runId,
+                requestPerSecond = t,
+                timeSeconds = time,
+                expectedRequests = (long)t * time,
+                scheduledCount = scheduledCount,
+                completedCount = completedCount,
+                successCount = successCount,
+                failCount = failCount,
+                exceptionCount = exceptionCount,
+                riskControlCount = riskControlCount,
+                peakActiveCount = peakActiveCount,
+                averageElapsedMs = completedCount == 0 ? 0 : Math.Round(totalElapsedMs / (double)completedCount, 2),
+                elapsedSeconds = Math.Round(stopwatch.Elapsed.TotalSeconds, 2),
+                messageCounts = messageCounts.OrderByDescending(item => item.Value).ToDictionary(item => item.Key, item => item.Value),
+                codeCounts = codeCounts.OrderByDescending(item => item.Value).ToDictionary(item => item.Key, item => item.Value),
+                log = new JdStressLogInfo
+                {
+                    type = "LoggerLibrary",
+                    dir = "jd_api_stress",
+                    name = logName,
+                    status = logStatus
+                }
+            };
+        }
+
+        private static async Task ObserveCompletedTasksAsync(List<Task> runningTasks)
+        {
+            for (int i = runningTasks.Count - 1; i >= 0; i--)
+            {
+                if (!runningTasks[i].IsCompleted) continue;
+
+                await runningTasks[i];
+                runningTasks.RemoveAt(i);
+            }
+        }
+
+        private static async Task<JdStressRequestResult> ExecuteJdStressRequestAsync(long sequence, string ip, string oaid, CancellationToken cancellationToken)
+        {
+            var sw = Stopwatch.StartNew();
+            try
+            {
+                var request = new UnionParseRequest
+                {
+                    Content = JdStressDeeplinkContent,
+                    Channel = "jd",
+                    CommerceType = 0,
+                    Ip = ip,
+                    Oaid = oaid,
+                    RiskStrategy = string.Empty,
+                    LaunchScene = 0,
+                    AccountId = 0,
+                    SpecialText = 0,
+                    QueryText = string.Empty,
+                    ClickId = string.Empty,
+                    Type = "dp",
+                    Pic = string.Empty
+                };
+
+                var result = await UnionParseCore.DeeplinkJdParseAsync(request, cancellationToken);
+                sw.Stop();
+
+                string response = result.Content ?? string.Empty;
+                var parsed = ParseJdStressResponse(response);
+                parsed.Sequence = sequence;
+                parsed.ElapsedMs = sw.ElapsedMilliseconds;
+                parsed.Response = response;
+                parsed.FinishedAt = DateTime.Now;
+                return parsed;
+            }
+            catch (Exception ex)
+            {
+                sw.Stop();
+                return new JdStressRequestResult
+                {
+                    Sequence = sequence,
+                    ElapsedMs = sw.ElapsedMilliseconds,
+                    Success = false,
+                    Message = "exception",
+                    SubMessage = ex.Message,
+                    Code = 0,
+                    Response = ex.ToString(),
+                    FinishedAt = DateTime.Now,
+                    IsException = true
+                };
+            }
+        }
+
+        private static JdStressRequestResult ParseJdStressResponse(string response)
+        {
+            var result = new JdStressRequestResult();
+            if (string.IsNullOrWhiteSpace(response))
+            {
+                result.Message = "empty response";
+                return result;
+            }
+
+            try
+            {
+                using var doc = JsonDocument.Parse(response);
+                var root = doc.RootElement;
+                result.Success = ReadBool(root, "success");
+                result.Message = ReadString(root, "message");
+                result.SubMessage = ReadString(root, "sub_message");
+                result.Code = ReadInt(root, "code");
+            }
+            catch (Exception ex)
+            {
+                result.Success = false;
+                result.Message = "parse response error";
+                result.SubMessage = ex.Message;
+                result.Code = 0;
+            }
+            return result;
+        }
+
+        private static bool ReadBool(JsonElement root, string propertyName)
+        {
+            if (!root.TryGetProperty(propertyName, out var property)) return false;
+
+            return property.ValueKind switch
+            {
+                JsonValueKind.True => true,
+                JsonValueKind.False => false,
+                JsonValueKind.Number => property.TryGetInt32(out int value) && value != 0,
+                JsonValueKind.String => bool.TryParse(property.GetString(), out bool value) && value,
+                _ => false
+            };
+        }
+
+        private static int ReadInt(JsonElement root, string propertyName)
+        {
+            if (!root.TryGetProperty(propertyName, out var property)) return 0;
+
+            return property.ValueKind switch
+            {
+                JsonValueKind.Number => property.TryGetInt32(out int value) ? value : 0,
+                JsonValueKind.String => int.TryParse(property.GetString(), out int value) ? value : 0,
+                _ => 0
+            };
+        }
+
+        private static string ReadString(JsonElement root, string propertyName)
+        {
+            if (!root.TryGetProperty(propertyName, out var property)) return string.Empty;
+            if (property.ValueKind == JsonValueKind.Null || property.ValueKind == JsonValueKind.Undefined) return string.Empty;
+            return property.ValueKind == JsonValueKind.String ? property.GetString() ?? string.Empty : property.ToString();
+        }
+
+        private static bool IsRiskControlResult(JdStressRequestResult result)
+        {
+            if (!"放弃转链".Equals(result.Message, StringComparison.Ordinal)) return false;
+
+            return result.SubMessage.Contains("控制", StringComparison.Ordinal)
+                || result.SubMessage.Contains("风控", StringComparison.Ordinal)
+                || result.SubMessage.Contains("限流", StringComparison.Ordinal)
+                || result.SubMessage.Contains("频", StringComparison.Ordinal)
+                || result.SubMessage.Contains("系统繁忙", StringComparison.Ordinal);
+        }
+
+        private static string NormalizeLogValue(string value)
+        {
+            return (value ?? string.Empty)
+                .Replace("\r", "\\r")
+                .Replace("\n", "\\n")
+                .Replace("\t", " ");
+        }
+
+        private static void UpdateMax(ref int target, int value)
+        {
+            int snapshot;
+            while (value > (snapshot = Volatile.Read(ref target))
+                   && Interlocked.CompareExchange(ref target, value, snapshot) != snapshot)
+            {
+            }
+        }
+
+        private sealed class JdStressRequestResult
+        {
+            public long Sequence { get; set; }
+            public bool Success { get; set; }
+            public string Message { get; set; } = string.Empty;
+            public string SubMessage { get; set; } = string.Empty;
+            public int Code { get; set; }
+            public long ElapsedMs { get; set; }
+            public string Response { get; set; } = string.Empty;
+            public DateTime FinishedAt { get; set; }
+            public bool IsException { get; set; }
+        }
+
+        private sealed class JdStressRunResult
+        {
+            public bool success { get; set; }
+            public string message { get; set; } = string.Empty;
+            public string runId { get; set; } = string.Empty;
+            public int requestPerSecond { get; set; }
+            public int timeSeconds { get; set; }
+            public long expectedRequests { get; set; }
+            public long scheduledCount { get; set; }
+            public long completedCount { get; set; }
+            public long successCount { get; set; }
+            public long failCount { get; set; }
+            public long exceptionCount { get; set; }
+            public long riskControlCount { get; set; }
+            public int peakActiveCount { get; set; }
+            public double averageElapsedMs { get; set; }
+            public double elapsedSeconds { get; set; }
+            public Dictionary<string, int> messageCounts { get; set; } = [];
+            public Dictionary<string, int> codeCounts { get; set; } = [];
+            public JdStressLogInfo log { get; set; } = new();
+        }
+
+        private sealed class JdStressLogInfo
+        {
+            public string type { get; set; } = string.Empty;
+            public string dir { get; set; } = string.Empty;
+            public string name { get; set; } = string.Empty;
+            public string status { get; set; } = string.Empty;
+        }
+
         [HttpGet]
         public async Task<ActionResult> xxx()
         {

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
molilian.api/Properties/PublishProfiles/latest.pubxml.user


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است