优化取名逻辑
This commit is contained in:
@@ -54,7 +54,11 @@ namespace DouyinApi.Api.Controllers.MiniProgram
|
||||
try
|
||||
{
|
||||
var response = await _namingService.GenerateNamesAsync(normalizedRequest);
|
||||
return Ok(new { results = response.Results });
|
||||
return Ok(new
|
||||
{
|
||||
analysis = response.Analysis,
|
||||
results = response.Results
|
||||
});
|
||||
}
|
||||
catch (ArgumentException ex) when (ex.Message == "invalid_surname")
|
||||
{
|
||||
|
||||
@@ -359,5 +359,13 @@
|
||||
"Enabled": false,
|
||||
"Address": "http://localhost:5341/",
|
||||
"ApiKey": ""
|
||||
},
|
||||
"DeepSeek": {
|
||||
"BaseUrl": "https://api.deepseek.com/v1",
|
||||
"ApiKey": "",
|
||||
"Model": "deepseek-chat",
|
||||
"TimeoutSeconds": 15,
|
||||
"Temperature": 0.6,
|
||||
"MaxTokens": 800
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
40
DouyinApi.Common/Option/DeepSeekOptions.cs
Normal file
40
DouyinApi.Common/Option/DeepSeekOptions.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using DouyinApi.Common.Option.Core;
|
||||
|
||||
namespace DouyinApi.Common.Option;
|
||||
|
||||
/// <summary>
|
||||
/// DeepSeek 接口配置。
|
||||
/// </summary>
|
||||
public class DeepSeekOptions : IConfigurableOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// DeepSeek API 基础地址,例如 https://api.deepseek.com/v1 。
|
||||
/// </summary>
|
||||
public string BaseUrl { get; set; } = "https://api.deepseek.com/v1";
|
||||
|
||||
/// <summary>
|
||||
/// DeepSeek API 密钥,需通过环境变量或安全配置注入。
|
||||
/// </summary>
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 使用的模型标识。
|
||||
/// </summary>
|
||||
public string Model { get; set; } = "deepseek-chat";
|
||||
|
||||
/// <summary>
|
||||
/// 接口超时时间(秒)。
|
||||
/// </summary>
|
||||
public int TimeoutSeconds { get; set; } = 15;
|
||||
|
||||
/// <summary>
|
||||
/// 生成温度,控制结果多样性。
|
||||
/// </summary>
|
||||
public double Temperature { get; set; } = 0.6;
|
||||
|
||||
/// <summary>
|
||||
/// 最大生成 token 数。
|
||||
/// </summary>
|
||||
public int MaxTokens { get; set; } = 800;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace DouyinApi.Model.Naming
|
||||
{
|
||||
public class NamingResponse
|
||||
{
|
||||
public NamingAnalysis Analysis { get; set; } = new NamingAnalysis();
|
||||
|
||||
public IEnumerable<NamingSuggestion> Results { get; set; } = new List<NamingSuggestion>();
|
||||
}
|
||||
|
||||
@@ -12,5 +14,22 @@ namespace DouyinApi.Model.Naming
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string Meaning { get; set; } = string.Empty;
|
||||
|
||||
public string ElementReason { get; set; } = string.Empty;
|
||||
}
|
||||
public class NamingAnalysis
|
||||
{
|
||||
public string MatchSummary { get; set; } = string.Empty;
|
||||
|
||||
public IReadOnlyList<string> Pillars { get; set; } = new List<string>();
|
||||
|
||||
public IReadOnlyList<FiveElementScore> ElementDistribution { get; set; } = new List<FiveElementScore>();
|
||||
}
|
||||
|
||||
public class FiveElementScore
|
||||
{
|
||||
public string Element { get; set; } = string.Empty;
|
||||
|
||||
public int Count { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DouyinApi.IServices\DouyinApi.IServices.csproj" />
|
||||
<ProjectReference Include="..\DouyinApi.Repository\DouyinApi.Repository.csproj" />
|
||||
<ProjectReference Include="..\DouyinApi.Common\DouyinApi.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
303
DouyinApi.Services/Naming/BaziCalculator.cs
Normal file
303
DouyinApi.Services/Naming/BaziCalculator.cs
Normal file
@@ -0,0 +1,303 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace DouyinApi.Services.Naming;
|
||||
|
||||
internal sealed class BaziCalculator
|
||||
{
|
||||
private static readonly string[] HeavenlyStems =
|
||||
{
|
||||
"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"
|
||||
};
|
||||
|
||||
private static readonly string[] EarthlyBranches =
|
||||
{
|
||||
"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"
|
||||
};
|
||||
|
||||
private static readonly int[] MonthNumberToBranchIndex =
|
||||
{
|
||||
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 1
|
||||
};
|
||||
|
||||
private static readonly string[] ElementOrder = { "木", "火", "土", "金", "水" };
|
||||
|
||||
private static readonly Dictionary<string, string> StemElements = new()
|
||||
{
|
||||
["甲"] = "木",
|
||||
["乙"] = "木",
|
||||
["丙"] = "火",
|
||||
["丁"] = "火",
|
||||
["戊"] = "土",
|
||||
["己"] = "土",
|
||||
["庚"] = "金",
|
||||
["辛"] = "金",
|
||||
["壬"] = "水",
|
||||
["癸"] = "水"
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, string> BranchElements = new()
|
||||
{
|
||||
["子"] = "水",
|
||||
["丑"] = "土",
|
||||
["寅"] = "木",
|
||||
["卯"] = "木",
|
||||
["辰"] = "土",
|
||||
["巳"] = "火",
|
||||
["午"] = "火",
|
||||
["未"] = "土",
|
||||
["申"] = "金",
|
||||
["酉"] = "金",
|
||||
["戌"] = "土",
|
||||
["亥"] = "水"
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, int> HourStemBaseIndex = new()
|
||||
{
|
||||
["甲"] = 0,
|
||||
["己"] = 0,
|
||||
["乙"] = 2,
|
||||
["庚"] = 2,
|
||||
["丙"] = 4,
|
||||
["辛"] = 4,
|
||||
["丁"] = 6,
|
||||
["壬"] = 6,
|
||||
["戊"] = 8,
|
||||
["癸"] = 8
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, int> MonthStemBaseIndex = new()
|
||||
{
|
||||
["甲"] = 2,
|
||||
["己"] = 2,
|
||||
["乙"] = 4,
|
||||
["庚"] = 4,
|
||||
["丙"] = 6,
|
||||
["辛"] = 6,
|
||||
["丁"] = 8,
|
||||
["壬"] = 8,
|
||||
["戊"] = 0,
|
||||
["癸"] = 0
|
||||
};
|
||||
|
||||
private static readonly DateTime DayBaseDate = new(1900, 1, 31);
|
||||
private const int DayBaseIndex = 0; // 1900-01-31 对应甲子日
|
||||
|
||||
public BaziProfile Calculate(DateTime birthDateTime)
|
||||
{
|
||||
var solarYear = birthDateTime.Year;
|
||||
var currentYearSpring = new DateTime(birthDateTime.Year, 2, 4);
|
||||
if (birthDateTime < currentYearSpring)
|
||||
{
|
||||
solarYear -= 1;
|
||||
}
|
||||
|
||||
var yearStemIndex = Mod(solarYear - 4, HeavenlyStems.Length);
|
||||
var yearBranchIndex = Mod(solarYear - 4, EarthlyBranches.Length);
|
||||
|
||||
var monthNumber = ResolveMonthNumber(birthDateTime, solarYear);
|
||||
var monthStemBase = MonthStemBaseIndex[HeavenlyStems[yearStemIndex]];
|
||||
var monthStemIndex = Mod(monthStemBase + monthNumber - 1, HeavenlyStems.Length);
|
||||
var monthBranchIndex = MonthNumberToBranchIndex[monthNumber - 1];
|
||||
|
||||
var dayCycleIndex = Mod(DayBaseIndex + (int)(birthDateTime.Date - DayBaseDate).TotalDays, 60);
|
||||
var dayStemIndex = Mod(dayCycleIndex, HeavenlyStems.Length);
|
||||
var dayBranchIndex = Mod(dayCycleIndex, EarthlyBranches.Length);
|
||||
|
||||
var hourBranchIndex = GetHourBranchIndex(birthDateTime.Hour);
|
||||
var hourStemIndex = Mod(HourStemBaseIndex[HeavenlyStems[dayStemIndex]] + hourBranchIndex, HeavenlyStems.Length);
|
||||
|
||||
var yearStem = HeavenlyStems[yearStemIndex];
|
||||
var yearBranch = EarthlyBranches[yearBranchIndex];
|
||||
var monthStem = HeavenlyStems[monthStemIndex];
|
||||
var monthBranch = EarthlyBranches[monthBranchIndex];
|
||||
var dayStem = HeavenlyStems[dayStemIndex];
|
||||
var dayBranch = EarthlyBranches[dayBranchIndex];
|
||||
var hourStem = HeavenlyStems[hourStemIndex];
|
||||
var hourBranch = EarthlyBranches[hourBranchIndex];
|
||||
|
||||
var elementCounts = ElementOrder.ToDictionary(element => element, _ => 0);
|
||||
IncrementElement(elementCounts, StemElements[yearStem]);
|
||||
IncrementElement(elementCounts, BranchElements[yearBranch]);
|
||||
IncrementElement(elementCounts, StemElements[monthStem]);
|
||||
IncrementElement(elementCounts, BranchElements[monthBranch]);
|
||||
IncrementElement(elementCounts, StemElements[dayStem]);
|
||||
IncrementElement(elementCounts, BranchElements[dayBranch]);
|
||||
IncrementElement(elementCounts, StemElements[hourStem]);
|
||||
IncrementElement(elementCounts, BranchElements[hourBranch]);
|
||||
|
||||
var counts = ElementOrder
|
||||
.Select(element => new KeyValuePair<string, int>(element, elementCounts[element]))
|
||||
.ToList();
|
||||
|
||||
var max = counts.Max(pair => pair.Value);
|
||||
var min = counts.Min(pair => pair.Value);
|
||||
|
||||
var isBalanced = max - min <= 1;
|
||||
var weakElements = isBalanced
|
||||
? Array.Empty<string>()
|
||||
: counts.Where(pair => pair.Value == min).Select(pair => pair.Key).ToArray();
|
||||
var strongElements = counts.Where(pair => pair.Value == max).Select(pair => pair.Key).ToArray();
|
||||
|
||||
return new BaziProfile(
|
||||
yearStem,
|
||||
yearBranch,
|
||||
monthStem,
|
||||
monthBranch,
|
||||
dayStem,
|
||||
dayBranch,
|
||||
hourStem,
|
||||
hourBranch,
|
||||
elementCounts,
|
||||
ElementOrder,
|
||||
weakElements,
|
||||
strongElements,
|
||||
isBalanced);
|
||||
}
|
||||
|
||||
private static int ResolveMonthNumber(DateTime dt, int solarYear)
|
||||
{
|
||||
var springStart = new DateTime(solarYear, 2, 4);
|
||||
var jingZhe = new DateTime(solarYear, 3, 6);
|
||||
var qingMing = new DateTime(solarYear, 4, 5);
|
||||
var liXia = new DateTime(solarYear, 5, 5);
|
||||
var mangZhong = new DateTime(solarYear, 6, 6);
|
||||
var xiaoShu = new DateTime(solarYear, 7, 7);
|
||||
var liQiu = new DateTime(solarYear, 8, 8);
|
||||
var baiLu = new DateTime(solarYear, 9, 8);
|
||||
var hanLu = new DateTime(solarYear, 10, 8);
|
||||
var liDong = new DateTime(solarYear, 11, 7);
|
||||
var daXue = new DateTime(solarYear, 12, 7);
|
||||
var xiaoHan = new DateTime(solarYear + 1, 1, 6);
|
||||
var nextSpring = new DateTime(solarYear + 1, 2, 4);
|
||||
|
||||
if (dt >= springStart && dt < jingZhe)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if (dt >= jingZhe && dt < qingMing)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
if (dt >= qingMing && dt < liXia)
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
if (dt >= liXia && dt < mangZhong)
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
if (dt >= mangZhong && dt < xiaoShu)
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
if (dt >= xiaoShu && dt < liQiu)
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
if (dt >= liQiu && dt < baiLu)
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
if (dt >= baiLu && dt < hanLu)
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
if (dt >= hanLu && dt < liDong)
|
||||
{
|
||||
return 9;
|
||||
}
|
||||
if (dt >= liDong && dt < daXue)
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
if (dt >= daXue && dt < xiaoHan)
|
||||
{
|
||||
return 11;
|
||||
}
|
||||
if (dt >= xiaoHan && dt < nextSpring)
|
||||
{
|
||||
return 12;
|
||||
}
|
||||
return 11;
|
||||
}
|
||||
|
||||
private static int GetHourBranchIndex(int hour)
|
||||
{
|
||||
return ((hour + 1) / 2) % EarthlyBranches.Length;
|
||||
}
|
||||
|
||||
private static void IncrementElement(IDictionary<string, int> container, string element)
|
||||
{
|
||||
if (container.TryGetValue(element, out var value))
|
||||
{
|
||||
container[element] = value + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static int Mod(int value, int modulus)
|
||||
{
|
||||
var result = value % modulus;
|
||||
return result < 0 ? result + modulus : result;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class BaziProfile
|
||||
{
|
||||
public BaziProfile(
|
||||
string yearStem,
|
||||
string yearBranch,
|
||||
string monthStem,
|
||||
string monthBranch,
|
||||
string dayStem,
|
||||
string dayBranch,
|
||||
string hourStem,
|
||||
string hourBranch,
|
||||
IReadOnlyDictionary<string, int> elementCounts,
|
||||
IReadOnlyList<string> elementOrder,
|
||||
IReadOnlyList<string> weakElements,
|
||||
IReadOnlyList<string> strongElements,
|
||||
bool isBalanced)
|
||||
{
|
||||
YearStem = yearStem;
|
||||
YearBranch = yearBranch;
|
||||
MonthStem = monthStem;
|
||||
MonthBranch = monthBranch;
|
||||
DayStem = dayStem;
|
||||
DayBranch = dayBranch;
|
||||
HourStem = hourStem;
|
||||
HourBranch = hourBranch;
|
||||
ElementCounts = elementCounts;
|
||||
ElementOrder = elementOrder;
|
||||
WeakElements = weakElements;
|
||||
StrongElements = strongElements;
|
||||
IsBalanced = isBalanced;
|
||||
}
|
||||
|
||||
public string YearStem { get; }
|
||||
|
||||
public string YearBranch { get; }
|
||||
|
||||
public string MonthStem { get; }
|
||||
|
||||
public string MonthBranch { get; }
|
||||
|
||||
public string DayStem { get; }
|
||||
|
||||
public string DayBranch { get; }
|
||||
|
||||
public string HourStem { get; }
|
||||
|
||||
public string HourBranch { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, int> ElementCounts { get; }
|
||||
|
||||
public IReadOnlyList<string> ElementOrder { get; }
|
||||
|
||||
public IReadOnlyList<string> WeakElements { get; }
|
||||
|
||||
public IReadOnlyList<string> StrongElements { get; }
|
||||
|
||||
public bool IsBalanced { get; }
|
||||
}
|
||||
290
DouyinApi.Services/Naming/FallbackNameGenerator.cs
Normal file
290
DouyinApi.Services/Naming/FallbackNameGenerator.cs
Normal file
@@ -0,0 +1,290 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using DouyinApi.Model.Naming;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace DouyinApi.Services.Naming;
|
||||
|
||||
internal sealed class FallbackNameGenerator
|
||||
{
|
||||
private const int TargetCount = 5;
|
||||
|
||||
private static readonly string[] ElementOrder = { "木", "火", "土", "金", "水" };
|
||||
|
||||
private readonly CharacterLibrary _library = new();
|
||||
|
||||
public IReadOnlyList<NamingSuggestion> Generate(NamingRequest request, BaziProfile profile)
|
||||
{
|
||||
var surname = request.Surname;
|
||||
var isSingle = string.Equals(request.NameLength, "single", StringComparison.OrdinalIgnoreCase);
|
||||
var gender = NormalizeGender(request.Gender);
|
||||
|
||||
var results = new List<NamingSuggestion>();
|
||||
var usedCharacters = new HashSet<string>(StringComparer.Ordinal);
|
||||
var usedNames = new HashSet<string>(StringComparer.Ordinal);
|
||||
var sequence = BuildElementSequence(profile);
|
||||
|
||||
var attempts = 0;
|
||||
while (results.Count < TargetCount && attempts < 80)
|
||||
{
|
||||
var primaryElement = sequence[attempts % sequence.Count];
|
||||
var first = _library.Pick(primaryElement, gender, usedCharacters);
|
||||
if (first == null)
|
||||
{
|
||||
attempts++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isSingle)
|
||||
{
|
||||
var fullName = surname + first.Character;
|
||||
if (usedNames.Add(fullName))
|
||||
{
|
||||
results.Add(new NamingSuggestion
|
||||
{
|
||||
Name = fullName,
|
||||
Meaning = first.Meaning,
|
||||
ElementReason = BuildSingleReason(profile, first)
|
||||
});
|
||||
usedCharacters.Add(first.Character);
|
||||
}
|
||||
|
||||
attempts++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var tempUsed = new HashSet<string>(usedCharacters, StringComparer.Ordinal)
|
||||
{
|
||||
first.Character
|
||||
};
|
||||
|
||||
var secondaryElement = sequence[(attempts + 1) % sequence.Count];
|
||||
var second = _library.Pick(secondaryElement, gender, tempUsed);
|
||||
if (second == null)
|
||||
{
|
||||
attempts++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var givenName = first.Character + second.Character;
|
||||
var full = surname + givenName;
|
||||
if (usedNames.Add(full))
|
||||
{
|
||||
results.Add(new NamingSuggestion
|
||||
{
|
||||
Name = full,
|
||||
Meaning = $"{first.Meaning},{second.Meaning}",
|
||||
ElementReason = BuildDoubleReason(profile, first, second)
|
||||
});
|
||||
usedCharacters.Add(first.Character);
|
||||
usedCharacters.Add(second.Character);
|
||||
}
|
||||
|
||||
attempts++;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static string NormalizeGender(string gender)
|
||||
{
|
||||
return string.Equals(gender, "female", StringComparison.OrdinalIgnoreCase) ? "female" : "male";
|
||||
}
|
||||
|
||||
private static List<string> BuildElementSequence(BaziProfile profile)
|
||||
{
|
||||
var sequence = new List<string>();
|
||||
if (profile.WeakElements.Count > 0)
|
||||
{
|
||||
sequence.AddRange(profile.WeakElements);
|
||||
}
|
||||
|
||||
foreach (var element in ElementOrder)
|
||||
{
|
||||
if (!sequence.Contains(element))
|
||||
{
|
||||
sequence.Add(element);
|
||||
}
|
||||
}
|
||||
|
||||
return sequence;
|
||||
}
|
||||
|
||||
private static string BuildSingleReason(BaziProfile profile, CharacterProfile profileChar)
|
||||
{
|
||||
if (!profile.IsBalanced && profile.WeakElements.Contains(profileChar.Element))
|
||||
{
|
||||
return $"命局中{profileChar.Element}势偏弱,选用同属性的“{profileChar.Character}”以补足生机。";
|
||||
}
|
||||
|
||||
return $"“{profileChar.Character}”属{profileChar.Element},与命局相协,凸显个人气韵。";
|
||||
}
|
||||
|
||||
private static string BuildDoubleReason(BaziProfile profile, CharacterProfile first, CharacterProfile second)
|
||||
{
|
||||
var clauses = new List<string>();
|
||||
|
||||
if (!profile.IsBalanced && profile.WeakElements.Contains(first.Element))
|
||||
{
|
||||
clauses.Add($"命局中{first.Element}势弱,首取“{first.Character}”以增添此行之力");
|
||||
}
|
||||
else
|
||||
{
|
||||
clauses.Add($"“{first.Character}”属{first.Element},奠定名字的核心气场");
|
||||
}
|
||||
|
||||
if (first.Element == second.Element)
|
||||
{
|
||||
clauses.Add($"再以同为{second.Element}属性的“{second.Character}”相辅,巩固能量");
|
||||
}
|
||||
else if (!profile.IsBalanced && profile.WeakElements.Contains(second.Element))
|
||||
{
|
||||
clauses.Add($"辅以{second.Element}属性“{second.Character}”同步补益");
|
||||
}
|
||||
else
|
||||
{
|
||||
clauses.Add($"叠加{second.Element}属性“{second.Character}”,使五行流转更趋和谐");
|
||||
}
|
||||
|
||||
return string.Join(",", clauses) + "。";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class CharacterLibrary
|
||||
{
|
||||
private readonly Dictionary<string, List<CharacterProfile>> _profiles;
|
||||
|
||||
public CharacterLibrary()
|
||||
{
|
||||
_profiles = new Dictionary<string, List<CharacterProfile>>(StringComparer.Ordinal)
|
||||
{
|
||||
["木"] = new List<CharacterProfile>
|
||||
{
|
||||
new("林", "木", 8, "林木森郁,生机盎然", GenderAffinity.Neutral),
|
||||
new("杉", "木", 7, "杉木挺拔,坚韧自持", GenderAffinity.Male),
|
||||
new("柏", "木", 9, "柏树常青,守正不渝", GenderAffinity.Male),
|
||||
new("桐", "木", 10, "梧桐清雅,自带高洁", GenderAffinity.Neutral),
|
||||
new("柯", "木", 9, "柯木坚劲,胸怀正义", GenderAffinity.Male),
|
||||
new("梓", "木", 11, "梓木葱郁,延续家风", GenderAffinity.Neutral),
|
||||
new("芸", "木", 10, "芸草幽香,温婉灵动", GenderAffinity.Female),
|
||||
new("茜", "木", 9, "茜草明艳,朝气蓬勃", GenderAffinity.Female)
|
||||
},
|
||||
["火"] = new List<CharacterProfile>
|
||||
{
|
||||
new("炜", "火", 9, "炜火明亮,奋发向上", GenderAffinity.Male),
|
||||
new("炎", "火", 8, "炎光熠熠,激情澎湃", GenderAffinity.Male),
|
||||
new("晗", "火", 11, "晨晗初照,温暖柔和", GenderAffinity.Female),
|
||||
new("晟", "火", 11, "晟意光盛,事业昌隆", GenderAffinity.Male),
|
||||
new("旭", "火", 6, "旭日东升,胸怀朝阳", GenderAffinity.Neutral),
|
||||
new("烁", "火", 10, "烁光璀璨,灵动敏捷", GenderAffinity.Neutral),
|
||||
new("炫", "火", 9, "炫彩熠熠,才华外露", GenderAffinity.Neutral)
|
||||
},
|
||||
["土"] = new List<CharacterProfile>
|
||||
{
|
||||
new("坤", "土", 8, "坤厚载物,包容从容", GenderAffinity.Neutral),
|
||||
new("均", "土", 7, "均衡端稳,踏实可靠", GenderAffinity.Neutral),
|
||||
new("垣", "土", 9, "垣墙坚实,守护安宁", GenderAffinity.Neutral),
|
||||
new("城", "土", 9, "城池稳固,守正不移", GenderAffinity.Male),
|
||||
new("岳", "土", 8, "山岳巍峨,志向高远", GenderAffinity.Male),
|
||||
new("培", "土", 11, "培土厚植,涵养成长", GenderAffinity.Neutral),
|
||||
new("堇", "土", 12, "堇色沉静,内敛温润", GenderAffinity.Female),
|
||||
new("坦", "土", 8, "坦荡诚笃,胸怀光明", GenderAffinity.Male)
|
||||
},
|
||||
["金"] = new List<CharacterProfile>
|
||||
{
|
||||
new("钧", "金", 9, "钧衡公正,持中守衡", GenderAffinity.Neutral),
|
||||
new("铭", "金", 11, "铭记初心,彰显才华", GenderAffinity.Neutral),
|
||||
new("锐", "金", 11, "锋芒锐利,果敢决断", GenderAffinity.Male),
|
||||
new("钦", "金", 9, "钦敬谦和,端方稳重", GenderAffinity.Neutral),
|
||||
new("钥", "金", 9, "钥启智慧,开拓视野", GenderAffinity.Neutral),
|
||||
new("铠", "金", 12, "铠甲护身,坚毅勇敢", GenderAffinity.Male),
|
||||
new("钰", "金", 10, "钰石珍贵,坚贞明亮", GenderAffinity.Female)
|
||||
},
|
||||
["水"] = new List<CharacterProfile>
|
||||
{
|
||||
new("泽", "水", 9, "泽润万物,胸怀宽广", GenderAffinity.Neutral),
|
||||
new("泓", "水", 8, "泓水清澈,心境澄明", GenderAffinity.Neutral),
|
||||
new("润", "水", 10, "润泽柔润,涵养细腻", GenderAffinity.Female),
|
||||
new("涵", "水", 11, "涵容自守,温婉内敛", GenderAffinity.Female),
|
||||
new("沐", "水", 7, "沐浴春风,清新怡然", GenderAffinity.Neutral),
|
||||
new("泊", "水", 8, "泊舟安然,沉稳淡泊", GenderAffinity.Male),
|
||||
new("洵", "水", 9, "洵美诚笃,信义笃实", GenderAffinity.Male),
|
||||
new("沁", "水", 8, "沁香流淌,气质清雅", GenderAffinity.Female)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public CharacterProfile? Pick(string element, string gender, HashSet<string> usedCharacters)
|
||||
{
|
||||
if (!_profiles.TryGetValue(element, out var candidates))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 优先匹配性别偏好
|
||||
foreach (var profile in candidates)
|
||||
{
|
||||
if (usedCharacters.Contains(profile.Character))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (profile.Affinity == GenderAffinity.Neutral ||
|
||||
(profile.Affinity == GenderAffinity.Male && gender == "male") ||
|
||||
(profile.Affinity == GenderAffinity.Female && gender == "female"))
|
||||
{
|
||||
if (profile.StrokeCount <= 12)
|
||||
{
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 兜底选择中性字
|
||||
foreach (var profile in candidates)
|
||||
{
|
||||
if (usedCharacters.Contains(profile.Character))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (profile.Affinity == GenderAffinity.Neutral && profile.StrokeCount <= 12)
|
||||
{
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class CharacterProfile
|
||||
{
|
||||
public CharacterProfile(string character, string element, int strokeCount, string meaning, GenderAffinity affinity)
|
||||
{
|
||||
Character = character;
|
||||
Element = element;
|
||||
StrokeCount = strokeCount;
|
||||
Meaning = meaning;
|
||||
Affinity = affinity;
|
||||
}
|
||||
|
||||
public string Character { get; }
|
||||
|
||||
public string Element { get; }
|
||||
|
||||
public int StrokeCount { get; }
|
||||
|
||||
public string Meaning { get; }
|
||||
|
||||
public GenderAffinity Affinity { get; }
|
||||
}
|
||||
|
||||
internal enum GenderAffinity
|
||||
{
|
||||
Neutral,
|
||||
Male,
|
||||
Female
|
||||
}
|
||||
@@ -1,204 +1,350 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using DouyinApi.Common.Option;
|
||||
using DouyinApi.IServices;
|
||||
using DouyinApi.Model.Naming;
|
||||
using DouyinApi.Services.Naming;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace DouyinApi.Services
|
||||
namespace DouyinApi.Services;
|
||||
|
||||
public class NamingService : INamingService
|
||||
{
|
||||
public class NamingService : INamingService
|
||||
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)
|
||||
{
|
||||
private static readonly Regex SurnameRegex = new(@"^[\u4e00-\u9fa5]{1,2}$", RegexOptions.Compiled);
|
||||
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
|
||||
_deepSeekOptions = deepSeekOptions?.Value ?? new DeepSeekOptions();
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
private static readonly IReadOnlyList<char> MaleCharacters = new[]
|
||||
public Task<bool> ValidateSurnameAsync(string surname)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(surname))
|
||||
{
|
||||
'辰', '昇', '曜', '瀚', '宸', '沐', '景', '霖', '晟', '玥', '骁', '煜', '澜', '珩', '聿', '澄', '钧'
|
||||
};
|
||||
|
||||
private static readonly IReadOnlyList<char> FemaleCharacters = new[]
|
||||
{
|
||||
'瑶', '霏', '婉', '绮', '璇', '芷', '乂', '灵', '沁', '语', '晴', '若', '绫', '芸', '络', '梦', '澜'
|
||||
};
|
||||
|
||||
private static readonly IReadOnlyList<char> NeutralCharacters = new[]
|
||||
{
|
||||
'玄', '洛', '岚', '澈', '岑', '泓', '澜', '烨', '闻', '黎', '墨', '夙', '羲', '霆', '渊', '翎'
|
||||
};
|
||||
|
||||
private static readonly Dictionary<char, string> CharacterMeanings = new()
|
||||
{
|
||||
['辰'] = "辰曜星光",
|
||||
['昇'] = "旭日东升",
|
||||
['曜'] = "光耀万里",
|
||||
['瀚'] = "瀚海无垠",
|
||||
['宸'] = "王者气度",
|
||||
['沐'] = "沐浴祥瑞",
|
||||
['景'] = "景行德高",
|
||||
['霖'] = "甘霖润泽",
|
||||
['晟'] = "光明昌盛",
|
||||
['玥'] = "王者之玉",
|
||||
['骁'] = "英勇不屈",
|
||||
['煜'] = "照耀四方",
|
||||
['澜'] = "碧波浩渺",
|
||||
['珩'] = "璞玉内敛",
|
||||
['聿'] = "持守正道",
|
||||
['澄'] = "心境澄明",
|
||||
['钧'] = "乾坤平衡",
|
||||
['瑶'] = "瑶光琼华",
|
||||
['霏'] = "霏霏瑞雪",
|
||||
['婉'] = "柔婉清扬",
|
||||
['绮'] = "绮丽灵动",
|
||||
['璇'] = "璇玑回转",
|
||||
['芷'] = "香草高洁",
|
||||
['乂'] = "安然有序",
|
||||
['灵'] = "灵秀夺目",
|
||||
['沁'] = "沁心甘露",
|
||||
['语'] = "言语有光",
|
||||
['晴'] = "晴空暖阳",
|
||||
['若'] = "若水明澈",
|
||||
['绫'] = "绫罗轻盈",
|
||||
['芸'] = "芸芸芳草",
|
||||
['络'] = "络绎华彩",
|
||||
['梦'] = "梦想成真",
|
||||
['玄'] = "玄妙莫测",
|
||||
['洛'] = "洛水灵韵",
|
||||
['岚'] = "山岚清气",
|
||||
['澈'] = "心境通透",
|
||||
['岑'] = "峻岭深沉",
|
||||
['泓'] = "泓泉清澈",
|
||||
['烨'] = "火光通明",
|
||||
['闻'] = "名闻四海",
|
||||
['黎'] = "黎明曙光",
|
||||
['墨'] = "墨香书韵",
|
||||
['夙'] = "夙愿成真",
|
||||
['羲'] = "伏羲灵息",
|
||||
['霆'] = "雷霆万钧",
|
||||
['渊'] = "渊源深厚",
|
||||
['翎'] = "翎羽轻灵"
|
||||
};
|
||||
|
||||
private static readonly string[] Blessings =
|
||||
{
|
||||
"引瑞气入怀,扶摇直上",
|
||||
"承先祖之德,守家风之和",
|
||||
"与四时共鸣,步步生辉",
|
||||
"纳天地灵气,行稳致远",
|
||||
"凝万象之精魄,护佑昌隆",
|
||||
"藏锋于怀,静待时来",
|
||||
"兼济天下情怀,拥抱广阔未来"
|
||||
};
|
||||
|
||||
public Task<bool> ValidateSurnameAsync(string surname)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(surname))
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
return Task.FromResult(SurnameRegex.IsMatch(surname.Trim()));
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
public async Task<NamingResponse> GenerateNamesAsync(NamingRequest request)
|
||||
return Task.FromResult(SurnamePattern.IsMatch(surname.Trim()));
|
||||
}
|
||||
|
||||
public async Task<NamingResponse> GenerateNamesAsync(NamingRequest request)
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
if (request == null)
|
||||
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())
|
||||
{
|
||||
suggestions = _fallbackGenerator.Generate(request, baziProfile);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
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())
|
||||
{
|
||||
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>();
|
||||
}
|
||||
|
||||
if (!await ValidateSurnameAsync(request.Surname))
|
||||
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
|
||||
{
|
||||
throw new ArgumentException("invalid_surname");
|
||||
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
|
||||
});
|
||||
|
||||
var lengthDescriptor = string.Equals(request.NameLength, "single", StringComparison.OrdinalIgnoreCase) ? "单字" : "双字";
|
||||
var genderLabel = string.Equals(request.Gender, "female", StringComparison.OrdinalIgnoreCase) ? "女孩" : "男孩";
|
||||
|
||||
var userPrompt = new StringBuilder();
|
||||
userPrompt.AppendLine("请扮演专业的中华姓名学顾问,根据八字五行提出姓名建议。");
|
||||
userPrompt.AppendLine($"需基于以下 JSON 数据为姓氏“{request.Surname}”的{genderLabel}提供 5 个{lengthDescriptor}中文名字:");
|
||||
userPrompt.AppendLine(contextJson);
|
||||
userPrompt.AppendLine("要求:");
|
||||
userPrompt.AppendLine("1. 每条建议输出对象包含 name、meaning、fiveElementReason 三个字段;");
|
||||
userPrompt.AppendLine("2. 所有汉字需为生活常用的简体字,单字笔画尽量小于等于 12;");
|
||||
userPrompt.AppendLine("3. name 必须包含姓氏且满足给定的字数;");
|
||||
userPrompt.AppendLine("4. fiveElementReason 需指明所补足的五行依据。");
|
||||
userPrompt.AppendLine("请仅返回 JSON 数据,格式为 {\"suggestions\":[{...}]},不要附加额外说明。");
|
||||
|
||||
var requestBody = new
|
||||
{
|
||||
model = _deepSeekOptions.Model,
|
||||
temperature = _deepSeekOptions.Temperature,
|
||||
max_tokens = _deepSeekOptions.MaxTokens,
|
||||
messages = new[]
|
||||
{
|
||||
new { role = "system", content = "你是一名资深的中文姓名学专家,擅长根据八字喜用神给出实用建议。" },
|
||||
new { role = "user", content = userPrompt.ToString() }
|
||||
}
|
||||
};
|
||||
|
||||
var message = new HttpRequestMessage(HttpMethod.Post, endpoint)
|
||||
{
|
||||
Content = new StringContent(JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json")
|
||||
};
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private IReadOnlyList<NamingSuggestion> ParseDeepSeekResponse(string payload, NamingRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var document = JsonDocument.Parse(payload);
|
||||
var choices = document.RootElement.GetProperty("choices");
|
||||
if (choices.ValueKind != JsonValueKind.Array || choices.GetArrayLength() == 0)
|
||||
{
|
||||
return Array.Empty<NamingSuggestion>();
|
||||
}
|
||||
|
||||
var seed = CreateSeed(request);
|
||||
var random = new Random(seed);
|
||||
var pool = request.Gender == "female" ? FemaleCharacters : MaleCharacters;
|
||||
var fallbackPool = NeutralCharacters;
|
||||
var content = choices[0]
|
||||
.GetProperty("message")
|
||||
.GetProperty("content")
|
||||
.GetString();
|
||||
|
||||
var suggestions = new List<NamingSuggestion>();
|
||||
var attempts = 0;
|
||||
|
||||
while (suggestions.Count < 5 && attempts < 100)
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
attempts++;
|
||||
var given = GenerateGivenName(request.NameLength, pool, fallbackPool, random);
|
||||
var fullName = $"{request.Surname}{given}";
|
||||
if (suggestions.Any(s => s.Name == fullName))
|
||||
return Array.Empty<NamingSuggestion>();
|
||||
}
|
||||
|
||||
using var suggestionsDoc = JsonDocument.Parse(content);
|
||||
if (!suggestionsDoc.RootElement.TryGetProperty("suggestions", out var suggestionsElement) ||
|
||||
suggestionsElement.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
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))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var meaning = ComposeMeaning(request.Surname, given, request.BirthDate, request.BirthTime, random);
|
||||
suggestions.Add(new NamingSuggestion
|
||||
name = name.Trim();
|
||||
if (!name.StartsWith(request.Surname, StringComparison.Ordinal))
|
||||
{
|
||||
Name = fullName,
|
||||
Meaning = meaning
|
||||
});
|
||||
}
|
||||
|
||||
if (!suggestions.Any())
|
||||
{
|
||||
suggestions.Add(new NamingSuggestion
|
||||
{
|
||||
Name = $"{request.Surname}玄珏",
|
||||
Meaning = "玄珠映月,珏石生辉,与生辰相应,寓意行稳致远"
|
||||
});
|
||||
}
|
||||
|
||||
return new NamingResponse
|
||||
{
|
||||
Results = suggestions
|
||||
};
|
||||
}
|
||||
|
||||
private static int CreateSeed(NamingRequest request)
|
||||
{
|
||||
var raw = $"{request.Surname}-{request.Gender}-{request.BirthDate}-{request.BirthTime}-{request.NameLength}";
|
||||
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(raw));
|
||||
return Math.Abs(BitConverter.ToInt32(bytes, 0));
|
||||
}
|
||||
|
||||
private static string GenerateGivenName(
|
||||
string nameLength,
|
||||
IReadOnlyList<char> primaryPool,
|
||||
IReadOnlyList<char> fallbackPool,
|
||||
Random random)
|
||||
{
|
||||
var buffer = new StringBuilder();
|
||||
var length = nameLength == "single" ? 1 : 2;
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
var pool = (i == 0) ? primaryPool : fallbackPool;
|
||||
buffer.Append(pool[random.Next(pool.Count)]);
|
||||
}
|
||||
return buffer.ToString();
|
||||
}
|
||||
|
||||
private static string ComposeMeaning(string surname, string given, string birthDate, string birthTime, Random random)
|
||||
{
|
||||
var fragments = new List<string>();
|
||||
foreach (var ch in given)
|
||||
{
|
||||
if (CharacterMeanings.TryGetValue(ch, out var desc))
|
||||
{
|
||||
fragments.Add(desc);
|
||||
name = $"{request.Surname}{name}";
|
||||
}
|
||||
else
|
||||
|
||||
if (!ValidateNameLength(name, request))
|
||||
{
|
||||
fragments.Add($"{ch}寓意祥瑞");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!uniqueNames.Add(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
normalized.Add(new NamingSuggestion
|
||||
{
|
||||
Name = name,
|
||||
Meaning = string.IsNullOrWhiteSpace(meaning) ? "寓意待补充" : meaning.Trim(),
|
||||
ElementReason = string.IsNullOrWhiteSpace(reason) ? "结合八字五行进行补益。" : reason.Trim()
|
||||
});
|
||||
|
||||
if (normalized.Count >= 5)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var blessing = Blessings[random.Next(Blessings.Length)];
|
||||
var birthFragment = string.IsNullOrWhiteSpace(birthTime)
|
||||
? $"{birthDate}之辰"
|
||||
: $"{birthDate} {birthTime} 时刻";
|
||||
|
||||
return $"{string.Join(",", fragments)},与{surname}氏气脉相连,于{birthFragment}呼应,{blessing}。";
|
||||
return normalized;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "解析 DeepSeek 响应失败");
|
||||
return Array.Empty<NamingSuggestion>();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user