| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- using Dapper;
- using dodohold.core;
- using System.Text;
- using System.Text.Json;
- namespace molilian.core
- {
- public static class PromotionQuerySamplingCore
- {
- private const string SamplingRemainingKey = "promotion_query_sampling:remaining";
- private const int SamplingSwitchExpireSeconds = 7 * 86400;
- private const int MaxEnableCount = 5000;
- public const int MaxQueryCount = 1100;
- private static readonly SemaphoreSlim EnsureTableLock = new(1, 1);
- private static readonly JsonSerializerOptions JsonOptions = new()
- {
- Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
- WriteIndented = true
- };
- private static bool _tableEnsured = false;
- public static int EnableSampling(int count)
- {
- count = Math.Clamp(count, 0, MaxEnableCount);
- if (count == 0)
- {
- RedisHelper.Del(SamplingRemainingKey);
- return 0;
- }
- RedisHelper.Set(SamplingRemainingKey, count, SamplingSwitchExpireSeconds);
- return count;
- }
- public static int GetRemainingCount()
- {
- return Math.Max(0, RedisHelper.Get<int>(SamplingRemainingKey));
- }
- public static async Task TrySampleAsync(string imageBase64, string ip, string oaid, string responseJson)
- {
- if (string.IsNullOrWhiteSpace(imageBase64)
- && string.IsNullOrWhiteSpace(ip)
- && string.IsNullOrWhiteSpace(oaid)
- && string.IsNullOrWhiteSpace(responseJson))
- {
- return;
- }
- if (!TryConsumeQuota())
- {
- return;
- }
- string sampleId = BuildSampleId();
- try
- {
- await EnsureTableAsync();
- using var conn = DBContext.GetOpenConnection();
- const string sql = @"
- INSERT INTO promotion_query_samples
- (id, ip, oaid, image_base64, response_json, create_time)
- VALUES
- (@id, @ip, @oaid, @image_base64, @response_json, @create_time);";
- await conn.ExecuteAsync(sql, new
- {
- id = sampleId,
- ip = ip ?? string.Empty,
- oaid = oaid ?? string.Empty,
- image_base64 = imageBase64 ?? string.Empty,
- response_json = responseJson ?? string.Empty,
- create_time = DateTime.Now
- });
- }
- catch (Exception ex)
- {
- RedisHelper.IncrBy(SamplingRemainingKey);
- RedisHelper.Expire(SamplingRemainingKey, SamplingSwitchExpireSeconds);
- _ = new LoggerLibrary("promotion_query_samples", "save_error")
- .Info(sampleId)
- .Info(ex.Message, ex.StackTrace)
- .SaveAsync();
- }
- }
- public static async Task<PromotionQuerySampleQueryResultDTO> QueryLogsAsync(int page, int pageSize, string keyword)
- {
- page = Math.Max(page, 1);
- pageSize = Math.Clamp(pageSize, 1, MaxQueryCount);
- keyword = NormalizeKeyword(keyword);
- await EnsureTableAsync();
- using var conn = DBContext.GetOpenConnection();
- string where = string.Empty;
- var args = new DynamicParameters();
- args.Add("count", pageSize);
- if (!string.IsNullOrWhiteSpace(keyword))
- {
- where = "WHERE id LIKE @keyword";
- args.Add("keyword", $"%{keyword}%");
- }
- string countSql = $@"
- SELECT COUNT(*)
- FROM promotion_query_samples
- {where}";
- int totalCount = await conn.QuerySingleAsync<int>(countSql, args);
- int totalPages = totalCount <= 0 ? 0 : (int)Math.Ceiling(totalCount / (double)pageSize);
- if (totalPages > 0 && page > totalPages)
- {
- page = totalPages;
- }
- int offset = (page - 1) * pageSize;
- args.Add("offset", offset);
- string sql = $@"
- SELECT id, ip, oaid, image_base64, response_json, create_time
- FROM promotion_query_samples
- {where}
- ORDER BY create_time DESC
- LIMIT @offset, @count";
- var list = (await conn.QueryAsync<PromotionQuerySampleDTO>(sql, args)).ToList();
- return new PromotionQuerySampleQueryResultDTO
- {
- page = totalCount > 0 ? page : 1,
- pageSize = pageSize,
- totalCount = totalCount,
- logs = list.Select(ToLogDto).ToList()
- };
- }
- private static PromotionQuerySampleLogDTO ToLogDto(PromotionQuerySampleDTO item)
- {
- return new PromotionQuerySampleLogDTO
- {
- id = item.id,
- fileName = $"{item.id}.log",
- writeTime = item.create_time.ToString("yyyy-MM-dd HH:mm:ss.fff"),
- content = BuildLegacyContent(item),
- ip = item.ip,
- oaid = item.oaid,
- imageBase64 = item.image_base64,
- responseJson = item.response_json
- };
- }
- private static string BuildLegacyContent(PromotionQuerySampleDTO item)
- {
- var builder = new StringBuilder();
- if (!string.IsNullOrWhiteSpace(item.image_base64))
- {
- string clientPost = JsonSerializer.Serialize(new
- {
- img = item.image_base64,
- ip = item.ip,
- oaid = item.oaid
- }, JsonOptions);
- AppendSection(builder, item.create_time, "clientPost", clientPost);
- }
- else if (!string.IsNullOrWhiteSpace(item.ip) || !string.IsNullOrWhiteSpace(item.oaid))
- {
- string clientPost = JsonSerializer.Serialize(new
- {
- ip = item.ip,
- oaid = item.oaid
- }, JsonOptions);
- AppendSection(builder, item.create_time, "clientPost", clientPost);
- }
- if (!string.IsNullOrWhiteSpace(item.response_json))
- {
- AppendSection(builder, item.create_time, "responseJson", item.response_json.Trim());
- }
- return builder.ToString().TrimEnd();
- }
- private static void AppendSection(StringBuilder builder, DateTime timestamp, string name, string content)
- {
- if (string.IsNullOrWhiteSpace(content))
- {
- return;
- }
- builder.Append(timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff"))
- .Append('\t')
- .Append(name)
- .AppendLine()
- .AppendLine(content);
- }
- private static string NormalizeKeyword(string keyword)
- {
- keyword = (keyword ?? string.Empty).Trim();
- if (keyword.EndsWith(".log", StringComparison.OrdinalIgnoreCase))
- {
- keyword = keyword[..^4];
- }
- return keyword;
- }
- private static bool TryConsumeQuota()
- {
- int remaining = GetRemainingCount();
- if (remaining <= 0)
- {
- return false;
- }
- long after = RedisHelper.IncrBy(SamplingRemainingKey, -1);
- RedisHelper.Expire(SamplingRemainingKey, SamplingSwitchExpireSeconds);
- if (after < 0)
- {
- RedisHelper.Set(SamplingRemainingKey, 0, SamplingSwitchExpireSeconds);
- return false;
- }
- return true;
- }
- private static string BuildSampleId()
- {
- var now = DateTime.Now;
- return $"{now:yyyyMMdd_HHmmss_fff}_{Guid.NewGuid():N}_{now:yyyyMMdd}";
- }
- private static async Task EnsureTableAsync()
- {
- if (_tableEnsured)
- {
- return;
- }
- await EnsureTableLock.WaitAsync();
- try
- {
- if (_tableEnsured)
- {
- return;
- }
- const string sql = @"
- CREATE TABLE IF NOT EXISTS promotion_query_samples (
- id VARCHAR(80) NOT NULL PRIMARY KEY COMMENT '采样日志ID',
- ip VARCHAR(64) NOT NULL DEFAULT '' COMMENT '请求IP',
- oaid VARCHAR(128) NOT NULL DEFAULT '' COMMENT '请求OAID',
- image_base64 LONGTEXT NOT NULL COMMENT '入参图片base64',
- response_json LONGTEXT NOT NULL COMMENT '返回JSON',
- create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
- KEY idx_create_time (create_time)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PromotionQuery采样记录';";
- using var conn = DBContext.GetOpenConnection();
- await conn.ExecuteAsync(sql);
- _tableEnsured = true;
- }
- finally
- {
- EnsureTableLock.Release();
- }
- }
- }
- }
|