feat: MBTI头像与用户规则链路升级,三端页面与接口同步
Made-with: Cursor
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"soul-api/internal/database"
|
||||
@@ -11,7 +13,34 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// batchSuperIndividualClicks 与 AdminSuperIndividualStats 一致:user_tracks 中 action=card_click 且 target 前缀「超级个体_」
|
||||
const superIndividualWebhookConfigKey = "super_individual_webhook_map"
|
||||
|
||||
func loadSuperIndividualWebhookMap(db *gorm.DB) map[string]string {
|
||||
out := map[string]string{}
|
||||
var cfg model.SystemConfig
|
||||
if err := db.Where("config_key = ?", superIndividualWebhookConfigKey).First(&cfg).Error; err != nil {
|
||||
return out
|
||||
}
|
||||
if len(cfg.ConfigValue) == 0 {
|
||||
return out
|
||||
}
|
||||
raw := map[string]string{}
|
||||
if err := json.Unmarshal(cfg.ConfigValue, &raw); err != nil {
|
||||
return out
|
||||
}
|
||||
for k, v := range raw {
|
||||
k = strings.TrimSpace(k)
|
||||
v = strings.TrimSpace(v)
|
||||
if k == "" || v == "" {
|
||||
continue
|
||||
}
|
||||
out[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// batchSuperIndividualClicks 统计「点击头像」行为:
|
||||
// user_tracks 中 action=avatar_click(兼容历史 btn_click)且 target 前缀「链接头像_」。
|
||||
func batchSuperIndividualClicks(db *gorm.DB, userIDs []string) map[string]int64 {
|
||||
out := make(map[string]int64)
|
||||
if len(userIDs) == 0 {
|
||||
@@ -23,9 +52,13 @@ func batchSuperIndividualClicks(db *gorm.DB, userIDs []string) map[string]int64
|
||||
}
|
||||
var rows []row
|
||||
_ = db.Raw(`
|
||||
SELECT SUBSTRING(target, 6) AS user_id, COUNT(*) AS clicks
|
||||
SELECT
|
||||
SUBSTRING(target, 6) AS user_id,
|
||||
COUNT(*) AS clicks
|
||||
FROM user_tracks
|
||||
WHERE action = 'card_click' AND target LIKE '超级个体\_%' AND SUBSTRING(target, 6) IN ?
|
||||
WHERE action IN ('avatar_click', 'btn_click')
|
||||
AND target LIKE '链接头像\_%'
|
||||
AND SUBSTRING(target, 6) IN ?
|
||||
GROUP BY user_id
|
||||
`, userIDs).Scan(&rows)
|
||||
for _, r := range rows {
|
||||
@@ -103,6 +136,7 @@ func DBVipMembersList(c *gin.Context) {
|
||||
}
|
||||
clickByUser := batchSuperIndividualClicks(db, ids)
|
||||
leadByUser := batchSuperIndividualLeads(db, ids)
|
||||
webhookMap := loadSuperIndividualWebhookMap(db)
|
||||
|
||||
list := make([]gin.H, 0, len(users))
|
||||
for i := range users {
|
||||
@@ -110,8 +144,74 @@ func DBVipMembersList(c *gin.Context) {
|
||||
uid := users[i].ID
|
||||
item["clickCount"] = clickByUser[uid]
|
||||
item["leadCount"] = leadByUser[uid]
|
||||
item["webhookUrl"] = strings.TrimSpace(webhookMap[uid])
|
||||
list = append(list, item)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": list, "total": len(list)})
|
||||
}
|
||||
|
||||
// DBVipMemberWebhookSet PUT /api/db/vip-members/webhook
|
||||
// 按超级个体用户维度配置飞书群 webhook(VOX 地址)。
|
||||
func DBVipMemberWebhookSet(c *gin.Context) {
|
||||
var body struct {
|
||||
UserID string `json:"userId"`
|
||||
WebhookURL string `json:"webhookUrl"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "请求体无效"})
|
||||
return
|
||||
}
|
||||
userID := strings.TrimSpace(body.UserID)
|
||||
webhookURL := strings.TrimSpace(body.WebhookURL)
|
||||
if userID == "" {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "userId 不能为空"})
|
||||
return
|
||||
}
|
||||
if webhookURL != "" && !strings.HasPrefix(webhookURL, "http") {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "Webhook 地址必须是 http/https"})
|
||||
return
|
||||
}
|
||||
|
||||
db := database.DB()
|
||||
var count int64
|
||||
db.Model(&model.User{}).Where("id = ?", userID).Count(&count)
|
||||
if count == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "用户不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
webhookMap := loadSuperIndividualWebhookMap(db)
|
||||
if webhookURL == "" {
|
||||
delete(webhookMap, userID)
|
||||
} else {
|
||||
webhookMap[userID] = webhookURL
|
||||
}
|
||||
val, _ := json.Marshal(webhookMap)
|
||||
desc := "超级个体飞书群Webhook映射(按userId)"
|
||||
var row model.SystemConfig
|
||||
if err := db.Where("config_key = ?", superIndividualWebhookConfigKey).First(&row).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
row = model.SystemConfig{
|
||||
ConfigKey: superIndividualWebhookConfigKey,
|
||||
ConfigValue: val,
|
||||
Description: &desc,
|
||||
}
|
||||
if e := db.Create(&row).Error; e != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": e.Error()})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
row.ConfigValue = val
|
||||
row.Description = &desc
|
||||
if e := db.Save(&row).Error; e != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": e.Error()})
|
||||
return
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user