Files
Mycontent/soul-api/internal/handler/open_platform_api.go
2026-04-02 19:20:19 +08:00

149 lines
4.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 KeyAuthorization: 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,
},
},
})
}