feat: P1 合盘/星座/问答与测测完整设计包及模拟器取证工具

落地 synastry/star/ask API 与 H5 页面,补齐 cece-frontend-re complete-design 证据文档,并加入 Android 模拟器截图抓取脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-03 11:37:53 +08:00
co-authored by Cursor
parent 15a9db374a
commit bd22d9dddd
248 changed files with 26309 additions and 842 deletions
+137
View File
@@ -2,6 +2,7 @@ package handler
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
@@ -19,7 +20,12 @@ type ReportHandler struct {
// Register mounts report/commerce routes.
func (h *ReportHandler) Register(rg *gin.RouterGroup) {
rg.POST("/reports/portrait", h.CreatePortrait)
rg.POST("/reports/star", h.CreateStar)
rg.POST("/reports/synastry", h.CreateSynastry)
rg.POST("/reports/rhythm", h.CreateRhythm)
rg.GET("/reports", h.List)
rg.GET("/reports/:id", h.Get)
rg.GET("/membership/me", h.GetMembership)
rg.POST("/orders", h.CreateOrder)
rg.POST("/orders/:id/pay-mock", h.PayMock)
}
@@ -51,6 +57,122 @@ func (h *ReportHandler) CreatePortrait(c *gin.Context) {
response.OK(c, rep)
}
// CreateStar handles POST /reports/star (星象性格).
func (h *ReportHandler) CreateStar(c *gin.Context) {
userID, ok := middleware.UserIDFromContext(c)
if !ok {
response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized")
return
}
var req struct {
ProfileID string `json:"profile_id" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Fail(c, http.StatusBadRequest, 10000, "invalid request")
return
}
pid, err := uuid.Parse(req.ProfileID)
if err != nil {
response.Fail(c, http.StatusBadRequest, 10000, "invalid profile_id")
return
}
rep, err := h.Svc.CreateStar(c.Request.Context(), userID, pid)
if err != nil {
response.Fail(c, http.StatusBadRequest, 30002, err.Error())
return
}
response.OK(c, rep)
}
// CreateSynastry handles POST /reports/synastry (五主盘 + 推运合盘).
func (h *ReportHandler) CreateSynastry(c *gin.Context) {
userID, ok := middleware.UserIDFromContext(c)
if !ok {
response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized")
return
}
var req struct {
ProfileIDA string `json:"profile_id_a" binding:"required"`
ProfileIDB string `json:"profile_id_b" binding:"required"`
AsOf *string `json:"as_of"` // YYYY-MM-DD optional
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Fail(c, http.StatusBadRequest, 10000, "invalid request")
return
}
aid, err := uuid.Parse(req.ProfileIDA)
if err != nil {
response.Fail(c, http.StatusBadRequest, 10000, "invalid profile_id_a")
return
}
bid, err := uuid.Parse(req.ProfileIDB)
if err != nil {
response.Fail(c, http.StatusBadRequest, 10000, "invalid profile_id_b")
return
}
if aid == bid {
response.Fail(c, http.StatusBadRequest, 10000, "需要两个不同档案")
return
}
var asOfPtr *time.Time
if req.AsOf != nil && *req.AsOf != "" {
t, err := time.ParseInLocation("2006-01-02", *req.AsOf, time.FixedZone("CST", 8*3600))
if err != nil {
response.Fail(c, http.StatusBadRequest, 10000, "invalid as_of")
return
}
asOfPtr = &t
}
rep, err := h.Svc.CreateSynastry(c.Request.Context(), userID, aid, bid, asOfPtr)
if err != nil {
response.Fail(c, http.StatusBadRequest, 30002, err.Error())
return
}
response.OK(c, rep)
}
// CreateRhythm handles POST /reports/rhythm (身心节律) — wired when service ready.
func (h *ReportHandler) CreateRhythm(c *gin.Context) {
userID, ok := middleware.UserIDFromContext(c)
if !ok {
response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized")
return
}
var req struct {
ProfileID string `json:"profile_id" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Fail(c, http.StatusBadRequest, 10000, "invalid request")
return
}
pid, err := uuid.Parse(req.ProfileID)
if err != nil {
response.Fail(c, http.StatusBadRequest, 10000, "invalid profile_id")
return
}
rep, err := h.Svc.CreateRhythm(c.Request.Context(), userID, pid)
if err != nil {
response.Fail(c, http.StatusBadRequest, 30002, err.Error())
return
}
response.OK(c, rep)
}
// List handles GET /reports.
func (h *ReportHandler) List(c *gin.Context) {
userID, ok := middleware.UserIDFromContext(c)
if !ok {
response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized")
return
}
items, err := h.Svc.List(c.Request.Context(), userID)
if err != nil {
response.Fail(c, http.StatusInternalServerError, 50000, err.Error())
return
}
response.OK(c, gin.H{"items": items})
}
// Get handles GET /reports/:id.
func (h *ReportHandler) Get(c *gin.Context) {
userID, ok := middleware.UserIDFromContext(c)
@@ -71,6 +193,21 @@ func (h *ReportHandler) Get(c *gin.Context) {
response.OK(c, rep)
}
// GetMembership handles GET /membership/me.
func (h *ReportHandler) GetMembership(c *gin.Context) {
userID, ok := middleware.UserIDFromContext(c)
if !ok {
response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized")
return
}
me, err := h.Svc.GetMembership(c.Request.Context(), userID)
if err != nil {
response.Fail(c, http.StatusInternalServerError, 50000, err.Error())
return
}
response.OK(c, me)
}
// CreateOrder handles POST /orders.
func (h *ReportHandler) CreateOrder(c *gin.Context) {
userID, ok := middleware.UserIDFromContext(c)