2025-11-05 00:22:21 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
2025-11-05 17:26:45 +08:00
|
|
|
|
using System.Globalization;
|
2025-11-05 00:22:21 +08:00
|
|
|
|
using System.Linq;
|
2025-11-05 17:26:45 +08:00
|
|
|
|
using System.Net.Http;
|
|
|
|
|
|
using System.Net.Http.Headers;
|
2025-11-05 00:22:21 +08:00
|
|
|
|
using System.Text;
|
2025-11-05 17:26:45 +08:00
|
|
|
|
using System.Text.Encodings.Web;
|
|
|
|
|
|
using System.Text.Json;
|
2025-11-05 00:22:21 +08:00
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
|
using System.Threading.Tasks;
|
2025-11-05 17:26:45 +08:00
|
|
|
|
using DouyinApi.Common.Option;
|
2025-11-05 00:22:21 +08:00
|
|
|
|
using DouyinApi.IServices;
|
|
|
|
|
|
using DouyinApi.Model.Naming;
|
2025-11-05 17:26:45 +08:00
|
|
|
|
using DouyinApi.Services.Naming;
|
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
using Microsoft.Extensions.Options;
|
2025-11-05 00:22:21 +08:00
|
|
|
|
|
2025-11-05 17:26:45 +08:00
|
|
|
|
namespace DouyinApi.Services;
|
|
|
|
|
|
|
|
|
|
|
|
public class NamingService : INamingService
|
2025-11-05 00:22:21 +08:00
|
|
|
|
{
|
2025-11-05 17:26:45 +08:00
|
|
|
|
private static readonly Regex SurnamePattern = new(@"^[\u4e00-\u9fa5]{1,2}$", RegexOptions.Compiled);
|
|
|
|
|
|
|
|
|
|
|
|
private readonly IHttpClientFactory _httpClientFactory;
|
|
|
|
|
|
private readonly DeepSeekOptions _deepSeekOptions;
|
|
|
|
|
|
private readonly ILogger<NamingService> _logger;
|
|
|
|
|
|
private readonly BaziCalculator _baziCalculator = new();
|
|
|
|
|
|
private readonly FallbackNameGenerator _fallbackGenerator = new();
|
|
|
|
|
|
|
|
|
|
|
|
public NamingService(
|
|
|
|
|
|
IHttpClientFactory httpClientFactory,
|
|
|
|
|
|
IOptions<DeepSeekOptions> deepSeekOptions,
|
|
|
|
|
|
ILogger<NamingService> logger)
|
2025-11-05 00:22:21 +08:00
|
|
|
|
{
|
2025-11-05 17:26:45 +08:00
|
|
|
|
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
|
|
|
|
|
|
_deepSeekOptions = deepSeekOptions?.Value ?? new DeepSeekOptions();
|
|
|
|
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
|
|
|
|
}
|
2025-11-05 00:22:21 +08:00
|
|
|
|
|
2025-11-05 17:26:45 +08:00
|
|
|
|
public Task<bool> ValidateSurnameAsync(string surname)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(surname))
|
2025-11-05 00:22:21 +08:00
|
|
|
|
{
|
2025-11-05 17:26:45 +08:00
|
|
|
|
return Task.FromResult(false);
|
2025-11-05 00:22:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-05 17:26:45 +08:00
|
|
|
|
return Task.FromResult(SurnamePattern.IsMatch(surname.Trim()));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<NamingResponse> GenerateNamesAsync(NamingRequest request)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (request == null)
|
2025-11-05 00:22:21 +08:00
|
|
|
|
{
|
2025-11-05 17:26:45 +08:00
|
|
|
|
throw new ArgumentNullException(nameof(request));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!await ValidateSurnameAsync(request.Surname).ConfigureAwait(false))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentException("invalid_surname");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var birthDateTime = ComposeBirthDateTime(request);
|
|
|
|
|
|
var baziProfile = _baziCalculator.Calculate(birthDateTime);
|
|
|
|
|
|
var analysis = BuildAnalysis(request, baziProfile);
|
|
|
|
|
|
|
|
|
|
|
|
var suggestions = await TryGenerateViaDeepSeekAsync(request, baziProfile).ConfigureAwait(false);
|
|
|
|
|
|
if (!suggestions.Any())
|
|
|
|
|
|
{
|
2025-11-06 19:23:42 +08:00
|
|
|
|
_logger.LogWarning("DeepSeek returned no suggestions, falling back to local generator. Request={@Request}", new
|
|
|
|
|
|
{
|
|
|
|
|
|
request.Surname,
|
|
|
|
|
|
request.Gender,
|
|
|
|
|
|
request.NameLength,
|
|
|
|
|
|
request.BirthDate,
|
|
|
|
|
|
request.BirthTime
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-05 17:26:45 +08:00
|
|
|
|
suggestions = _fallbackGenerator.Generate(request, baziProfile);
|
2025-11-06 19:23:42 +08:00
|
|
|
|
if (!suggestions.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError("Fallback generator also returned empty results. Request={@Request}", new
|
|
|
|
|
|
{
|
|
|
|
|
|
request.Surname,
|
|
|
|
|
|
request.Gender,
|
|
|
|
|
|
request.NameLength,
|
|
|
|
|
|
request.BirthDate,
|
|
|
|
|
|
request.BirthTime
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogInformation("DeepSeek generated {Count} suggestions for {Surname}", suggestions.Count, request.Surname);
|
2025-11-05 17:26:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new NamingResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
Analysis = analysis,
|
|
|
|
|
|
Results = suggestions
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static DateTime ComposeBirthDateTime(NamingRequest request)
|
|
|
|
|
|
{
|
|
|
|
|
|
var dateFormats = new[] { "yyyy-MM-dd", "yyyy/M/d", "yyyy.M.d" };
|
|
|
|
|
|
if (!DateTime.TryParseExact(request.BirthDate, dateFormats, CultureInfo.InvariantCulture, DateTimeStyles.None, out var datePart))
|
|
|
|
|
|
{
|
|
|
|
|
|
datePart = DateTime.Today;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(request.BirthTime) &&
|
|
|
|
|
|
TimeSpan.TryParse(request.BirthTime, CultureInfo.InvariantCulture, out var timePart))
|
|
|
|
|
|
{
|
|
|
|
|
|
return datePart.Date.Add(timePart);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return datePart.Date.AddHours(12);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static NamingAnalysis BuildAnalysis(NamingRequest request, BaziProfile profile)
|
|
|
|
|
|
{
|
|
|
|
|
|
var pillars = new List<string>
|
|
|
|
|
|
{
|
|
|
|
|
|
$"{profile.YearStem}{profile.YearBranch}年",
|
|
|
|
|
|
$"{profile.MonthStem}{profile.MonthBranch}月",
|
|
|
|
|
|
$"{profile.DayStem}{profile.DayBranch}日",
|
|
|
|
|
|
$"{profile.HourStem}{profile.HourBranch}时"
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var distribution = profile.ElementOrder
|
|
|
|
|
|
.Select(element => new FiveElementScore
|
2025-11-05 00:22:21 +08:00
|
|
|
|
{
|
2025-11-05 17:26:45 +08:00
|
|
|
|
Element = element,
|
|
|
|
|
|
Count = profile.ElementCounts[element]
|
|
|
|
|
|
})
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
var summaryBuilder = new StringBuilder();
|
|
|
|
|
|
summaryBuilder.Append("八字:");
|
|
|
|
|
|
summaryBuilder.Append(string.Join("|", pillars));
|
|
|
|
|
|
summaryBuilder.Append("。五行分布:");
|
|
|
|
|
|
summaryBuilder.Append(string.Join("、", distribution.Select(d => $"{d.Element}{d.Count}")));
|
|
|
|
|
|
|
|
|
|
|
|
if (profile.IsBalanced)
|
|
|
|
|
|
{
|
|
|
|
|
|
summaryBuilder.Append("。五行较为均衡,可结合个人志趣择名。");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
var weak = string.Join("、", profile.WeakElements);
|
|
|
|
|
|
summaryBuilder.Append("。");
|
|
|
|
|
|
summaryBuilder.Append($"命局中{weak}之气偏弱,宜以对应意象入名调和。");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new NamingAnalysis
|
|
|
|
|
|
{
|
|
|
|
|
|
MatchSummary = summaryBuilder.ToString(),
|
|
|
|
|
|
Pillars = pillars,
|
|
|
|
|
|
ElementDistribution = distribution
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<IReadOnlyList<NamingSuggestion>> TryGenerateViaDeepSeekAsync(NamingRequest request, BaziProfile profile)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (IsDeepSeekConfigInvalid())
|
|
|
|
|
|
{
|
2025-11-06 19:23:42 +08:00
|
|
|
|
_logger.LogWarning("DeepSeek skipped: configuration missing.");
|
2025-11-05 17:26:45 +08:00
|
|
|
|
return Array.Empty<NamingSuggestion>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var client = _httpClientFactory.CreateClient();
|
|
|
|
|
|
client.Timeout = TimeSpan.FromSeconds(_deepSeekOptions.TimeoutSeconds > 0 ? _deepSeekOptions.TimeoutSeconds : 15);
|
|
|
|
|
|
|
|
|
|
|
|
var httpRequest = BuildDeepSeekRequest(request, profile);
|
|
|
|
|
|
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _deepSeekOptions.ApiKey);
|
|
|
|
|
|
httpRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
|
|
|
|
|
|
|
|
|
|
|
var response = await client.SendAsync(httpRequest).ConfigureAwait(false);
|
|
|
|
|
|
var payload = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.IsSuccessStatusCode)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogWarning("DeepSeek 调用失败,状态码:{StatusCode},返回:{Payload}", (int)response.StatusCode, payload);
|
|
|
|
|
|
return Array.Empty<NamingSuggestion>();
|
2025-11-05 00:22:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 19:23:42 +08:00
|
|
|
|
_logger.LogInformation("DeepSeek 调用成功,状态码:{StatusCode},响应长度:{Length}", (int)response.StatusCode, payload?.Length ?? 0);
|
2025-11-05 17:26:45 +08:00
|
|
|
|
return ParseDeepSeekResponse(payload, request);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogWarning(ex, "DeepSeek 调用异常,启用兜底逻辑");
|
|
|
|
|
|
return Array.Empty<NamingSuggestion>();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private HttpRequestMessage BuildDeepSeekRequest(NamingRequest request, BaziProfile profile)
|
|
|
|
|
|
{
|
|
|
|
|
|
var endpoint = string.IsNullOrWhiteSpace(_deepSeekOptions.BaseUrl)
|
|
|
|
|
|
? "https://api.deepseek.com/v1/chat/completions"
|
|
|
|
|
|
: $"{_deepSeekOptions.BaseUrl.TrimEnd('/')}/chat/completions";
|
|
|
|
|
|
|
|
|
|
|
|
var context = new
|
|
|
|
|
|
{
|
|
|
|
|
|
surname = request.Surname,
|
|
|
|
|
|
gender = request.Gender,
|
|
|
|
|
|
nameLength = request.NameLength,
|
|
|
|
|
|
birthDate = request.BirthDate,
|
|
|
|
|
|
birthTime = request.BirthTime,
|
|
|
|
|
|
pillars = new
|
2025-11-05 00:22:21 +08:00
|
|
|
|
{
|
2025-11-05 17:26:45 +08:00
|
|
|
|
year = $"{profile.YearStem}{profile.YearBranch}",
|
|
|
|
|
|
month = $"{profile.MonthStem}{profile.MonthBranch}",
|
|
|
|
|
|
day = $"{profile.DayStem}{profile.DayBranch}",
|
|
|
|
|
|
hour = $"{profile.HourStem}{profile.HourBranch}"
|
|
|
|
|
|
},
|
|
|
|
|
|
elementDistribution = profile.ElementOrder.ToDictionary(
|
|
|
|
|
|
element => element,
|
|
|
|
|
|
element => profile.ElementCounts[element]),
|
|
|
|
|
|
weakElements = profile.WeakElements,
|
|
|
|
|
|
strongElements = profile.StrongElements,
|
|
|
|
|
|
isBalanced = profile.IsBalanced
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var contextJson = JsonSerializer.Serialize(context, new JsonSerializerOptions
|
|
|
|
|
|
{
|
|
|
|
|
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-06 19:23:42 +08:00
|
|
|
|
var lengthDescriptor = string.Equals(request.NameLength, "single", StringComparison.OrdinalIgnoreCase) ? 1 : 2;
|
|
|
|
|
|
var nameLength = request.Surname.Length + lengthDescriptor;
|
2025-11-05 17:26:45 +08:00
|
|
|
|
var genderLabel = string.Equals(request.Gender, "female", StringComparison.OrdinalIgnoreCase) ? "女孩" : "男孩";
|
|
|
|
|
|
|
|
|
|
|
|
var userPrompt = new StringBuilder();
|
|
|
|
|
|
userPrompt.AppendLine("请扮演专业的中华姓名学顾问,根据八字五行提出姓名建议。");
|
2025-11-06 19:23:42 +08:00
|
|
|
|
userPrompt.AppendLine($"需基于以下 JSON 数据为姓氏“{request.Surname}”的{genderLabel}提供 5 个{nameLength}字中文名字:");
|
2025-11-05 17:26:45 +08:00
|
|
|
|
userPrompt.AppendLine(contextJson);
|
|
|
|
|
|
userPrompt.AppendLine("要求:");
|
|
|
|
|
|
userPrompt.AppendLine("1. 每条建议输出对象包含 name、meaning、fiveElementReason 三个字段;");
|
2025-11-06 19:23:42 +08:00
|
|
|
|
userPrompt.AppendLine("2. meaning为姓名的含义和出处;");
|
|
|
|
|
|
userPrompt.AppendLine("3. 所有汉字需为生活常用的简体字,单字笔画尽量小于等于 12;");
|
|
|
|
|
|
userPrompt.AppendLine("4. name 必须包含姓氏且满足给定的字数;");
|
|
|
|
|
|
userPrompt.AppendLine("5. fiveElementReason 需指明所补足的五行依据。");
|
2025-11-05 17:26:45 +08:00
|
|
|
|
userPrompt.AppendLine("请仅返回 JSON 数据,格式为 {\"suggestions\":[{...}]},不要附加额外说明。");
|
|
|
|
|
|
|
|
|
|
|
|
var requestBody = new
|
|
|
|
|
|
{
|
|
|
|
|
|
model = _deepSeekOptions.Model,
|
|
|
|
|
|
temperature = _deepSeekOptions.Temperature,
|
2025-11-06 19:23:42 +08:00
|
|
|
|
//max_tokens = _deepSeekOptions.MaxTokens,
|
|
|
|
|
|
response_format = new { type = _deepSeekOptions.ResponseFormat },
|
2025-11-05 17:26:45 +08:00
|
|
|
|
messages = new[]
|
|
|
|
|
|
{
|
2025-11-06 19:23:42 +08:00
|
|
|
|
new { role = "system", content = "你是一名资深的中文姓名学专家,擅长古诗词,诗经,易经中国传统文化根据八字喜用神给出实用建议。" },
|
2025-11-05 17:26:45 +08:00
|
|
|
|
new { role = "user", content = userPrompt.ToString() }
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var message = new HttpRequestMessage(HttpMethod.Post, endpoint)
|
|
|
|
|
|
{
|
2025-11-06 19:23:42 +08:00
|
|
|
|
Content = new StringContent(JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json"),
|
2025-11-05 17:26:45 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return message;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private IReadOnlyList<NamingSuggestion> ParseDeepSeekResponse(string payload, NamingRequest request)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-11-06 19:23:42 +08:00
|
|
|
|
if (string.IsNullOrWhiteSpace(payload))
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogWarning("DeepSeek 响应为空字符串");
|
|
|
|
|
|
return Array.Empty<NamingSuggestion>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-05 17:26:45 +08:00
|
|
|
|
using var document = JsonDocument.Parse(payload);
|
|
|
|
|
|
var choices = document.RootElement.GetProperty("choices");
|
|
|
|
|
|
if (choices.ValueKind != JsonValueKind.Array || choices.GetArrayLength() == 0)
|
|
|
|
|
|
{
|
2025-11-06 19:23:42 +08:00
|
|
|
|
_logger.LogWarning("DeepSeek 响应缺少 choices 数组,payload={Payload}", payload);
|
2025-11-05 17:26:45 +08:00
|
|
|
|
return Array.Empty<NamingSuggestion>();
|
2025-11-05 00:22:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-05 17:26:45 +08:00
|
|
|
|
var content = choices[0]
|
|
|
|
|
|
.GetProperty("message")
|
|
|
|
|
|
.GetProperty("content")
|
|
|
|
|
|
.GetString();
|
2025-11-05 00:22:21 +08:00
|
|
|
|
|
2025-11-05 17:26:45 +08:00
|
|
|
|
if (string.IsNullOrWhiteSpace(content))
|
2025-11-05 00:22:21 +08:00
|
|
|
|
{
|
2025-11-06 19:23:42 +08:00
|
|
|
|
_logger.LogWarning("DeepSeek 响应 message.content 为空");
|
2025-11-05 17:26:45 +08:00
|
|
|
|
return Array.Empty<NamingSuggestion>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
using var suggestionsDoc = JsonDocument.Parse(content);
|
|
|
|
|
|
if (!suggestionsDoc.RootElement.TryGetProperty("suggestions", out var suggestionsElement) ||
|
|
|
|
|
|
suggestionsElement.ValueKind != JsonValueKind.Array)
|
|
|
|
|
|
{
|
2025-11-06 19:23:42 +08:00
|
|
|
|
_logger.LogWarning("DeepSeek 返回内容缺少 suggestions 数组,content={Content}", content);
|
2025-11-05 17:26:45 +08:00
|
|
|
|
return Array.Empty<NamingSuggestion>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var normalized = new List<NamingSuggestion>();
|
|
|
|
|
|
var uniqueNames = new HashSet<string>(StringComparer.Ordinal);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var item in suggestionsElement.EnumerateArray())
|
|
|
|
|
|
{
|
|
|
|
|
|
var name = item.GetPropertyOrDefault("name");
|
|
|
|
|
|
var meaning = item.GetPropertyOrDefault("meaning");
|
|
|
|
|
|
var reason = item.GetPropertyOrDefault("fiveElementReason");
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(name))
|
2025-11-05 00:22:21 +08:00
|
|
|
|
{
|
2025-11-06 19:23:42 +08:00
|
|
|
|
_logger.LogWarning("DeepSeek suggestion缺少 name 字段,item={@Item}", item.ToString());
|
2025-11-05 00:22:21 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-05 17:26:45 +08:00
|
|
|
|
name = name.Trim();
|
|
|
|
|
|
if (!name.StartsWith(request.Surname, StringComparison.Ordinal))
|
2025-11-05 00:22:21 +08:00
|
|
|
|
{
|
2025-11-05 17:26:45 +08:00
|
|
|
|
name = $"{request.Surname}{name}";
|
2025-11-05 00:22:21 +08:00
|
|
|
|
}
|
2025-11-05 17:26:45 +08:00
|
|
|
|
|
|
|
|
|
|
if (!ValidateNameLength(name, request))
|
2025-11-05 00:22:21 +08:00
|
|
|
|
{
|
2025-11-06 19:23:42 +08:00
|
|
|
|
_logger.LogWarning(
|
|
|
|
|
|
"DeepSeek 建议姓名长度与请求不符,已丢弃。Name={Name}, Expect={Expect}, Request={@Request}",
|
|
|
|
|
|
name,
|
|
|
|
|
|
request.NameLength,
|
|
|
|
|
|
new
|
|
|
|
|
|
{
|
|
|
|
|
|
request.Surname,
|
|
|
|
|
|
request.Gender,
|
|
|
|
|
|
request.NameLength
|
|
|
|
|
|
});
|
2025-11-05 17:26:45 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!uniqueNames.Add(name))
|
|
|
|
|
|
{
|
2025-11-06 19:23:42 +08:00
|
|
|
|
_logger.LogInformation("DeepSeek 建议出现重复姓名,已跳过。Name={Name}", name);
|
2025-11-05 17:26:45 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
normalized.Add(new NamingSuggestion
|
|
|
|
|
|
{
|
|
|
|
|
|
Name = name,
|
|
|
|
|
|
Meaning = string.IsNullOrWhiteSpace(meaning) ? "寓意待补充" : meaning.Trim(),
|
|
|
|
|
|
ElementReason = string.IsNullOrWhiteSpace(reason) ? "结合八字五行进行补益。" : reason.Trim()
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (normalized.Count >= 5)
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
2025-11-05 00:22:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 19:23:42 +08:00
|
|
|
|
if (normalized.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogWarning("DeepSeek suggestions 解析后为空,原始 content={Content}", content);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-05 17:26:45 +08:00
|
|
|
|
return normalized;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogWarning(ex, "解析 DeepSeek 响应失败");
|
|
|
|
|
|
return Array.Empty<NamingSuggestion>();
|
2025-11-05 00:22:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-05 17:26:45 +08:00
|
|
|
|
|
|
|
|
|
|
private static bool ValidateNameLength(string fullName, NamingRequest request)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(fullName) || fullName.Length <= request.Surname.Length)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var expectedLength = string.Equals(request.NameLength, "single", StringComparison.OrdinalIgnoreCase) ? 1 : 2;
|
|
|
|
|
|
var givenName = fullName.Substring(request.Surname.Length);
|
|
|
|
|
|
return givenName.Length == expectedLength;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool IsDeepSeekConfigInvalid()
|
|
|
|
|
|
{
|
|
|
|
|
|
return string.IsNullOrWhiteSpace(_deepSeekOptions.ApiKey);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal static class JsonElementExtensions
|
|
|
|
|
|
{
|
|
|
|
|
|
public static string GetPropertyOrDefault(this JsonElement element, string propertyName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (element.TryGetProperty(propertyName, out var value) && value.ValueKind == JsonValueKind.String)
|
|
|
|
|
|
{
|
|
|
|
|
|
return value.GetString() ?? string.Empty;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return string.Empty;
|
|
|
|
|
|
}
|
2025-11-05 00:22:21 +08:00
|
|
|
|
}
|