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 @@