- 超级个体:去掉首位特例;列表仅展示有头像且非微信默认昵称(vip.go) - 个人资料:居中头像、低调联系方式、点头像优先走存客宝 lead(ckbLeadToken) - 阅读页分享朋友圈复制与 toast 去重 - soul-api: miniprogram users 带 ckbLeadToken;其它 handler 与路由调整 - 脚本:content_upload、miniprogram 上传辅助等 Made-with: Cursor
118 lines
3.6 KiB
Go
118 lines
3.6 KiB
Go
package handler
|
||
|
||
import (
|
||
"net/http"
|
||
"time"
|
||
|
||
"soul-api/internal/database"
|
||
"soul-api/internal/model"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// batchSuperIndividualClicks 与 AdminSuperIndividualStats 一致:user_tracks 中 action=card_click 且 target 前缀「超级个体_」
|
||
func batchSuperIndividualClicks(db *gorm.DB, userIDs []string) map[string]int64 {
|
||
out := make(map[string]int64)
|
||
if len(userIDs) == 0 {
|
||
return out
|
||
}
|
||
type row struct {
|
||
UserID string `gorm:"column:user_id"`
|
||
Clicks int64 `gorm:"column:clicks"`
|
||
}
|
||
var rows []row
|
||
_ = db.Raw(`
|
||
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 ?
|
||
GROUP BY user_id
|
||
`, userIDs).Scan(&rows)
|
||
for _, r := range rows {
|
||
if r.UserID != "" {
|
||
out[r.UserID] = r.Clicks
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// batchSuperIndividualLeads 与 AdminSuperIndividualStats 一致:persons.user_id 绑定 + ckb_lead_records 去重获客人数
|
||
func batchSuperIndividualLeads(db *gorm.DB, userIDs []string) map[string]int64 {
|
||
out := make(map[string]int64)
|
||
if len(userIDs) == 0 {
|
||
return out
|
||
}
|
||
type row struct {
|
||
UserID string `gorm:"column:user_id"`
|
||
Leads int64 `gorm:"column:leads"`
|
||
}
|
||
var rows []row
|
||
_ = db.Raw(`
|
||
SELECT p.user_id AS user_id, COUNT(DISTINCT l.user_id) AS leads
|
||
FROM persons p
|
||
INNER JOIN ckb_lead_records l ON l.target_person_id = p.person_id
|
||
WHERE p.user_id IN ?
|
||
GROUP BY p.user_id
|
||
`, userIDs).Scan(&rows)
|
||
for _, r := range rows {
|
||
if r.UserID != "" {
|
||
out[r.UserID] = r.Leads
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// DBVipMembersList GET /api/db/vip-members 管理端 - VIP 成员列表(用于超级个体排序)
|
||
// 与小程序端 VipMembers 的列表逻辑保持一致:仅列出仍在有效期内的 VIP 用户。
|
||
// 额外聚合:clickCount(首页超级个体卡片点击)、leadCount(绑定人物后的去重获客),供管理端表格展示。
|
||
func DBVipMembersList(c *gin.Context) {
|
||
limit := 200
|
||
if l := c.Query("limit"); l != "" {
|
||
if n, err := parseInt(l); err == nil && n > 0 && n <= 500 {
|
||
limit = n
|
||
}
|
||
}
|
||
|
||
db := database.DB()
|
||
|
||
// 与 VipMembers 一致:优先 users 表(is_vip=1 且 vip_expire_date>NOW),排序使用 vip_sort
|
||
var users []model.User
|
||
err := db.Table("users").
|
||
Select("id", "nickname", "avatar", "vip_name", "vip_role", "vip_project", "vip_avatar", "vip_bio", "vip_activated_at", "vip_sort", "vip_expire_date", "is_vip", "phone", "wechat_id").
|
||
Where("is_vip = 1 AND vip_expire_date > ?", time.Now()).
|
||
Order("COALESCE(vip_sort, 999999) ASC, COALESCE(vip_activated_at, vip_expire_date) DESC").
|
||
Limit(limit).
|
||
Find(&users).Error
|
||
|
||
if err != nil || len(users) == 0 {
|
||
// 兜底:从 orders 查,逻辑与 VipMembers 保持一致
|
||
var userIDs []string
|
||
db.Model(&model.Order{}).Select("DISTINCT user_id").
|
||
Where("(status = ? OR status = ?) AND (product_type = ? OR product_type = ?)", "paid", "completed", "fullbook", "vip").
|
||
Pluck("user_id", &userIDs)
|
||
if len(userIDs) == 0 {
|
||
c.JSON(http.StatusOK, gin.H{"success": true, "data": []interface{}{}, "total": 0})
|
||
return
|
||
}
|
||
db.Where("id IN ?", userIDs).Find(&users)
|
||
}
|
||
|
||
ids := make([]string, 0, len(users))
|
||
for i := range users {
|
||
ids = append(ids, users[i].ID)
|
||
}
|
||
clickByUser := batchSuperIndividualClicks(db, ids)
|
||
leadByUser := batchSuperIndividualLeads(db, ids)
|
||
|
||
list := make([]gin.H, 0, len(users))
|
||
for i := range users {
|
||
item := formatVipMember(db, &users[i], true)
|
||
uid := users[i].ID
|
||
item["clickCount"] = clickByUser[uid]
|
||
item["leadCount"] = leadByUser[uid]
|
||
list = append(list, item)
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{"success": true, "data": list, "total": len(list)})
|
||
}
|