- Added new entries for meetings discussing the integration of the "超级个体" and "@" lists across various project components. - Updated the last modified date to March 31, 2026, in multiple project index files. - Enhanced the read page functionality to include user phone number in navigation to other mini-programs. - Documented changes in the sync log for assistant-doc-sync regarding the meeting outcomes.
149 lines
4.2 KiB
Go
149 lines
4.2 KiB
Go
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,
|
||
},
|
||
},
|
||
})
|
||
}
|