feat: 接入起卦后端流程并完善积分扣减链路

This commit is contained in:
qzl
2026-04-03 19:04:46 +08:00
parent a136e42290
commit d87b2e1e3a
56 changed files with 3310 additions and 809 deletions
@@ -14,6 +14,8 @@ from pydantic import (
field_validator,
)
from ..domain.divination import DivinationPayload
_RFC3339_WITH_TZ_PATTERN = re.compile(
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$"
)
@@ -69,6 +71,9 @@ class ForwardedPropsPayload(BaseModel):
runtime_mode: RuntimeMode
client_time: ClientTimeContext | None = None
divination_payload: DivinationPayload | None = Field(
default=None, alias="divinationPayload"
)
def parse_forwarded_props(forwarded_props: object) -> ForwardedPropsPayload:
@@ -90,3 +95,10 @@ def parse_forwarded_props_client_time(
def parse_forwarded_props_runtime_mode(forwarded_props: object) -> RuntimeMode:
payload = parse_forwarded_props(forwarded_props)
return payload.runtime_mode
def parse_forwarded_props_divination_payload(
forwarded_props: object,
) -> DivinationPayload | None:
payload = parse_forwarded_props(forwarded_props)
return payload.divination_payload
+114
View File
@@ -0,0 +1,114 @@
from __future__ import annotations
from datetime import datetime
from enum import Enum
from pydantic import BaseModel, ConfigDict, Field, field_validator
class DivinationMethod(str, Enum):
MANUAL = "手动起卦"
AUTO = "自动起卦"
class YaoType(str, Enum):
SHAO_YANG = "少阳"
SHAO_YIN = "少阴"
LAO_YANG = "老阳"
LAO_YIN = "老阴"
class SpecialMark(str, Enum):
SHI = ""
YING = ""
NONE = ""
class YaoDetail(BaseModel):
model_config = ConfigDict(extra="forbid")
position: int = Field(ge=1, le=6)
spirit_name: str = Field(alias="spiritName", min_length=1)
relation_name: str = Field(alias="relationName", min_length=1)
tigan_name: str = Field(alias="tiganName", min_length=1)
element_name: str = Field(alias="elementName", min_length=1)
is_yang: bool = Field(alias="isYang")
is_changing: bool = Field(alias="isChanging")
special_mark: SpecialMark = Field(alias="specialMark", default=SpecialMark.NONE)
class FushenDetail(BaseModel):
model_config = ConfigDict(extra="forbid")
position: int = Field(ge=1, le=6)
relation_name: str = Field(alias="relationName", min_length=1)
tigan_name: str = Field(alias="tiganName", min_length=1)
element_name: str = Field(alias="elementName", min_length=1)
class GanzhiDetail(BaseModel):
model_config = ConfigDict(extra="forbid")
year_gan_zhi: str = Field(alias="yearGanZhi", min_length=2, max_length=2)
month_gan_zhi: str = Field(alias="monthGanZhi", min_length=2, max_length=2)
day_gan_zhi: str = Field(alias="dayGanZhi", min_length=2, max_length=2)
time_gan_zhi: str = Field(alias="timeGanZhi", min_length=2, max_length=2)
year_kong_wang: str = Field(alias="yearKongWang", min_length=2, max_length=2)
month_kong_wang: str = Field(alias="monthKongWang", min_length=2, max_length=2)
day_kong_wang: str = Field(alias="dayKongWang", min_length=2, max_length=2)
time_kong_wang: str = Field(alias="timeKongWang", min_length=2, max_length=2)
yue_jian: str = Field(alias="yueJian", min_length=2)
ri_chen: str = Field(alias="riChen", min_length=2)
yue_po: str = Field(alias="yuePo", min_length=2)
ri_chong: str = Field(alias="riChong", min_length=2)
class DivinationPayload(BaseModel):
model_config = ConfigDict(extra="forbid", populate_by_name=True)
divination_method: DivinationMethod = Field(alias="divinationMethod")
question_type: str = Field(alias="questionType", min_length=1, max_length=32)
question: str = Field(min_length=1, max_length=300)
divination_time_iso: str = Field(alias="divinationTimeIso", min_length=20)
yao_lines: list[YaoType] = Field(alias="yaoLines", min_length=6, max_length=6)
@field_validator("divination_time_iso")
@classmethod
def validate_divination_time_iso(cls, value: str) -> str:
normalized = value.replace("Z", "+00:00")
dt = datetime.fromisoformat(normalized)
if dt.tzinfo is None:
raise ValueError("divinationTimeIso must include timezone")
return value
class DerivedDivinationData(BaseModel):
model_config = ConfigDict(extra="forbid", populate_by_name=True)
question: str = Field(min_length=1)
question_type: str = Field(alias="questionType", min_length=1)
divination_method: DivinationMethod = Field(alias="divinationMethod")
divination_time: str = Field(alias="divinationTime", min_length=1)
binary_code: str = Field(alias="binaryCode", min_length=6, max_length=6)
changed_binary_code: str = Field(
alias="changedBinaryCode", min_length=6, max_length=6
)
gua_name: str = Field(alias="guaName", min_length=2)
upper_name: str = Field(alias="upperName", min_length=1)
lower_name: str = Field(alias="lowerName", min_length=1)
target_gua_name: str = Field(alias="targetGuaName", min_length=2)
world_position: int = Field(alias="worldPosition", ge=1, le=6)
response_position: int = Field(alias="responsePosition", ge=1, le=6)
has_changing_yao: bool = Field(alias="hasChangingYao")
ganzhi: GanzhiDetail
wu_xing_statuses: dict[str, str] = Field(alias="wuXingStatuses")
yao_info_list: list[YaoDetail] = Field(
alias="yaoInfoList", min_length=6, max_length=6
)
target_yao_info_list: list[YaoDetail] = Field(
alias="targetYaoInfoList", default_factory=list
)
fushen_positions: list[int] = Field(alias="fushenPositions", default_factory=list)
fushen_info_list: list[FushenDetail] = Field(
alias="fushenInfoList", default_factory=list
)