package handler import ( "encoding/json" "net/http" "strings" "time" "soul-api/internal/database" "soul-api/internal/model" "github.com/gin-gonic/gin" ) const ctxOpenPlatformApiKeyID = "openPlatformApiKeyID" // OpenPlatformAuthMiddleware 开放平台:Authorization: Bearer op_sk_... 或 X-API-Key func OpenPlatformAuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { secret := extractOpenPlatformSecretFromRequest(c) if secret == "" { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "success": false, "error": "缺少 API Key(Authorization: Bearer op_sk_... 或 Header X-API-Key)", }) return } db := database.DB() row, ok := OpenPlatformVerifySecret(db, secret) if !ok || row == nil { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"success": false, "error": "API Key 无效或已吊销"}) return } c.Set(ctxOpenPlatformApiKeyID, row.ID) c.Next() } } func maskPhoneForLog(p string) string { if len(p) < 8 { return "***" } return p[:3] + "****" + p[len(p)-4:] } func extractOpenPlatformSecretFromRequest(c *gin.Context) string { auth := strings.TrimSpace(c.GetHeader("Authorization")) if len(auth) > 7 && strings.EqualFold(auth[:7], "Bearer ") { return strings.TrimSpace(auth[7:]) } if x := strings.TrimSpace(c.GetHeader("X-API-Key")); x != "" { return x } return "" } // OpenPlatformPatchUserProfile POST /api/open/user/profile // Body: { "phone": "必填", "mbti"?: "...", "disc"?: "...", "pdp"?: "..." } — 仅更新传入的字段,至少传 mbti/disc/pdp 之一 func OpenPlatformPatchUserProfile(c *gin.Context) { var body struct { Phone string `json:"phone"` Mbti *string `json:"mbti"` Disc *string `json:"disc"` Pdp *string `json:"pdp"` } if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "JSON 格式错误"}) return } phone := strings.TrimSpace(strings.ReplaceAll(body.Phone, " ", "")) if phone == "" { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "phone 不能为空"}) return } if body.Mbti == nil && body.Disc == nil && body.Pdp == nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "请至少提供 mbti、disc、pdp 之一"}) return } db := database.DB() var user model.User if err := db.Where("phone = ? AND deleted_at IS NULL", phone).First(&user).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": "未找到该手机号对应的用户"}) return } updates := make(map[string]interface{}) if body.Mbti != nil { v := strings.TrimSpace(*body.Mbti) updates["mbti"] = v } if body.Disc != nil { v := strings.TrimSpace(*body.Disc) updates["disc"] = v } if body.Pdp != nil { v := strings.TrimSpace(*body.Pdp) updates["pdp"] = v } if len(updates) == 0 { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "没有需要更新的字段"}) return } updates["updated_at"] = time.Now() t0 := time.Now() if err := db.Model(&model.User{}).Where("id = ?", user.ID).Updates(updates).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } var keyID uint var keyPrefix string if v, ok := c.Get(ctxOpenPlatformApiKeyID); ok { if id, ok := v.(uint); ok { keyID = id var k model.OpenPlatformApiKey if err := db.Select("key_prefix").First(&k, id).Error; err == nil { keyPrefix = k.KeyPrefix } } } reqSnap, _ := json.Marshal(gin.H{"phone": maskPhoneForLog(phone), "mbti": body.Mbti, "disc": body.Disc, "pdp": body.Pdp}) respSnap, _ := json.Marshal(gin.H{"success": true, "userId": user.ID}) _ = OpenPlatformInsertLog(db, &model.OpenPlatformApiLog{ ApiKeyID: keyID, KeyPrefix: keyPrefix, Method: c.Request.Method, Path: c.Request.URL.Path, StatusCode: 200, ClientIP: c.ClientIP(), DurationMs: int(time.Since(t0).Milliseconds()), RequestBody: string(reqSnap), ResponseBody: string(respSnap), }) c.JSON(http.StatusOK, gin.H{ "success": true, "data": gin.H{ "userId": user.ID, "phone": phone, "updated": gin.H{ "mbti": body.Mbti != nil, "disc": body.Disc != nil, "pdp": body.Pdp != nil, }, }, }) }