Переглянути джерело

Merge branch 'feature/646-bowen-contact_form'

* feature/646-bowen-contact_form:
  fixbug
  调整时区
  调整获取聊天记录范围
  1. 调整联系人自定义字段定时任务 2. 增加联系人自定义字段分析接口
  临时暂存
  临时暂存

# Conflicts:
#	crontask/init.go
boweniac 16 годин тому
батько
коміт
e4af777751

+ 3 - 454
crontask/contact_form.go

@@ -1,22 +1,6 @@
 package crontask
 
-import (
-	"encoding/json"
-	"fmt"
-	"github.com/google/uuid"
-	"github.com/zeromicro/go-zero/core/logx"
-	"strconv"
-	"strings"
-	"time"
-	"wechat-api/ent/contact"
-	"wechat-api/ent/contactfield"
-	"wechat-api/ent/contactfieldtemplate"
-	"wechat-api/ent/custom_types"
-	"wechat-api/ent/predicate"
-	"wechat-api/ent/usagedetail"
-	"wechat-api/internal/types"
-	"wechat-api/internal/utils/compapi"
-)
+import "wechat-api/internal/service/wechat"
 
 type ResponseItem struct {
 	DataIndex string   `json:"dataIndex"`
@@ -39,441 +23,6 @@ type FormData struct {
 	FieldProps FieldProps `json:"fieldProps"`
 }
 
-func (l *CronTask) analyze() {
-	usageDetails := make(map[string]map[string]string)
-	contactFieldTemplates := make(map[string][]custom_types.ContactFieldTemplate)
-	template_type_text := "text"
-	template_type_radio := "radio"
-	//template_type_date := "date"
-	template_sex_id := "sex"
-	template_sex_label := "性别"
-	template_sex_options_man_label := "男"
-	template_sex_options_man_value := "男"
-	template_sex_options_woman_label := "女"
-	template_sex_options_woman_value := "男"
-
-	template_phone_id := "phone"
-	template_phone_label := "手机号"
-
-	//template_name_id := "name"
-	//template_name_label := "姓名"
-	//
-	//template_age_id := "age"
-	//template_age_label := "年龄(以字符串形式返回阿拉伯数字)"
-	//
-	//template_area_id := "area"
-	//template_area_label := "地区"
-	//
-	//template_birthday_id := "birthday"
-	//template_birthday_label := "出生日期"
-	//
-	//template_birtharea_id := "birtharea"
-	//template_birtharea_label := "出生地"
-	//
-	//template_idcard_no_id := "idcard_no"
-	//template_idcard_no_label := "身份证号"
-	//
-	//template_title_id := "title"
-	//template_title_label := "称呼"
-	contactBasicFieldTemplates := []custom_types.ContactFieldTemplate{
-		{
-			Label: &template_sex_label,
-			Id:    &template_sex_id,
-			Type:  &template_type_radio,
-			Options: []custom_types.ContactFieldTemplateOptions{
-				{
-					Label: &template_sex_options_man_label,
-					Value: &template_sex_options_man_value,
-				}, {
-					Label: &template_sex_options_woman_label,
-					Value: &template_sex_options_woman_value,
-				},
-			},
-		},
-		{
-			Label: &template_phone_label,
-			Id:    &template_phone_id,
-			Type:  &template_type_text,
-		},
-		//{
-		//	Label: &template_name_label,
-		//	Id:    &template_name_id,
-		//	Type:  &template_type_text,
-		//}, {
-		//	Label: &template_age_label,
-		//	Id:    &template_age_id,
-		//	Type:  &template_type_text,
-		//}, {
-		//	Label: &template_area_label,
-		//	Id:    &template_area_id,
-		//	Type:  &template_type_text,
-		//}, {
-		//	Label: &template_birthday_label,
-		//	Id:    &template_birthday_id,
-		//	Type:  &template_type_date,
-		//}, {
-		//	Label: &template_birtharea_label,
-		//	Id:    &template_birtharea_id,
-		//	Type:  &template_type_text,
-		//}, {
-		//	Label: &template_idcard_no_label,
-		//	Id:    &template_idcard_no_id,
-		//	Type:  &template_type_text,
-		//}, {
-		//	Label: &template_title_label,
-		//	Id:    &template_title_id,
-		//	Type:  &template_type_text,
-		//},
-	}
-
-	var predicates []predicate.UsageDetail
-	predicates = append(predicates, usagedetail.TypeIn(1, 3, 4, 6))
-	predicates = append(predicates, usagedetail.AppIn(1, 3, 4, 5))
-	//yesterdayStart := time.Now().AddDate(0, 0, -1).Truncate(24 * time.Hour)
-	//yesterdayEnd := yesterdayStart.Add(24 * time.Hour)
-	loc, _ := time.LoadLocation("Asia/Shanghai")
-	now := time.Now().In(loc)
-	yesterdayEnd := now.Truncate(24 * time.Hour)
-	yesterdayStart := yesterdayEnd.AddDate(0, 0, -2)
-	predicates = append(predicates, usagedetail.CreatedAtGTE(yesterdayStart))
-	predicates = append(predicates, usagedetail.CreatedAtLT(yesterdayEnd))
-
-	//todayStart := time.Now().AddDate(0, 0, 0).Truncate(24 * time.Hour)
-	//todayEnd := todayStart.Add(24 * time.Hour)
-	//predicates = append(predicates, usagedetail.CreatedAtGTE(todayStart))
-	//predicates = append(predicates, usagedetail.CreatedAtLT(todayEnd))
-
-	data, err := l.svcCtx.DB.UsageDetail.Query().Where(predicates...).All(l.ctx)
-	logx.Info("usageDetails: ", data)
-	if err != nil {
-		return
-	}
-
-	wxs, err := l.svcCtx.DB.Wx.Query().All(l.ctx)
-	if err != nil {
-		return
-	}
-
-	for _, wx := range wxs {
-		c, _ := l.svcCtx.DB.ContactFieldTemplate.Query().Where(contactfieldtemplate.OrganizationID(wx.OrganizationID)).First(l.ctx)
-		if c != nil {
-			contactFieldTemplates[wx.Wxid] = c.Template
-		} else {
-			contactFieldTemplates[wx.Wxid] = nil
-		}
-	}
-
-	for _, u := range data {
-		if contactFieldTemplates[u.BotID] == nil {
-			continue
-		}
-		if _, ok := usageDetails[u.BotID]; !ok {
-			usageDetails[u.BotID] = make(map[string]string)
-		}
-		usageDetails[u.BotID][u.ReceiverID] += fmt.Sprintf("用户:%s\n机器人:%s\n", u.Request, u.Response)
-	}
-	logx.Info("usageDetails: ", usageDetails)
-	for botID, template := range contactFieldTemplates {
-		if template == nil {
-			template = contactBasicFieldTemplates
-		} else {
-			template = append(template, contactBasicFieldTemplates...)
-		}
-		for receiverID, messages := range usageDetails[botID] {
-			result, _ := l.openaiRequest(messages, template)
-			logx.Info("result: ", result)
-			if result == nil {
-				continue
-			}
-			_ = l.UpdateContactFields(botID, receiverID, result)
-		}
-	}
-
-}
-
-func (l *CronTask) openaiRequest(messages string, template []custom_types.ContactFieldTemplate) ([]ResponseItem, error) {
-	formData := ConvertFormData(template)
-	jsonBytes, err := json.Marshal(formData)
-	if err != nil {
-		return nil, err
-	}
-	jsonStr := string(jsonBytes)
-	logx.Info("contactFieldTemplates: ", jsonStr)
-	req := &types.CompApiReq{
-		types.CompCtlReq{
-			"form",
-			"",
-			false,
-			"",
-		},
-		types.StdCompApiReq{
-			"gpt-4o",
-			[]types.StdCompMessage{},
-			false,
-			nil,
-		},
-		types.FastGptSpecReq{
-			"",
-			"",
-			"",
-			false,
-			map[string]string{
-				"form_data":    jsonStr,
-				"chat_history": messages,
-				"external_id":  uuid.New().String(),
-			},
-		},
-	}
-	resp, err := compapi.NewClient(l.ctx, compapi.WithApiBase("http://new-api.gkscrm.com/v1/"),
-		compapi.WithApiKey("sk-wwttAtdLcTfeF7F2Eb9d3592Bd4c487f8e8fA544D6C4BbA9")).
-		Chat(req)
-	logx.Info("resp: ", resp)
-	if err == nil && resp != nil && len(resp.Choices) > 0 {
-		logx.Info("resp.Choices: ", resp.Choices[0].Message.Content)
-		// 尝试第一层解析成 string
-
-		items, err := parseContent(resp.Choices[0].Message.Content)
-		if err != nil {
-			return nil, err
-		}
-		return items, nil
-	} else if resp != nil && len(resp.Choices) == 0 {
-		return nil, err
-	}
-	//url := "https://toolsapi-debug.gkscrm.com/call_center/form/extract"
-	//bodyData := map[string]interface{}{
-	//	"form_data":    ConvertFormData(template),
-	//	"chat_history": messages,
-	//	"external_id":  uuid.New().String(),
-	//}
-	//logx.Info("bodyData: %+v", bodyData)
-	//bodyBytes, err := json.Marshal(bodyData)
-	//if err != nil {
-	//	return nil, err
-	//}
-	//
-	//req, err := http.NewRequest("POST", url, bytes.NewBuffer(bodyBytes))
-	//if err != nil {
-	//	return nil, err
-	//}
-	//req.Header.Set("Content-Type", "application/json")
-	//req.Header.Set("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIn0.ZS9jnsLPCnmc8L_lu4yaQFp34vwWF1mHlHSBYrY5JVs")
-	//
-	//client := &http.Client{}
-	//resp, err := client.Do(req)
-	//if err != nil || resp == nil || resp.Body == nil {
-	//	logx.Error("read body error: ", err)
-	//	return nil, err
-	//}
-	//
-	//logx.Info("err: ", err)
-	//if err != nil {
-	//	return nil, err
-	//}
-	//defer resp.Body.Close()
-	//
-	//if resp.StatusCode != http.StatusOK {
-	//	return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
-	//}
-	//
-	////var result []ResponseItem
-	//var fullResp struct {
-	//	Data []ResponseItem `json:"data"`
-	//}
-	//err = json.NewDecoder(resp.Body).Decode(&fullResp)
-	//if err != nil {
-	//	return nil, err
-	//}
-	//
-	//return fullResp.Data, nil
-	return nil, err
-}
-
-func (l *CronTask) UpdateContactFields(botID string, receiverID string, fields []ResponseItem) error {
-	basic_ids := []string{"sex", "phone", "name", "age", "area", "birthday", "birtharea", "idcard_no", "title"}
-	c, _ := l.svcCtx.DB.Contact.Query().Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).First(l.ctx)
-	if c == nil {
-		return fmt.Errorf("Contact not find")
-	}
-	for _, field := range fields {
-		if contains(basic_ids, field.DataIndex) {
-			if len(field.Value) == 0 {
-				continue
-			}
-			value := 0
-			if field.DataIndex == "sex" && c.Sex == 0 {
-				if field.Value[0] == "男" {
-					value = 1
-				} else if field.Value[0] == "女" {
-					value = 2
-				}
-				_, err := l.svcCtx.DB.Contact.Update().
-					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
-					SetSex(value).
-					Save(l.ctx)
-				if err != nil {
-					continue
-				}
-			} else if field.DataIndex == "phone" && c.Phone == "" {
-				_, err := l.svcCtx.DB.Contact.Update().
-					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
-					SetPhone(field.Value[0]).
-					Save(l.ctx)
-				if err != nil {
-					continue
-				}
-			} else if field.DataIndex == "name" && c.Cname == "" {
-				_, err := l.svcCtx.DB.Contact.Update().
-					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
-					SetCname(field.Value[0]).
-					Save(l.ctx)
-				if err != nil {
-					continue
-				}
-			} else if field.DataIndex == "age" && c.Cage == 0 {
-				num, err := strconv.Atoi(field.Value[0])
-				if err != nil {
-					continue
-				}
-				_, err = l.svcCtx.DB.Contact.Update().
-					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
-					SetCage(num).
-					Save(l.ctx)
-				if err != nil {
-					continue
-				}
-			} else if field.DataIndex == "area" && c.Carea == "" {
-				_, err := l.svcCtx.DB.Contact.Update().
-					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
-					SetCarea(field.Value[0]).
-					Save(l.ctx)
-				if err != nil {
-					continue
-				}
-			} else if field.DataIndex == "birthday" && c.Cbirthday == "" {
-				_, err := l.svcCtx.DB.Contact.Update().
-					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
-					SetCbirthday(field.Value[0]).
-					Save(l.ctx)
-				if err != nil {
-					continue
-				}
-			} else if field.DataIndex == "birtharea" && c.Cbirtharea == "" {
-				_, err := l.svcCtx.DB.Contact.Update().
-					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
-					SetCbirtharea(field.Value[0]).
-					Save(l.ctx)
-				if err != nil {
-					continue
-				}
-			} else if field.DataIndex == "idcard_no" && c.CidcardNo == "" {
-				_, err := l.svcCtx.DB.Contact.Update().
-					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
-					SetCidcardNo(field.Value[0]).
-					Save(l.ctx)
-				if err != nil {
-					continue
-				}
-			} else if field.DataIndex == "title" && c.Ctitle == "" {
-				_, err := l.svcCtx.DB.Contact.Update().
-					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
-					SetCtitle(field.Value[0]).
-					Save(l.ctx)
-				if err != nil {
-					continue
-				}
-			}
-		} else {
-			f, _ := l.svcCtx.DB.ContactField.Query().Where(contactfield.ContactID(c.ID), contactfield.FormID(field.DataIndex)).First(l.ctx)
-			if f == nil {
-				if field.Value != nil && len(field.Value) > 0 && field.Value[0] != "" {
-					_, err := l.svcCtx.DB.ContactField.Create().
-						SetContactID(c.ID).
-						SetFormID(field.DataIndex).
-						SetValue(field.Value).
-						Save(l.ctx)
-					if err != nil {
-						continue
-					}
-				}
-			} else if len(f.Value) == 0 || f.Value[0] == "" {
-				if field.Value != nil && len(field.Value) > 0 && field.Value[0] != "" {
-					_, err := l.svcCtx.DB.ContactField.UpdateOneID(f.ID).
-						SetValue(field.Value).
-						Save(l.ctx)
-					if err != nil {
-						continue
-					}
-				}
-			}
-		}
-	}
-	return nil
-}
-
-func ConvertFormData(input []custom_types.ContactFieldTemplate) []FormData {
-	result := make([]FormData, len(input))
-	for i, item := range input {
-		options := make([]FieldPropsOptions, len(item.Options))
-		for j, opt := range item.Options {
-			options[j] = FieldPropsOptions{
-				Label: *opt.Label,
-				Value: *opt.Value,
-			}
-		}
-		result[i] = FormData{
-			Title:     *item.Label,
-			DataIndex: *item.Id,
-			ValueType: *item.Type,
-			FieldProps: FieldProps{
-				Options: options,
-			},
-		}
-	}
-	return result
-}
-
-func contains(strs []string, target string) bool {
-	for _, s := range strs {
-		if s == target {
-			return true
-		}
-	}
-	return false
-}
-
-func parseContent(content string) ([]ResponseItem, error) {
-	content = strings.TrimSpace(content)
-
-	// ① 双引号包裹的再来一次:"{\"dataIndex\":...}"
-	if unq, err := strconv.Unquote(content); err == nil {
-		return parseContent(unq) // 尝试递归
-	}
-
-	// ② 直接数组形式: [...]
-	if strings.HasPrefix(content, "[") {
-		var list []ResponseItem
-		if err := json.Unmarshal([]byte(content), &list); err == nil {
-			return list, nil
-		}
-	}
-
-	// ③ 对象形式
-	if strings.HasPrefix(content, "{") {
-		// 3‑1 尝试对象包裹数组: {"values":[...]}
-		var wrapper struct {
-			Values []ResponseItem `json:"values"`
-		}
-		if err := json.Unmarshal([]byte(content), &wrapper); err == nil && len(wrapper.Values) > 0 {
-			return wrapper.Values, nil
-		}
-
-		// 3‑2 尝试单对象形式: {"dataIndex": ...}
-		var item ResponseItem
-		if err := json.Unmarshal([]byte(content), &item); err == nil {
-			return []ResponseItem{item}, nil
-		}
-	}
-
-	return nil, fmt.Errorf("unsupported content format: %q", content)
+func (l *CronTask) analyze(bot_wxid *string) {
+	wechat.NewAnalyzeContactField(l.ctx, l.svcCtx).Analyze(bot_wxid, nil)
 }

+ 35 - 29
crontask/init.go

@@ -24,35 +24,41 @@ func NewCronTask(ctx context.Context, svcCtx *svc.ServiceContext) *CronTask {
 
 func ScheduleRun(c *cron.Cron, serverCtx *svc.ServiceContext) {
 
-	//l := NewCronTask(context.Background(), serverCtx)
-	//c.AddFunc("* * * * *", func() {
-	//	l.sendMsg()
-	//})
-	//
-	//sendWx := NewCronTask(context.Background(), serverCtx)
-	//c.AddFunc("* * * * *", func() {
-	//	sendWx.sendWx()
-	//})
-	//
-	//sendWxOnTimeout := NewCronTask(context.Background(), serverCtx)
-	//c.AddFunc("* * * * *", func() {
-	//	sendWxOnTimeout.sendWxOnTimeout()
-	//})
-	//
-	//computeStatistic := NewCronTask(context.Background(), serverCtx)
-	//c.AddFunc("0 * * * *", func() {
-	//	computeStatistic.computeStatistic()
-	//})
-	//
-	//contactForm := NewCronTask(context.Background(), serverCtx)
-	//c.AddFunc("10 0 * * *", func() {
-	//	contactForm.analyze()
-	//})
-	//
-	//l = NewCronTask(context.Background(), serverCtx)
-	//c.AddFunc("*/30 * * * *", func() {
-	//	l.wxAddFriend()
-	//})
+	l := NewCronTask(context.Background(), serverCtx)
+	c.AddFunc("* * * * *", func() {
+		l.sendMsg()
+	})
+
+	sendWx := NewCronTask(context.Background(), serverCtx)
+	c.AddFunc("* * * * *", func() {
+		sendWx.sendWx()
+	})
+
+	sendWxOnTimeout := NewCronTask(context.Background(), serverCtx)
+	c.AddFunc("* * * * *", func() {
+		sendWxOnTimeout.sendWxOnTimeout()
+	})
+
+	computeStatistic := NewCronTask(context.Background(), serverCtx)
+	c.AddFunc("0 * * * *", func() {
+		computeStatistic.computeStatistic()
+	})
+
+	contactForm := NewCronTask(context.Background(), serverCtx)
+	c.AddFunc("10 0 * * *", func() {
+		contactForm.analyze(nil)
+	})
+
+	contactForm2 := NewCronTask(context.Background(), serverCtx)
+	c.AddFunc("0 12 * * *", func() {
+		bot_wxid := "wxid_ifzo7uu0cl3b22"
+		contactForm2.analyze(&bot_wxid)
+	})
+
+	l = NewCronTask(context.Background(), serverCtx)
+	c.AddFunc("*/30 * * * *", func() {
+		l.wxAddFriend()
+	})
 
 	//l = NewCronTask(context.Background(), serverCtx)
 	//c.AddFunc("* * * * *", func() {

+ 14 - 0
desc/wechat/contact.api

@@ -129,6 +129,16 @@ type (
 		CidcardNo *string `json:"cidcardNo,optional"`
 		Ctitle *string `json:"ctitle,optional"`
 	}
+
+	AnalyzeContactFieldReq {
+		WxWxid  *string `json:"wxWxid,optional"`
+
+        Wxid  *string `json:"wxid,optional"`
+	}
+
+	AnalyzeContactFieldResp {
+	    CustomFields []ContactFieldTemplate `json:"customFields,optional"`
+	}
 )
 
 @server(
@@ -191,4 +201,8 @@ service Wechat {
 	// Whatsapp联系人详情
 	@handler getWhatsappContact
 	post /contact/getWhatsappContact (IDReq) returns (ContactInfoResp)
+
+	// 分析联系人自定义字段
+    @handler analyzeContactField
+    post /contact/analyzeContactField (AnalyzeContactFieldReq) returns (BaseMsgResp)
 }

+ 44 - 0
internal/handler/contact/analyze_contact_field_handler.go

@@ -0,0 +1,44 @@
+package contact
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/contact"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /contact/analyzeContactField contact AnalyzeContactField
+//
+// 分析联系人自定义字段
+//
+// 分析联系人自定义字段
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: AnalyzeContactFieldReq
+//
+// Responses:
+//  200: AnalyzeContactFieldResp
+
+func AnalyzeContactFieldHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.AnalyzeContactFieldReq
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := contact.NewAnalyzeContactFieldLogic(r.Context(), svcCtx)
+		resp, err := l.AnalyzeContactField(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 5 - 0
internal/handler/routes.go

@@ -449,6 +449,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
 					Path:    "/contact/getWhatsappContact",
 					Handler: contact.GetWhatsappContactHandler(serverCtx),
 				},
+				{
+					Method:  http.MethodPost,
+					Path:    "/contact/analyzeContactField",
+					Handler: contact.AnalyzeContactFieldHandler(serverCtx),
+				},
 			}...,
 		),
 		rest.WithJwt(serverCtx.Config.Auth.AccessSecret),

+ 31 - 0
internal/logic/contact/analyze_contact_field_logic.go

@@ -0,0 +1,31 @@
+package contact
+
+import (
+	"context"
+	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"wechat-api/internal/service/wechat"
+
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type AnalyzeContactFieldLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewAnalyzeContactFieldLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AnalyzeContactFieldLogic {
+	return &AnalyzeContactFieldLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *AnalyzeContactFieldLogic) AnalyzeContactField(req *types.AnalyzeContactFieldReq) (resp *types.BaseMsgResp, err error) {
+	wechat.NewAnalyzeContactField(l.ctx, l.svcCtx).Analyze(req.WxWxid, req.Wxid)
+
+	return &types.BaseMsgResp{Msg: errormsg.Success}, nil
+}

+ 508 - 0
internal/service/wechat/analyze_contact_field.go

@@ -0,0 +1,508 @@
+package wechat
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"github.com/google/uuid"
+	"github.com/zeromicro/go-zero/core/logx"
+	"strconv"
+	"strings"
+	"time"
+	"wechat-api/ent/contact"
+	"wechat-api/ent/contactfield"
+	"wechat-api/ent/contactfieldtemplate"
+	"wechat-api/ent/custom_types"
+	"wechat-api/ent/predicate"
+	"wechat-api/ent/usagedetail"
+	"wechat-api/ent/wx"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+	"wechat-api/internal/utils/compapi"
+)
+
+type ResponseItem struct {
+	DataIndex string   `json:"dataIndex"`
+	Value     []string `json:"value"`
+}
+
+type FieldPropsOptions struct {
+	Label string `json:"label"`
+	Value string `json:"value"`
+}
+
+type FieldProps struct {
+	Options []FieldPropsOptions `json:"options"`
+}
+
+type FormData struct {
+	Title      string     `json:"title"`
+	DataIndex  string     `json:"dataIndex"`
+	ValueType  string     `json:"valueType"`
+	FieldProps FieldProps `json:"fieldProps"`
+}
+
+type AnalyzeContactField struct {
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewAnalyzeContactField(ctx context.Context, svcCtx *svc.ServiceContext) *AnalyzeContactField {
+	return &AnalyzeContactField{
+		ctx:    ctx,
+		svcCtx: svcCtx,
+	}
+}
+
+func (l *AnalyzeContactField) Analyze(bot_wxid *string, contact_wxid *string) {
+	usageDetails := make(map[string]map[string]string)
+	contactFieldTemplates := make(map[string][]custom_types.ContactFieldTemplate)
+	template_type_text := "text"
+	template_type_radio := "radio"
+	//template_type_date := "date"
+	template_sex_id := "sex"
+	template_sex_label := "性别"
+	template_sex_options_man_label := "男"
+	template_sex_options_man_value := "男"
+	template_sex_options_woman_label := "女"
+	template_sex_options_woman_value := "男"
+
+	template_phone_id := "phone"
+	template_phone_label := "手机号"
+
+	//template_name_id := "name"
+	//template_name_label := "姓名"
+	//
+	//template_age_id := "age"
+	//template_age_label := "年龄(以字符串形式返回阿拉伯数字)"
+	//
+	//template_area_id := "area"
+	//template_area_label := "地区"
+	//
+	//template_birthday_id := "birthday"
+	//template_birthday_label := "出生日期"
+	//
+	//template_birtharea_id := "birtharea"
+	//template_birtharea_label := "出生地"
+	//
+	//template_idcard_no_id := "idcard_no"
+	//template_idcard_no_label := "身份证号"
+	//
+	//template_title_id := "title"
+	//template_title_label := "称呼"
+	contactBasicFieldTemplates := []custom_types.ContactFieldTemplate{
+		{
+			Label: &template_sex_label,
+			Id:    &template_sex_id,
+			Type:  &template_type_radio,
+			Options: []custom_types.ContactFieldTemplateOptions{
+				{
+					Label: &template_sex_options_man_label,
+					Value: &template_sex_options_man_value,
+				}, {
+					Label: &template_sex_options_woman_label,
+					Value: &template_sex_options_woman_value,
+				},
+			},
+		},
+		{
+			Label: &template_phone_label,
+			Id:    &template_phone_id,
+			Type:  &template_type_text,
+		},
+		//{
+		//	Label: &template_name_label,
+		//	Id:    &template_name_id,
+		//	Type:  &template_type_text,
+		//}, {
+		//	Label: &template_age_label,
+		//	Id:    &template_age_id,
+		//	Type:  &template_type_text,
+		//}, {
+		//	Label: &template_area_label,
+		//	Id:    &template_area_id,
+		//	Type:  &template_type_text,
+		//}, {
+		//	Label: &template_birthday_label,
+		//	Id:    &template_birthday_id,
+		//	Type:  &template_type_date,
+		//}, {
+		//	Label: &template_birtharea_label,
+		//	Id:    &template_birtharea_id,
+		//	Type:  &template_type_text,
+		//}, {
+		//	Label: &template_idcard_no_label,
+		//	Id:    &template_idcard_no_id,
+		//	Type:  &template_type_text,
+		//}, {
+		//	Label: &template_title_label,
+		//	Id:    &template_title_id,
+		//	Type:  &template_type_text,
+		//},
+	}
+
+	var predicates []predicate.UsageDetail
+	predicates = append(predicates, usagedetail.TypeIn(1, 3, 4, 6))
+	predicates = append(predicates, usagedetail.AppIn(1, 3, 4, 5))
+	//yesterdayStart := time.Now().AddDate(0, 0, -1).Truncate(24 * time.Hour)
+	//yesterdayEnd := yesterdayStart.Add(24 * time.Hour)
+	loc, _ := time.LoadLocation("Asia/Shanghai")
+	now := time.Now().In(loc)
+	yesterdayEnd := now
+	yesterdayStart := now.Add(-24 * time.Hour)
+	//yesterdayEnd := now.Truncate(24 * time.Hour).Add(-16 * time.Hour)
+	//yesterdayStart := yesterdayEnd.AddDate(0, 0, -1)
+	predicates = append(predicates, usagedetail.CreatedAtGTE(yesterdayStart))
+	predicates = append(predicates, usagedetail.CreatedAtLT(yesterdayEnd))
+	if bot_wxid != nil && *bot_wxid != "" {
+		predicates = append(predicates, usagedetail.BotIDEQ(*bot_wxid))
+	}
+	if bot_wxid != nil && *bot_wxid != "" && contact_wxid != nil && *contact_wxid != "" {
+		predicates = append(predicates, usagedetail.BotIDEQ(*bot_wxid))
+		predicates = append(predicates, usagedetail.ReceiverIDEQ(*contact_wxid))
+	}
+
+	//todayStart := time.Now().AddDate(0, 0, 0).Truncate(24 * time.Hour)
+	//todayEnd := todayStart.Add(24 * time.Hour)
+	//predicates = append(predicates, usagedetail.CreatedAtGTE(todayStart))
+	//predicates = append(predicates, usagedetail.CreatedAtLT(todayEnd))
+
+	data, err := l.svcCtx.DB.UsageDetail.Query().Where(predicates...).All(l.ctx)
+	if err != nil {
+		return
+	}
+
+	var wxPredicates []predicate.Wx
+	if bot_wxid != nil && *bot_wxid != "" {
+		wxPredicates = append(wxPredicates, wx.WxidEQ(*bot_wxid))
+	}
+	wxs, err := l.svcCtx.DB.Wx.Query().Where(wxPredicates...).All(l.ctx)
+	if err != nil {
+		return
+	}
+	logx.Info("data: ", data)
+	logx.Info("wxs: ", wxs)
+	for _, wx := range wxs {
+		c, _ := l.svcCtx.DB.ContactFieldTemplate.Query().Where(contactfieldtemplate.OrganizationID(wx.OrganizationID)).First(l.ctx)
+		if c != nil {
+			contactFieldTemplates[wx.Wxid] = c.Template
+		} else {
+			contactFieldTemplates[wx.Wxid] = nil
+		}
+	}
+
+	for _, u := range data {
+		if contactFieldTemplates[u.BotID] == nil {
+			continue
+		}
+		if _, ok := usageDetails[u.BotID]; !ok {
+			usageDetails[u.BotID] = make(map[string]string)
+		}
+		usageDetails[u.BotID][u.ReceiverID] += fmt.Sprintf("用户:%s\n机器人:%s\n", u.Request, u.Response)
+	}
+	logx.Info("usageDetails: ", usageDetails)
+	for botID, template := range contactFieldTemplates {
+		if template == nil {
+			template = contactBasicFieldTemplates
+		} else {
+			template = append(template, contactBasicFieldTemplates...)
+		}
+		for receiverID, messages := range usageDetails[botID] {
+			logx.Info("messages: ", messages)
+			logx.Info("template: ", template)
+			result, _ := l.openaiRequest(messages, template)
+			logx.Info("result: ", result)
+			if result == nil {
+				continue
+			}
+			_ = l.UpdateContactFields(botID, receiverID, result)
+		}
+	}
+}
+
+func (l *AnalyzeContactField) openaiRequest(messages string, template []custom_types.ContactFieldTemplate) ([]ResponseItem, error) {
+	formData := ConvertFormData(template)
+	jsonBytes, err := json.Marshal(formData)
+	if err != nil {
+		return nil, err
+	}
+	jsonStr := string(jsonBytes)
+	logx.Info("contactFieldTemplates: ", jsonStr)
+	req := &types.CompApiReq{
+		types.CompCtlReq{
+			"form",
+			"",
+			false,
+			"",
+		},
+		types.StdCompApiReq{
+			"gpt-4o",
+			[]types.StdCompMessage{},
+			false,
+			nil,
+		},
+		types.FastGptSpecReq{
+			"",
+			"",
+			"",
+			false,
+			map[string]string{
+				"form_data":    jsonStr,
+				"chat_history": messages,
+				"external_id":  uuid.New().String(),
+			},
+		},
+	}
+	resp, err := compapi.NewClient(l.ctx, compapi.WithApiBase("http://new-api.gkscrm.com/v1/"),
+		compapi.WithApiKey("sk-wwttAtdLcTfeF7F2Eb9d3592Bd4c487f8e8fA544D6C4BbA9")).
+		Chat(req)
+	logx.Info("resp: ", resp)
+	if err == nil && resp != nil && len(resp.Choices) > 0 {
+		logx.Info("resp.Choices: ", resp.Choices[0].Message.Content)
+		// 尝试第一层解析成 string
+
+		items, err := parseContent(resp.Choices[0].Message.Content)
+		if err != nil {
+			return nil, err
+		}
+		return items, nil
+	} else if resp != nil && len(resp.Choices) == 0 {
+		return nil, err
+	}
+	//url := "https://toolsapi-debug.gkscrm.com/call_center/form/extract"
+	//bodyData := map[string]interface{}{
+	//	"form_data":    ConvertFormData(template),
+	//	"chat_history": messages,
+	//	"external_id":  uuid.New().String(),
+	//}
+	//logx.Info("bodyData: %+v", bodyData)
+	//bodyBytes, err := json.Marshal(bodyData)
+	//if err != nil {
+	//	return nil, err
+	//}
+	//
+	//req, err := http.NewRequest("POST", url, bytes.NewBuffer(bodyBytes))
+	//if err != nil {
+	//	return nil, err
+	//}
+	//req.Header.Set("Content-Type", "application/json")
+	//req.Header.Set("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIn0.ZS9jnsLPCnmc8L_lu4yaQFp34vwWF1mHlHSBYrY5JVs")
+	//
+	//client := &http.Client{}
+	//resp, err := client.Do(req)
+	//if err != nil || resp == nil || resp.Body == nil {
+	//	logx.Error("read body error: ", err)
+	//	return nil, err
+	//}
+	//
+	//logx.Info("err: ", err)
+	//if err != nil {
+	//	return nil, err
+	//}
+	//defer resp.Body.Close()
+	//
+	//if resp.StatusCode != http.StatusOK {
+	//	return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
+	//}
+	//
+	////var result []ResponseItem
+	//var fullResp struct {
+	//	Data []ResponseItem `json:"data"`
+	//}
+	//err = json.NewDecoder(resp.Body).Decode(&fullResp)
+	//if err != nil {
+	//	return nil, err
+	//}
+	//
+	//return fullResp.Data, nil
+	return nil, err
+}
+
+func (l *AnalyzeContactField) UpdateContactFields(botID string, receiverID string, fields []ResponseItem) error {
+	basic_ids := []string{"sex", "phone", "name", "age", "area", "birthday", "birtharea", "idcard_no", "title"}
+	c, _ := l.svcCtx.DB.Contact.Query().Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).First(l.ctx)
+	if c == nil {
+		return fmt.Errorf("Contact not find")
+	}
+	for _, field := range fields {
+		if contains(basic_ids, field.DataIndex) {
+			if len(field.Value) == 0 {
+				continue
+			}
+			value := 0
+			if field.DataIndex == "sex" && c.Sex == 0 {
+				if field.Value[0] == "男" {
+					value = 1
+				} else if field.Value[0] == "女" {
+					value = 2
+				}
+				_, err := l.svcCtx.DB.Contact.Update().
+					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
+					SetSex(value).
+					Save(l.ctx)
+				if err != nil {
+					continue
+				}
+			} else if field.DataIndex == "phone" && c.Phone == "" {
+				_, err := l.svcCtx.DB.Contact.Update().
+					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
+					SetPhone(field.Value[0]).
+					Save(l.ctx)
+				if err != nil {
+					continue
+				}
+			} else if field.DataIndex == "name" && c.Cname == "" {
+				_, err := l.svcCtx.DB.Contact.Update().
+					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
+					SetCname(field.Value[0]).
+					Save(l.ctx)
+				if err != nil {
+					continue
+				}
+			} else if field.DataIndex == "age" && c.Cage == 0 {
+				num, err := strconv.Atoi(field.Value[0])
+				if err != nil {
+					continue
+				}
+				_, err = l.svcCtx.DB.Contact.Update().
+					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
+					SetCage(num).
+					Save(l.ctx)
+				if err != nil {
+					continue
+				}
+			} else if field.DataIndex == "area" && c.Carea == "" {
+				_, err := l.svcCtx.DB.Contact.Update().
+					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
+					SetCarea(field.Value[0]).
+					Save(l.ctx)
+				if err != nil {
+					continue
+				}
+			} else if field.DataIndex == "birthday" && c.Cbirthday == "" {
+				_, err := l.svcCtx.DB.Contact.Update().
+					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
+					SetCbirthday(field.Value[0]).
+					Save(l.ctx)
+				if err != nil {
+					continue
+				}
+			} else if field.DataIndex == "birtharea" && c.Cbirtharea == "" {
+				_, err := l.svcCtx.DB.Contact.Update().
+					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
+					SetCbirtharea(field.Value[0]).
+					Save(l.ctx)
+				if err != nil {
+					continue
+				}
+			} else if field.DataIndex == "idcard_no" && c.CidcardNo == "" {
+				_, err := l.svcCtx.DB.Contact.Update().
+					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
+					SetCidcardNo(field.Value[0]).
+					Save(l.ctx)
+				if err != nil {
+					continue
+				}
+			} else if field.DataIndex == "title" && c.Ctitle == "" {
+				_, err := l.svcCtx.DB.Contact.Update().
+					Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
+					SetCtitle(field.Value[0]).
+					Save(l.ctx)
+				if err != nil {
+					continue
+				}
+			}
+		} else {
+			f, _ := l.svcCtx.DB.ContactField.Query().Where(contactfield.ContactID(c.ID), contactfield.FormID(field.DataIndex)).First(l.ctx)
+			if f == nil {
+				if field.Value != nil && len(field.Value) > 0 && field.Value[0] != "" {
+					_, err := l.svcCtx.DB.ContactField.Create().
+						SetContactID(c.ID).
+						SetFormID(field.DataIndex).
+						SetValue(field.Value).
+						Save(l.ctx)
+					if err != nil {
+						continue
+					}
+				}
+			} else if len(f.Value) == 0 || f.Value[0] == "" {
+				if field.Value != nil && len(field.Value) > 0 && field.Value[0] != "" {
+					_, err := l.svcCtx.DB.ContactField.UpdateOneID(f.ID).
+						SetValue(field.Value).
+						Save(l.ctx)
+					if err != nil {
+						continue
+					}
+				}
+			}
+		}
+	}
+	return nil
+}
+
+func ConvertFormData(input []custom_types.ContactFieldTemplate) []FormData {
+	result := make([]FormData, len(input))
+	for i, item := range input {
+		options := make([]FieldPropsOptions, len(item.Options))
+		for j, opt := range item.Options {
+			options[j] = FieldPropsOptions{
+				Label: *opt.Label,
+				Value: *opt.Value,
+			}
+		}
+		result[i] = FormData{
+			Title:     *item.Label,
+			DataIndex: *item.Id,
+			ValueType: *item.Type,
+			FieldProps: FieldProps{
+				Options: options,
+			},
+		}
+	}
+	return result
+}
+
+func contains(strs []string, target string) bool {
+	for _, s := range strs {
+		if s == target {
+			return true
+		}
+	}
+	return false
+}
+
+func parseContent(content string) ([]ResponseItem, error) {
+	content = strings.TrimSpace(content)
+
+	// ① 双引号包裹的再来一次:"{\"dataIndex\":...}"
+	if unq, err := strconv.Unquote(content); err == nil {
+		return parseContent(unq) // 尝试递归
+	}
+
+	// ② 直接数组形式: [...]
+	if strings.HasPrefix(content, "[") {
+		var list []ResponseItem
+		if err := json.Unmarshal([]byte(content), &list); err == nil {
+			return list, nil
+		}
+	}
+
+	// ③ 对象形式
+	if strings.HasPrefix(content, "{") {
+		// 3‑1 尝试对象包裹数组: {"values":[...]}
+		var wrapper struct {
+			Values []ResponseItem `json:"values"`
+		}
+		if err := json.Unmarshal([]byte(content), &wrapper); err == nil && len(wrapper.Values) > 0 {
+			return wrapper.Values, nil
+		}
+
+		// 3‑2 尝试单对象形式: {"dataIndex": ...}
+		var item ResponseItem
+		if err := json.Unmarshal([]byte(content), &item); err == nil {
+			return []ResponseItem{item}, nil
+		}
+	}
+
+	return nil, fmt.Errorf("unsupported content format: %q", content)
+}

+ 11 - 0
internal/types/types.go

@@ -1156,6 +1156,17 @@ type CreateWhatsappContactReq struct {
 	Ctitle     *string `json:"ctitle,optional"`
 }
 
+// swagger:model AnalyzeContactFieldReq
+type AnalyzeContactFieldReq struct {
+	WxWxid *string `json:"wxWxid,optional"`
+	Wxid   *string `json:"wxid,optional"`
+}
+
+// swagger:model AnalyzeContactFieldResp
+type AnalyzeContactFieldResp struct {
+	CustomFields []ContactFieldTemplate `json:"customFields,optional"`
+}
+
 // The response data of message information | Message信息
 // swagger:model MessageInfo
 type MessageInfo struct {