diff --git a/backend/FateMaster.API/.vs/FateMaster.API/DesignTimeBuild/.dtbcache.v2 b/backend/FateMaster.API/.vs/FateMaster.API/DesignTimeBuild/.dtbcache.v2 deleted file mode 100644 index 16afe37..0000000 Binary files a/backend/FateMaster.API/.vs/FateMaster.API/DesignTimeBuild/.dtbcache.v2 and /dev/null differ diff --git a/backend/FateMaster.API/.vs/FateMaster.API/FileContentIndex/6df2a7c3-84eb-4692-8dc9-a1385e100411.vsidx b/backend/FateMaster.API/.vs/FateMaster.API/FileContentIndex/6df2a7c3-84eb-4692-8dc9-a1385e100411.vsidx deleted file mode 100644 index d47f90c..0000000 Binary files a/backend/FateMaster.API/.vs/FateMaster.API/FileContentIndex/6df2a7c3-84eb-4692-8dc9-a1385e100411.vsidx and /dev/null differ diff --git a/backend/FateMaster.API/FateMaster.API/.claude/settings.local.json b/backend/FateMaster.API/FateMaster.API/.claude/settings.local.json new file mode 100644 index 0000000..ed6bdce --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/.claude/settings.local.json @@ -0,0 +1,15 @@ +{ + "permissions": { + "allow": [ + "Read(//d/fatemaster/backend/**)", + "Bash(dotnet new:*)", + "Bash(mv:*)", + "Bash(dotnet sln:*)", + "Bash(dotnet add:*)", + "Bash(rm:*)", + "Bash(dotnet build:*)" + ], + "deny": [], + "ask": [] + } +} \ No newline at end of file diff --git a/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/DesignTimeBuild/.dtbcache.v2 b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/DesignTimeBuild/.dtbcache.v2 new file mode 100644 index 0000000..62c2a3e Binary files /dev/null and b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/DesignTimeBuild/.dtbcache.v2 differ diff --git a/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/FileContentIndex/02dc5d48-31bd-47e6-bf2b-35c521138732.vsidx b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/FileContentIndex/02dc5d48-31bd-47e6-bf2b-35c521138732.vsidx new file mode 100644 index 0000000..223dc54 Binary files /dev/null and b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/FileContentIndex/02dc5d48-31bd-47e6-bf2b-35c521138732.vsidx differ diff --git a/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/FileContentIndex/178a272a-821a-472d-8c8d-51cff4bfbeec.vsidx b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/FileContentIndex/178a272a-821a-472d-8c8d-51cff4bfbeec.vsidx new file mode 100644 index 0000000..c31f527 Binary files /dev/null and b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/FileContentIndex/178a272a-821a-472d-8c8d-51cff4bfbeec.vsidx differ diff --git a/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/FileContentIndex/6af3c25e-5958-4720-9fbe-23e159844c6a.vsidx b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/FileContentIndex/6af3c25e-5958-4720-9fbe-23e159844c6a.vsidx new file mode 100644 index 0000000..b71e7ba Binary files /dev/null and b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/FileContentIndex/6af3c25e-5958-4720-9fbe-23e159844c6a.vsidx differ diff --git a/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/FileContentIndex/6efa3979-3ca3-4d97-9ef7-741d0a2ab991.vsidx b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/FileContentIndex/6efa3979-3ca3-4d97-9ef7-741d0a2ab991.vsidx new file mode 100644 index 0000000..fe67c87 Binary files /dev/null and b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/FileContentIndex/6efa3979-3ca3-4d97-9ef7-741d0a2ab991.vsidx differ diff --git a/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/FileContentIndex/ce930036-c604-4a73-a92e-7fea58a05fe1.vsidx b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/FileContentIndex/ce930036-c604-4a73-a92e-7fea58a05fe1.vsidx new file mode 100644 index 0000000..2d14ba8 Binary files /dev/null and b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/FileContentIndex/ce930036-c604-4a73-a92e-7fea58a05fe1.vsidx differ diff --git a/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/v17/.futdcache.v2 b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/v17/.futdcache.v2 new file mode 100644 index 0000000..37bb9b7 Binary files /dev/null and b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster.API/v17/.futdcache.v2 differ diff --git a/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/DesignTimeBuild/.dtbcache.v2 b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/DesignTimeBuild/.dtbcache.v2 new file mode 100644 index 0000000..ed88398 Binary files /dev/null and b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/DesignTimeBuild/.dtbcache.v2 differ diff --git a/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/FileContentIndex/053f0843-ef85-4857-8ccd-87c818b3c248.vsidx b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/FileContentIndex/053f0843-ef85-4857-8ccd-87c818b3c248.vsidx new file mode 100644 index 0000000..1efa0e4 Binary files /dev/null and b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/FileContentIndex/053f0843-ef85-4857-8ccd-87c818b3c248.vsidx differ diff --git a/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/FileContentIndex/6b0f227b-ae48-4f94-8348-212b93c67b9f.vsidx b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/FileContentIndex/6b0f227b-ae48-4f94-8348-212b93c67b9f.vsidx new file mode 100644 index 0000000..7c6485f Binary files /dev/null and b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/FileContentIndex/6b0f227b-ae48-4f94-8348-212b93c67b9f.vsidx differ diff --git a/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/FileContentIndex/9162da77-0cc9-400e-9596-b7aeb56973e4.vsidx b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/FileContentIndex/9162da77-0cc9-400e-9596-b7aeb56973e4.vsidx new file mode 100644 index 0000000..ab1610a Binary files /dev/null and b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/FileContentIndex/9162da77-0cc9-400e-9596-b7aeb56973e4.vsidx differ diff --git a/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/FileContentIndex/f22928ac-3dfb-427b-9d28-02ce44c26d90.vsidx b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/FileContentIndex/f22928ac-3dfb-427b-9d28-02ce44c26d90.vsidx new file mode 100644 index 0000000..04bf1b9 Binary files /dev/null and b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/FileContentIndex/f22928ac-3dfb-427b-9d28-02ce44c26d90.vsidx differ diff --git a/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/FileContentIndex/fe402726-679e-408e-9088-fcf495ad9272.vsidx b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/FileContentIndex/fe402726-679e-408e-9088-fcf495ad9272.vsidx new file mode 100644 index 0000000..12b0472 Binary files /dev/null and b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/FileContentIndex/fe402726-679e-408e-9088-fcf495ad9272.vsidx differ diff --git a/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/config/applicationhost.config b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/config/applicationhost.config new file mode 100644 index 0000000..cdd2df8 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/config/applicationhost.config @@ -0,0 +1,1026 @@ + + + + + + + +
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/v17/.futdcache.v2 b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/v17/.futdcache.v2 new file mode 100644 index 0000000..25c36ac Binary files /dev/null and b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/v17/.futdcache.v2 differ diff --git a/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/v17/DocumentLayout.backup.json b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/v17/DocumentLayout.backup.json new file mode 100644 index 0000000..b4f8f8e --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/v17/DocumentLayout.backup.json @@ -0,0 +1,54 @@ +{ + "Version": 1, + "WorkspaceRootPath": "D:\\project\\fatemaster\\backend\\FateMaster.API\\FateMaster.API\\", + "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{55411F9B-BBD1-42F8-B915-D7FF01B95A1F}|FateMaster.Web.API\\FateMaster.Web.API.csproj|d:\\project\\fatemaster\\backend\\fatemaster.api\\fatemaster.api\\fatemaster.web.api\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}", + "RelativeMoniker": "D:0:0:{55411F9B-BBD1-42F8-B915-D7FF01B95A1F}|FateMaster.Web.API\\FateMaster.Web.API.csproj|solutionrelative:fatemaster.web.api\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}" + }, + { + "AbsoluteMoniker": "D:0:0:{BD8A2E8A-6F14-4EF1-A73E-16D63EF5BCFB}|FateMaster.Application\\FateMaster.Application.csproj|d:\\project\\fatemaster\\backend\\fatemaster.api\\fatemaster.api\\fatemaster.application\\services\\aiservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{BD8A2E8A-6F14-4EF1-A73E-16D63EF5BCFB}|FateMaster.Application\\FateMaster.Application.csproj|solutionrelative:fatemaster.application\\services\\aiservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + } + ], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": 0, + "Children": [ + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "appsettings.json", + "DocumentMoniker": "D:\\project\\fatemaster\\backend\\FateMaster.API\\FateMaster.API\\FateMaster.Web.API\\appsettings.json", + "RelativeDocumentMoniker": "FateMaster.Web.API\\appsettings.json", + "ToolTip": "D:\\project\\fatemaster\\backend\\FateMaster.API\\FateMaster.API\\FateMaster.Web.API\\appsettings.json", + "RelativeToolTip": "FateMaster.Web.API\\appsettings.json", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAA0AAAAUAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|", + "WhenOpened": "2025-10-03T15:25:28.614Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 1, + "Title": "AiService.cs", + "DocumentMoniker": "D:\\project\\fatemaster\\backend\\FateMaster.API\\FateMaster.API\\FateMaster.Application\\Services\\AiService.cs", + "RelativeDocumentMoniker": "FateMaster.Application\\Services\\AiService.cs", + "ToolTip": "D:\\project\\fatemaster\\backend\\FateMaster.API\\FateMaster.API\\FateMaster.Application\\Services\\AiService.cs", + "RelativeToolTip": "FateMaster.Application\\Services\\AiService.cs", + "ViewState": "AgIAAAAAAAAAAAAAAAArwAwAAAAOAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2025-10-03T15:15:34.829Z", + "EditorCaption": "" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/v17/DocumentLayout.json b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/v17/DocumentLayout.json new file mode 100644 index 0000000..7caf1ce --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/.vs/FateMaster/v17/DocumentLayout.json @@ -0,0 +1,54 @@ +{ + "Version": 1, + "WorkspaceRootPath": "D:\\project\\fatemaster\\backend\\FateMaster.API\\FateMaster.API\\", + "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{55411F9B-BBD1-42F8-B915-D7FF01B95A1F}|FateMaster.Web.API\\FateMaster.Web.API.csproj|d:\\project\\fatemaster\\backend\\fatemaster.api\\fatemaster.api\\fatemaster.web.api\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}", + "RelativeMoniker": "D:0:0:{55411F9B-BBD1-42F8-B915-D7FF01B95A1F}|FateMaster.Web.API\\FateMaster.Web.API.csproj|solutionrelative:fatemaster.web.api\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}" + }, + { + "AbsoluteMoniker": "D:0:0:{BD8A2E8A-6F14-4EF1-A73E-16D63EF5BCFB}|FateMaster.Application\\FateMaster.Application.csproj|d:\\project\\fatemaster\\backend\\fatemaster.api\\fatemaster.api\\fatemaster.application\\services\\aiservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{BD8A2E8A-6F14-4EF1-A73E-16D63EF5BCFB}|FateMaster.Application\\FateMaster.Application.csproj|solutionrelative:fatemaster.application\\services\\aiservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + } + ], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": 0, + "Children": [ + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "appsettings.json", + "DocumentMoniker": "D:\\project\\fatemaster\\backend\\FateMaster.API\\FateMaster.API\\FateMaster.Web.API\\appsettings.json", + "RelativeDocumentMoniker": "FateMaster.Web.API\\appsettings.json", + "ToolTip": "D:\\project\\fatemaster\\backend\\FateMaster.API\\FateMaster.API\\FateMaster.Web.API\\appsettings.json", + "RelativeToolTip": "FateMaster.Web.API\\appsettings.json", + "ViewState": "AgIAAAAAAAAAAAAAAADwvxMAAAAVAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|", + "WhenOpened": "2025-10-03T15:25:28.614Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 1, + "Title": "AiService.cs", + "DocumentMoniker": "D:\\project\\fatemaster\\backend\\FateMaster.API\\FateMaster.API\\FateMaster.Application\\Services\\AiService.cs", + "RelativeDocumentMoniker": "FateMaster.Application\\Services\\AiService.cs", + "ToolTip": "D:\\project\\fatemaster\\backend\\FateMaster.API\\FateMaster.API\\FateMaster.Application\\Services\\AiService.cs", + "RelativeToolTip": "FateMaster.Application\\Services\\AiService.cs", + "ViewState": "AgIAAAAAAAAAAAAAAAArwAwAAAAOAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2025-10-03T15:15:34.829Z", + "EditorCaption": "" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/backend/FateMaster.API/FateMaster.API/.vs/ProjectEvaluation/fatemaster.metadata.v9.bin b/backend/FateMaster.API/FateMaster.API/.vs/ProjectEvaluation/fatemaster.metadata.v9.bin new file mode 100644 index 0000000..ba7b3d9 Binary files /dev/null and b/backend/FateMaster.API/FateMaster.API/.vs/ProjectEvaluation/fatemaster.metadata.v9.bin differ diff --git a/backend/FateMaster.API/FateMaster.API/.vs/ProjectEvaluation/fatemaster.projects.v9.bin b/backend/FateMaster.API/FateMaster.API/.vs/ProjectEvaluation/fatemaster.projects.v9.bin new file mode 100644 index 0000000..253c585 Binary files /dev/null and b/backend/FateMaster.API/FateMaster.API/.vs/ProjectEvaluation/fatemaster.projects.v9.bin differ diff --git a/backend/FateMaster.API/FateMaster.API/.vs/ProjectEvaluation/fatemaster.strings.v9.bin b/backend/FateMaster.API/FateMaster.API/.vs/ProjectEvaluation/fatemaster.strings.v9.bin new file mode 100644 index 0000000..dea6c4e Binary files /dev/null and b/backend/FateMaster.API/FateMaster.API/.vs/ProjectEvaluation/fatemaster.strings.v9.bin differ diff --git a/backend/FateMaster.API/Controllers/Admin/PricesController.cs b/backend/FateMaster.API/FateMaster.API/Controllers/Admin/PricesController.cs similarity index 100% rename from backend/FateMaster.API/Controllers/Admin/PricesController.cs rename to backend/FateMaster.API/FateMaster.API/Controllers/Admin/PricesController.cs diff --git a/backend/FateMaster.API/Controllers/Admin/RecordsController.cs b/backend/FateMaster.API/FateMaster.API/Controllers/Admin/RecordsController.cs similarity index 100% rename from backend/FateMaster.API/Controllers/Admin/RecordsController.cs rename to backend/FateMaster.API/FateMaster.API/Controllers/Admin/RecordsController.cs diff --git a/backend/FateMaster.API/Controllers/DivinationController.cs b/backend/FateMaster.API/FateMaster.API/Controllers/DivinationController.cs similarity index 100% rename from backend/FateMaster.API/Controllers/DivinationController.cs rename to backend/FateMaster.API/FateMaster.API/Controllers/DivinationController.cs diff --git a/backend/FateMaster.API/Data/ApplicationDbContext.cs b/backend/FateMaster.API/FateMaster.API/Data/ApplicationDbContext.cs similarity index 100% rename from backend/FateMaster.API/Data/ApplicationDbContext.cs rename to backend/FateMaster.API/FateMaster.API/Data/ApplicationDbContext.cs diff --git a/backend/FateMaster.API/FateMaster.API.csproj b/backend/FateMaster.API/FateMaster.API/FateMaster.API.csproj similarity index 100% rename from backend/FateMaster.API/FateMaster.API.csproj rename to backend/FateMaster.API/FateMaster.API/FateMaster.API.csproj diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/Controllers/PricesController.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/Controllers/PricesController.cs new file mode 100644 index 0000000..a204a76 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/Controllers/PricesController.cs @@ -0,0 +1,97 @@ +using FateMaster.Application.Interfaces; +using FateMaster.Shared.DTOs; +using Microsoft.AspNetCore.Mvc; + +namespace FateMaster.Admin.API.Controllers; + +[ApiController] +[Route("api/admin/[controller]")] +public class PricesController : ControllerBase +{ + private readonly IPriceConfigService _priceService; + private readonly ILogger _logger; + + public PricesController( + IPriceConfigService priceService, + ILogger logger) + { + _priceService = priceService; + _logger = logger; + } + + /// + /// 获取所有价格配置 + /// + [HttpGet] + public async Task>>> GetAll() + { + try + { + var prices = await _priceService.GetAllPricesAsync(); + return Ok(ApiResponse>.SuccessResult(prices)); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting all prices"); + return StatusCode(500, ApiResponse>.FailureResult("获取价格配置失败")); + } + } + + /// + /// 更新价格配置 + /// + [HttpPut("{id}")] + public async Task>> Update(int id, [FromBody] UpsertPriceConfigRequest request) + { + try + { + var price = await _priceService.UpsertPriceConfigAsync(id, request); + return Ok(ApiResponse.SuccessResult(price)); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error updating price config"); + return StatusCode(500, ApiResponse.FailureResult("更新价格配置失败")); + } + } + + /// + /// 创建价格配置 + /// + [HttpPost] + public async Task>> Create([FromBody] UpsertPriceConfigRequest request) + { + try + { + var price = await _priceService.UpsertPriceConfigAsync(null, request); + return Ok(ApiResponse.SuccessResult(price)); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error creating price config"); + return StatusCode(500, ApiResponse.FailureResult("创建价格配置失败")); + } + } + + /// + /// 删除价格配置 + /// + [HttpDelete("{id}")] + public async Task>> Delete(int id) + { + try + { + var success = await _priceService.DeletePriceConfigAsync(id); + if (!success) + { + return NotFound(ApiResponse.FailureResult("价格配置不存在")); + } + return Ok(ApiResponse.SuccessResult(true)); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting price config"); + return StatusCode(500, ApiResponse.FailureResult("删除价格配置失败")); + } + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/Controllers/RecordsController.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/Controllers/RecordsController.cs new file mode 100644 index 0000000..f39c346 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/Controllers/RecordsController.cs @@ -0,0 +1,62 @@ +using FateMaster.Application.Interfaces; +using FateMaster.Shared.DTOs; +using Microsoft.AspNetCore.Mvc; + +namespace FateMaster.Admin.API.Controllers; + +[ApiController] +[Route("api/admin/[controller]")] +public class RecordsController : ControllerBase +{ + private readonly IAdminService _adminService; + private readonly ILogger _logger; + + public RecordsController( + IAdminService adminService, + ILogger logger) + { + _adminService = adminService; + _logger = logger; + } + + /// + /// 获取卜卦记录列表 + /// + [HttpGet] + public async Task>>> GetRecords( + [FromQuery] int page = 1, + [FromQuery] int pageSize = 20, + [FromQuery] string? type = null, + [FromQuery] string? paymentStatus = null) + { + try + { + var request = new PagedRequest(page, pageSize, type, paymentStatus); + var result = await _adminService.GetRecordsAsync(request); + return Ok(ApiResponse>.SuccessResult(result)); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting records"); + return StatusCode(500, ApiResponse>.FailureResult("获取记录列表失败")); + } + } + + /// + /// 获取统计数据 + /// + [HttpGet("statistics")] + public async Task>> GetStatistics() + { + try + { + var stats = await _adminService.GetStatisticsAsync(); + return Ok(ApiResponse.SuccessResult(stats)); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting statistics"); + return StatusCode(500, ApiResponse.FailureResult("获取统计数据失败")); + } + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/FateMaster.Admin.API.csproj b/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/FateMaster.Admin.API.csproj new file mode 100644 index 0000000..b1ce769 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/FateMaster.Admin.API.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/FateMaster.Admin.API.http b/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/FateMaster.Admin.API.http new file mode 100644 index 0000000..e9aa78d --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/FateMaster.Admin.API.http @@ -0,0 +1,6 @@ +@FateMaster.Admin.API_HostAddress = http://localhost:5127 + +GET {{FateMaster.Admin.API_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/Program.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/Program.cs new file mode 100644 index 0000000..31129c9 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/Program.cs @@ -0,0 +1,41 @@ +using FateMaster.Application; +using FateMaster.Infrastructure; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +// 添加Application和Infrastructure层 +builder.Services.AddApplication(); +builder.Services.AddInfrastructure(builder.Configuration); + +// Configure CORS - Admin需要更严格的CORS配置 +builder.Services.AddCors(options => +{ + options.AddPolicy("AllowAdmin", policy => + { + policy.WithOrigins("http://localhost:3002", "http://localhost:3003") // Admin前端端口 + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials(); + }); +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseCors("AllowAdmin"); +app.UseHttpsRedirection(); +app.UseAuthorization(); +app.MapControllers(); + +app.Run(); diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/Properties/launchSettings.json b/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/Properties/launchSettings.json new file mode 100644 index 0000000..491c726 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:7582", + "sslPort": 44338 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5127", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7141;http://localhost:5127", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/appsettings.json b/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/appsettings.json new file mode 100644 index 0000000..c005171 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Admin.API/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "Server=localhost;Port=3306;Database=fatemaster;User=root;Password=your_password;" + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Application/DependencyInjection.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/DependencyInjection.cs new file mode 100644 index 0000000..93eaa07 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/DependencyInjection.cs @@ -0,0 +1,26 @@ +using FateMaster.Application.Interfaces; +using FateMaster.Application.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace FateMaster.Application; + +/// +/// Application层依赖注入扩展 +/// +public static class DependencyInjection +{ + public static IServiceCollection AddApplication(this IServiceCollection services) + { + // 注册应用服务 + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + // 注册HttpClient用于AI服务 + services.AddHttpClient(); + + return services; + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Application/FateMaster.Application.csproj b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/FateMaster.Application.csproj new file mode 100644 index 0000000..aff3518 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/FateMaster.Application.csproj @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + net8.0 + enable + enable + + + diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Interfaces/IAdminService.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Interfaces/IAdminService.cs new file mode 100644 index 0000000..104cb42 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Interfaces/IAdminService.cs @@ -0,0 +1,19 @@ +using FateMaster.Shared.DTOs; + +namespace FateMaster.Application.Interfaces; + +/// +/// Admin管理服务接口 +/// +public interface IAdminService +{ + /// + /// 获取分页的卜卦记录列表 + /// + Task> GetRecordsAsync(PagedRequest request); + + /// + /// 获取统计数据 + /// + Task GetStatisticsAsync(); +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Interfaces/IAiService.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Interfaces/IAiService.cs new file mode 100644 index 0000000..0b163c8 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Interfaces/IAiService.cs @@ -0,0 +1,19 @@ +using FateMaster.Shared.DTOs; + +namespace FateMaster.Application.Interfaces; + +/// +/// AI服务接口 +/// +public interface IAiService +{ + /// + /// AI解读八字 + /// + Task InterpretBaziAsync( + BaziChart chart, + FiveElementsAnalysis fiveElements, + TenGodsAnalysis tenGods, + LuckCycleInfo luckCycles, + BaziCalculateRequest request); +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Interfaces/IBaziService.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Interfaces/IBaziService.cs new file mode 100644 index 0000000..be02545 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Interfaces/IBaziService.cs @@ -0,0 +1,19 @@ +using FateMaster.Shared.DTOs; + +namespace FateMaster.Application.Interfaces; + +/// +/// 八字服务接口 +/// +public interface IBaziService +{ + /// + /// 计算八字 + /// + Task CalculateBaziAsync(BaziCalculateRequest request, string? clientIp); + + /// + /// 获取八字结果 + /// + Task GetBaziResultAsync(long recordId); +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Interfaces/IDivinationService.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Interfaces/IDivinationService.cs new file mode 100644 index 0000000..d691175 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Interfaces/IDivinationService.cs @@ -0,0 +1,29 @@ +using FateMaster.Shared.DTOs; + +namespace FateMaster.Application.Interfaces; + +/// +/// 卜卦服务接口 +/// +public interface IDivinationService +{ + /// + /// 获取所有启用的价格配置 + /// + Task> GetEnabledPricesAsync(); + + /// + /// 提交卜卦请求 + /// + Task SubmitDivinationAsync(SubmitDivinationRequest request, string? clientIp); + + /// + /// 根据ID获取卜卦记录 + /// + Task GetDivinationRecordAsync(long id); + + /// + /// 更新支付状态 + /// + Task UpdatePaymentStatusAsync(UpdatePaymentStatusRequest request); +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Interfaces/IPriceConfigService.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Interfaces/IPriceConfigService.cs new file mode 100644 index 0000000..6527df0 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Interfaces/IPriceConfigService.cs @@ -0,0 +1,29 @@ +using FateMaster.Shared.DTOs; + +namespace FateMaster.Application.Interfaces; + +/// +/// 价格配置服务接口 +/// +public interface IPriceConfigService +{ + /// + /// 获取所有价格配置 + /// + Task> GetAllPricesAsync(); + + /// + /// 根据服务类型获取价格 + /// + Task GetPriceByServiceTypeAsync(string serviceType); + + /// + /// 创建或更新价格配置 + /// + Task UpsertPriceConfigAsync(int? id, UpsertPriceConfigRequest request); + + /// + /// 删除价格配置 + /// + Task DeletePriceConfigAsync(int id); +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Services/AdminService.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Services/AdminService.cs new file mode 100644 index 0000000..75f124c --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Services/AdminService.cs @@ -0,0 +1,98 @@ +using FateMaster.Application.Interfaces; +using FateMaster.Domain.Entities; +using FateMaster.Domain.Interfaces; +using FateMaster.Shared.DTOs; + +namespace FateMaster.Application.Services; + +/// +/// Admin管理服务实现 +/// +public class AdminService : IAdminService +{ + private readonly IRepository _divinationRepository; + + public AdminService(IRepository divinationRepository) + { + _divinationRepository = divinationRepository; + } + + public async Task> GetRecordsAsync(PagedRequest request) + { + var allRecords = await _divinationRepository.GetAllAsync(); + var query = allRecords.AsQueryable(); + + // 过滤 + if (!string.IsNullOrEmpty(request.Type)) + { + query = query.Where(r => r.Type == request.Type); + } + + if (!string.IsNullOrEmpty(request.PaymentStatus)) + { + query = query.Where(r => r.PaymentStatus == request.PaymentStatus); + } + + // 排序 + query = query.OrderByDescending(r => r.CreatedAt); + + // 分页 + var total = query.Count(); + var records = query + .Skip((request.Page - 1) * request.PageSize) + .Take(request.PageSize) + .Select(r => new DivinationRecordResponse + { + Id = r.Id, + Type = r.Type, + InputData = r.InputData, + TraditionalResult = r.TraditionalResult, + AIInterpretation = r.AIInterpretation, + PaymentStatus = r.PaymentStatus, + PaymentMethod = r.PaymentMethod, + Amount = r.Amount, + PaymentOrderId = r.PaymentOrderId, + Language = r.Language, + CreatedAt = r.CreatedAt, + UpdatedAt = r.UpdatedAt + }) + .ToList(); + + return new PagedResponse + { + Total = total, + Page = request.Page, + PageSize = request.PageSize, + Data = records + }; + } + + public async Task GetStatisticsAsync() + { + var allRecords = await _divinationRepository.GetAllAsync(); + var recordsList = allRecords.ToList(); + + var total = recordsList.Count; + var paidCount = recordsList.Count(r => r.PaymentStatus == "paid"); + var totalRevenue = recordsList + .Where(r => r.PaymentStatus == "paid") + .Sum(r => r.Amount); + + var typeStats = recordsList + .GroupBy(r => r.Type) + .Select(g => new TypeStatistics + { + Type = g.Key, + Count = g.Count() + }) + .ToList(); + + return new StatisticsResponse + { + Total = total, + PaidCount = paidCount, + TotalRevenue = totalRevenue, + TypeStats = typeStats + }; + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Services/AiService.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Services/AiService.cs new file mode 100644 index 0000000..31ac34f --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Services/AiService.cs @@ -0,0 +1,225 @@ +using FateMaster.Application.Interfaces; +using FateMaster.Shared.DTOs; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using System.Net.Http.Json; +using System.Text; +using System.Text.Json; + +namespace FateMaster.Application.Services; + +/// +/// AI服务实现(对接Claude API) +/// +public class AiService : IAiService +{ + private readonly IHttpClientFactory _httpClientFactory; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + + public AiService( + IHttpClientFactory httpClientFactory, + IConfiguration configuration, + ILogger logger) + { + _httpClientFactory = httpClientFactory; + _configuration = configuration; + _logger = logger; + } + + public async Task InterpretBaziAsync( + BaziChart chart, + FiveElementsAnalysis fiveElements, + TenGodsAnalysis tenGods, + LuckCycleInfo luckCycles, + BaziCalculateRequest request) + { + try + { + var apiKey = _configuration["Claude:ApiKey"]; + if (string.IsNullOrEmpty(apiKey)) + { + _logger.LogWarning("Claude API key not configured"); + return new AiInterpretation + { + Summary = "AI解读服务未配置,请联系管理员" + }; + } + + // 构建提示词 + var prompt = BuildBaziPrompt(chart, fiveElements, tenGods, luckCycles, request); + + // 调用Claude API + var httpClient = _httpClientFactory.CreateClient(); + httpClient.DefaultRequestHeaders.Add("x-api-key", apiKey); + httpClient.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01"); + + var requestBody = new + { + model = "claude-3-5-sonnet-20241022", + max_tokens = 4096, + messages = new[] + { + new + { + role = "user", + content = prompt + } + } + }; + + var response = await httpClient.PostAsJsonAsync( + "https://api.anthropic.com/v1/messages", + requestBody); + + if (!response.IsSuccessStatusCode) + { + _logger.LogError("Claude API error: {StatusCode}", response.StatusCode); + return new AiInterpretation + { + Summary = "AI解读服务暂时不可用,请稍后再试" + }; + } + + var result = await response.Content.ReadFromJsonAsync(); + var aiText = result?.Content?.FirstOrDefault()?.Text ?? ""; + + // 解析AI返回的结果 + return ParseAiResponse(aiText); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error calling Claude API"); + return new AiInterpretation + { + Summary = "AI解读过程中出现错误" + }; + } + } + + /// + /// 构建八字分析提示词 + /// + private string BuildBaziPrompt( + BaziChart chart, + FiveElementsAnalysis fiveElements, + TenGodsAnalysis tenGods, + LuckCycleInfo luckCycles, + BaziCalculateRequest request) + { + var sb = new StringBuilder(); + sb.AppendLine("你是一位专业的命理大师,精通八字命理学。请根据以下八字信息,进行全面深入的分析。"); + sb.AppendLine(); + sb.AppendLine($"## 基本信息"); + sb.AppendLine($"- 姓名:{request.Name}"); + sb.AppendLine($"- 性别:{(request.Gender == "male" ? "男" : "女")}"); + sb.AppendLine($"- 出生时间:{request.BirthDate:yyyy年MM月dd日 HH:mm}"); + sb.AppendLine(); + sb.AppendLine($"## 八字排盘"); + sb.AppendLine($"- 年柱:{chart.YearPillar.Stem}{chart.YearPillar.Branch}({chart.YearPillar.Element})"); + sb.AppendLine($"- 月柱:{chart.MonthPillar.Stem}{chart.MonthPillar.Branch}({chart.MonthPillar.Element})"); + sb.AppendLine($"- 日柱:{chart.DayPillar.Stem}{chart.DayPillar.Branch}({chart.DayPillar.Element})"); + sb.AppendLine($"- 时柱:{chart.HourPillar.Stem}{chart.HourPillar.Branch}({chart.HourPillar.Element})"); + sb.AppendLine($"- 日主:{chart.DayMaster}"); + sb.AppendLine(); + sb.AppendLine($"## 五行分析"); + sb.AppendLine($"- 五行统计:"); + foreach (var element in fiveElements.Elements) + { + sb.AppendLine($" - {element.Key}:{element.Value}"); + } + sb.AppendLine($"- 最旺五行:{fiveElements.StrongestElement}"); + sb.AppendLine($"- 最弱五行:{fiveElements.WeakestElement}"); + if (fiveElements.MissingElements.Any()) + { + sb.AppendLine($"- 缺失五行:{string.Join("、", fiveElements.MissingElements)}"); + } + sb.AppendLine($"- 八字强弱:{fiveElements.BaziStrength}"); + sb.AppendLine(); + sb.AppendLine($"## 大运信息"); + if (luckCycles.CurrentCycle != null) + { + sb.AppendLine($"- 当前大运:{luckCycles.CurrentCycle.Stem}{luckCycles.CurrentCycle.Branch}"); + sb.AppendLine($"- 起运年龄:{luckCycles.StartAge}岁"); + } + sb.AppendLine(); + sb.AppendLine("请从以下几个方面进行详细分析,每个方面约200-300字:"); + sb.AppendLine(); + sb.AppendLine("### 1. 性格特点"); + sb.AppendLine("分析此人的性格特征、优缺点、性情气质等。"); + sb.AppendLine(); + sb.AppendLine("### 2. 事业运势"); + sb.AppendLine("分析适合的职业方向、事业发展趋势、职场表现等。"); + sb.AppendLine(); + sb.AppendLine("### 3. 财运分析"); + sb.AppendLine("分析财运状况、理财能力、财富积累方式等。"); + sb.AppendLine(); + sb.AppendLine("### 4. 婚姻感情"); + sb.AppendLine("分析感情运势、婚姻状况、配偶特点等。"); + sb.AppendLine(); + sb.AppendLine("### 5. 健康状况"); + sb.AppendLine("分析身体健康方面需要注意的问题。"); + sb.AppendLine(); + sb.AppendLine("### 6. 综合建议"); + sb.AppendLine("给出人生发展建议、注意事项等。"); + sb.AppendLine(); + sb.AppendLine("请用专业但易懂的语言,以JSON格式返回,格式如下:"); + sb.AppendLine("{"); + sb.AppendLine(" \"character\": \"性格分析内容\","); + sb.AppendLine(" \"career\": \"事业分析内容\","); + sb.AppendLine(" \"wealth\": \"财运分析内容\","); + sb.AppendLine(" \"marriage\": \"婚姻分析内容\","); + sb.AppendLine(" \"health\": \"健康分析内容\","); + sb.AppendLine(" \"summary\": \"综合分析\","); + sb.AppendLine(" \"suggestions\": \"建议\""); + sb.AppendLine("}"); + + return sb.ToString(); + } + + /// + /// 解析AI响应 + /// + private AiInterpretation ParseAiResponse(string aiText) + { + try + { + // 尝试提取JSON部分 + var jsonStart = aiText.IndexOf('{'); + var jsonEnd = aiText.LastIndexOf('}'); + + if (jsonStart >= 0 && jsonEnd > jsonStart) + { + var json = aiText.Substring(jsonStart, jsonEnd - jsonStart + 1); + return JsonSerializer.Deserialize(json) ?? new AiInterpretation(); + } + + // 如果无法解析JSON,返回原文 + return new AiInterpretation + { + Summary = aiText + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error parsing AI response"); + return new AiInterpretation + { + Summary = aiText + }; + } + } + + /// + /// Claude API响应模型 + /// + private class ClaudeResponse + { + public List? Content { get; set; } + } + + private class ContentItem + { + public string? Text { get; set; } + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Services/BaziService.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Services/BaziService.cs new file mode 100644 index 0000000..df75b85 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Services/BaziService.cs @@ -0,0 +1,402 @@ +using FateMaster.Application.Interfaces; +using FateMaster.Domain.Entities; +using FateMaster.Domain.Interfaces; +using FateMaster.Shared.DTOs; +using Microsoft.Extensions.Logging; +using System.Text.Json; + +namespace FateMaster.Application.Services; + +/// +/// 八字服务实现 +/// +public class BaziService : IBaziService +{ + private readonly IRepository _divinationRepository; + private readonly IUnitOfWork _unitOfWork; + private readonly IAiService _aiService; + private readonly ILogger _logger; + + // 天干 + private static readonly string[] HeavenlyStems = { "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸" }; + + // 地支 + private static readonly string[] EarthlyBranches = { "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥" }; + + // 五行 + private static readonly Dictionary StemElements = new() + { + { "甲", "木" }, { "乙", "木" }, { "丙", "火" }, { "丁", "火" }, + { "戊", "土" }, { "己", "土" }, { "庚", "金" }, { "辛", "金" }, + { "壬", "水" }, { "癸", "水" } + }; + + private static readonly Dictionary BranchElements = new() + { + { "子", "水" }, { "丑", "土" }, { "寅", "木" }, { "卯", "木" }, + { "辰", "土" }, { "巳", "火" }, { "午", "火" }, { "未", "土" }, + { "申", "金" }, { "酉", "金" }, { "戌", "土" }, { "亥", "水" } + }; + + // 地支藏干 + private static readonly Dictionary BranchHiddenStems = new() + { + { "子", "癸" }, { "丑", "己癸辛" }, { "寅", "甲丙戊" }, { "卯", "乙" }, + { "辰", "戊乙癸" }, { "巳", "丙戊庚" }, { "午", "丁己" }, { "未", "己丁乙" }, + { "申", "庚壬戊" }, { "酉", "辛" }, { "戌", "戊辛丁" }, { "亥", "壬甲" } + }; + + // 十神对应关系 + private static readonly Dictionary TenGods = new() + { + { "比肩", "比肩" }, { "劫财", "劫财" }, { "食神", "食神" }, { "伤官", "伤官" }, + { "偏财", "偏财" }, { "正财", "正财" }, { "七杀", "七杀" }, { "正官", "正官" }, + { "偏印", "偏印" }, { "正印", "正印" } + }; + + public BaziService( + IRepository divinationRepository, + IUnitOfWork unitOfWork, + IAiService aiService, + ILogger logger) + { + _divinationRepository = divinationRepository; + _unitOfWork = unitOfWork; + _aiService = aiService; + _logger = logger; + } + + public async Task CalculateBaziAsync(BaziCalculateRequest request, string? clientIp) + { + try + { + // 1. 计算八字排盘 + var chart = CalculateBaziChart(request); + + // 2. 五行分析 + var fiveElements = AnalyzeFiveElements(chart); + + // 3. 十神分析 + var tenGods = AnalyzeTenGods(chart); + + // 4. 大运流年 + var luckCycles = CalculateLuckCycles(chart, request); + + // 5. 保存传统结果到数据库 + var traditionalResult = new + { + chart, + fiveElements, + tenGods, + luckCycles + }; + + var record = new DivinationRecord + { + Type = "bazi", + InputData = JsonSerializer.Serialize(request), + TraditionalResult = JsonSerializer.Serialize(traditionalResult), + PaymentStatus = "pending", + Amount = 0, + ClientIp = clientIp, + Language = request.Language + }; + + var createdRecord = await _divinationRepository.AddAsync(record); + await _unitOfWork.SaveChangesAsync(); + + // 6. AI解读(异步处理) + _ = Task.Run(async () => + { + try + { + var aiAnalysis = await _aiService.InterpretBaziAsync(chart, fiveElements, tenGods, luckCycles, request); + + record.AIInterpretation = JsonSerializer.Serialize(aiAnalysis); + await _divinationRepository.UpdateAsync(record); + await _unitOfWork.SaveChangesAsync(); + } + catch (Exception ex) + { + _logger.LogError(ex, "AI interpretation failed for record {RecordId}", createdRecord.Id); + } + }); + + // 7. 返回结果 + return new BaziCalculateResponse + { + RecordId = createdRecord.Id, + BasicInfo = new BaziBasicInfo + { + Name = request.Name, + Gender = request.Gender, + BirthDateTime = request.BirthDate?.ToString("yyyy-MM-dd HH:mm") ?? "", + BirthPlace = request.BirthPlace + }, + Chart = chart, + FiveElements = fiveElements, + TenGods = tenGods, + LuckCycles = luckCycles, + AiAnalysis = new AiInterpretation() // AI解读会异步更新 + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error calculating bazi"); + throw; + } + } + + public async Task GetBaziResultAsync(long recordId) + { + var record = await _divinationRepository.GetByIdAsync(recordId); + if (record == null || record.Type != "bazi") + { + return null; + } + + var request = JsonSerializer.Deserialize(record.InputData); + var traditionalResult = JsonSerializer.Deserialize(record.TraditionalResult ?? "{}"); + var aiAnalysis = string.IsNullOrEmpty(record.AIInterpretation) + ? new AiInterpretation() + : JsonSerializer.Deserialize(record.AIInterpretation); + + return new BaziCalculateResponse + { + RecordId = record.Id, + BasicInfo = new BaziBasicInfo + { + Name = request?.Name ?? "", + Gender = request?.Gender ?? "", + BirthDateTime = request?.BirthDate?.ToString("yyyy-MM-dd HH:mm") ?? "", + BirthPlace = request?.BirthPlace + }, + Chart = traditionalResult?.chart ?? new BaziChart(), + FiveElements = traditionalResult?.fiveElements ?? new FiveElementsAnalysis(), + TenGods = traditionalResult?.tenGods ?? new TenGodsAnalysis(), + LuckCycles = traditionalResult?.luckCycles ?? new LuckCycleInfo(), + AiAnalysis = aiAnalysis ?? new AiInterpretation() + }; + } + + /// + /// 计算八字排盘 + /// + private BaziChart CalculateBaziChart(BaziCalculateRequest request) + { + // 如果直接输入了八字 + if (!string.IsNullOrEmpty(request.YearStem) && !string.IsNullOrEmpty(request.YearBranch)) + { + return new BaziChart + { + YearPillar = CreatePillar(request.YearStem, request.YearBranch), + MonthPillar = CreatePillar(request.MonthStem ?? "甲", request.MonthBranch ?? "子"), + DayPillar = CreatePillar(request.DayStem ?? "甲", request.DayBranch ?? "子"), + HourPillar = CreatePillar(request.HourStem ?? "甲", request.HourBranch ?? "子"), + DayMaster = request.DayStem ?? "甲" + }; + } + + // 从出生日期计算八字 + var birthDate = request.BirthDate ?? DateTime.Now; + + // 计算年柱 + var yearPillar = CalculateYearPillar(birthDate); + + // 计算月柱 + var monthPillar = CalculateMonthPillar(birthDate, yearPillar.Stem); + + // 计算日柱 + var dayPillar = CalculateDayPillar(birthDate); + + // 计算时柱 + var hourPillar = request.KnowTime + ? CalculateHourPillar(request.BirthHour ?? 0, dayPillar.Stem) + : new Pillar { Stem = "?", Branch = "?", Element = "?", HiddenStems = "?" }; + + return new BaziChart + { + YearPillar = yearPillar, + MonthPillar = monthPillar, + DayPillar = dayPillar, + HourPillar = hourPillar, + DayMaster = dayPillar.Stem + }; + } + + /// + /// 计算年柱 + /// + private Pillar CalculateYearPillar(DateTime date) + { + // 简化算法:以立春为界 + var year = date.Year; + if (date.Month < 2 || (date.Month == 2 && date.Day < 4)) + { + year--; // 立春前算上一年 + } + + // 1984年是甲子年 + var baseYear = 1984; + var index = (year - baseYear) % 60; + if (index < 0) index += 60; + + var stemIndex = index % 10; + var branchIndex = index % 12; + + return CreatePillar(HeavenlyStems[stemIndex], EarthlyBranches[branchIndex]); + } + + /// + /// 计算月柱 + /// + private Pillar CalculateMonthPillar(DateTime date, string yearStem) + { + // 月份地支(以节气为准,这里简化处理) + var monthBranches = new[] { "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥", "子", "丑" }; + var monthIndex = (date.Month + 1) % 12; + var monthBranch = monthBranches[monthIndex]; + + // 月干根据年干推算(五虎遁月) + var yearStemIndex = Array.IndexOf(HeavenlyStems, yearStem); + var monthStemIndex = (yearStemIndex % 5 * 2 + monthIndex) % 10; + + return CreatePillar(HeavenlyStems[monthStemIndex], monthBranch); + } + + /// + /// 计算日柱 + /// + private Pillar CalculateDayPillar(DateTime date) + { + // 使用儒略日数计算 + var baseDate = new DateTime(1900, 1, 1); + var days = (date - baseDate).Days; + + // 1900-01-01是庚午日 + var baseDayIndex = 46; // 庚午 + var dayIndex = (baseDayIndex + days) % 60; + if (dayIndex < 0) dayIndex += 60; + + var stemIndex = dayIndex % 10; + var branchIndex = dayIndex % 12; + + return CreatePillar(HeavenlyStems[stemIndex], EarthlyBranches[branchIndex]); + } + + /// + /// 计算时柱 + /// + private Pillar CalculateHourPillar(int hour, string dayStem) + { + // 时辰地支 + var hourBranchIndex = (hour + 1) / 2 % 12; + var hourBranch = EarthlyBranches[hourBranchIndex]; + + // 时干根据日干推算(五鼠遁日) + var dayStemIndex = Array.IndexOf(HeavenlyStems, dayStem); + var hourStemIndex = (dayStemIndex % 5 * 2 + hourBranchIndex) % 10; + + return CreatePillar(HeavenlyStems[hourStemIndex], hourBranch); + } + + /// + /// 创建柱 + /// + private Pillar CreatePillar(string stem, string branch) + { + return new Pillar + { + Stem = stem, + Branch = branch, + Element = StemElements.GetValueOrDefault(stem, "?"), + HiddenStems = BranchHiddenStems.GetValueOrDefault(branch, "?") + }; + } + + /// + /// 五行分析 + /// + private FiveElementsAnalysis AnalyzeFiveElements(BaziChart chart) + { + var elements = new Dictionary + { + { "木", 0 }, { "火", 0 }, { "土", 0 }, { "金", 0 }, { "水", 0 } + }; + + // 统计五行 + void CountElement(string element) + { + if (elements.ContainsKey(element)) + elements[element]++; + } + + CountElement(StemElements.GetValueOrDefault(chart.YearPillar.Stem, "")); + CountElement(BranchElements.GetValueOrDefault(chart.YearPillar.Branch, "")); + CountElement(StemElements.GetValueOrDefault(chart.MonthPillar.Stem, "")); + CountElement(BranchElements.GetValueOrDefault(chart.MonthPillar.Branch, "")); + CountElement(StemElements.GetValueOrDefault(chart.DayPillar.Stem, "")); + CountElement(BranchElements.GetValueOrDefault(chart.DayPillar.Branch, "")); + CountElement(StemElements.GetValueOrDefault(chart.HourPillar.Stem, "")); + CountElement(BranchElements.GetValueOrDefault(chart.HourPillar.Branch, "")); + + var strongest = elements.OrderByDescending(e => e.Value).First().Key; + var weakest = elements.OrderBy(e => e.Value).First().Key; + var missing = elements.Where(e => e.Value == 0).Select(e => e.Key).ToList(); + + return new FiveElementsAnalysis + { + Elements = elements, + StrongestElement = strongest, + WeakestElement = weakest, + MissingElements = missing, + BaziStrength = elements[StemElements[chart.DayMaster]] >= 3 ? "身强" : "身弱" + }; + } + + /// + /// 十神分析 + /// + private TenGodsAnalysis AnalyzeTenGods(BaziChart chart) + { + var gods = new Dictionary>(); + + // 简化的十神分析 + // 实际应根据日主与其他干支的关系来确定十神 + return new TenGodsAnalysis + { + Gods = gods, + MainGod = "比肩" // 这里需要根据实际算法计算 + }; + } + + /// + /// 计算大运流年 + /// + private LuckCycleInfo CalculateLuckCycles(BaziChart chart, BaziCalculateRequest request) + { + var cycles = new List(); + var startAge = 5; // 简化:起运5岁 + + // 生成10个大运 + for (int i = 0; i < 10; i++) + { + var age = startAge + i * 10; + cycles.Add(new LuckCycle + { + Stem = HeavenlyStems[i % 10], + Branch = EarthlyBranches[i % 12], + StartAge = age, + EndAge = age + 9, + Element = StemElements[HeavenlyStems[i % 10]] + }); + } + + return new LuckCycleInfo + { + MajorCycles = cycles, + StartAge = startAge, + CurrentCycle = cycles.FirstOrDefault() // 需要根据当前年龄计算 + }; + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Services/DivinationService.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Services/DivinationService.cs new file mode 100644 index 0000000..b60a586 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Services/DivinationService.cs @@ -0,0 +1,124 @@ +using FateMaster.Application.Interfaces; +using FateMaster.Domain.Entities; +using FateMaster.Domain.Interfaces; +using FateMaster.Shared.DTOs; +using Microsoft.Extensions.Logging; + +namespace FateMaster.Application.Services; + +/// +/// 卜卦服务实现 +/// +public class DivinationService : IDivinationService +{ + private readonly IRepository _divinationRepository; + private readonly IRepository _priceRepository; + private readonly IUnitOfWork _unitOfWork; + private readonly ILogger _logger; + + public DivinationService( + IRepository divinationRepository, + IRepository priceRepository, + IUnitOfWork unitOfWork, + ILogger logger) + { + _divinationRepository = divinationRepository; + _priceRepository = priceRepository; + _unitOfWork = unitOfWork; + _logger = logger; + } + + public async Task> GetEnabledPricesAsync() + { + var prices = await _priceRepository.FindAsync(p => p.IsEnabled); + return prices.Select(p => new PriceConfigResponse + { + Id = p.Id, + ServiceType = p.ServiceType, + Price = p.Price, + Currency = p.Currency, + IsEnabled = p.IsEnabled + }); + } + + public async Task SubmitDivinationAsync(SubmitDivinationRequest request, string? clientIp) + { + try + { + var record = new DivinationRecord + { + Type = request.Type, + InputData = request.InputData, + PaymentStatus = "pending", + PaymentMethod = request.PaymentMethod, + Amount = request.Amount, + ClientIp = clientIp, + Language = request.Language ?? "zh-CN" + }; + + var createdRecord = await _divinationRepository.AddAsync(record); + await _unitOfWork.SaveChangesAsync(); + + return MapToResponse(createdRecord); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error submitting divination request"); + throw; + } + } + + public async Task GetDivinationRecordAsync(long id) + { + var record = await _divinationRepository.GetByIdAsync(id); + return record == null ? null : MapToResponse(record); + } + + public async Task UpdatePaymentStatusAsync(UpdatePaymentStatusRequest request) + { + try + { + var record = await _divinationRepository.GetByIdAsync(request.RecordId); + if (record == null) + { + return false; + } + + record.PaymentStatus = request.PaymentStatus; + if (!string.IsNullOrEmpty(request.PaymentOrderId)) + { + record.PaymentOrderId = request.PaymentOrderId; + } + record.UpdatedAt = DateTime.UtcNow; + + await _divinationRepository.UpdateAsync(record); + await _unitOfWork.SaveChangesAsync(); + + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error updating payment status"); + return false; + } + } + + private static DivinationRecordResponse MapToResponse(DivinationRecord record) + { + return new DivinationRecordResponse + { + Id = record.Id, + Type = record.Type, + InputData = record.InputData, + TraditionalResult = record.TraditionalResult, + AIInterpretation = record.AIInterpretation, + PaymentStatus = record.PaymentStatus, + PaymentMethod = record.PaymentMethod, + Amount = record.Amount, + PaymentOrderId = record.PaymentOrderId, + Language = record.Language, + CreatedAt = record.CreatedAt, + UpdatedAt = record.UpdatedAt + }; + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Services/PriceConfigService.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Services/PriceConfigService.cs new file mode 100644 index 0000000..c82db06 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Application/Services/PriceConfigService.cs @@ -0,0 +1,97 @@ +using FateMaster.Application.Interfaces; +using FateMaster.Domain.Entities; +using FateMaster.Domain.Interfaces; +using FateMaster.Shared.DTOs; + +namespace FateMaster.Application.Services; + +/// +/// 价格配置服务实现 +/// +public class PriceConfigService : IPriceConfigService +{ + private readonly IRepository _priceRepository; + private readonly IUnitOfWork _unitOfWork; + + public PriceConfigService( + IRepository priceRepository, + IUnitOfWork unitOfWork) + { + _priceRepository = priceRepository; + _unitOfWork = unitOfWork; + } + + public async Task> GetAllPricesAsync() + { + var prices = await _priceRepository.GetAllAsync(); + return prices.Select(MapToResponse); + } + + public async Task GetPriceByServiceTypeAsync(string serviceType) + { + var prices = await _priceRepository.FindAsync(p => p.ServiceType == serviceType); + var price = prices.FirstOrDefault(); + return price == null ? null : MapToResponse(price); + } + + public async Task UpsertPriceConfigAsync(int? id, UpsertPriceConfigRequest request) + { + PriceConfig priceConfig; + + if (id.HasValue) + { + // Update existing + priceConfig = await _priceRepository.GetByIdAsync(id.Value) + ?? throw new InvalidOperationException($"Price config with ID {id} not found"); + + priceConfig.ServiceType = request.ServiceType; + priceConfig.Price = request.Price; + priceConfig.Currency = request.Currency; + priceConfig.IsEnabled = request.IsEnabled; + priceConfig.UpdatedAt = DateTime.UtcNow; + + await _priceRepository.UpdateAsync(priceConfig); + } + else + { + // Create new + priceConfig = new PriceConfig + { + ServiceType = request.ServiceType, + Price = request.Price, + Currency = request.Currency, + IsEnabled = request.IsEnabled + }; + + priceConfig = await _priceRepository.AddAsync(priceConfig); + } + + await _unitOfWork.SaveChangesAsync(); + return MapToResponse(priceConfig); + } + + public async Task DeletePriceConfigAsync(int id) + { + var priceConfig = await _priceRepository.GetByIdAsync(id); + if (priceConfig == null) + { + return false; + } + + await _priceRepository.DeleteAsync(priceConfig); + await _unitOfWork.SaveChangesAsync(); + return true; + } + + private static PriceConfigResponse MapToResponse(PriceConfig config) + { + return new PriceConfigResponse + { + Id = config.Id, + ServiceType = config.ServiceType, + Price = config.Price, + Currency = config.Currency, + IsEnabled = config.IsEnabled + }; + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Common/BaseEntity.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Common/BaseEntity.cs new file mode 100644 index 0000000..827504a --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Common/BaseEntity.cs @@ -0,0 +1,17 @@ +namespace FateMaster.Domain.Common; + +/// +/// 实体基类 +/// +public abstract class BaseEntity +{ + /// + /// 创建时间 + /// + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + /// + /// 更新时间 + /// + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Entities/DivinationRecord.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Entities/DivinationRecord.cs new file mode 100644 index 0000000..e71c512 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Entities/DivinationRecord.cs @@ -0,0 +1,77 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using FateMaster.Domain.Common; + +namespace FateMaster.Domain.Entities; + +/// +/// 卜卦记录表 +/// +public class DivinationRecord : BaseEntity +{ + [Key] + public long Id { get; set; } + + /// + /// 卜卦类型: bazi, career, marriage, tarot, zodiac + /// + [Required] + [MaxLength(50)] + public string Type { get; set; } = string.Empty; + + /// + /// 用户输入数据(JSON) + /// + [Required] + [Column(TypeName = "json")] + public string InputData { get; set; } = string.Empty; + + /// + /// 传统算法结果(JSON) + /// + [Column(TypeName = "json")] + public string? TraditionalResult { get; set; } + + /// + /// AI解读结果 + /// + [Column(TypeName = "text")] + public string? AIInterpretation { get; set; } + + /// + /// 支付状态: pending, paid, failed + /// + [Required] + [MaxLength(20)] + public string PaymentStatus { get; set; } = "pending"; + + /// + /// 支付方式: alipay, paypal, stripe + /// + [MaxLength(20)] + public string? PaymentMethod { get; set; } + + /// + /// 支付金额 + /// + [Column(TypeName = "decimal(10,2)")] + public decimal Amount { get; set; } + + /// + /// 支付订单号 + /// + [MaxLength(100)] + public string? PaymentOrderId { get; set; } + + /// + /// 客户端IP + /// + [MaxLength(50)] + public string? ClientIp { get; set; } + + /// + /// 语言 + /// + [MaxLength(10)] + public string? Language { get; set; } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Entities/PriceConfig.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Entities/PriceConfig.cs new file mode 100644 index 0000000..0ce6139 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Entities/PriceConfig.cs @@ -0,0 +1,40 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using FateMaster.Domain.Common; + +namespace FateMaster.Domain.Entities; + +/// +/// 价格配置表 +/// +public class PriceConfig : BaseEntity +{ + [Key] + public int Id { get; set; } + + /// + /// 服务类型: bazi, career, marriage, tarot, zodiac + /// + [Required] + [MaxLength(50)] + public string ServiceType { get; set; } = string.Empty; + + /// + /// 价格 + /// + [Required] + [Column(TypeName = "decimal(10,2)")] + public decimal Price { get; set; } + + /// + /// 货币: CNY, USD + /// + [Required] + [MaxLength(10)] + public string Currency { get; set; } = "CNY"; + + /// + /// 是否启用 + /// + public bool IsEnabled { get; set; } = true; +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Entities/SystemConfig.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Entities/SystemConfig.cs new file mode 100644 index 0000000..77f1fa4 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Entities/SystemConfig.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; +using FateMaster.Domain.Common; + +namespace FateMaster.Domain.Entities; + +/// +/// 系统配置表 +/// +public class SystemConfig : BaseEntity +{ + [Key] + public int Id { get; set; } + + /// + /// 配置键 + /// + [Required] + [MaxLength(100)] + public string ConfigKey { get; set; } = string.Empty; + + /// + /// 配置值 + /// + [Required] + public string ConfigValue { get; set; } = string.Empty; + + /// + /// 描述 + /// + [MaxLength(500)] + public string? Description { get; set; } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Enums/DivinationType.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Enums/DivinationType.cs new file mode 100644 index 0000000..2edf078 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Enums/DivinationType.cs @@ -0,0 +1,32 @@ +namespace FateMaster.Domain.Enums; + +/// +/// 卜卦类型 +/// +public enum DivinationType +{ + /// + /// 八字算命 + /// + Bazi, + + /// + /// 事业运势 + /// + Career, + + /// + /// 婚姻运势 + /// + Marriage, + + /// + /// 塔罗牌 + /// + Tarot, + + /// + /// 星座运势 + /// + Zodiac +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Enums/PaymentMethod.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Enums/PaymentMethod.cs new file mode 100644 index 0000000..5a2987b --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Enums/PaymentMethod.cs @@ -0,0 +1,22 @@ +namespace FateMaster.Domain.Enums; + +/// +/// 支付方式 +/// +public enum PaymentMethod +{ + /// + /// 支付宝 + /// + Alipay, + + /// + /// PayPal + /// + PayPal, + + /// + /// Stripe + /// + Stripe +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Enums/PaymentStatus.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Enums/PaymentStatus.cs new file mode 100644 index 0000000..4f0e0ed --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Enums/PaymentStatus.cs @@ -0,0 +1,22 @@ +namespace FateMaster.Domain.Enums; + +/// +/// 支付状态 +/// +public enum PaymentStatus +{ + /// + /// 待支付 + /// + Pending, + + /// + /// 已支付 + /// + Paid, + + /// + /// 支付失败 + /// + Failed +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/FateMaster.Domain.csproj b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/FateMaster.Domain.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/FateMaster.Domain.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Interfaces/IRepository.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Interfaces/IRepository.cs new file mode 100644 index 0000000..1fa9985 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Interfaces/IRepository.cs @@ -0,0 +1,19 @@ +using System.Linq.Expressions; + +namespace FateMaster.Domain.Interfaces; + +/// +/// 通用仓储接口 +/// +/// 实体类型 +public interface IRepository where T : class +{ + Task GetByIdAsync(object id); + Task> GetAllAsync(); + Task> FindAsync(Expression> predicate); + Task AddAsync(T entity); + Task UpdateAsync(T entity); + Task DeleteAsync(T entity); + Task ExistsAsync(Expression> predicate); + Task CountAsync(Expression>? predicate = null); +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Interfaces/IUnitOfWork.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Interfaces/IUnitOfWork.cs new file mode 100644 index 0000000..05b6b0e --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Domain/Interfaces/IUnitOfWork.cs @@ -0,0 +1,12 @@ +namespace FateMaster.Domain.Interfaces; + +/// +/// 工作单元接口 +/// +public interface IUnitOfWork : IDisposable +{ + Task SaveChangesAsync(CancellationToken cancellationToken = default); + Task BeginTransactionAsync(); + Task CommitTransactionAsync(); + Task RollbackTransactionAsync(); +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Data/ApplicationDbContext.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Data/ApplicationDbContext.cs new file mode 100644 index 0000000..6371c1c --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Data/ApplicationDbContext.cs @@ -0,0 +1,47 @@ +using FateMaster.Domain.Entities; +using Microsoft.EntityFrameworkCore; + +namespace FateMaster.Infrastructure.Data; + +public class ApplicationDbContext : DbContext +{ + public ApplicationDbContext(DbContextOptions options) + : base(options) + { + } + + public DbSet DivinationRecords { get; set; } + public DbSet SystemConfigs { get; set; } + public DbSet PriceConfigs { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // 配置索引 + modelBuilder.Entity() + .HasIndex(d => d.Type); + + modelBuilder.Entity() + .HasIndex(d => d.PaymentStatus); + + modelBuilder.Entity() + .HasIndex(d => d.CreatedAt); + + modelBuilder.Entity() + .HasIndex(s => s.ConfigKey) + .IsUnique(); + + modelBuilder.Entity() + .HasIndex(p => p.ServiceType); + + // 初始化数据 + modelBuilder.Entity().HasData( + new PriceConfig { Id = 1, ServiceType = "bazi", Price = 99, Currency = "CNY", CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }, + new PriceConfig { Id = 2, ServiceType = "career", Price = 88, Currency = "CNY", CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }, + new PriceConfig { Id = 3, ServiceType = "marriage", Price = 88, Currency = "CNY", CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }, + new PriceConfig { Id = 4, ServiceType = "tarot", Price = 66, Currency = "CNY", CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }, + new PriceConfig { Id = 5, ServiceType = "zodiac", Price = 29, Currency = "CNY", CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow } + ); + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/DependencyInjection.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/DependencyInjection.cs new file mode 100644 index 0000000..a9f6aba --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/DependencyInjection.cs @@ -0,0 +1,30 @@ +using FateMaster.Domain.Interfaces; +using FateMaster.Infrastructure.Data; +using FateMaster.Infrastructure.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace FateMaster.Infrastructure; + +/// +/// Infrastructure层依赖注入扩展 +/// +public static class DependencyInjection +{ + public static IServiceCollection AddInfrastructure( + this IServiceCollection services, + IConfiguration configuration) + { + // 配置数据库 + var connectionString = configuration.GetConnectionString("DefaultConnection"); + services.AddDbContext(options => + options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))); + + // 注册仓储和工作单元 + services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); + services.AddScoped(); + + return services; + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/FateMaster.Infrastructure.csproj b/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/FateMaster.Infrastructure.csproj new file mode 100644 index 0000000..77d4cec --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/FateMaster.Infrastructure.csproj @@ -0,0 +1,23 @@ + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + net8.0 + enable + enable + + + diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Migrations/20251003155521_InitialCreate.Designer.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Migrations/20251003155521_InitialCreate.Designer.cs new file mode 100644 index 0000000..81fd856 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Migrations/20251003155521_InitialCreate.Designer.cs @@ -0,0 +1,218 @@ +// +using System; +using FateMaster.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace FateMaster.Infrastructure.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20251003155521_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0-preview.1.24081.2") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("FateMaster.Domain.Entities.DivinationRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AIInterpretation") + .HasColumnType("text"); + + b.Property("Amount") + .HasColumnType("decimal(10,2)"); + + b.Property("ClientIp") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("InputData") + .IsRequired() + .HasColumnType("json"); + + b.Property("Language") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("PaymentMethod") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("PaymentOrderId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("PaymentStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TraditionalResult") + .HasColumnType("json"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("PaymentStatus"); + + b.HasIndex("Type"); + + b.ToTable("DivinationRecords"); + }); + + modelBuilder.Entity("FateMaster.Domain.Entities.PriceConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("Price") + .HasColumnType("decimal(10,2)"); + + b.Property("ServiceType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ServiceType"); + + b.ToTable("PriceConfigs"); + + b.HasData( + new + { + Id = 1, + CreatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8683), + Currency = "CNY", + IsEnabled = true, + Price = 99m, + ServiceType = "bazi", + UpdatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8685) + }, + new + { + Id = 2, + CreatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8689), + Currency = "CNY", + IsEnabled = true, + Price = 88m, + ServiceType = "career", + UpdatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8690) + }, + new + { + Id = 3, + CreatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8693), + Currency = "CNY", + IsEnabled = true, + Price = 88m, + ServiceType = "marriage", + UpdatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8693) + }, + new + { + Id = 4, + CreatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8696), + Currency = "CNY", + IsEnabled = true, + Price = 66m, + ServiceType = "tarot", + UpdatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8696) + }, + new + { + Id = 5, + CreatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8699), + Currency = "CNY", + IsEnabled = true, + Price = 29m, + ServiceType = "zodiac", + UpdatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8700) + }); + }); + + modelBuilder.Entity("FateMaster.Domain.Entities.SystemConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ConfigKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ConfigValue") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ConfigKey") + .IsUnique(); + + b.ToTable("SystemConfigs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Migrations/20251003155521_InitialCreate.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Migrations/20251003155521_InitialCreate.cs new file mode 100644 index 0000000..5358e07 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Migrations/20251003155521_InitialCreate.cs @@ -0,0 +1,148 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace FateMaster.Infrastructure.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "DivinationRecords", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Type = table.Column(type: "varchar(50)", maxLength: 50, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + InputData = table.Column(type: "json", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + TraditionalResult = table.Column(type: "json", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + AIInterpretation = table.Column(type: "text", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + PaymentStatus = table.Column(type: "varchar(20)", maxLength: 20, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + PaymentMethod = table.Column(type: "varchar(20)", maxLength: 20, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Amount = table.Column(type: "decimal(10,2)", nullable: false), + PaymentOrderId = table.Column(type: "varchar(100)", maxLength: 100, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + ClientIp = table.Column(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Language = table.Column(type: "varchar(10)", maxLength: 10, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + UpdatedAt = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DivinationRecords", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "PriceConfigs", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + ServiceType = table.Column(type: "varchar(50)", maxLength: 50, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Price = table.Column(type: "decimal(10,2)", nullable: false), + Currency = table.Column(type: "varchar(10)", maxLength: 10, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + IsEnabled = table.Column(type: "tinyint(1)", nullable: false), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + UpdatedAt = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PriceConfigs", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "SystemConfigs", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + ConfigKey = table.Column(type: "varchar(100)", maxLength: 100, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + ConfigValue = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Description = table.Column(type: "varchar(500)", maxLength: 500, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + UpdatedAt = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SystemConfigs", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.InsertData( + table: "PriceConfigs", + columns: new[] { "Id", "CreatedAt", "Currency", "IsEnabled", "Price", "ServiceType", "UpdatedAt" }, + values: new object[,] + { + { 1, new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8683), "CNY", true, 99m, "bazi", new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8685) }, + { 2, new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8689), "CNY", true, 88m, "career", new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8690) }, + { 3, new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8693), "CNY", true, 88m, "marriage", new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8693) }, + { 4, new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8696), "CNY", true, 66m, "tarot", new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8696) }, + { 5, new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8699), "CNY", true, 29m, "zodiac", new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8700) } + }); + + migrationBuilder.CreateIndex( + name: "IX_DivinationRecords_CreatedAt", + table: "DivinationRecords", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_DivinationRecords_PaymentStatus", + table: "DivinationRecords", + column: "PaymentStatus"); + + migrationBuilder.CreateIndex( + name: "IX_DivinationRecords_Type", + table: "DivinationRecords", + column: "Type"); + + migrationBuilder.CreateIndex( + name: "IX_PriceConfigs_ServiceType", + table: "PriceConfigs", + column: "ServiceType"); + + migrationBuilder.CreateIndex( + name: "IX_SystemConfigs_ConfigKey", + table: "SystemConfigs", + column: "ConfigKey", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DivinationRecords"); + + migrationBuilder.DropTable( + name: "PriceConfigs"); + + migrationBuilder.DropTable( + name: "SystemConfigs"); + } + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000..daa407b --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,215 @@ +// +using System; +using FateMaster.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace FateMaster.Infrastructure.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0-preview.1.24081.2") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("FateMaster.Domain.Entities.DivinationRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AIInterpretation") + .HasColumnType("text"); + + b.Property("Amount") + .HasColumnType("decimal(10,2)"); + + b.Property("ClientIp") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("InputData") + .IsRequired() + .HasColumnType("json"); + + b.Property("Language") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("PaymentMethod") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("PaymentOrderId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("PaymentStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TraditionalResult") + .HasColumnType("json"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("PaymentStatus"); + + b.HasIndex("Type"); + + b.ToTable("DivinationRecords"); + }); + + modelBuilder.Entity("FateMaster.Domain.Entities.PriceConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("Price") + .HasColumnType("decimal(10,2)"); + + b.Property("ServiceType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ServiceType"); + + b.ToTable("PriceConfigs"); + + b.HasData( + new + { + Id = 1, + CreatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8683), + Currency = "CNY", + IsEnabled = true, + Price = 99m, + ServiceType = "bazi", + UpdatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8685) + }, + new + { + Id = 2, + CreatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8689), + Currency = "CNY", + IsEnabled = true, + Price = 88m, + ServiceType = "career", + UpdatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8690) + }, + new + { + Id = 3, + CreatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8693), + Currency = "CNY", + IsEnabled = true, + Price = 88m, + ServiceType = "marriage", + UpdatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8693) + }, + new + { + Id = 4, + CreatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8696), + Currency = "CNY", + IsEnabled = true, + Price = 66m, + ServiceType = "tarot", + UpdatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8696) + }, + new + { + Id = 5, + CreatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8699), + Currency = "CNY", + IsEnabled = true, + Price = 29m, + ServiceType = "zodiac", + UpdatedAt = new DateTime(2025, 10, 3, 15, 55, 21, 572, DateTimeKind.Utc).AddTicks(8700) + }); + }); + + modelBuilder.Entity("FateMaster.Domain.Entities.SystemConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ConfigKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ConfigValue") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ConfigKey") + .IsUnique(); + + b.ToTable("SystemConfigs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Repositories/Repository.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Repositories/Repository.cs new file mode 100644 index 0000000..8f2a9fc --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Repositories/Repository.cs @@ -0,0 +1,66 @@ +using System.Linq.Expressions; +using FateMaster.Domain.Interfaces; +using FateMaster.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace FateMaster.Infrastructure.Repositories; + +/// +/// 通用仓储实现 +/// +public class Repository : IRepository where T : class +{ + protected readonly ApplicationDbContext _context; + protected readonly DbSet _dbSet; + + public Repository(ApplicationDbContext context) + { + _context = context; + _dbSet = context.Set(); + } + + public virtual async Task GetByIdAsync(object id) + { + return await _dbSet.FindAsync(id); + } + + public virtual async Task> GetAllAsync() + { + return await _dbSet.ToListAsync(); + } + + public virtual async Task> FindAsync(Expression> predicate) + { + return await _dbSet.Where(predicate).ToListAsync(); + } + + public virtual async Task AddAsync(T entity) + { + await _dbSet.AddAsync(entity); + return entity; + } + + public virtual Task UpdateAsync(T entity) + { + _dbSet.Update(entity); + return Task.CompletedTask; + } + + public virtual Task DeleteAsync(T entity) + { + _dbSet.Remove(entity); + return Task.CompletedTask; + } + + public virtual async Task ExistsAsync(Expression> predicate) + { + return await _dbSet.AnyAsync(predicate); + } + + public virtual async Task CountAsync(Expression>? predicate = null) + { + return predicate == null + ? await _dbSet.CountAsync() + : await _dbSet.CountAsync(predicate); + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Repositories/UnitOfWork.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Repositories/UnitOfWork.cs new file mode 100644 index 0000000..baf4cf3 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Infrastructure/Repositories/UnitOfWork.cs @@ -0,0 +1,55 @@ +using FateMaster.Domain.Interfaces; +using FateMaster.Infrastructure.Data; +using Microsoft.EntityFrameworkCore.Storage; + +namespace FateMaster.Infrastructure.Repositories; + +/// +/// 工作单元实现 +/// +public class UnitOfWork : IUnitOfWork +{ + private readonly ApplicationDbContext _context; + private IDbContextTransaction? _transaction; + + public UnitOfWork(ApplicationDbContext context) + { + _context = context; + } + + public async Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + return await _context.SaveChangesAsync(cancellationToken); + } + + public async Task BeginTransactionAsync() + { + _transaction = await _context.Database.BeginTransactionAsync(); + } + + public async Task CommitTransactionAsync() + { + if (_transaction != null) + { + await _transaction.CommitAsync(); + await _transaction.DisposeAsync(); + _transaction = null; + } + } + + public async Task RollbackTransactionAsync() + { + if (_transaction != null) + { + await _transaction.RollbackAsync(); + await _transaction.DisposeAsync(); + _transaction = null; + } + } + + public void Dispose() + { + _transaction?.Dispose(); + _context.Dispose(); + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/Constants/AppConstants.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/Constants/AppConstants.cs new file mode 100644 index 0000000..887e577 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/Constants/AppConstants.cs @@ -0,0 +1,48 @@ +namespace FateMaster.Shared.Constants; + +/// +/// 应用常量 +/// +public static class AppConstants +{ + /// + /// 支付状态 + /// + public static class PaymentStatuses + { + public const string Pending = "pending"; + public const string Paid = "paid"; + public const string Failed = "failed"; + } + + /// + /// 服务类型 + /// + public static class ServiceTypes + { + public const string Bazi = "bazi"; + public const string Career = "career"; + public const string Marriage = "marriage"; + public const string Tarot = "tarot"; + public const string Zodiac = "zodiac"; + } + + /// + /// 支付方式 + /// + public static class PaymentMethods + { + public const string Alipay = "alipay"; + public const string PayPal = "paypal"; + public const string Stripe = "stripe"; + } + + /// + /// 货币类型 + /// + public static class Currencies + { + public const string CNY = "CNY"; + public const string USD = "USD"; + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/DTOs/AdminDTOs.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/DTOs/AdminDTOs.cs new file mode 100644 index 0000000..798bc3a --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/DTOs/AdminDTOs.cs @@ -0,0 +1,42 @@ +namespace FateMaster.Shared.DTOs; + +/// +/// 分页查询请求 +/// +public record PagedRequest( + int Page = 1, + int PageSize = 20, + string? Type = null, + string? PaymentStatus = null +); + +/// +/// 分页响应 +/// +public record PagedResponse +{ + public int Total { get; init; } + public int Page { get; init; } + public int PageSize { get; init; } + public IEnumerable Data { get; init; } = new List(); +} + +/// +/// 统计数据响应 +/// +public record StatisticsResponse +{ + public int Total { get; init; } + public int PaidCount { get; init; } + public decimal TotalRevenue { get; init; } + public IEnumerable TypeStats { get; init; } = new List(); +} + +/// +/// 类型统计 +/// +public record TypeStatistics +{ + public string Type { get; init; } = string.Empty; + public int Count { get; init; } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/DTOs/ApiResponse.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/DTOs/ApiResponse.cs new file mode 100644 index 0000000..a730a29 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/DTOs/ApiResponse.cs @@ -0,0 +1,32 @@ +namespace FateMaster.Shared.DTOs; + +/// +/// 通用响应包装 +/// +public record ApiResponse +{ + public bool Success { get; init; } + public T? Data { get; init; } + public string? Message { get; init; } + public string? ErrorCode { get; init; } + + public static ApiResponse SuccessResult(T data, string? message = null) + { + return new ApiResponse + { + Success = true, + Data = data, + Message = message + }; + } + + public static ApiResponse FailureResult(string message, string? errorCode = null) + { + return new ApiResponse + { + Success = false, + Message = message, + ErrorCode = errorCode + }; + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/DTOs/BaziDTOs.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/DTOs/BaziDTOs.cs new file mode 100644 index 0000000..3ac5564 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/DTOs/BaziDTOs.cs @@ -0,0 +1,148 @@ +namespace FateMaster.Shared.DTOs; + +/// +/// 八字计算请求DTO +/// +public record BaziCalculateRequest +{ + public string Name { get; init; } = string.Empty; + public string Gender { get; init; } = string.Empty; + public string CalendarType { get; init; } = "solar"; // solar, lunar + + // 公历日期 + public DateTime? BirthDate { get; init; } + + // 农历日期 + public int? LunarYear { get; init; } + public int? LunarMonth { get; init; } + public int? LunarDay { get; init; } + + // 时间 + public bool KnowTime { get; init; } + public int? BirthHour { get; init; } + public int? BirthMinute { get; init; } + + // 出生地点 + public string? BirthPlace { get; init; } + public bool UseTrueSolarTime { get; init; } + public bool UseEarlyChildTime { get; init; } + + // 直接输入八字 + public string? YearStem { get; init; } + public string? YearBranch { get; init; } + public string? MonthStem { get; init; } + public string? MonthBranch { get; init; } + public string? DayStem { get; init; } + public string? DayBranch { get; init; } + public string? HourStem { get; init; } + public string? HourBranch { get; init; } + + public string Language { get; init; } = "zh-CN"; +} + +/// +/// 八字计算响应DTO +/// +public record BaziCalculateResponse +{ + public long RecordId { get; init; } + public BaziBasicInfo BasicInfo { get; init; } = new(); + public BaziChart Chart { get; init; } = new(); + public FiveElementsAnalysis FiveElements { get; init; } = new(); + public TenGodsAnalysis TenGods { get; init; } = new(); + public LuckCycleInfo LuckCycles { get; init; } = new(); + public AiInterpretation AiAnalysis { get; init; } = new(); +} + +/// +/// 八字基本信息 +/// +public record BaziBasicInfo +{ + public string Name { get; init; } = string.Empty; + public string Gender { get; init; } = string.Empty; + public string BirthDateTime { get; init; } = string.Empty; + public string LunarDateTime { get; init; } = string.Empty; + public string? BirthPlace { get; init; } +} + +/// +/// 八字排盘 +/// +public record BaziChart +{ + public Pillar YearPillar { get; init; } = new(); + public Pillar MonthPillar { get; init; } = new(); + public Pillar DayPillar { get; init; } = new(); + public Pillar HourPillar { get; init; } = new(); + public string DayMaster { get; init; } = string.Empty; // 日主 + public string? NaYin { get; init; } // 纳音 +} + +/// +/// 柱(干支) +/// +public record Pillar +{ + public string Stem { get; init; } = string.Empty; // 天干 + public string Branch { get; init; } = string.Empty; // 地支 + public string HiddenStems { get; init; } = string.Empty; // 藏干 + public string Element { get; init; } = string.Empty; // 五行 +} + +/// +/// 五行分析 +/// +public record FiveElementsAnalysis +{ + public Dictionary Elements { get; init; } = new(); + public string StrongestElement { get; init; } = string.Empty; + public string WeakestElement { get; init; } = string.Empty; + public List MissingElements { get; init; } = new(); + public string BaziStrength { get; init; } = string.Empty; // 身强/身弱 +} + +/// +/// 十神分析 +/// +public record TenGodsAnalysis +{ + public Dictionary> Gods { get; init; } = new(); + public string MainGod { get; init; } = string.Empty; +} + +/// +/// 大运流年 +/// +public record LuckCycleInfo +{ + public List MajorCycles { get; init; } = new(); // 大运 + public LuckCycle? CurrentCycle { get; init; } // 当前大运 + public int StartAge { get; init; } // 起运岁数 +} + +/// +/// 运势周期 +/// +public record LuckCycle +{ + public string Stem { get; init; } = string.Empty; + public string Branch { get; init; } = string.Empty; + public int StartAge { get; init; } + public int EndAge { get; init; } + public string Element { get; init; } = string.Empty; +} + +/// +/// AI解读 +/// +public record AiInterpretation +{ + public string? Character { get; init; } // 性格分析 + public string? Career { get; init; } // 事业分析 + public string? Wealth { get; init; } // 财运分析 + public string? Marriage { get; init; } // 婚姻分析 + public string? Health { get; init; } // 健康分析 + public string? Summary { get; init; } // 综合分析 + public string? Suggestions { get; init; } // 建议 +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/DTOs/DivinationDTOs.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/DTOs/DivinationDTOs.cs new file mode 100644 index 0000000..7afbc82 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/DTOs/DivinationDTOs.cs @@ -0,0 +1,40 @@ +namespace FateMaster.Shared.DTOs; + +/// +/// 提交卜卦请求DTO +/// +public record SubmitDivinationRequest( + string Type, + string InputData, + string? PaymentMethod, + decimal Amount, + string? Language = "zh-CN" +); + +/// +/// 卜卦记录响应DTO +/// +public record DivinationRecordResponse +{ + public long Id { get; init; } + public string Type { get; init; } = string.Empty; + public string InputData { get; init; } = string.Empty; + public string? TraditionalResult { get; init; } + public string? AIInterpretation { get; init; } + public string PaymentStatus { get; init; } = string.Empty; + public string? PaymentMethod { get; init; } + public decimal Amount { get; init; } + public string? PaymentOrderId { get; init; } + public string? Language { get; init; } + public DateTime CreatedAt { get; init; } + public DateTime UpdatedAt { get; init; } +} + +/// +/// 更新支付状态请求DTO +/// +public record UpdatePaymentStatusRequest( + long RecordId, + string PaymentStatus, + string? PaymentOrderId = null +); diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/DTOs/PriceConfigDTOs.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/DTOs/PriceConfigDTOs.cs new file mode 100644 index 0000000..367593f --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/DTOs/PriceConfigDTOs.cs @@ -0,0 +1,23 @@ +namespace FateMaster.Shared.DTOs; + +/// +/// 价格配置响应DTO +/// +public record PriceConfigResponse +{ + public int Id { get; init; } + public string ServiceType { get; init; } = string.Empty; + public decimal Price { get; init; } + public string Currency { get; init; } = string.Empty; + public bool IsEnabled { get; init; } +} + +/// +/// 创建/更新价格配置请求DTO +/// +public record UpsertPriceConfigRequest( + string ServiceType, + decimal Price, + string Currency = "CNY", + bool IsEnabled = true +); diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/FateMaster.Shared.csproj b/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/FateMaster.Shared.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Shared/FateMaster.Shared.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/Controllers/BaziController.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/Controllers/BaziController.cs new file mode 100644 index 0000000..72da696 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/Controllers/BaziController.cs @@ -0,0 +1,66 @@ +using FateMaster.Application.Interfaces; +using FateMaster.Shared.DTOs; +using Microsoft.AspNetCore.Mvc; + +namespace FateMaster.Web.API.Controllers; + +/// +/// 八字算命Controller +/// +[ApiController] +[Route("api/[controller]")] +public class BaziController : ControllerBase +{ + private readonly IBaziService _baziService; + private readonly ILogger _logger; + + public BaziController( + IBaziService baziService, + ILogger logger) + { + _baziService = baziService; + _logger = logger; + } + + /// + /// 计算八字 + /// + [HttpPost("calculate")] + public async Task>> Calculate([FromBody] BaziCalculateRequest request) + { + try + { + var clientIp = HttpContext.Connection.RemoteIpAddress?.ToString(); + var result = await _baziService.CalculateBaziAsync(request, clientIp); + return Ok(ApiResponse.SuccessResult(result)); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error calculating bazi"); + return StatusCode(500, ApiResponse.FailureResult("计算失败,请稍后再试")); + } + } + + /// + /// 获取八字结果 + /// + [HttpGet("result/{recordId}")] + public async Task>> GetResult(long recordId) + { + try + { + var result = await _baziService.GetBaziResultAsync(recordId); + if (result == null) + { + return NotFound(ApiResponse.FailureResult("记录不存在", "NOT_FOUND")); + } + + return Ok(ApiResponse.SuccessResult(result)); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting bazi result"); + return StatusCode(500, ApiResponse.FailureResult("获取结果失败")); + } + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/Controllers/DivinationController.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/Controllers/DivinationController.cs new file mode 100644 index 0000000..5f486ff --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/Controllers/DivinationController.cs @@ -0,0 +1,81 @@ +using FateMaster.Application.Interfaces; +using FateMaster.Shared.DTOs; +using Microsoft.AspNetCore.Mvc; + +namespace FateMaster.Web.API.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class DivinationController : ControllerBase +{ + private readonly IDivinationService _divinationService; + private readonly ILogger _logger; + + public DivinationController( + IDivinationService divinationService, + ILogger logger) + { + _divinationService = divinationService; + _logger = logger; + } + + /// + /// 获取价格配置 + /// + [HttpGet("prices")] + public async Task>>> GetPrices() + { + try + { + var prices = await _divinationService.GetEnabledPricesAsync(); + return Ok(ApiResponse>.SuccessResult(prices)); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting prices"); + return StatusCode(500, ApiResponse>.FailureResult("获取价格配置失败")); + } + } + + /// + /// 提交卜卦请求 + /// + [HttpPost("submit")] + public async Task>> Submit([FromBody] SubmitDivinationRequest request) + { + try + { + var clientIp = HttpContext.Connection.RemoteIpAddress?.ToString(); + var record = await _divinationService.SubmitDivinationAsync(request, clientIp); + return Ok(ApiResponse.SuccessResult(record)); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error submitting divination request"); + return StatusCode(500, ApiResponse.FailureResult("提交失败")); + } + } + + /// + /// 获取卜卦结果 + /// + [HttpGet("{id}")] + public async Task>> GetResult(long id) + { + try + { + var record = await _divinationService.GetDivinationRecordAsync(id); + if (record == null) + { + return NotFound(ApiResponse.FailureResult("记录不存在", "NOT_FOUND")); + } + + return Ok(ApiResponse.SuccessResult(record)); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting divination record"); + return StatusCode(500, ApiResponse.FailureResult("获取记录失败")); + } + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/FateMaster.Web.API.csproj b/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/FateMaster.Web.API.csproj new file mode 100644 index 0000000..f11467e --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/FateMaster.Web.API.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/FateMaster.Web.API.http b/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/FateMaster.Web.API.http new file mode 100644 index 0000000..9bc338b --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/FateMaster.Web.API.http @@ -0,0 +1,6 @@ +@FateMaster.Web.API_HostAddress = http://localhost:5238 + +GET {{FateMaster.Web.API_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/Program.cs b/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/Program.cs new file mode 100644 index 0000000..de4a36d --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/Program.cs @@ -0,0 +1,54 @@ +using FateMaster.Application; +using FateMaster.Infrastructure; +using FateMaster.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +// 添加Application和Infrastructure层 +builder.Services.AddApplication(); +builder.Services.AddInfrastructure(builder.Configuration); + +// Configure CORS +builder.Services.AddCors(options => +{ + options.AddPolicy("AllowFrontend", policy => + { + policy.WithOrigins("http://localhost:3000", "http://localhost:3001") + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials(); + }); +}); + +var app = builder.Build(); + +// 自动数据库迁移 +var autoMigrate = builder.Configuration.GetValue("DatabaseSettings:AutoMigrate"); +if (autoMigrate) +{ + using (var scope = app.Services.CreateScope()) + { + var dbContext = scope.ServiceProvider.GetRequiredService(); + dbContext.Database.Migrate(); + } +} + +// Configure the HTTP request pipeline +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseCors("AllowFrontend"); +app.UseHttpsRedirection(); +app.UseAuthorization(); +app.MapControllers(); + +app.Run(); diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/Program.cs.bak b/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/Program.cs.bak new file mode 100644 index 0000000..720b582 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/Program.cs.bak @@ -0,0 +1,41 @@ +using FateMaster.Application; +using FateMaster.Infrastructure; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +// 添加Application和Infrastructure层 +builder.Services.AddApplication(); +builder.Services.AddInfrastructure(builder.Configuration); + +// Configure CORS +builder.Services.AddCors(options => +{ + options.AddPolicy("AllowFrontend", policy => + { + policy.WithOrigins("http://localhost:3000", "http://localhost:3001") + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials(); + }); +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseCors("AllowFrontend"); +app.UseHttpsRedirection(); +app.UseAuthorization(); +app.MapControllers(); + +app.Run(); diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/Properties/launchSettings.json b/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/Properties/launchSettings.json new file mode 100644 index 0000000..f272bcd --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:20980", + "sslPort": 44332 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5238", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7100;http://localhost:5238", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/appsettings.json b/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/appsettings.json new file mode 100644 index 0000000..169d4a9 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.Web.API/appsettings.json @@ -0,0 +1,37 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "Server=localhost;Port=3306;Database=fatemaster;User=root;Password=root;" + }, + "DatabaseSettings": { + "AutoMigrate": true, + "SeedData": true + }, + "PaymentSettings": { + "Alipay": { + "AppId": "", + "PrivateKey": "", + "PublicKey": "" + }, + "PayPal": { + "ClientId": "", + "ClientSecret": "", + "Mode": "sandbox" + }, + "Stripe": { + "SecretKey": "", + "PublishableKey": "" + } + }, + "Claude": { + "ApiKey": "your-claude-api-key-here", + "Model": "claude-3-5-sonnet-20241022", + "BaseUrl": "https://api.anthropic.com/v1" + } +} diff --git a/backend/FateMaster.API/FateMaster.API/FateMaster.sln b/backend/FateMaster.API/FateMaster.API/FateMaster.sln new file mode 100644 index 0000000..2a72e30 --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/FateMaster.sln @@ -0,0 +1,61 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FateMaster.Domain", "FateMaster.Domain\FateMaster.Domain.csproj", "{BE5930BA-82BB-4925-B5A3-2CC67554D46A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FateMaster.Shared", "FateMaster.Shared\FateMaster.Shared.csproj", "{7B3DCF56-B449-4958-9ABC-48A0841261B2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FateMaster.Application", "FateMaster.Application\FateMaster.Application.csproj", "{BD8A2E8A-6F14-4EF1-A73E-16D63EF5BCFB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FateMaster.Infrastructure", "FateMaster.Infrastructure\FateMaster.Infrastructure.csproj", "{6FCBB14E-2F5F-4F8B-870C-678311866318}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FateMaster.Web.API", "FateMaster.Web.API\FateMaster.Web.API.csproj", "{55411F9B-BBD1-42F8-B915-D7FF01B95A1F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FateMaster.Admin.API", "FateMaster.Admin.API\FateMaster.Admin.API.csproj", "{1571483B-959B-4FCB-8D6E-67A57BF57EC0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Api", "Api", "{CDE3BA32-2B7E-4A11-B87E-B4F1760A859A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BE5930BA-82BB-4925-B5A3-2CC67554D46A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE5930BA-82BB-4925-B5A3-2CC67554D46A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE5930BA-82BB-4925-B5A3-2CC67554D46A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE5930BA-82BB-4925-B5A3-2CC67554D46A}.Release|Any CPU.Build.0 = Release|Any CPU + {7B3DCF56-B449-4958-9ABC-48A0841261B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B3DCF56-B449-4958-9ABC-48A0841261B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B3DCF56-B449-4958-9ABC-48A0841261B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B3DCF56-B449-4958-9ABC-48A0841261B2}.Release|Any CPU.Build.0 = Release|Any CPU + {BD8A2E8A-6F14-4EF1-A73E-16D63EF5BCFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD8A2E8A-6F14-4EF1-A73E-16D63EF5BCFB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD8A2E8A-6F14-4EF1-A73E-16D63EF5BCFB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD8A2E8A-6F14-4EF1-A73E-16D63EF5BCFB}.Release|Any CPU.Build.0 = Release|Any CPU + {6FCBB14E-2F5F-4F8B-870C-678311866318}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6FCBB14E-2F5F-4F8B-870C-678311866318}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6FCBB14E-2F5F-4F8B-870C-678311866318}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6FCBB14E-2F5F-4F8B-870C-678311866318}.Release|Any CPU.Build.0 = Release|Any CPU + {55411F9B-BBD1-42F8-B915-D7FF01B95A1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55411F9B-BBD1-42F8-B915-D7FF01B95A1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55411F9B-BBD1-42F8-B915-D7FF01B95A1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55411F9B-BBD1-42F8-B915-D7FF01B95A1F}.Release|Any CPU.Build.0 = Release|Any CPU + {1571483B-959B-4FCB-8D6E-67A57BF57EC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1571483B-959B-4FCB-8D6E-67A57BF57EC0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1571483B-959B-4FCB-8D6E-67A57BF57EC0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1571483B-959B-4FCB-8D6E-67A57BF57EC0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {55411F9B-BBD1-42F8-B915-D7FF01B95A1F} = {CDE3BA32-2B7E-4A11-B87E-B4F1760A859A} + {1571483B-959B-4FCB-8D6E-67A57BF57EC0} = {CDE3BA32-2B7E-4A11-B87E-B4F1760A859A} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CCBFA1D4-AD7C-4B85-A94E-E43515B6C070} + EndGlobalSection +EndGlobal diff --git a/backend/FateMaster.API/Models/DivinationRecord.cs b/backend/FateMaster.API/FateMaster.API/Models/DivinationRecord.cs similarity index 100% rename from backend/FateMaster.API/Models/DivinationRecord.cs rename to backend/FateMaster.API/FateMaster.API/Models/DivinationRecord.cs diff --git a/backend/FateMaster.API/Models/PriceConfig.cs b/backend/FateMaster.API/FateMaster.API/Models/PriceConfig.cs similarity index 100% rename from backend/FateMaster.API/Models/PriceConfig.cs rename to backend/FateMaster.API/FateMaster.API/Models/PriceConfig.cs diff --git a/backend/FateMaster.API/Models/SystemConfig.cs b/backend/FateMaster.API/FateMaster.API/Models/SystemConfig.cs similarity index 100% rename from backend/FateMaster.API/Models/SystemConfig.cs rename to backend/FateMaster.API/FateMaster.API/Models/SystemConfig.cs diff --git a/backend/FateMaster.API/Program.cs b/backend/FateMaster.API/FateMaster.API/Program.cs similarity index 100% rename from backend/FateMaster.API/Program.cs rename to backend/FateMaster.API/FateMaster.API/Program.cs diff --git a/backend/FateMaster.API/Properties/launchSettings.json b/backend/FateMaster.API/FateMaster.API/Properties/launchSettings.json similarity index 100% rename from backend/FateMaster.API/Properties/launchSettings.json rename to backend/FateMaster.API/FateMaster.API/Properties/launchSettings.json diff --git a/backend/FateMaster.API/FateMaster.API/README.md b/backend/FateMaster.API/FateMaster.API/README.md new file mode 100644 index 0000000..09240fa --- /dev/null +++ b/backend/FateMaster.API/FateMaster.API/README.md @@ -0,0 +1,254 @@ +# 命运大师 API - 重构后的架构 + +本项目采用 **Clean Architecture(整洁架构)** 模式重构,提供清晰的分层结构,易于维护和扩展。 + +## 项目结构 + +``` +FateMaster.API/ +├── FateMaster.Domain/ # 领域层(实体、枚举、领域接口) +│ ├── Entities/ # 实体类 +│ │ ├── DivinationRecord.cs +│ │ ├── PriceConfig.cs +│ │ └── SystemConfig.cs +│ ├── Enums/ # 枚举 +│ │ ├── DivinationType.cs +│ │ ├── PaymentStatus.cs +│ │ └── PaymentMethod.cs +│ ├── Common/ # 公共基类 +│ │ └── BaseEntity.cs +│ └── Interfaces/ # 领域接口 +│ ├── IRepository.cs +│ └── IUnitOfWork.cs +│ +├── FateMaster.Shared/ # 共享层(DTOs、常量、工具类) +│ ├── DTOs/ +│ │ ├── DivinationDTOs.cs # 卜卦相关DTOs +│ │ ├── PriceConfigDTOs.cs # 价格配置DTOs +│ │ ├── AdminDTOs.cs # 管理后台DTOs +│ │ └── ApiResponse.cs # 统一响应包装 +│ └── Constants/ +│ └── AppConstants.cs # 应用常量 +│ +├── FateMaster.Application/ # 应用服务层(业务逻辑) +│ ├── Interfaces/ +│ │ ├── IDivinationService.cs +│ │ ├── IPriceConfigService.cs +│ │ └── IAdminService.cs +│ ├── Services/ +│ │ ├── DivinationService.cs +│ │ ├── PriceConfigService.cs +│ │ └── AdminService.cs +│ └── DependencyInjection.cs # 服务注册 +│ +├── FateMaster.Infrastructure/ # 基础设施层(数据访问) +│ ├── Data/ +│ │ └── ApplicationDbContext.cs +│ ├── Repositories/ +│ │ ├── Repository.cs # 通用仓储实现 +│ │ └── UnitOfWork.cs # 工作单元实现 +│ └── DependencyInjection.cs # 基础设施服务注册 +│ +├── FateMaster.Web.API/ # 网站前端API +│ ├── Controllers/ +│ │ └── DivinationController.cs +│ ├── Program.cs +│ └── appsettings.json +│ +└── FateMaster.Admin.API/ # 管理后台API + ├── Controllers/ + │ ├── PricesController.cs + │ └── RecordsController.cs + ├── Program.cs + └── appsettings.json +``` + +## 架构层次 + +### 1. Domain(领域层) +- **职责**:定义核心实体、枚举和领域接口 +- **依赖**:无依赖(最内层) +- **特点**:独立于外部框架和技术 + +### 2. Shared(共享层) +- **职责**:提供跨层共享的DTOs、常量和工具类 +- **依赖**:无依赖 +- **特点**:可被所有层引用 + +### 3. Application(应用服务层) +- **职责**:实现业务逻辑和用例 +- **依赖**:Domain、Shared +- **特点**:定义服务接口和实现 + +### 4. Infrastructure(基础设施层) +- **职责**:实现数据访问和外部服务集成 +- **依赖**:Domain、Application、Shared +- **特点**:实现Repository模式和UnitOfWork模式 + +### 5. Web.API(网站前端API) +- **职责**:为网站前端提供HTTP API +- **端口**:默认 5000 (HTTP), 5001 (HTTPS) +- **依赖**:Application、Infrastructure、Shared +- **CORS**:允许 `http://localhost:3000`, `http://localhost:3001` + +### 6. Admin.API(管理后台API) +- **职责**:为管理后台提供HTTP API +- **端口**:默认 5002 (HTTP), 5003 (HTTPS) +- **依赖**:Application、Infrastructure、Shared +- **CORS**:允许 `http://localhost:3002`, `http://localhost:3003` + +## 运行项目 + +### 前置条件 +- .NET 8.0 SDK +- MySQL 8.0+ + +### 配置数据库 +修改 `appsettings.json` 中的连接字符串: +```json +{ + "ConnectionStrings": { + "DefaultConnection": "Server=localhost;Port=3306;Database=fatemaster;User=root;Password=your_password;" + } +} +``` + +### 运行迁移(首次运行) +```bash +# 在 FateMaster.API 目录下执行 +dotnet ef migrations add InitialCreate --project FateMaster.Infrastructure --startup-project FateMaster.Web.API +dotnet ef database update --project FateMaster.Infrastructure --startup-project FateMaster.Web.API +``` + +### 启动网站API +```bash +cd FateMaster.Web.API +dotnet run +# 访问 https://localhost:5001/swagger +``` + +### 启动管理后台API +```bash +cd FateMaster.Admin.API +dotnet run +# 访问 https://localhost:5003/swagger +``` + +## API 端点 + +### Web.API (网站前端) + +#### 获取价格配置 +```http +GET /api/divination/prices +``` + +#### 提交卜卦请求 +```http +POST /api/divination/submit +Content-Type: application/json + +{ + "type": "bazi", + "inputData": "{\"name\":\"张三\",\"birthDate\":\"1990-01-01\"}", + "paymentMethod": "alipay", + "amount": 99, + "language": "zh-CN" +} +``` + +#### 获取卜卦结果 +```http +GET /api/divination/{id} +``` + +### Admin.API (管理后台) + +#### 获取所有价格配置 +```http +GET /api/admin/prices +``` + +#### 更新价格配置 +```http +PUT /api/admin/prices/{id} +Content-Type: application/json + +{ + "serviceType": "bazi", + "price": 99.00, + "currency": "CNY", + "isEnabled": true +} +``` + +#### 获取卜卦记录列表(分页) +```http +GET /api/admin/records?page=1&pageSize=20&type=bazi&paymentStatus=paid +``` + +#### 获取统计数据 +```http +GET /api/admin/records/statistics +``` + +## 架构优势 + +### 1. 关注点分离 +- 每一层都有明确的职责 +- 业务逻辑与基础设施解耦 +- 易于理解和维护 + +### 2. 可测试性 +- 依赖注入使单元测试更容易 +- Repository模式便于Mock数据层 +- 服务层可独立测试 + +### 3. 可扩展性 +- 新增功能只需添加相应的服务 +- 更换数据库只需修改Infrastructure层 +- 支持多个API项目共享业务逻辑 + +### 4. 依赖规则 +- 内层不依赖外层 +- 依赖方向:API → Application → Domain +- Infrastructure实现Domain定义的接口 + +## 开发指南 + +### 添加新功能 +1. 在 `Domain/Entities` 中定义新实体 +2. 在 `Shared/DTOs` 中定义对应的DTOs +3. 在 `Application/Interfaces` 中定义服务接口 +4. 在 `Application/Services` 中实现服务 +5. 在 `Infrastructure` 中添加数据访问代码(如需要) +6. 在 API 项目中添加控制器 + +### 代码规范 +- 使用 `record` 定义 DTOs +- 使用 `async/await` 处理异步操作 +- 统一使用 `ApiResponse` 包装响应 +- 使用 `ILogger` 记录日志 +- 遵循 SOLID 原则 + +## 技术栈 + +- **.NET 8.0**:最新的.NET平台 +- **ASP.NET Core**:Web API框架 +- **Entity Framework Core 8.0**:ORM框架 +- **Pomelo.EntityFrameworkCore.MySql**:MySQL数据库提供程序 +- **Swagger/OpenAPI**:API文档 + +## 下一步建议 + +1. **添加认证授权**:为Admin.API添加JWT认证 +2. **添加缓存**:使用Redis缓存热点数据 +3. **添加日志系统**:集成Serilog或NLog +4. **添加单元测试**:为核心业务逻辑编写测试 +5. **添加API限流**:防止恶意请求 +6. **添加健康检查**:监控应用状态 +7. **添加Docker支持**:容器化部署 + +## 许可证 + +本项目遵循 MIT 许可证。 diff --git a/backend/FateMaster.API/appsettings.json b/backend/FateMaster.API/FateMaster.API/appsettings.json similarity index 100% rename from backend/FateMaster.API/appsettings.json rename to backend/FateMaster.API/FateMaster.API/appsettings.json diff --git a/frontend/fatemaster-web/.claude/settings.local.json b/frontend/fatemaster-web/.claude/settings.local.json new file mode 100644 index 0000000..871a3c0 --- /dev/null +++ b/frontend/fatemaster-web/.claude/settings.local.json @@ -0,0 +1,19 @@ +{ + "permissions": { + "allow": [ + "Read(//d/project/fatemaster/backend/FateMaster.API/FateMaster.API/**)", + "Read(//d/project/fatemaster/backend/**)", + "Bash(cat:*)", + "Bash(dotnet build)", + "Bash(dotnet tool list:*)", + "Bash(dotnet tool install:*)", + "Bash(dotnet ef migrations add:*)", + "Bash(dotnet build:*)", + "Bash(taskkill:*)", + "Bash(dotnet add package:*)", + "Bash(dotnet remove:*)" + ], + "deny": [], + "ask": [] + } +} \ No newline at end of file diff --git a/frontend/fatemaster-web/src/api/bazi.ts b/frontend/fatemaster-web/src/api/bazi.ts new file mode 100644 index 0000000..7665ae2 --- /dev/null +++ b/frontend/fatemaster-web/src/api/bazi.ts @@ -0,0 +1,112 @@ +import axios from 'axios' + +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:5238/api' + +const apiClient = axios.create({ + baseURL: API_BASE_URL, + headers: { + 'Content-Type': 'application/json', + }, +}) + +export interface BaziCalculateRequest { + name: string + gender: string + calendarType: string + birthDate?: string + lunarYear?: number + lunarMonth?: number + lunarDay?: number + knowTime: boolean + birthHour?: number + birthMinute?: number + birthPlace?: string + useTrueSolarTime: boolean + useEarlyChildTime: boolean + yearStem?: string + yearBranch?: string + monthStem?: string + monthBranch?: string + dayStem?: string + dayBranch?: string + hourStem?: string + hourBranch?: string + language?: string +} + +export interface BaziCalculateResponse { + recordId: number + basicInfo: { + name: string + gender: string + birthDateTime: string + lunarDateTime?: string + birthPlace?: string + } + chart: { + yearPillar: Pillar + monthPillar: Pillar + dayPillar: Pillar + hourPillar: Pillar + dayMaster: string + naYin?: string + } + fiveElements: { + elements: Record + strongestElement: string + weakestElement: string + missingElements: string[] + baziStrength: string + } + tenGods: { + gods: Record + mainGod: string + } + luckCycles: { + majorCycles: LuckCycle[] + currentCycle?: LuckCycle + startAge: number + } + aiAnalysis: { + character?: string + career?: string + wealth?: string + marriage?: string + health?: string + summary?: string + suggestions?: string + } +} + +interface Pillar { + stem: string + branch: string + hiddenStems: string + element: string +} + +interface LuckCycle { + stem: string + branch: string + startAge: number + endAge: number + element: string +} + +export const baziApi = { + /** + * 计算八字 + */ + async calculate(request: BaziCalculateRequest): Promise { + const response = await apiClient.post('/bazi/calculate', request) + return response.data.data + }, + + /** + * 获取八字结果 + */ + async getResult(recordId: number): Promise { + const response = await apiClient.get(`/bazi/result/${recordId}`) + return response.data.data + }, +} diff --git a/frontend/fatemaster-web/src/i18n/index.ts b/frontend/fatemaster-web/src/i18n/index.ts index 82099a0..ecc6a75 100644 --- a/frontend/fatemaster-web/src/i18n/index.ts +++ b/frontend/fatemaster-web/src/i18n/index.ts @@ -1080,9 +1080,32 @@ const messages = { }, } +// 从 URL 检测语言 +function detectLocaleFromPath(): string { + const path = window.location.pathname + const pathSegments = path.split('/').filter(Boolean) + const firstSegment = pathSegments[0] || '' + + // 路径前缀到语言代码的映射 + const pathToLocaleMap: Record = { + '': 'zh-CN', + 'en': 'en-US', + 'cn-tw': 'zh-TW', + 'jap': 'ja-JP', + } + + // 检查第一段是否是语言前缀 + if (['en', 'cn-tw', 'jap'].includes(firstSegment)) { + return pathToLocaleMap[firstSegment] + } + + // 否则返回默认语言或存储的语言 + return localStorage.getItem('locale') || 'zh-CN' +} + const i18n = createI18n({ legacy: false, - locale: localStorage.getItem('locale') || 'zh-CN', + locale: detectLocaleFromPath(), fallbackLocale: 'zh-CN', messages, }) diff --git a/frontend/fatemaster-web/src/layouts/MainLayout.vue b/frontend/fatemaster-web/src/layouts/MainLayout.vue index a5aba22..07a791c 100644 --- a/frontend/fatemaster-web/src/layouts/MainLayout.vue +++ b/frontend/fatemaster-web/src/layouts/MainLayout.vue @@ -3,8 +3,8 @@
-