diff --git a/DouyinApi.Api/Controllers/MiniProgram/NamingController.cs b/DouyinApi.Api/Controllers/MiniProgram/NamingController.cs new file mode 100644 index 0000000..cc72070 --- /dev/null +++ b/DouyinApi.Api/Controllers/MiniProgram/NamingController.cs @@ -0,0 +1,114 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using DouyinApi.Controllers; +using DouyinApi.IServices; +using DouyinApi.Model.Naming; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace DouyinApi.Api.Controllers.MiniProgram +{ + [Produces("application/json")] + [Route("api/naming")] + [AllowAnonymous] + public class NamingController : BaseApiController + { + private readonly INamingService _namingService; + private readonly ILogger _logger; + + public NamingController(INamingService namingService, ILogger logger) + { + _namingService = namingService; + _logger = logger; + } + + [HttpPost("validate-surname")] + public async Task ValidateSurname([FromBody] SurnameValidationRequest request) + { + if (!ModelState.IsValid) + { + return Ok(new { isValid = false, message = "请输入合法姓氏" }); + } + + var surname = request.Surname?.Trim() ?? string.Empty; + var isValid = await _namingService.ValidateSurnameAsync(surname); + return Ok(new + { + isValid, + message = isValid ? string.Empty : "请输入合法姓氏" + }); + } + + [HttpPost("generate")] + public async Task Generate([FromBody] NamingRequest request) + { + if (!ModelState.IsValid) + { + return BadRequest(new { message = "参数不合法" }); + } + + var normalizedRequest = NormalizeRequest(request); + + try + { + var response = await _namingService.GenerateNamesAsync(normalizedRequest); + return Ok(new { results = response.Results }); + } + catch (ArgumentException ex) when (ex.Message == "invalid_surname") + { + return BadRequest(new { message = "INVALID_SURNAME" }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Generate naming failed"); + return StatusCode(500, new { message = "GENERATION_FAILED" }); + } + } + + private static NamingRequest NormalizeRequest(NamingRequest request) + { + var normalized = new NamingRequest + { + Surname = request.Surname?.Trim() ?? string.Empty, + Gender = string.IsNullOrWhiteSpace(request.Gender) ? "male" : request.Gender.Trim().ToLowerInvariant(), + NameLength = string.IsNullOrWhiteSpace(request.NameLength) ? "double" : request.NameLength.Trim().ToLowerInvariant(), + BirthDate = NormalizeDate(request.BirthDate), + BirthTime = NormalizeTime(request.BirthTime) + }; + + return normalized; + } + + private static string NormalizeDate(string date) + { + if (string.IsNullOrWhiteSpace(date)) + { + return DateTime.UtcNow.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); + } + + if (DateTime.TryParse(date, out var parsed)) + { + return parsed.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); + } + + return date; + } + + private static string NormalizeTime(string time) + { + if (string.IsNullOrWhiteSpace(time)) + { + return string.Empty; + } + + if (TimeSpan.TryParse(time, out var parsed)) + { + return DateTime.Today.Add(parsed).ToString("HH:mm", CultureInfo.InvariantCulture); + } + + return time; + } + } +} diff --git a/DouyinApi.IServices/INamingService.cs b/DouyinApi.IServices/INamingService.cs new file mode 100644 index 0000000..cd5a683 --- /dev/null +++ b/DouyinApi.IServices/INamingService.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using DouyinApi.Model.Naming; + +namespace DouyinApi.IServices +{ + public interface INamingService + { + Task ValidateSurnameAsync(string surname); + + Task GenerateNamesAsync(NamingRequest request); + } +} diff --git a/DouyinApi.Model/Naming/NamingRequest.cs b/DouyinApi.Model/Naming/NamingRequest.cs new file mode 100644 index 0000000..2d64538 --- /dev/null +++ b/DouyinApi.Model/Naming/NamingRequest.cs @@ -0,0 +1,26 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace DouyinApi.Model.Naming +{ + public class NamingRequest + { + [Required] + [MinLength(1)] + [MaxLength(2)] + public string Surname { get; set; } = string.Empty; + + [Required] + [RegularExpression("male|female", ErrorMessage = "gender_invalid")] + public string Gender { get; set; } = "male"; + + [Required] + public string BirthDate { get; set; } = string.Empty; + + public string BirthTime { get; set; } = string.Empty; + + [Required] + [RegularExpression("single|double", ErrorMessage = "name_length_invalid")] + public string NameLength { get; set; } = "double"; + } +} diff --git a/DouyinApi.Model/Naming/NamingResponse.cs b/DouyinApi.Model/Naming/NamingResponse.cs new file mode 100644 index 0000000..1fcf4b1 --- /dev/null +++ b/DouyinApi.Model/Naming/NamingResponse.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace DouyinApi.Model.Naming +{ + public class NamingResponse + { + public IEnumerable Results { get; set; } = new List(); + } + + public class NamingSuggestion + { + public string Name { get; set; } = string.Empty; + + public string Meaning { get; set; } = string.Empty; + } +} diff --git a/DouyinApi.Model/Naming/SurnameValidationRequest.cs b/DouyinApi.Model/Naming/SurnameValidationRequest.cs new file mode 100644 index 0000000..b01f573 --- /dev/null +++ b/DouyinApi.Model/Naming/SurnameValidationRequest.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace DouyinApi.Model.Naming +{ + public class SurnameValidationRequest + { + [Required] + [MinLength(1)] + [MaxLength(2)] + public string Surname { get; set; } = string.Empty; + } +} diff --git a/DouyinApi.Services/NamingService.cs b/DouyinApi.Services/NamingService.cs new file mode 100644 index 0000000..affa4b6 --- /dev/null +++ b/DouyinApi.Services/NamingService.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using DouyinApi.IServices; +using DouyinApi.Model.Naming; + +namespace DouyinApi.Services +{ + public class NamingService : INamingService + { + private static readonly Regex SurnameRegex = new(@"^[\u4e00-\u9fa5]{1,2}$", RegexOptions.Compiled); + + private static readonly IReadOnlyList MaleCharacters = new[] + { + '辰', '昇', '曜', '瀚', '宸', '沐', '景', '霖', '晟', '玥', '骁', '煜', '澜', '珩', '聿', '澄', '钧' + }; + + private static readonly IReadOnlyList FemaleCharacters = new[] + { + '瑶', '霏', '婉', '绮', '璇', '芷', '乂', '灵', '沁', '语', '晴', '若', '绫', '芸', '络', '梦', '澜' + }; + + private static readonly IReadOnlyList NeutralCharacters = new[] + { + '玄', '洛', '岚', '澈', '岑', '泓', '澜', '烨', '闻', '黎', '墨', '夙', '羲', '霆', '渊', '翎' + }; + + private static readonly Dictionary CharacterMeanings = new() + { + ['辰'] = "辰曜星光", + ['昇'] = "旭日东升", + ['曜'] = "光耀万里", + ['瀚'] = "瀚海无垠", + ['宸'] = "王者气度", + ['沐'] = "沐浴祥瑞", + ['景'] = "景行德高", + ['霖'] = "甘霖润泽", + ['晟'] = "光明昌盛", + ['玥'] = "王者之玉", + ['骁'] = "英勇不屈", + ['煜'] = "照耀四方", + ['澜'] = "碧波浩渺", + ['珩'] = "璞玉内敛", + ['聿'] = "持守正道", + ['澄'] = "心境澄明", + ['钧'] = "乾坤平衡", + ['瑶'] = "瑶光琼华", + ['霏'] = "霏霏瑞雪", + ['婉'] = "柔婉清扬", + ['绮'] = "绮丽灵动", + ['璇'] = "璇玑回转", + ['芷'] = "香草高洁", + ['乂'] = "安然有序", + ['灵'] = "灵秀夺目", + ['沁'] = "沁心甘露", + ['语'] = "言语有光", + ['晴'] = "晴空暖阳", + ['若'] = "若水明澈", + ['绫'] = "绫罗轻盈", + ['芸'] = "芸芸芳草", + ['络'] = "络绎华彩", + ['梦'] = "梦想成真", + ['玄'] = "玄妙莫测", + ['洛'] = "洛水灵韵", + ['岚'] = "山岚清气", + ['澈'] = "心境通透", + ['岑'] = "峻岭深沉", + ['泓'] = "泓泉清澈", + ['烨'] = "火光通明", + ['闻'] = "名闻四海", + ['黎'] = "黎明曙光", + ['墨'] = "墨香书韵", + ['夙'] = "夙愿成真", + ['羲'] = "伏羲灵息", + ['霆'] = "雷霆万钧", + ['渊'] = "渊源深厚", + ['翎'] = "翎羽轻灵" + }; + + private static readonly string[] Blessings = + { + "引瑞气入怀,扶摇直上", + "承先祖之德,守家风之和", + "与四时共鸣,步步生辉", + "纳天地灵气,行稳致远", + "凝万象之精魄,护佑昌隆", + "藏锋于怀,静待时来", + "兼济天下情怀,拥抱广阔未来" + }; + + public Task ValidateSurnameAsync(string surname) + { + if (string.IsNullOrWhiteSpace(surname)) + { + return Task.FromResult(false); + } + + return Task.FromResult(SurnameRegex.IsMatch(surname.Trim())); + } + + public async Task GenerateNamesAsync(NamingRequest request) + { + if (request == null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (!await ValidateSurnameAsync(request.Surname)) + { + throw new ArgumentException("invalid_surname"); + } + + var seed = CreateSeed(request); + var random = new Random(seed); + var pool = request.Gender == "female" ? FemaleCharacters : MaleCharacters; + var fallbackPool = NeutralCharacters; + + var suggestions = new List(); + var attempts = 0; + + while (suggestions.Count < 5 && attempts < 100) + { + attempts++; + var given = GenerateGivenName(request.NameLength, pool, fallbackPool, random); + var fullName = $"{request.Surname}{given}"; + if (suggestions.Any(s => s.Name == fullName)) + { + continue; + } + + var meaning = ComposeMeaning(request.Surname, given, request.BirthDate, request.BirthTime, random); + suggestions.Add(new NamingSuggestion + { + 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 primaryPool, + IReadOnlyList 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(); + foreach (var ch in given) + { + if (CharacterMeanings.TryGetValue(ch, out var desc)) + { + fragments.Add(desc); + } + else + { + fragments.Add($"{ch}寓意祥瑞"); + } + } + + var blessing = Blessings[random.Next(Blessings.Length)]; + var birthFragment = string.IsNullOrWhiteSpace(birthTime) + ? $"{birthDate}之辰" + : $"{birthDate} {birthTime} 时刻"; + + return $"{string.Join(",", fragments)},与{surname}氏气脉相连,于{birthFragment}呼应,{blessing}。"; + } + } +}