598 lines
18 KiB
Go
598 lines
18 KiB
Go
package handler
|
||
|
||
import (
|
||
"bytes"
|
||
"crypto/md5"
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"sort"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
|
||
"soul-api/internal/database"
|
||
"soul-api/internal/model"
|
||
)
|
||
|
||
const ckbAPIKey = "fyngh-ecy9h-qkdae-epwd5-rz6kd"
|
||
const ckbAPIURL = "https://ckbapi.quwanzhi.com/v1/api/scenarios"
|
||
|
||
var ckbSourceMap = map[string]string{"team": "团队招募", "investor": "资源对接", "mentor": "导师顾问", "partner": "创业合伙"}
|
||
var ckbTagsMap = map[string]string{"team": "切片团队,团队招募", "investor": "资源对接,资源群", "mentor": "导师顾问,咨询服务", "partner": "创业合伙,创业伙伴"}
|
||
|
||
type CKBRouteConfig struct {
|
||
APIURL string `json:"apiUrl"`
|
||
APIKey string `json:"apiKey"`
|
||
Source string `json:"source"`
|
||
Tags string `json:"tags"`
|
||
SiteTags string `json:"siteTags"`
|
||
Notes string `json:"notes"`
|
||
}
|
||
|
||
type CKBConfigPayload struct {
|
||
Routes map[string]CKBRouteConfig `json:"routes"`
|
||
DocNotes string `json:"docNotes"`
|
||
DocContent string `json:"docContent"`
|
||
APIURL string `json:"apiUrl,omitempty"`
|
||
APIKey string `json:"apiKey,omitempty"`
|
||
}
|
||
|
||
func defaultCKBRouteConfig(routeKey string) CKBRouteConfig {
|
||
cfg := CKBRouteConfig{
|
||
APIURL: ckbAPIURL,
|
||
APIKey: ckbAPIKey,
|
||
SiteTags: "创业实验APP",
|
||
}
|
||
switch routeKey {
|
||
case "join_partner":
|
||
cfg.Source = "创业实验-创业合伙"
|
||
cfg.Tags = "创业合伙,创业伙伴"
|
||
case "join_investor":
|
||
cfg.Source = "创业实验-资源对接"
|
||
cfg.Tags = "资源对接,资源群"
|
||
case "join_mentor":
|
||
cfg.Source = "创业实验-导师顾问"
|
||
cfg.Tags = "导师顾问,咨询服务"
|
||
case "join_team":
|
||
cfg.Source = "创业实验-团队招募"
|
||
cfg.Tags = "切片团队,团队招募"
|
||
case "match":
|
||
cfg.Source = "创业实验-找伙伴匹配"
|
||
cfg.Tags = "找伙伴"
|
||
cfg.SiteTags = "创业实验APP,匹配用户"
|
||
case "lead":
|
||
cfg.Source = "小程序-链接卡若"
|
||
cfg.Tags = "链接卡若,创业实验"
|
||
cfg.SiteTags = "创业实验APP,链接卡若"
|
||
}
|
||
return cfg
|
||
}
|
||
|
||
func getCKBConfigPayload() CKBConfigPayload {
|
||
payload := CKBConfigPayload{
|
||
Routes: map[string]CKBRouteConfig{},
|
||
}
|
||
var cfg model.SystemConfig
|
||
if err := database.DB().Where("config_key = ?", "ckb_config").First(&cfg).Error; err != nil {
|
||
return payload
|
||
}
|
||
var m map[string]interface{}
|
||
if err := json.Unmarshal(cfg.ConfigValue, &m); err != nil {
|
||
return payload
|
||
}
|
||
if v, ok := m["docNotes"].(string); ok {
|
||
payload.DocNotes = v
|
||
}
|
||
if v, ok := m["docContent"].(string); ok {
|
||
payload.DocContent = v
|
||
}
|
||
if v, ok := m["apiKey"].(string); ok {
|
||
payload.APIKey = v
|
||
}
|
||
if v, ok := m["apiUrl"].(string); ok {
|
||
payload.APIURL = v
|
||
}
|
||
if routes, ok := m["routes"].(map[string]interface{}); ok {
|
||
for key, raw := range routes {
|
||
itemMap, ok := raw.(map[string]interface{})
|
||
if !ok {
|
||
continue
|
||
}
|
||
item := defaultCKBRouteConfig(key)
|
||
if v, ok := itemMap["apiUrl"].(string); ok && strings.TrimSpace(v) != "" {
|
||
item.APIURL = strings.TrimSpace(v)
|
||
}
|
||
if v, ok := itemMap["apiKey"].(string); ok && strings.TrimSpace(v) != "" {
|
||
item.APIKey = strings.TrimSpace(v)
|
||
}
|
||
if v, ok := itemMap["source"].(string); ok && strings.TrimSpace(v) != "" {
|
||
item.Source = strings.TrimSpace(v)
|
||
}
|
||
if v, ok := itemMap["tags"].(string); ok && strings.TrimSpace(v) != "" {
|
||
item.Tags = strings.TrimSpace(v)
|
||
}
|
||
if v, ok := itemMap["siteTags"].(string); ok && strings.TrimSpace(v) != "" {
|
||
item.SiteTags = strings.TrimSpace(v)
|
||
}
|
||
if v, ok := itemMap["notes"].(string); ok {
|
||
item.Notes = v
|
||
}
|
||
payload.Routes[key] = item
|
||
}
|
||
}
|
||
return payload
|
||
}
|
||
|
||
func getCKBRouteConfig(routeKey string) (cfg CKBRouteConfig, docNotes string, docContent string) {
|
||
cfg = defaultCKBRouteConfig(routeKey)
|
||
payload := getCKBConfigPayload()
|
||
docNotes = payload.DocNotes
|
||
docContent = payload.DocContent
|
||
if item, ok := payload.Routes[routeKey]; ok {
|
||
cfg = item
|
||
} else {
|
||
if strings.TrimSpace(payload.APIURL) != "" {
|
||
cfg.APIURL = strings.TrimSpace(payload.APIURL)
|
||
}
|
||
if strings.TrimSpace(payload.APIKey) != "" {
|
||
cfg.APIKey = strings.TrimSpace(payload.APIKey)
|
||
}
|
||
}
|
||
return cfg, docNotes, docContent
|
||
}
|
||
|
||
// ckbSign 与 next-project app/api/ckb/join 一致:排除 sign/apiKey/portrait,空值跳过,按键升序拼接值,MD5(拼接串) 再 MD5(结果+apiKey)
|
||
func ckbSign(params map[string]interface{}, apiKey string) string {
|
||
keys := make([]string, 0, len(params))
|
||
for k := range params {
|
||
if k == "sign" || k == "apiKey" || k == "portrait" {
|
||
continue
|
||
}
|
||
v := params[k]
|
||
if v == nil || v == "" {
|
||
continue
|
||
}
|
||
keys = append(keys, k)
|
||
}
|
||
sort.Strings(keys)
|
||
var concat string
|
||
for _, k := range keys {
|
||
v := params[k]
|
||
switch val := v.(type) {
|
||
case string:
|
||
concat += val
|
||
case float64:
|
||
concat += strconv.FormatFloat(val, 'f', -1, 64)
|
||
case int:
|
||
concat += strconv.Itoa(val)
|
||
case int64:
|
||
concat += strconv.FormatInt(val, 10)
|
||
default:
|
||
concat += ""
|
||
}
|
||
}
|
||
h := md5.Sum([]byte(concat))
|
||
first := hex.EncodeToString(h[:])
|
||
h2 := md5.Sum([]byte(first + apiKey))
|
||
return hex.EncodeToString(h2[:])
|
||
}
|
||
|
||
// CKBJoin POST /api/ckb/join
|
||
func CKBJoin(c *gin.Context) {
|
||
var body struct {
|
||
Type string `json:"type" binding:"required"`
|
||
Phone string `json:"phone"`
|
||
Wechat string `json:"wechat"`
|
||
Name string `json:"name"`
|
||
UserID string `json:"userId"`
|
||
Remark string `json:"remark"`
|
||
CanHelp string `json:"canHelp"` // 资源对接:我能帮到你什么
|
||
NeedHelp string `json:"needHelp"` // 资源对接:我需要什么帮助
|
||
}
|
||
if err := c.ShouldBindJSON(&body); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "请提供手机号或微信号"})
|
||
return
|
||
}
|
||
if body.Phone == "" && body.Wechat == "" {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "请提供手机号或微信号"})
|
||
return
|
||
}
|
||
if body.Type != "team" && body.Type != "investor" && body.Type != "mentor" && body.Type != "partner" {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的加入类型"})
|
||
return
|
||
}
|
||
routeCfg, _, _ := getCKBRouteConfig("join_" + body.Type)
|
||
// 先写入 match_records(无论 CKB 是否成功,用户确实提交了表单)
|
||
if body.UserID != "" {
|
||
rec := model.MatchRecord{
|
||
ID: fmt.Sprintf("mr_ckb_%d", time.Now().UnixNano()),
|
||
UserID: body.UserID,
|
||
MatchType: body.Type,
|
||
}
|
||
if body.Phone != "" {
|
||
rec.Phone = &body.Phone
|
||
}
|
||
if body.Wechat != "" {
|
||
rec.WechatID = &body.Wechat
|
||
}
|
||
if err := database.DB().Create(&rec).Error; err != nil {
|
||
fmt.Printf("[CKBJoin] 写入 match_records 失败: %v\n", err)
|
||
}
|
||
}
|
||
|
||
ts := time.Now().Unix()
|
||
params := map[string]interface{}{
|
||
"timestamp": ts,
|
||
"source": routeCfg.Source,
|
||
"tags": routeCfg.Tags,
|
||
"siteTags": routeCfg.SiteTags,
|
||
"remark": body.Remark,
|
||
}
|
||
if body.Remark == "" {
|
||
remark := "用户通过创业实验APP申请" + ckbSourceMap[body.Type]
|
||
if body.Type == "investor" && (body.CanHelp != "" || body.NeedHelp != "") {
|
||
remark = fmt.Sprintf("能帮:%s 需要:%s", body.CanHelp, body.NeedHelp)
|
||
}
|
||
params["remark"] = remark
|
||
}
|
||
if body.Phone != "" {
|
||
params["phone"] = body.Phone
|
||
}
|
||
if body.Wechat != "" {
|
||
params["wechatId"] = body.Wechat
|
||
}
|
||
if body.Name != "" {
|
||
params["name"] = body.Name
|
||
}
|
||
params["apiKey"] = routeCfg.APIKey
|
||
params["sign"] = ckbSign(params, routeCfg.APIKey)
|
||
sourceData := map[string]interface{}{
|
||
"joinType": body.Type, "joinLabel": ckbSourceMap[body.Type], "userId": body.UserID,
|
||
"device": "webapp", "timestamp": time.Now().Format(time.RFC3339),
|
||
}
|
||
if body.Type == "investor" {
|
||
if body.CanHelp != "" {
|
||
sourceData["canHelp"] = body.CanHelp
|
||
}
|
||
if body.NeedHelp != "" {
|
||
sourceData["needHelp"] = body.NeedHelp
|
||
}
|
||
}
|
||
params["portrait"] = map[string]interface{}{
|
||
"type": 4, "source": 0,
|
||
"sourceData": sourceData,
|
||
"remark": ckbSourceMap[body.Type] + "申请",
|
||
"uniqueId": "soul_" + body.Phone + body.Wechat + strconv.FormatInt(ts, 10),
|
||
}
|
||
raw, _ := json.Marshal(params)
|
||
resp, err := http.Post(routeCfg.APIURL, "application/json", bytes.NewReader(raw))
|
||
if err != nil {
|
||
fmt.Printf("[CKBJoin] CKB 请求失败: %v (match_records 已写入)\n", err)
|
||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "已提交(存客宝暂不可达,稍后自动重试)"})
|
||
return
|
||
}
|
||
defer resp.Body.Close()
|
||
b, _ := io.ReadAll(resp.Body)
|
||
var result struct {
|
||
Code int `json:"code"`
|
||
Message string `json:"message"`
|
||
Data interface{} `json:"data"`
|
||
}
|
||
_ = json.Unmarshal(b, &result)
|
||
if result.Code == 200 {
|
||
// 资源对接:同步更新用户资料中的 help_offer、help_need、phone、wechat_id
|
||
if body.Type == "investor" && body.UserID != "" {
|
||
updates := map[string]interface{}{}
|
||
if body.CanHelp != "" {
|
||
updates["help_offer"] = body.CanHelp
|
||
}
|
||
if body.NeedHelp != "" {
|
||
updates["help_need"] = body.NeedHelp
|
||
}
|
||
if body.Phone != "" {
|
||
updates["phone"] = body.Phone
|
||
}
|
||
if body.Wechat != "" {
|
||
updates["wechat_id"] = body.Wechat
|
||
}
|
||
if len(updates) > 0 {
|
||
database.DB().Model(&model.User{}).Where("id = ?", body.UserID).Updates(updates)
|
||
}
|
||
}
|
||
msg := "成功加入" + ckbSourceMap[body.Type]
|
||
if result.Message == "已存在" {
|
||
msg = "您已加入,我们会尽快联系您"
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"success": true, "message": msg, "data": result.Data})
|
||
return
|
||
}
|
||
errMsg := result.Message
|
||
if errMsg == "" {
|
||
errMsg = "加入失败,请稍后重试"
|
||
}
|
||
// 打印 CKB 原始响应便于排查
|
||
fmt.Printf("[CKBJoin] 失败 type=%s wechat=%s code=%d message=%s raw=%s\n",
|
||
body.Type, body.Wechat, result.Code, result.Message, string(b))
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "message": errMsg})
|
||
}
|
||
|
||
// CKBMatch POST /api/ckb/match
|
||
func CKBMatch(c *gin.Context) {
|
||
routeCfg, _, _ := getCKBRouteConfig("match")
|
||
var body struct {
|
||
MatchType string `json:"matchType"`
|
||
Phone string `json:"phone"`
|
||
Wechat string `json:"wechat"`
|
||
UserID string `json:"userId"`
|
||
Nickname string `json:"nickname"`
|
||
MatchedUser interface{} `json:"matchedUser"`
|
||
}
|
||
_ = c.ShouldBindJSON(&body)
|
||
if body.Phone == "" && body.Wechat == "" {
|
||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "请提供手机号或微信号"})
|
||
return
|
||
}
|
||
ts := time.Now().Unix()
|
||
label := ckbSourceMap[body.MatchType]
|
||
if label == "" {
|
||
label = "创业合伙"
|
||
}
|
||
tags := routeCfg.Tags
|
||
if label != "" && tags != "" && !strings.Contains(tags, label) {
|
||
tags = tags + "," + label
|
||
}
|
||
params := map[string]interface{}{
|
||
"timestamp": ts,
|
||
"source": routeCfg.Source,
|
||
"tags": tags,
|
||
"siteTags": routeCfg.SiteTags,
|
||
"remark": "用户发起" + label + "匹配",
|
||
}
|
||
if body.Phone != "" {
|
||
params["phone"] = body.Phone
|
||
}
|
||
if body.Wechat != "" {
|
||
params["wechatId"] = body.Wechat
|
||
}
|
||
if body.Nickname != "" {
|
||
params["name"] = body.Nickname
|
||
}
|
||
params["apiKey"] = routeCfg.APIKey
|
||
params["sign"] = ckbSign(params, routeCfg.APIKey)
|
||
params["portrait"] = map[string]interface{}{
|
||
"type": 4, "source": 0,
|
||
"sourceData": map[string]interface{}{
|
||
"action": "match", "matchType": body.MatchType, "matchLabel": label,
|
||
"userId": body.UserID, "device": "webapp", "timestamp": time.Now().Format(time.RFC3339),
|
||
},
|
||
"remark": "找伙伴匹配-" + label,
|
||
"uniqueId": "soul_match_" + body.Phone + body.Wechat + strconv.FormatInt(ts, 10),
|
||
}
|
||
raw, _ := json.Marshal(params)
|
||
resp, err := http.Post(routeCfg.APIURL, "application/json", bytes.NewReader(raw))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "匹配成功"})
|
||
return
|
||
}
|
||
defer resp.Body.Close()
|
||
b, _ := io.ReadAll(resp.Body)
|
||
var result struct {
|
||
Code int `json:"code"`
|
||
Message string `json:"message"`
|
||
}
|
||
_ = json.Unmarshal(b, &result)
|
||
if result.Code == 200 {
|
||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "匹配记录已上报", "data": nil})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "匹配成功"})
|
||
}
|
||
|
||
// CKBSync GET/POST /api/ckb/sync
|
||
func CKBSync(c *gin.Context) {
|
||
c.JSON(http.StatusOK, gin.H{"success": true})
|
||
}
|
||
|
||
// CKBLead POST /api/miniprogram/ckb/lead 小程序-链接卡若:上报线索到存客宝,便于卡若添加好友
|
||
// 请求体:phone(可选)、wechatId(可选)、name(可选)、userId(可选,用于补全昵称)
|
||
// 至少传 phone 或 wechatId 之一;签名规则同 api_v1.md
|
||
func CKBLead(c *gin.Context) {
|
||
routeCfg, _, _ := getCKBRouteConfig("lead")
|
||
var body struct {
|
||
UserID string `json:"userId"`
|
||
Phone string `json:"phone"`
|
||
WechatID string `json:"wechatId"`
|
||
Name string `json:"name"`
|
||
}
|
||
_ = c.ShouldBindJSON(&body)
|
||
phone := strings.TrimSpace(body.Phone)
|
||
wechatId := strings.TrimSpace(body.WechatID)
|
||
if phone == "" && wechatId == "" {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "请提供手机号或微信号"})
|
||
return
|
||
}
|
||
name := strings.TrimSpace(body.Name)
|
||
if name == "" && body.UserID != "" {
|
||
var u model.User
|
||
if database.DB().Select("nickname").Where("id = ?", body.UserID).First(&u).Error == nil && u.Nickname != nil && *u.Nickname != "" {
|
||
name = *u.Nickname
|
||
}
|
||
}
|
||
if name == "" {
|
||
name = "小程序用户"
|
||
}
|
||
ts := time.Now().Unix()
|
||
params := map[string]interface{}{
|
||
"timestamp": ts,
|
||
"source": routeCfg.Source,
|
||
"tags": routeCfg.Tags,
|
||
"siteTags": routeCfg.SiteTags,
|
||
"remark": "首页点击「链接卡若」留资",
|
||
"name": name,
|
||
}
|
||
if phone != "" {
|
||
params["phone"] = phone
|
||
}
|
||
if wechatId != "" {
|
||
params["wechatId"] = wechatId
|
||
}
|
||
params["apiKey"] = routeCfg.APIKey
|
||
params["sign"] = ckbSign(params, routeCfg.APIKey)
|
||
raw, _ := json.Marshal(params)
|
||
fmt.Printf("[CKBLead] 请求: phone=%s wechatId=%s name=%s\n", phone, wechatId, name)
|
||
resp, err := http.Post(routeCfg.APIURL, "application/json", bytes.NewReader(raw))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "网络异常,请稍后重试"})
|
||
return
|
||
}
|
||
defer resp.Body.Close()
|
||
b, _ := io.ReadAll(resp.Body)
|
||
var result struct {
|
||
Code int `json:"code"`
|
||
Message string `json:"message"`
|
||
Data interface{} `json:"data"`
|
||
}
|
||
_ = json.Unmarshal(b, &result)
|
||
fmt.Printf("[CKBLead] 响应: code=%d message=%s raw=%s\n", result.Code, result.Message, string(b))
|
||
if result.Code == 200 {
|
||
msg := "提交成功,卡若会尽快联系您"
|
||
if result.Message == "已存在" {
|
||
msg = "您已留资,我们会尽快联系您"
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"success": true, "message": msg, "data": result.Data})
|
||
return
|
||
}
|
||
errMsg := result.Message
|
||
if errMsg == "" {
|
||
errMsg = "提交失败,请稍后重试"
|
||
}
|
||
fmt.Printf("[CKBLead] 失败: phone=%s code=%d message=%s\n", phone, result.Code, result.Message)
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "message": errMsg})
|
||
}
|
||
|
||
// CKBPlanStats GET /api/db/ckb-plan-stats 代理存客宝获客计划统计
|
||
func CKBPlanStats(c *gin.Context) {
|
||
routeCfg, docNotes, docContent := getCKBRouteConfig("lead")
|
||
ts := time.Now().Unix()
|
||
// 用 scenarios 接口查询方式不可行,存客宝 plan-stats 需要 JWT
|
||
// 这里用本地 match_records + CKB 签名信息返回聚合统计
|
||
db := database.DB()
|
||
// 各类型提交数量(通过 CKBJoin 写入的 mr_ckb_ 开头的记录)
|
||
type TypeStat struct {
|
||
MatchType string `gorm:"column:match_type" json:"matchType"`
|
||
Total int64 `gorm:"column:total" json:"total"`
|
||
}
|
||
var ckbStats []TypeStat
|
||
db.Raw("SELECT match_type, COUNT(*) as total FROM match_records WHERE id LIKE 'mr_ckb_%' GROUP BY match_type").Scan(&ckbStats)
|
||
var ckbTotal int64
|
||
db.Raw("SELECT COUNT(*) FROM match_records WHERE id LIKE 'mr_ckb_%'").Scan(&ckbTotal)
|
||
// 各类型有联系方式的数量
|
||
var withContact int64
|
||
db.Raw("SELECT COUNT(*) FROM match_records WHERE id LIKE 'mr_ckb_%' AND ((phone IS NOT NULL AND phone != '') OR (wechat_id IS NOT NULL AND wechat_id != ''))").Scan(&withContact)
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"data": gin.H{
|
||
"ckbTotal": ckbTotal,
|
||
"withContact": withContact,
|
||
"byType": ckbStats,
|
||
"ckbApiKey": routeCfg.APIKey[:minInt(len(routeCfg.APIKey), 8)] + "...",
|
||
"ckbApiUrl": routeCfg.APIURL,
|
||
"lastSignTest": ts,
|
||
"docNotes": docNotes,
|
||
"docContent": docContent,
|
||
"routes": getCKBConfigPayload().Routes,
|
||
},
|
||
})
|
||
}
|
||
|
||
// DBCKBLeadList GET /api/db/ckb-leads 管理端-CKB线索明细
|
||
func DBCKBLeadList(c *gin.Context) {
|
||
db := database.DB()
|
||
mode := strings.TrimSpace(c.DefaultQuery("mode", "submitted")) // submitted|contact
|
||
matchType := strings.TrimSpace(c.Query("matchType"))
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20"))
|
||
if page < 1 {
|
||
page = 1
|
||
}
|
||
if pageSize < 1 || pageSize > 100 {
|
||
pageSize = 20
|
||
}
|
||
|
||
q := db.Model(&model.MatchRecord{}).Where("id LIKE 'mr_ckb_%'")
|
||
if matchType != "" {
|
||
q = q.Where("match_type = ?", matchType)
|
||
}
|
||
if mode == "contact" {
|
||
q = q.Where("((phone IS NOT NULL AND phone != '') OR (wechat_id IS NOT NULL AND wechat_id != ''))")
|
||
}
|
||
|
||
var total int64
|
||
q.Count(&total)
|
||
|
||
var records []model.MatchRecord
|
||
if err := q.Order("created_at DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&records).Error; err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
|
||
return
|
||
}
|
||
|
||
userIDs := make(map[string]bool)
|
||
for _, r := range records {
|
||
if r.UserID != "" {
|
||
userIDs[r.UserID] = true
|
||
}
|
||
}
|
||
ids := make([]string, 0, len(userIDs))
|
||
for id := range userIDs {
|
||
ids = append(ids, id)
|
||
}
|
||
var users []model.User
|
||
if len(ids) > 0 {
|
||
db.Where("id IN ?", ids).Find(&users)
|
||
}
|
||
userMap := make(map[string]*model.User)
|
||
for i := range users {
|
||
userMap[users[i].ID] = &users[i]
|
||
}
|
||
safeNickname := func(u *model.User) string {
|
||
if u == nil || u.Nickname == nil {
|
||
return ""
|
||
}
|
||
return *u.Nickname
|
||
}
|
||
|
||
out := make([]gin.H, 0, len(records))
|
||
for _, r := range records {
|
||
out = append(out, gin.H{
|
||
"id": r.ID,
|
||
"userId": r.UserID,
|
||
"userNickname": safeNickname(userMap[r.UserID]),
|
||
"matchType": r.MatchType,
|
||
"phone": func() string { if r.Phone == nil { return "" }; return *r.Phone }(),
|
||
"wechatId": func() string { if r.WechatID == nil { return "" }; return *r.WechatID }(),
|
||
"createdAt": r.CreatedAt,
|
||
})
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"records": out,
|
||
"total": total,
|
||
"page": page,
|
||
"pageSize": pageSize,
|
||
})
|
||
}
|
||
|
||
func minInt(a, b int) int {
|
||
if a < b {
|
||
return a
|
||
}
|
||
return b
|
||
}
|