今日新增修改:解决好友自动推送问题
This commit is contained in:
@@ -287,6 +287,7 @@ func pushLeadToCKB(name, phone, wechatId, leadKey string) (ckbLeadPushResult, er
|
||||
}
|
||||
q.Set("sign", params["sign"].(string))
|
||||
reqURL := ckbAPIURL + "?" + q.Encode()
|
||||
fmt.Printf("[存客宝 scenarios] 请求 URL(GET): %s\n", reqURL)
|
||||
resp, err := http.Get(reqURL)
|
||||
if err != nil {
|
||||
return ckbLeadPushResult{}, err
|
||||
@@ -327,6 +328,55 @@ func resolvePersonForLead(db *gorm.DB, targetUserID string) (model.Person, bool)
|
||||
return model.Person{}, false
|
||||
}
|
||||
|
||||
// resolveLeadPlanAPIKeyForRetry 与 GET /api/db/ckb-leads?mode=contact 中 planApiKey 展示顺序一致:
|
||||
// 1) ckb_lead_records.plan_api_key
|
||||
// 2) action/source 默认(join/match → 内置 ckbAPIKey;lead+index_link_button → getCkbLeadApiKey)
|
||||
// 3) 仍为空:按 target_person_id 或 params.targetUserId 查人物 persons.ckb_api_key
|
||||
// 4) index_link 且无 target:与列表一致,用「全局 leadKey 对应 Person」兜底
|
||||
// 5) 最后才 getCkbLeadApiKey()
|
||||
//
|
||||
// 旧逻辑在 plan_api_key 为空时直接 getCkbLeadApiKey(),且非 index_link 时用人物 key 无条件覆盖 —
|
||||
// 会导致:列表因人物兜底显示 2y4v5…,重推却带全局 IPQ9s…;或在库内已有 plan_api_key 时仍被人物旧 key 覆盖。
|
||||
func resolveLeadPlanAPIKeyForRetry(db *gorm.DB, r model.CkbLeadRecord, p map[string]interface{}) string {
|
||||
planKey := strings.TrimSpace(r.PlanAPIKey)
|
||||
action := strings.TrimSpace(r.Action)
|
||||
source := strings.TrimSpace(r.Source)
|
||||
|
||||
if planKey == "" {
|
||||
if action == "join" || action == "match" {
|
||||
planKey = ckbAPIKey
|
||||
} else if action == "lead" && source == "index_link_button" {
|
||||
planKey = getCkbLeadApiKey()
|
||||
}
|
||||
}
|
||||
if planKey == "" {
|
||||
targetTok := strings.TrimSpace(r.TargetPersonID)
|
||||
if targetTok == "" && p != nil {
|
||||
if v, ok := p["targetUserId"].(string); ok {
|
||||
targetTok = strings.TrimSpace(v)
|
||||
}
|
||||
}
|
||||
if targetTok != "" {
|
||||
if person, found := resolvePersonForLead(db, targetTok); found && strings.TrimSpace(person.CkbApiKey) != "" {
|
||||
planKey = strings.TrimSpace(person.CkbApiKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
if planKey == "" && source == "index_link_button" && strings.TrimSpace(r.TargetPersonID) == "" {
|
||||
gk := getCkbLeadApiKey()
|
||||
if gk != "" {
|
||||
var fp model.Person
|
||||
if db.Where("ckb_api_key = ? AND ckb_api_key != ''", gk).First(&fp).Error == nil && strings.TrimSpace(fp.CkbApiKey) != "" {
|
||||
planKey = strings.TrimSpace(fp.CkbApiKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
if planKey == "" {
|
||||
planKey = getCkbLeadApiKey()
|
||||
}
|
||||
return planKey
|
||||
}
|
||||
|
||||
// existsUnifiedLeadRecent join/match 幂等去重:同用户+动作+来源+联系方式在窗口期内仅保留一条,避免重复点击刷数据
|
||||
func existsUnifiedLeadRecent(db *gorm.DB, action, userID, source, phone, wechatID string, within time.Duration) bool {
|
||||
if db == nil {
|
||||
@@ -391,10 +441,14 @@ func retryOneLeadRecord(ctx context.Context, db *gorm.DB, r model.CkbLeadRecord)
|
||||
wechatId = strings.TrimSpace(v)
|
||||
}
|
||||
}
|
||||
leadKey := strings.TrimSpace(r.PlanAPIKey)
|
||||
if leadKey == "" {
|
||||
leadKey = getCkbLeadApiKey()
|
||||
if wechatId == "" && p != nil {
|
||||
if v, ok := p["wechat"].(string); ok {
|
||||
wechatId = strings.TrimSpace(v)
|
||||
}
|
||||
}
|
||||
|
||||
leadKey := resolveLeadPlanAPIKeyForRetry(db, r, p)
|
||||
|
||||
targetName := ""
|
||||
targetMemberID := ""
|
||||
targetMemberName := ""
|
||||
@@ -402,16 +456,23 @@ func retryOneLeadRecord(ctx context.Context, db *gorm.DB, r model.CkbLeadRecord)
|
||||
if v, ok := p["userId"].(string); ok && leadUserID == "" {
|
||||
leadUserID = strings.TrimSpace(v)
|
||||
}
|
||||
if source != "index_link_button" {
|
||||
if v, ok := p["targetUserId"].(string); ok && strings.TrimSpace(v) != "" {
|
||||
if person, found := resolvePersonForLead(db, v); found && strings.TrimSpace(person.CkbApiKey) != "" {
|
||||
leadKey = strings.TrimSpace(person.CkbApiKey)
|
||||
targetTok := strings.TrimSpace(r.TargetPersonID)
|
||||
if targetTok == "" && p != nil {
|
||||
if v, ok := p["targetUserId"].(string); ok {
|
||||
targetTok = strings.TrimSpace(v)
|
||||
}
|
||||
}
|
||||
if targetTok != "" {
|
||||
if person, found := resolvePersonForLead(db, targetTok); found {
|
||||
if strings.TrimSpace(person.Name) != "" {
|
||||
targetName = strings.TrimSpace(person.Name)
|
||||
if person.UserID != nil {
|
||||
targetMemberID = strings.TrimSpace(*person.UserID)
|
||||
}
|
||||
}
|
||||
if person.UserID != nil {
|
||||
targetMemberID = strings.TrimSpace(*person.UserID)
|
||||
}
|
||||
}
|
||||
}
|
||||
if p != nil {
|
||||
if v, ok := p["targetNickname"].(string); ok && strings.TrimSpace(v) != "" {
|
||||
targetName = strings.TrimSpace(v)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -13,6 +15,54 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// jsonValAsTrimmedString 从 JSON map 取值(兼容 number / string),用于 params 里手机号等字段
|
||||
func jsonValAsTrimmedString(v interface{}) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(t)
|
||||
case float64:
|
||||
if t == 0 {
|
||||
return ""
|
||||
}
|
||||
if t == float64(int64(t)) {
|
||||
return strconv.FormatInt(int64(t), 10)
|
||||
}
|
||||
return strings.TrimSpace(strconv.FormatFloat(t, 'f', -1, 64))
|
||||
case json.Number:
|
||||
return strings.TrimSpace(string(t))
|
||||
default:
|
||||
return strings.TrimSpace(fmt.Sprint(v))
|
||||
}
|
||||
}
|
||||
|
||||
// extractPhoneWechatFromParamsJSON 从线索 params JSON 抽取联系方式(加入/匹配上报结构可能只用顶层 phone/wechat)
|
||||
func extractPhoneWechatFromParamsJSON(paramsJSON string) (phone, wechat string) {
|
||||
s := strings.TrimSpace(paramsJSON)
|
||||
if s == "" {
|
||||
return "", ""
|
||||
}
|
||||
var p map[string]interface{}
|
||||
if json.Unmarshal([]byte(s), &p) != nil {
|
||||
return "", ""
|
||||
}
|
||||
for _, k := range []string{"phone", "mobile", "tel"} {
|
||||
if v := jsonValAsTrimmedString(p[k]); v != "" {
|
||||
phone = v
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, k := range []string{"wechat", "wechatId", "wechat_id", "wx", "wxId"} {
|
||||
if v := jsonValAsTrimmedString(p[k]); v != "" {
|
||||
wechat = v
|
||||
break
|
||||
}
|
||||
}
|
||||
return phone, wechat
|
||||
}
|
||||
|
||||
// DBCKBLeadList GET /api/db/ckb-leads 管理端-CKB线索明细
|
||||
// mode=submitted: ckb_lead_records(action=join/match,兼容旧面板命名)
|
||||
// mode=contact: ckb_lead_records(链接卡若留资,有 phone/wechat)
|
||||
@@ -232,22 +282,22 @@ func DBCKBLeadList(c *gin.Context) {
|
||||
for _, r := range records {
|
||||
phone := strings.TrimSpace(r.Phone)
|
||||
wechatID := strings.TrimSpace(r.WechatID)
|
||||
// 工作台 join/match 行曾只读 row.phone;老数据或仅写在 params 里的,回退解析便于运营核对
|
||||
if (phone == "" || wechatID == "") && strings.TrimSpace(r.Params) != "" {
|
||||
var p map[string]interface{}
|
||||
if json.Unmarshal([]byte(r.Params), &p) == nil {
|
||||
if phone == "" {
|
||||
if v, ok := p["phone"].(string); ok {
|
||||
phone = strings.TrimSpace(v)
|
||||
}
|
||||
}
|
||||
if wechatID == "" {
|
||||
if v, ok := p["wechat"].(string); ok {
|
||||
wechatID = strings.TrimSpace(v)
|
||||
} else if v, ok := p["wechatId"].(string); ok {
|
||||
wechatID = strings.TrimSpace(v)
|
||||
}
|
||||
}
|
||||
if phone == "" || wechatID == "" {
|
||||
pp, ww := extractPhoneWechatFromParamsJSON(r.Params)
|
||||
if phone == "" {
|
||||
phone = pp
|
||||
}
|
||||
if wechatID == "" {
|
||||
wechatID = ww
|
||||
}
|
||||
}
|
||||
// 仍为空:按 user_id 回填会员资料(autoCKBReport 历史误写、或库内列为空但用户已补全资料)
|
||||
if u := userMap[r.UserID]; u != nil {
|
||||
if phone == "" && u.Phone != nil {
|
||||
phone = strings.TrimSpace(*u.Phone)
|
||||
}
|
||||
if wechatID == "" && u.WechatID != nil {
|
||||
wechatID = strings.TrimSpace(*u.WechatID)
|
||||
}
|
||||
}
|
||||
out = append(out, gin.H{
|
||||
@@ -265,13 +315,48 @@ func DBCKBLeadList(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "records": out, "total": total, "page": page, "pageSize": pageSize})
|
||||
}
|
||||
|
||||
func logCkbLeadRetryResponse(resp gin.H) {
|
||||
b, err := json.MarshalIndent(resp, "", " ")
|
||||
if err != nil {
|
||||
fmt.Printf("[DBCKBLeadRetry] response(marshal err=%v): %+v\n", err, resp)
|
||||
return
|
||||
}
|
||||
fmt.Printf("[DBCKBLeadRetry] response JSON:\n%s\n", string(b))
|
||||
}
|
||||
|
||||
func logCkbLeadRetryRequest(raw []byte, readErr error) {
|
||||
if readErr != nil {
|
||||
fmt.Printf("[DBCKBLeadRetry] request body read error: %v\n", readErr)
|
||||
return
|
||||
}
|
||||
s := strings.TrimSpace(string(raw))
|
||||
if s == "" {
|
||||
fmt.Printf("[DBCKBLeadRetry] request body: (empty)\n")
|
||||
return
|
||||
}
|
||||
if json.Valid([]byte(s)) {
|
||||
var buf bytes.Buffer
|
||||
if err := json.Indent(&buf, []byte(s), "", " "); err == nil {
|
||||
fmt.Printf("[DBCKBLeadRetry] request JSON:\n%s\n", buf.String())
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Printf("[DBCKBLeadRetry] request body: %s\n", s)
|
||||
}
|
||||
|
||||
// DBCKBLeadRetry POST /api/db/ckb-leads/retry 管理端-手动重推单条失败线索
|
||||
func DBCKBLeadRetry(c *gin.Context) {
|
||||
raw, readErr := io.ReadAll(c.Request.Body)
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(raw))
|
||||
logCkbLeadRetryRequest(raw, readErr)
|
||||
|
||||
var body struct {
|
||||
ID int64 `json:"id" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil || body.ID <= 0 {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "缺少有效 id"})
|
||||
resp := gin.H{"success": false, "error": "缺少有效 id"}
|
||||
logCkbLeadRetryResponse(resp)
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
ok, err := RetryCkbLeadByID(c.Request.Context(), body.ID)
|
||||
@@ -280,16 +365,20 @@ func DBCKBLeadRetry(c *gin.Context) {
|
||||
if msg == "" {
|
||||
msg = "重推失败"
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": msg})
|
||||
resp := gin.H{"success": false, "error": msg}
|
||||
logCkbLeadRetryResponse(resp)
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
db := database.DB()
|
||||
var r model.CkbLeadRecord
|
||||
if err := db.Where("id = ?", body.ID).First(&r).Error; err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "pushed": ok})
|
||||
resp := gin.H{"success": true, "pushed": ok}
|
||||
logCkbLeadRetryResponse(resp)
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
resp := gin.H{
|
||||
"success": true,
|
||||
"pushed": ok,
|
||||
"record": gin.H{
|
||||
@@ -303,7 +392,9 @@ func DBCKBLeadRetry(c *gin.Context) {
|
||||
"lastPushAt": r.LastPushAt,
|
||||
"nextRetryAt": r.NextRetryAt,
|
||||
},
|
||||
})
|
||||
}
|
||||
logCkbLeadRetryResponse(resp)
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// DBCKBLeadDelete POST /api/db/ckb-leads/delete 管理端-删除一条留资记录(运营清理误报/测试数据)
|
||||
|
||||
@@ -403,8 +403,17 @@ func MatchUsers(c *gin.Context) {
|
||||
fmt.Printf("[MatchUsers] 写入 match_records 失败: %v\n", err)
|
||||
}
|
||||
// 后端兜底:匹配成功自动上报 CKB(小程序也会上报,CKBMatch 内部有 5 分钟去重)
|
||||
// 必须用「发起人」联系方式写入 ckb_lead_records;误用被匹配人 r 会导致 user_id 与手机号不一致或整列为空
|
||||
go func() {
|
||||
autoCKBReport(db, body.UserID, body.MatchType, phone, wechat)
|
||||
initPhone := strings.TrimSpace(body.Phone)
|
||||
initWechat := strings.TrimSpace(body.WechatID)
|
||||
if initPhone == "" && user.Phone != nil {
|
||||
initPhone = strings.TrimSpace(*user.Phone)
|
||||
}
|
||||
if initWechat == "" && user.WechatID != nil {
|
||||
initWechat = strings.TrimSpace(*user.WechatID)
|
||||
}
|
||||
autoCKBReport(db, body.UserID, body.MatchType, initPhone, initWechat)
|
||||
}()
|
||||
commonInterests := buildCommonInterests(user, r)
|
||||
dynamicTags := buildDynamicTags(r, tag)
|
||||
|
||||
Reference in New Issue
Block a user