Bladeren bron

Merge branch 'debug' into yhg_20250327

jimmyyem 1 week geleden
bovenliggende
commit
450570e231
57 gewijzigde bestanden met toevoegingen van 5428 en 180 verwijderingen
  1. 3 1
      .gitignore
  2. 715 0
      cli/asynctask/asynctask.go
  3. 21 0
      cli/asynctask/etc/asynctask.yaml
  4. 1 1
      crontask/compapi_callback.go
  5. 99 58
      crontask/contact_form.go
  6. 8 7
      crontask/init.go
  7. 4 1
      crontask/send_msg.go
  8. 4 1
      crontask/send_wx.go
  9. 11 0
      desc/openapi/chat.api
  10. 3 2
      desc/wechat/contact.api
  11. 146 4
      ent/client.go
  12. 2 0
      ent/ent.go
  13. 12 0
      ent/hook/hook.go
  14. 30 0
      ent/intercept/intercept.go
  15. 161 0
      ent/labellog.go
  16. 106 0
      ent/labellog/labellog.go
  17. 400 0
      ent/labellog/where.go
  18. 851 0
      ent/labellog_create.go
  19. 88 0
      ent/labellog_delete.go
  20. 526 0
      ent/labellog_query.go
  21. 406 0
      ent/labellog_update.go
  22. 32 0
      ent/migrate/schema.go
  23. 696 0
      ent/mutation.go
  24. 82 0
      ent/pagination.go
  25. 3 0
      ent/predicate/predicate.go
  26. 32 0
      ent/runtime/runtime.go
  27. 47 0
      ent/schema/label_log.go
  28. 120 0
      ent/set_not_nil.go
  29. 3 0
      ent/tx.go
  30. 0 6
      go.mod
  31. 4 0
      go.sum
  32. 3 1
      hook/message.go
  33. 44 0
      internal/handler/chat/send_text_msg_handler.go
  34. 13 0
      internal/handler/routes.go
  35. 5 3
      internal/logic/Wxhook/send_text_msg_logic.go
  36. 3 2
      internal/logic/batch_msg/get_batch_msg_by_id_logic.go
  37. 4 2
      internal/logic/batch_msg/get_batch_msg_list_logic.go
  38. 6 0
      internal/logic/chat/chat_completions_logic.go
  39. 111 0
      internal/logic/chat/send_text_msg_logic.go
  40. 6 6
      internal/logic/contact/get_contact_list_logic.go
  41. 1 1
      internal/logic/contact/update_contact_logic.go
  42. 16 0
      internal/logic/fastgpt/set_token_logic.go
  43. 2 0
      internal/logic/message_records/get_message_records_list_logic.go
  44. 48 0
      internal/service/MessageHandlers/chatroom_push_notice.go
  45. 120 0
      internal/service/MessageHandlers/contact_Label_info_notice.go
  46. 136 0
      internal/service/MessageHandlers/friend_push_notice.go
  47. 86 0
      internal/service/MessageHandlers/register_strategy.go
  48. 50 0
      internal/service/MessageHandlers/talk_to_friend_task_result_notice.go
  49. 2 2
      internal/types/types.go
  50. 3 0
      internal/utils/compapi/base.go
  51. 3 0
      internal/utils/compapi/config.go
  52. 89 0
      internal/utils/compapi/form.go
  53. 1 1
      proto/ContactLabelInfoNotice.proto
  54. 1 1
      proto/FriendPushNotice.proto
  55. 4 1
      wechat.go
  56. 29 41
      workphone/ContactLabelInfoNotice.pb.go
  57. 26 38
      workphone/FriendPushNotice.pb.go

+ 3 - 1
.gitignore

@@ -32,4 +32,6 @@ vendor/
 *_rpc
 wechat-api
 etc/wechat.yaml
-/etc/wechat.yaml
+/etc/wechat.yaml
+cli/asynctask/etc/asynctask-prod.yaml
+cli/asynctask/wechat_api_open

+ 715 - 0
cli/asynctask/asynctask.go

@@ -0,0 +1,715 @@
+package main
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+	"flag"
+	"fmt"
+	"hash/fnv"
+	"os"
+	"os/signal"
+	"reflect"
+	"runtime"
+	"strconv"
+	"strings"
+	"sync"
+	"sync/atomic"
+	"syscall"
+	"time"
+
+	"wechat-api/ent"
+	"wechat-api/ent/compapiasynctask"
+	"wechat-api/ent/predicate"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+	"wechat-api/internal/utils/compapi"
+
+	"github.com/suyuan32/simple-admin-common/config"
+	"github.com/zeromicro/go-zero/core/conf"
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+const (
+	Task_Ready    = 10            //任务就绪
+	ReqApi_Done   = 20            //请求API完成
+	Callback_Done = 30            //请求回调完成
+	All_Done      = Callback_Done //全部完成 (暂时将成功状态的终点标注于此)
+	Task_Suspend  = 60            //任务暂停
+	Task_Fail     = 70            //任务失败
+
+	LoopTryCount    = 3 //循环体内重试次数
+	LoopDelayFactor = 3
+	ErrTaskTryCount = 3 //最大允许错误任务重试次数
+
+	DefaultDisId = "DIS0001"
+)
+
+type Config struct {
+	BatchLoadTask uint `json:",default=100"`
+	MaxWorker     uint `json:",default=2"`
+	MaxChannel    uint `json:",default=1"`
+	Debug         bool `json:",default=false"`
+	DatabaseConf  config.DatabaseConf
+	RedisConf     config.RedisConf
+}
+
+type TaskStat struct {
+}
+
+type AsyncTask struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+	Conf   Config
+	Stats  TaskStat
+}
+
+type Task struct {
+	Data *ent.CompapiAsynctask
+	Idx  int
+	Code int
+}
+
+// 带会话管理的任务通道组
+type TaskDispatcher struct {
+	mu        sync.Mutex
+	workerChs []chan Task // 每个worker独立通道
+	Debug     bool
+}
+
+var configFile = flag.String("f", "./etc/asynctask.yaml", "the config file")
+
+func getGoroutineId() (int64, error) {
+	// 堆栈结果中需要消除的前缀符
+	var goroutineSpace = []byte("goroutine ")
+
+	bs := make([]byte, 128)
+	bs = bs[:runtime.Stack(bs, false)]
+	bs = bytes.TrimPrefix(bs, goroutineSpace)
+	i := bytes.IndexByte(bs, ' ')
+	if i < 0 {
+		return -1, errors.New("get current goroutine id failed")
+	}
+	return strconv.ParseInt(string(bs[:i]), 10, 64)
+}
+
+func NewTaskDispatcher(channelCount uint, chanSize uint, debug bool) *TaskDispatcher {
+	td := &TaskDispatcher{
+		workerChs: make([]chan Task, channelCount),
+		Debug:     debug,
+	}
+	// 初始化worker通道
+	for i := range td.workerChs {
+		td.workerChs[i] = make(chan Task, chanSize+1) // 每个worker带缓冲
+	}
+	return td
+}
+
+// 按woker下标索引选择workerChs
+func (td *TaskDispatcher) getWorkerChanByIdx(idx uint) (uint, chan Task) {
+	nidx := idx % uint(len(td.workerChs))
+	return nidx, td.workerChs[nidx]
+}
+
+// 按哈希分片选择workerChs
+func (td *TaskDispatcher) getWorkerChanByHash(disID string) (int, chan Task) {
+	if len(disID) == 0 {
+		disID = DefaultDisId
+	}
+	idx := 0
+	if len(td.workerChs) > 1 {
+		h := fnv.New32a()
+		h.Write([]byte(disID))
+		idx = int(h.Sum32()) % len(td.workerChs)
+	}
+	if td.Debug {
+		outStr := fmt.Sprintf("getWorkerChannel by disId Hash:'%s',from workerChs{", disID)
+		for i := range len(td.workerChs) {
+			outStr += fmt.Sprintf("#%d", i+1)
+			if i < len(td.workerChs)-1 {
+				outStr += ","
+			}
+		}
+		outStr += fmt.Sprintf("} choice chs:'#%d' by '%s'", idx+1, disID)
+		logx.Debug(outStr)
+	}
+
+	return idx, td.workerChs[idx]
+}
+
+// 分配任务到对应的消费channel
+func (td *TaskDispatcher) Dispatch(task Task) {
+	td.mu.Lock()
+	defer td.mu.Unlock()
+
+	// 根据chatId哈希获得其对应的workerChan
+	workerChIdx, workerCh := td.getWorkerChanByHash(task.Data.EventType)
+	// 将任务送入该chatid的workerChan
+	workerCh <- task
+	if td.Debug {
+		logx.Debugf("Producer:EventType:[%s] Task Push to WorkerChan:#%d", task.Data.EventType, workerChIdx+1)
+	}
+}
+
+// 更新任务状态
+func (me *AsyncTask) updateTaskStatus(taskId uint64, status int) error {
+	_, err := me.svcCtx.DB.CompapiAsynctask.UpdateOneID(taskId).
+		SetTaskStatus(int8(status)).
+		SetUpdatedAt(time.Now()).
+		Save(me.ctx)
+	return err
+}
+
+// 更新请求大模型后结果
+func (me *AsyncTask) updateApiResponse(taskId uint64, apiResponse string) error {
+	_, err := me.svcCtx.DB.CompapiAsynctask.UpdateOneID(taskId).
+		SetUpdatedAt(time.Now()).
+		SetResponseRaw(apiResponse).
+		SetLastError("").
+		SetRetryCount(0).
+		Save(me.ctx)
+	return err
+}
+
+func (me *AsyncTask) updateCallbackResponse(taskId uint64, callRes any) error {
+	callResStr := ""
+	switch v := callRes.(type) {
+	case []byte:
+		callResStr = string(v)
+	default:
+		if bs, err := json.Marshal(v); err == nil {
+			callResStr = string(bs)
+		} else {
+			return err
+		}
+	}
+	_, err := me.svcCtx.DB.CompapiAsynctask.UpdateOneID(taskId).
+		SetUpdatedAt(time.Now()).
+		SetCallbackResponseRaw(callResStr).
+		SetLastError("").
+		SetRetryCount(0).
+		Save(me.ctx)
+	return err
+}
+
+func (me *AsyncTask) checkErrRetry(taskData *ent.CompapiAsynctask) (bool, error) {
+	var err error
+	var needStop = false
+	if taskData.RetryCount >= ErrTaskTryCount { //错误任务尝试次数超过约定则将任务状态永久设为失败
+		_, err = me.svcCtx.DB.CompapiAsynctask.UpdateOneID(taskData.ID).
+			SetUpdatedAt(time.Now()).
+			SetTaskStatus(int8(Task_Fail)).
+			Save(me.ctx)
+		if err == nil {
+			needStop = true
+			taskData.TaskStatus = Task_Fail
+		}
+	}
+	return needStop, err
+}
+
+// 错误任务处理
+func (me *AsyncTask) dealErrorTask(taskData *ent.CompapiAsynctask, lasterr error) error {
+	logx.Debug("多次循环之后依然失败,进入错误任务处理环节")
+	cauo := me.svcCtx.DB.CompapiAsynctask.UpdateOneID(taskData.ID).
+		SetUpdatedAt(time.Now())
+	if taskData.RetryCount >= ErrTaskTryCount { //错误任务尝试次数超过约定则将任务状态永久设为失败
+		taskData.TaskStatus = Task_Fail
+		cauo = cauo.SetTaskStatus(int8(Task_Fail))
+	} else {
+		cauo = cauo.SetRetryCount(taskData.RetryCount + 1).
+			SetLastError(lasterr.Error())
+	}
+	_, err := cauo.Save(me.ctx)
+	return err
+}
+
+func (me *AsyncTask) requestCallback(taskData *ent.CompapiAsynctask) (int, error) {
+	var workerId int64
+	if me.Conf.Debug {
+		workerId, _ = getGoroutineId()
+		logx.Debugf("Worker:%d INTO requestCallback for task status:%d", workerId, taskData.TaskStatus)
+	}
+
+	if needStop, _ := me.checkErrRetry(taskData); needStop { //重试次数检测,如果超过直接标为永久失败而不再处理
+		return 1, errors.New("too many err retry")
+	}
+	if taskData.TaskStatus != ReqApi_Done {
+		return 0, fmt.Errorf("invalid task run order for status:%d", taskData.TaskStatus)
+	}
+
+	if len(taskData.CallbackURL) == 0 {
+		return 0, errors.New("callback url empty")
+	}
+	if len(taskData.ResponseRaw) == 0 {
+		return 0, errors.New("call api response empty")
+	}
+
+	/*
+		fstr := "mytest-svc:"
+		if taskData.RetryCount >= 0 && strings.Contains(taskData.CallbackURL, fstr) {
+			taskData.CallbackURL = strings.Replace(taskData.CallbackURL, fstr, "0.0.0.0:", 1)
+		}
+	*/
+
+	//请求预先给定的callback_url
+	res := map[string]any{}
+	var err error
+	//初始化client
+	client := compapi.NewClient(me.ctx)
+	for i := range LoopTryCount { //重试机制
+		select {
+		case <-me.ctx.Done(): //接到信号退出
+			goto endloopTry
+		default:
+			res, err = client.Callback(taskData.EventType, taskData.CallbackURL, taskData)
+			if err == nil {
+				//call succ
+				//fmt.Println("callback succ..........")
+				//fmt.Println(typekit.PrettyPrint(res))
+				if me.Conf.Debug {
+					logx.Infof("callback:'%s' succ", taskData.CallbackURL)
+				}
+				goto endloopTry
+			}
+			logx.Infof("Worker:%d call '%s' fail: '%s',sleep %d Second for next(%d/%d/%d)", workerId,
+				taskData.CallbackURL, err, 1+i*LoopDelayFactor, i+1, LoopTryCount, taskData.RetryCount)
+			time.Sleep(time.Duration(1+i*LoopDelayFactor) * time.Second)
+		}
+	}
+
+	//多次循环之后依然失败,进入错误任务处理环节
+endloopTry:
+	if err != nil {
+		err1 := me.dealErrorTask(taskData, err) //错误任务处理
+		et := 1
+		if err1 != nil {
+			et = 0
+		}
+		return et, err
+	}
+	//成功后处理环节
+
+	//更新任务状态 => Callback_Done(回调完成)
+	err = me.updateTaskStatus(taskData.ID, Callback_Done)
+	if err != nil {
+		return 0, err
+	}
+	//更新taskData.CallbackResponseRaw
+	if len(res) > 0 {
+		me.updateCallbackResponse(taskData.ID, res)
+	}
+
+	taskData.TaskStatus = Callback_Done //状态迁移
+	return 1, nil
+}
+
+func (me *AsyncTask) requestAPI(taskData *ent.CompapiAsynctask) (int, error) {
+	var workerId int64
+	if me.Conf.Debug {
+		workerId, _ = getGoroutineId()
+		logx.Debugf("Worker:%d INTO requestAPI for task status:%d", workerId, taskData.TaskStatus)
+	}
+
+	if needStop, _ := me.checkErrRetry(taskData); needStop { //重试次数检测,如果超过直接标为永久失败而不再处理
+		return 1, errors.New("too many err retry")
+	}
+
+	if taskData.TaskStatus != Task_Ready {
+		return 0, fmt.Errorf("invalid task run order for status:%d", taskData.TaskStatus)
+	}
+	var (
+		err     error
+		apiResp *types.CompOpenApiResp
+	)
+	req := types.CompApiReq{}
+	if err = json.Unmarshal([]byte(taskData.RequestRaw), &req); err != nil {
+		return 0, err
+	}
+	//初始化client
+	if !strings.HasSuffix(taskData.OpenaiBase, "/") {
+		taskData.OpenaiBase = taskData.OpenaiBase + "/"
+	}
+	client := compapi.NewClient(me.ctx, compapi.WithApiBase(taskData.OpenaiBase),
+		compapi.WithApiKey(taskData.OpenaiKey))
+
+	for i := range LoopTryCount { //重试机制
+
+		select {
+		case <-me.ctx.Done(): //接到信号退出
+			goto endloopTry
+		default:
+			apiResp, err = client.Chat(&req)
+			if err == nil && apiResp != nil && len(apiResp.Choices) > 0 {
+				//call succ
+				goto endloopTry
+			} else if apiResp != nil && len(apiResp.Choices) == 0 {
+				err = errors.New("返回结果缺失,请检查访问权限")
+			}
+			if me.Conf.Debug {
+				logx.Infof("Worker:%d call '%s' fail: '%s',sleep %d Second for next(%d/%d/%d)", workerId,
+					taskData.CallbackURL, err, 1+i*LoopDelayFactor, i+1, LoopTryCount, taskData.RetryCount)
+			}
+			time.Sleep(time.Duration(1+i*LoopDelayFactor) * time.Second)
+		}
+	}
+
+endloopTry:
+	//多次循环之后依然失败,进入错误任务处理环节
+	if err != nil || apiResp == nil {
+		if apiResp == nil && err == nil {
+			err = errors.New("resp is null")
+		}
+		err1 := me.dealErrorTask(taskData, err) //错误任务处理
+		et := 1
+		if err1 != nil {
+			et = 0
+		}
+		return et, err
+	}
+	//成功后处理环节
+
+	//更新任务状态 => ReqApi_Done(请求API完成)
+	err = me.updateTaskStatus(taskData.ID, ReqApi_Done)
+	if err != nil {
+		return 0, err
+	}
+	//更新taskData.ResponseRaw
+	taskData.ResponseRaw, err = (*apiResp).ToString()
+	if err != nil {
+		return 0, err
+	}
+	err = me.updateApiResponse(taskData.ID, taskData.ResponseRaw)
+	if err != nil {
+		return 0, err
+	}
+
+	taskData.TaskStatus = ReqApi_Done //状态迁移
+	return 1, nil
+}
+
+func EntStructGenScanField(structPtr any) (string, []any, error) {
+	t := reflect.TypeOf(structPtr)
+	v := reflect.ValueOf(structPtr)
+
+	if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
+		return "", nil, errors.New("input must be a pointer to a struct")
+	}
+	t = t.Elem()
+	v = v.Elem()
+
+	var fields []string
+	var scanArgs []any
+	for i := 0; i < t.NumField(); i++ {
+		field := t.Field(i)
+		value := v.Field(i)
+
+		// Skip unexported fields
+		if !field.IsExported() {
+			continue
+		}
+
+		// Get json tag
+		jsonTag := field.Tag.Get("json")
+		if jsonTag == "-" || jsonTag == "" {
+			continue
+		}
+
+		jsonParts := strings.Split(jsonTag, ",")
+		jsonName := jsonParts[0]
+		if jsonName == "" {
+			continue
+		}
+
+		fields = append(fields, jsonName)
+		scanArgs = append(scanArgs, value.Addr().Interface())
+	}
+	return strings.Join(fields, ", "), scanArgs, nil
+}
+
+/*
+CREATE INDEX idx_compapi_task_status_chat_id_id_desc
+ON compapi_asynctask (task_status, chat_id, id DESC);
+*/
+func (me *AsyncTask) getAsyncReqTaskFairList() ([]Task, error) {
+	fieldListStr, _, err := EntStructGenScanField(&ent.CompapiAsynctask{})
+	if err != nil {
+		return nil, err
+	}
+	rawQuery := fmt.Sprintf(`
+		    WITH ranked AS (
+		        SELECT %s,
+		               ROW_NUMBER() OVER (PARTITION BY chat_id ORDER BY id DESC) AS rn
+		        FROM compapi_asynctask
+		        WHERE task_status < ?
+		    )
+		    SELECT %s
+		    FROM ranked
+		    WHERE rn <= ?
+			ORDER BY rn,id DESC
+		    LIMIT ?;
+		    `, fieldListStr, fieldListStr)
+
+	// 执行原始查询
+	rows, err := me.svcCtx.DB.QueryContext(me.ctx, rawQuery, All_Done,
+		me.Conf.BatchLoadTask, me.Conf.BatchLoadTask)
+	if err != nil {
+		return nil, fmt.Errorf("fetch fair tasks query error: %w", err)
+	}
+	defer rows.Close()
+	Idx := 0
+	tasks := []Task{}
+	for rows.Next() {
+		taskrow := ent.CompapiAsynctask{}
+		var scanParams []any
+		_, scanParams, err = EntStructGenScanField(&taskrow)
+		if err != nil {
+			break
+		}
+		err = rows.Scan(scanParams...)
+		if err != nil {
+			break
+		}
+		task := Task{Data: &taskrow, Idx: Idx}
+		tasks = append(tasks, task)
+		Idx++
+	}
+	fmt.Printf("getAsyncReqTaskFairList get task:%d\n", len(tasks))
+	return tasks, nil
+}
+
+func (me *AsyncTask) getAsyncReqTaskList() ([]Task, error) {
+	var predicates []predicate.CompapiAsynctask
+	predicates = append(predicates, compapiasynctask.TaskStatusLT(All_Done))
+
+	var tasks []Task
+	res, err := me.svcCtx.DB.CompapiAsynctask.Query().Where(predicates...).
+		Order(ent.Desc(compapiasynctask.FieldID)).
+		Limit(int(me.Conf.BatchLoadTask)).
+		All(me.ctx)
+	if err == nil {
+		for idx, val := range res {
+			tasks = append(tasks, Task{Data: val, Idx: idx})
+		}
+	}
+	return tasks, err
+}
+
+func (me *AsyncTask) processTask(workerID uint, task Task) {
+	/*
+		fmt.Printf("In processTask,Consumer(%d) dealing Task Detail: User(%s/%s/%s) Async Call %s(%s) on Status:%d\n",
+			workerID, task.Data.EventType, task.Data.ChatID, task.Data.AuthToken,
+			task.Data.OpenaiBase, task.Data.OpenaiKey, task.Data.TaskStatus)
+	*/
+	_ = workerID
+	var err error
+	rt := 0
+	for {
+		select {
+		case <-me.ctx.Done(): //接到信号退出
+			return
+		default:
+			if task.Data.TaskStatus >= All_Done {
+				goto endfor //原来的break操作加了switch一层不好使了
+			}
+			switch task.Data.TaskStatus {
+			case Task_Ready:
+				//请求API平台
+				//	succ: taskStatus Task_Ready => ReqApi_Done
+				//  fail: taskStatus保持当前不变或Task_Fail
+				rt, err = me.requestAPI(task.Data)
+			case ReqApi_Done:
+				//结果回调
+				// succ: taskStatus ReqApi_Done => Callback_Done(All_Done)
+				// fail: taskStatus保持当前不变或Task_Fail
+				rt, err = me.requestCallback(task.Data)
+			}
+			if err != nil {
+				//收集错误
+				if rt == 0 {
+					//不可恢复错误处理....
+					logx.Errorf("Task error by '%s'", err)
+				} else {
+					logx.Debugf("Task ignore by '%s'", err)
+				}
+				return //先暂时忽略处理,也许应按错误类型分别对待
+			}
+		}
+	}
+endfor:
+}
+
+func (me *AsyncTask) batchWork() (int64, int64) {
+
+	var (
+		wg       sync.WaitGroup
+		produced int64 //生产数量(原子计数器)
+		consumed int64 //消费数量(原子计数器)
+	)
+	//创建任务分发器
+	dispatcher := NewTaskDispatcher(me.Conf.MaxChannel,
+		me.Conf.BatchLoadTask/me.Conf.MaxChannel, me.Conf.Debug)
+
+	//启动消费者
+	for widx := range me.Conf.MaxWorker {
+		cidx, ch := dispatcher.getWorkerChanByIdx(widx)
+		wg.Add(1)
+		go func(workerID uint, channelID uint, taskCh chan Task) {
+			defer wg.Done()
+			gidStr := ""
+			if me.Conf.Debug {
+				gid, _ := getGoroutineId()
+				gidStr = fmt.Sprintf("(Goroutine:%d)", gid)
+
+				logx.Infof("Consumer @%d%s bind WorkerChan:#%d start......",
+					workerID, gidStr, channelID)
+			}
+			for task := range taskCh {
+				me.processTask(widx, task)
+				atomic.AddInt64(&consumed, 1)
+			}
+		}(widx+1, cidx+1, ch)
+	}
+
+	// 生产者
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		gidStr := ""
+		if me.Conf.Debug {
+			gid, _ := getGoroutineId()
+			gidStr = fmt.Sprintf("(Goroutine:%d)", gid)
+			logx.Infof("Producer @1%s start......", gidStr)
+		}
+
+		//获得待处理异步任务列表
+		//tasks, err := me.getAsyncReqTaskList()
+		tasks, err := me.getAsyncReqTaskFairList()
+		if err != nil {
+			logx.Errorf("getAsyncReqTaskList err:%s", err)
+			return
+		}
+
+		// 分发任务
+		for _, task := range tasks {
+			dispatcher.Dispatch(task)
+			atomic.AddInt64(&produced, 1)
+		}
+
+		fmt.Printf("📦Producer @1 此批次共创建任务%d件\n", len(tasks))
+
+		// 关闭所有会话通道
+		dispatcher.mu.Lock()
+		for _, ch := range dispatcher.workerChs {
+			_ = ch
+			close(ch)
+		}
+		dispatcher.mu.Unlock()
+	}()
+
+	wg.Wait()
+	consumedRatStr := ""
+	if atomic.LoadInt64(&produced) > 0 {
+		consumedRatStr = fmt.Sprintf(" (%d/%d)*100=%d%%", atomic.LoadInt64(&produced), atomic.LoadInt64(&consumed),
+			(atomic.LoadInt64(&consumed)/atomic.LoadInt64(&produced))*100)
+	}
+	fmt.Printf("🏁本次任务完成度统计: Task dispatch: %d(%d)(Producer:1),Task deal: %d(Consumer:%d)%s\n", atomic.LoadInt64(&produced), me.Conf.BatchLoadTask, atomic.LoadInt64(&consumed), me.Conf.MaxWorker, consumedRatStr)
+
+	return produced, consumed
+}
+
+func (me *AsyncTask) InitServiceContext() *svc.ServiceContext {
+	rds := me.Conf.RedisConf.MustNewUniversalRedis()
+
+	dbOpts := []ent.Option{ent.Log(logx.Info),
+		ent.Driver(me.Conf.DatabaseConf.NewNoCacheDriver())}
+	if me.Conf.Debug {
+		dbOpts = append(dbOpts, ent.Debug())
+	}
+	db := ent.NewClient(dbOpts...)
+	svcCtx := svc.ServiceContext{DB: db, Rds: rds}
+	//svcCtx.Config
+	return &svcCtx
+}
+
+func (me *AsyncTask) adjustConf() {
+	if me.Conf.MaxWorker <= 0 {
+		me.Conf.MaxWorker = 2
+	}
+	if me.Conf.MaxChannel <= 0 || me.Conf.MaxChannel > me.Conf.MaxWorker {
+		me.Conf.MaxChannel = me.Conf.MaxWorker
+	}
+}
+
+func (me *AsyncTask) Run(ctx context.Context) {
+	me.ctx = ctx
+	me.Logger = logx.WithContext(ctx)
+	me.adjustConf()
+
+	/*
+		tasks, err := me.getAsyncReqTaskFairList()
+		if err != nil {
+			fmt.Println(err)
+			return
+		}
+		for idx, task := range tasks {
+			if idx > 20 {
+				break
+			}
+			fmt.Printf("#%d=>%d ||[%s]'%s' || '%s' || '%s'|| '%s'\n", idx, task.Data.ID, task.Data.CreatedAt, task.Data.EventType,
+				task.Data.Model, task.Data.OpenaiBase, task.Data.ChatID)
+		}
+	*/
+
+	batchId := 0
+	for {
+		batchId++
+		select {
+		case <-ctx.Done():
+			// 收到了取消信号
+			fmt.Printf("Main Will Shutting down gracefully... Reason: %v\n", ctx.Err())
+			return
+		default:
+			timeStart := time.Now()
+			secStart := timeStart.Unix()
+			fmt.Printf("[%s]batchWork#%d start......\n", timeStart.Format("2006-01-02 15:04:05"), batchId)
+			produced, _ := me.batchWork()
+			timeEnd := time.Now()
+			fmt.Printf("[%s]batchWork#%d end,spent %d sec\n", timeEnd.Format("2006-01-02 15:04:05"),
+				batchId, timeEnd.Unix()-secStart)
+			timeDurnum := 1
+
+			if produced == 0 {
+				timeDurnum = 5
+			}
+			fmt.Printf("batchWork will sleep %d sec\n", timeDurnum)
+			time.Sleep(time.Duration(timeDurnum) * time.Second)
+		}
+	}
+}
+
+func NewAsyncTask() *AsyncTask {
+
+	ataskObj := AsyncTask{}
+	flag.Parse() //将命令行参数也塞入flag.CommandLine结构
+	//fmt.Println(typekit.PrettyPrint(flag.CommandLine))
+	conf.MustLoad(*configFile, &ataskObj.Conf, conf.UseEnv())
+	//fmt.Println(typekit.PrettyPrint(ataskObj.Conf))
+	ataskObj.svcCtx = ataskObj.InitServiceContext()
+
+	return &ataskObj
+}
+
+func main() {
+
+	fmt.Println("Compapi Asynctask Start......")
+	//ctx, cancel := context.WithCancel(context.Background())
+	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
+	defer cancel()
+	NewAsyncTask().Run(ctx)
+
+	fmt.Println("Compapi Asynctask End......")
+
+}

+ 21 - 0
cli/asynctask/etc/asynctask.yaml

@@ -0,0 +1,21 @@
+BatchLoadTask: 200 #每批次取任务数
+MaxWorker: 10       #最大消费者数量
+MaxChannel: 2      #最大消费通道数量
+Debug: false
+
+DatabaseConf: #数据库配置
+  Type: mysql
+  Host: 127.0.0.1
+  Port: 3306
+  DBName: wechat
+  Username: root
+  Password: simple-admin.
+  MaxOpenConn: 100
+  SSLMode: disable
+  CacheTime: 5
+
+RedisConf: #redis配置
+  Host: 127.0.0.1:6379
+
+
+

+ 1 - 1
crontask/compapi_callback.go

@@ -33,7 +33,7 @@ const (
 	LoopTryCount    = 3 //循环体内重试次数
 	ErrTaskTryCount = 3 //最大允许错误任务重试次数
 
-	DefaultDisId = "DIS0001"
+	DefaultDisId = "DIS0002"
 )
 
 type Task struct {

+ 99 - 58
crontask/contact_form.go

@@ -1,12 +1,10 @@
 package crontask
 
 import (
-	"bytes"
 	"encoding/json"
 	"fmt"
 	"github.com/google/uuid"
 	"github.com/zeromicro/go-zero/core/logx"
-	"net/http"
 	"strconv"
 	"time"
 	"wechat-api/ent/contact"
@@ -15,6 +13,8 @@ import (
 	"wechat-api/ent/custom_types"
 	"wechat-api/ent/predicate"
 	"wechat-api/ent/usagedetail"
+	"wechat-api/internal/types"
+	"wechat-api/internal/utils/compapi"
 )
 
 type ResponseItem struct {
@@ -46,8 +46,6 @@ func (l *CronTask) analyze() {
 	template_type_date := "date"
 	template_sex_id := "sex"
 	template_sex_label := "性别"
-	template_sex_options_unknown_label := "未知"
-	template_sex_options_unknown_value := "未知"
 	template_sex_options_man_label := "男"
 	template_sex_options_man_value := "男"
 	template_sex_options_woman_label := "女"
@@ -83,9 +81,6 @@ func (l *CronTask) analyze() {
 			Type:  &template_type_radio,
 			Options: []custom_types.ContactFieldTemplateOptions{
 				{
-					Label: &template_sex_options_unknown_label,
-					Value: &template_sex_options_unknown_value,
-				}, {
 					Label: &template_sex_options_man_label,
 					Value: &template_sex_options_man_value,
 				}, {
@@ -173,7 +168,7 @@ func (l *CronTask) analyze() {
 			template = append(template, contactBasicFieldTemplates...)
 		}
 		for receiverID, messages := range usageDetails[botID] {
-			result, _ := openaiRequest(messages, template)
+			result, _ := l.openaiRequest(messages, template)
 			logx.Info("result: ", result)
 			if result == nil {
 				continue
@@ -184,53 +179,99 @@ func (l *CronTask) analyze() {
 
 }
 
-func openaiRequest(messages string, template []custom_types.ContactFieldTemplate) ([]ResponseItem, error) {
-	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))
+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
 	}
-	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"`
+	jsonStr := string(jsonBytes)
+	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(),
+			},
+		},
 	}
-	err = json.NewDecoder(resp.Body).Decode(&fullResp)
-	if err != nil {
+	resp, err := compapi.NewClient(l.ctx, compapi.WithApiBase("http://new-api.gkscrm.com/v1/"),
+		compapi.WithApiKey("sk-wwttAtdLcTfeF7F2Eb9d3592Bd4c487f8e8fA544D6C4BbA9")).
+		Chat(req)
+	if err == nil && resp != nil && len(resp.Choices) > 0 {
+		//logx.Info("resp.Choices: ", resp.Choices[0].Message.Content)
+		var items []ResponseItem
+		err = json.Unmarshal([]byte(resp.Choices[0].Message.Content), &items)
+		if err != nil {
+			return nil, err
+		}
+		return items, nil
+	} else if resp != nil && len(resp.Choices) == 0 {
 		return nil, err
 	}
-
-	return fullResp.Data, nil
+	//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 {
@@ -245,7 +286,7 @@ func (l *CronTask) UpdateContactFields(botID string, receiverID string, fields [
 				continue
 			}
 			value := 0
-			if field.DataIndex == "sex" {
+			if field.DataIndex == "sex" && c.Sex == 0 {
 				if field.Value[0] == "男" {
 					value = 1
 				} else if field.Value[0] == "女" {
@@ -258,7 +299,7 @@ func (l *CronTask) UpdateContactFields(botID string, receiverID string, fields [
 				if err != nil {
 					continue
 				}
-			} else if field.DataIndex == "phone" {
+			} else if field.DataIndex == "phone" && c.Phone == "" {
 				_, err := l.svcCtx.DB.Contact.Update().
 					Where(contact.WxidEQ(receiverID)).
 					SetPhone(field.Value[0]).
@@ -266,7 +307,7 @@ func (l *CronTask) UpdateContactFields(botID string, receiverID string, fields [
 				if err != nil {
 					continue
 				}
-			} else if field.DataIndex == "name" {
+			} else if field.DataIndex == "name" && c.Cname == "" {
 				_, err := l.svcCtx.DB.Contact.Update().
 					Where(contact.WxidEQ(receiverID)).
 					SetCname(field.Value[0]).
@@ -274,7 +315,7 @@ func (l *CronTask) UpdateContactFields(botID string, receiverID string, fields [
 				if err != nil {
 					continue
 				}
-			} else if field.DataIndex == "age" {
+			} else if field.DataIndex == "age" && c.Cage == 0 {
 				num, err := strconv.Atoi(field.Value[0])
 				if err != nil {
 					continue
@@ -286,7 +327,7 @@ func (l *CronTask) UpdateContactFields(botID string, receiverID string, fields [
 				if err != nil {
 					continue
 				}
-			} else if field.DataIndex == "area" {
+			} else if field.DataIndex == "area" && c.Carea == "" {
 				_, err := l.svcCtx.DB.Contact.Update().
 					Where(contact.WxidEQ(receiverID)).
 					SetCarea(field.Value[0]).
@@ -294,7 +335,7 @@ func (l *CronTask) UpdateContactFields(botID string, receiverID string, fields [
 				if err != nil {
 					continue
 				}
-			} else if field.DataIndex == "birthday" {
+			} else if field.DataIndex == "birthday" && c.Cbirthday == "" {
 				_, err := l.svcCtx.DB.Contact.Update().
 					Where(contact.WxidEQ(receiverID)).
 					SetCbirthday(field.Value[0]).
@@ -302,7 +343,7 @@ func (l *CronTask) UpdateContactFields(botID string, receiverID string, fields [
 				if err != nil {
 					continue
 				}
-			} else if field.DataIndex == "birtharea" {
+			} else if field.DataIndex == "birtharea" && c.Cbirtharea == "" {
 				_, err := l.svcCtx.DB.Contact.Update().
 					Where(contact.WxidEQ(receiverID)).
 					SetCbirtharea(field.Value[0]).
@@ -310,7 +351,7 @@ func (l *CronTask) UpdateContactFields(botID string, receiverID string, fields [
 				if err != nil {
 					continue
 				}
-			} else if field.DataIndex == "idcard_no" {
+			} else if field.DataIndex == "idcard_no" && c.CidcardNo == "" {
 				_, err := l.svcCtx.DB.Contact.Update().
 					Where(contact.WxidEQ(receiverID)).
 					SetCidcardNo(field.Value[0]).
@@ -318,7 +359,7 @@ func (l *CronTask) UpdateContactFields(botID string, receiverID string, fields [
 				if err != nil {
 					continue
 				}
-			} else if field.DataIndex == "title" {
+			} else if field.DataIndex == "title" && c.Ctitle == "" {
 				_, err := l.svcCtx.DB.Contact.Update().
 					Where(contact.WxidEQ(receiverID)).
 					SetCtitle(field.Value[0]).

+ 8 - 7
crontask/init.go

@@ -50,14 +50,15 @@ func ScheduleRun(c *cron.Cron, serverCtx *svc.ServiceContext) {
 	//})
 
 	contactForm := NewCronTask(context.Background(), serverCtx)
-	c.AddFunc("*/5 * * * *", func() {
+	c.AddFunc("0 0 * * *", func() {
 		contactForm.analyze()
 	})
 
-	l = NewCronTask(context.Background(), serverCtx)
-	c.AddFunc("@every 20s", func() {
-		MaxWorker := 80
-		MaxChannel := 2
-		l.compApiCallback(MaxWorker, MaxChannel)
-	})
+	//l = NewCronTask(context.Background(), serverCtx)
+	//c.AddFunc("* * * * *", func() {
+	//	MaxWorker := 10
+	//	MaxChannel := 3
+	//	l.compApiCallback(MaxWorker, MaxChannel)
+	//})
+
 }

+ 4 - 1
crontask/send_msg.go

@@ -2,6 +2,7 @@ package crontask
 
 import (
 	"encoding/json"
+	"fmt"
 	"net/url"
 	"path"
 	"time"
@@ -224,7 +225,9 @@ func (l *CronTask) sendMsg() {
 			// 这里之前只有文字消息(既 msgtype=1) 目前增加了图片 所以增加了msgtype=2
 			// 所以增加了一个判断,判断发送的内容类型,如果是文字就调用SendTextMsg,如果是图片就调用SendPicMsg
 			if msg.Msgtype == 1 {
-				err = hookClient.SendTextMsg(msg.Toid, msg.Msg, wxInfo.Wxid)
+				msgId := time.Now().UnixNano() / int64(time.Microsecond)
+				l.svcCtx.Rds.Set(l.ctx, fmt.Sprintf("MsgId_FriendId:%d", msgId), msg.Toid, 10*time.Minute)
+				err = hookClient.SendTextMsg(msg.Toid, msg.Msg, wxInfo.Wxid, msgId)
 			} else if msg.Msgtype == 2 {
 				diyfilename := getFileName(msg.Msg)
 				err = hookClient.SendPicMsg(msg.Toid, msg.Msg, diyfilename, wxInfo.Wxid)

+ 4 - 1
crontask/send_wx.go

@@ -3,6 +3,7 @@ package crontask
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"regexp"
 	"strconv"
 	"strings"
@@ -144,7 +145,9 @@ func (l *CronTask) sendWx() {
 					contactInfo, _ := getContactInfo(v.BotWxid, v.ContactWxid)
 					content = varReplace(content, contactInfo)
 				}
-				err = hookClient.SendTextMsg(v.ContactWxid, content, v.BotWxid)
+				msgId := time.Now().UnixNano() / int64(time.Microsecond)
+				l.svcCtx.Rds.Set(l.ctx, fmt.Sprintf("MsgId_FriendId:%d", msgId), v.ContactWxid, 10*time.Minute)
+				err = hookClient.SendTextMsg(v.ContactWxid, content, v.BotWxid, msgId)
 			} else {
 				re := regexp.MustCompile(`[^/]+$`)
 				fileName := re.FindString(v.Content)

+ 11 - 0
desc/openapi/chat.api

@@ -210,3 +210,14 @@ service Wechat {
     @handler chatCompletions
     post /chat/completions (CompApiReq) returns (CompOpenApiResp)
 }
+
+@server(
+	
+    group: chat
+    middleware: OpenAuthority
+)
+
+service Wechat {
+	@handler sendTextMsg
+	post /wx/sendTextMsg (SendTextMsgReq) returns (BaseMsgResp)
+}	

+ 3 - 2
desc/wechat/contact.api

@@ -66,8 +66,9 @@ type (
 		Ctype *uint64 `json:"ctype,optional"`
 
 		//标签搜索开始结束日期
-		StartDate *string `json:"start_date,optional"`
-        EndDate *string `json:"end_date,optional"`
+		SearchDate []*string `json:"date,optional"`
+
+        Status *uint8 `json:"status,optional"`
     }
 
     // Contact information response | Contact信息返回体

+ 146 - 4
ent/client.go

@@ -29,6 +29,7 @@ import (
 	"wechat-api/ent/employee"
 	"wechat-api/ent/employeeconfig"
 	"wechat-api/ent/label"
+	"wechat-api/ent/labellog"
 	"wechat-api/ent/labelrelationship"
 	"wechat-api/ent/labeltagging"
 	"wechat-api/ent/message"
@@ -107,6 +108,8 @@ type Client struct {
 	EmployeeConfig *EmployeeConfigClient
 	// Label is the client for interacting with the Label builders.
 	Label *LabelClient
+	// LabelLog is the client for interacting with the LabelLog builders.
+	LabelLog *LabelLogClient
 	// LabelRelationship is the client for interacting with the LabelRelationship builders.
 	LabelRelationship *LabelRelationshipClient
 	// LabelTagging is the client for interacting with the LabelTagging builders.
@@ -192,6 +195,7 @@ func (c *Client) init() {
 	c.Employee = NewEmployeeClient(c.config)
 	c.EmployeeConfig = NewEmployeeConfigClient(c.config)
 	c.Label = NewLabelClient(c.config)
+	c.LabelLog = NewLabelLogClient(c.config)
 	c.LabelRelationship = NewLabelRelationshipClient(c.config)
 	c.LabelTagging = NewLabelTaggingClient(c.config)
 	c.Message = NewMessageClient(c.config)
@@ -330,6 +334,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
 		Employee:             NewEmployeeClient(cfg),
 		EmployeeConfig:       NewEmployeeConfigClient(cfg),
 		Label:                NewLabelClient(cfg),
+		LabelLog:             NewLabelLogClient(cfg),
 		LabelRelationship:    NewLabelRelationshipClient(cfg),
 		LabelTagging:         NewLabelTaggingClient(cfg),
 		Message:              NewMessageClient(cfg),
@@ -395,6 +400,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
 		Employee:             NewEmployeeClient(cfg),
 		EmployeeConfig:       NewEmployeeConfigClient(cfg),
 		Label:                NewLabelClient(cfg),
+		LabelLog:             NewLabelLogClient(cfg),
 		LabelRelationship:    NewLabelRelationshipClient(cfg),
 		LabelTagging:         NewLabelTaggingClient(cfg),
 		Message:              NewMessageClient(cfg),
@@ -455,7 +461,7 @@ func (c *Client) Use(hooks ...Hook) {
 		c.Agent, c.AgentBase, c.AliyunAvatar, c.AllocAgent, c.ApiKey, c.BatchMsg,
 		c.Category, c.ChatRecords, c.ChatSession, c.CompapiAsynctask, c.Contact,
 		c.ContactField, c.ContactFieldTemplate, c.CreditBalance, c.CreditUsage,
-		c.Employee, c.EmployeeConfig, c.Label, c.LabelRelationship, c.LabelTagging,
+		c.Employee, c.EmployeeConfig, c.Label, c.LabelLog,c.LabelRelationship, c.LabelTagging,
 		c.Message, c.MessageRecords, c.Msg, c.PayRecharge, c.Server, c.SopNode,
 		c.SopStage, c.SopTask, c.Token, c.Tutorial, c.UsageDetail, c.UsageStatisticDay,
 		c.UsageStatisticHour, c.UsageStatisticMonth, c.UsageTotal, c.Whatsapp,
@@ -473,7 +479,7 @@ func (c *Client) Intercept(interceptors ...Interceptor) {
 		c.Agent, c.AgentBase, c.AliyunAvatar, c.AllocAgent, c.ApiKey, c.BatchMsg,
 		c.Category, c.ChatRecords, c.ChatSession, c.CompapiAsynctask, c.Contact,
 		c.ContactField, c.ContactFieldTemplate, c.CreditBalance, c.CreditUsage,
-		c.Employee, c.EmployeeConfig, c.Label, c.LabelRelationship, c.LabelTagging,
+		c.Employee, c.EmployeeConfig, c.Label, c.LabelLog,c.LabelRelationship, c.LabelTagging,
 		c.Message, c.MessageRecords, c.Msg, c.PayRecharge, c.Server, c.SopNode,
 		c.SopStage, c.SopTask, c.Token, c.Tutorial, c.UsageDetail, c.UsageStatisticDay,
 		c.UsageStatisticHour, c.UsageStatisticMonth, c.UsageTotal, c.Whatsapp,
@@ -523,6 +529,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
 		return c.EmployeeConfig.mutate(ctx, m)
 	case *LabelMutation:
 		return c.Label.mutate(ctx, m)
+	case *LabelLogMutation:
+		return c.LabelLog.mutate(ctx, m)
 	case *LabelRelationshipMutation:
 		return c.LabelRelationship.mutate(ctx, m)
 	case *LabelTaggingMutation:
@@ -2166,6 +2174,7 @@ func (c *ContactClient) QueryContactRelationships(co *Contact) *LabelRelationshi
 	return query
 }
 
+
 // QueryContactFields queries the contact_fields edge of a Contact.
 func (c *ContactClient) QueryContactFields(co *Contact) *ContactFieldQuery {
 	query := (&ContactFieldClient{config: c.config}).Query()
@@ -3232,6 +3241,139 @@ func (c *LabelClient) mutate(ctx context.Context, m *LabelMutation) (Value, erro
 	}
 }
 
+// LabelLogClient is a client for the LabelLog schema.
+type LabelLogClient struct {
+	config
+}
+
+// NewLabelLogClient returns a client for the LabelLog from the given config.
+func NewLabelLogClient(c config) *LabelLogClient {
+	return &LabelLogClient{config: c}
+}
+
+// Use adds a list of mutation hooks to the hooks stack.
+// A call to `Use(f, g, h)` equals to `labellog.Hooks(f(g(h())))`.
+func (c *LabelLogClient) Use(hooks ...Hook) {
+	c.hooks.LabelLog = append(c.hooks.LabelLog, hooks...)
+}
+
+// Intercept adds a list of query interceptors to the interceptors stack.
+// A call to `Intercept(f, g, h)` equals to `labellog.Intercept(f(g(h())))`.
+func (c *LabelLogClient) Intercept(interceptors ...Interceptor) {
+	c.inters.LabelLog = append(c.inters.LabelLog, interceptors...)
+}
+
+// Create returns a builder for creating a LabelLog entity.
+func (c *LabelLogClient) Create() *LabelLogCreate {
+	mutation := newLabelLogMutation(c.config, OpCreate)
+	return &LabelLogCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// CreateBulk returns a builder for creating a bulk of LabelLog entities.
+func (c *LabelLogClient) CreateBulk(builders ...*LabelLogCreate) *LabelLogCreateBulk {
+	return &LabelLogCreateBulk{config: c.config, builders: builders}
+}
+
+// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
+// a builder and applies setFunc on it.
+func (c *LabelLogClient) MapCreateBulk(slice any, setFunc func(*LabelLogCreate, int)) *LabelLogCreateBulk {
+	rv := reflect.ValueOf(slice)
+	if rv.Kind() != reflect.Slice {
+		return &LabelLogCreateBulk{err: fmt.Errorf("calling to LabelLogClient.MapCreateBulk with wrong type %T, need slice", slice)}
+	}
+	builders := make([]*LabelLogCreate, rv.Len())
+	for i := 0; i < rv.Len(); i++ {
+		builders[i] = c.Create()
+		setFunc(builders[i], i)
+	}
+	return &LabelLogCreateBulk{config: c.config, builders: builders}
+}
+
+// Update returns an update builder for LabelLog.
+func (c *LabelLogClient) Update() *LabelLogUpdate {
+	mutation := newLabelLogMutation(c.config, OpUpdate)
+	return &LabelLogUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// UpdateOne returns an update builder for the given entity.
+func (c *LabelLogClient) UpdateOne(ll *LabelLog) *LabelLogUpdateOne {
+	mutation := newLabelLogMutation(c.config, OpUpdateOne, withLabelLog(ll))
+	return &LabelLogUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// UpdateOneID returns an update builder for the given id.
+func (c *LabelLogClient) UpdateOneID(id uint64) *LabelLogUpdateOne {
+	mutation := newLabelLogMutation(c.config, OpUpdateOne, withLabelLogID(id))
+	return &LabelLogUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// Delete returns a delete builder for LabelLog.
+func (c *LabelLogClient) Delete() *LabelLogDelete {
+	mutation := newLabelLogMutation(c.config, OpDelete)
+	return &LabelLogDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// DeleteOne returns a builder for deleting the given entity.
+func (c *LabelLogClient) DeleteOne(ll *LabelLog) *LabelLogDeleteOne {
+	return c.DeleteOneID(ll.ID)
+}
+
+// DeleteOneID returns a builder for deleting the given entity by its id.
+func (c *LabelLogClient) DeleteOneID(id uint64) *LabelLogDeleteOne {
+	builder := c.Delete().Where(labellog.ID(id))
+	builder.mutation.id = &id
+	builder.mutation.op = OpDeleteOne
+	return &LabelLogDeleteOne{builder}
+}
+
+// Query returns a query builder for LabelLog.
+func (c *LabelLogClient) Query() *LabelLogQuery {
+	return &LabelLogQuery{
+		config: c.config,
+		ctx:    &QueryContext{Type: TypeLabelLog},
+		inters: c.Interceptors(),
+	}
+}
+
+// Get returns a LabelLog entity by its id.
+func (c *LabelLogClient) Get(ctx context.Context, id uint64) (*LabelLog, error) {
+	return c.Query().Where(labellog.ID(id)).Only(ctx)
+}
+
+// GetX is like Get, but panics if an error occurs.
+func (c *LabelLogClient) GetX(ctx context.Context, id uint64) *LabelLog {
+	obj, err := c.Get(ctx, id)
+	if err != nil {
+		panic(err)
+	}
+	return obj
+}
+
+// Hooks returns the client hooks.
+func (c *LabelLogClient) Hooks() []Hook {
+	return c.hooks.LabelLog
+}
+
+// Interceptors returns the client interceptors.
+func (c *LabelLogClient) Interceptors() []Interceptor {
+	return c.inters.LabelLog
+}
+
+func (c *LabelLogClient) mutate(ctx context.Context, m *LabelLogMutation) (Value, error) {
+	switch m.Op() {
+	case OpCreate:
+		return (&LabelLogCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
+	case OpUpdate:
+		return (&LabelLogUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
+	case OpUpdateOne:
+		return (&LabelLogUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
+	case OpDelete, OpDeleteOne:
+		return (&LabelLogDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
+	default:
+		return nil, fmt.Errorf("ent: unknown LabelLog mutation op: %q", m.Op())
+	}
+}
+
 // LabelRelationshipClient is a client for the LabelRelationship schema.
 type LabelRelationshipClient struct {
 	config
@@ -7308,7 +7450,7 @@ type (
 		Agent, AgentBase, AliyunAvatar, AllocAgent, ApiKey, BatchMsg, Category,
 		ChatRecords, ChatSession, CompapiAsynctask, Contact, ContactField,
 		ContactFieldTemplate, CreditBalance, CreditUsage, Employee, EmployeeConfig,
-		Label, LabelRelationship, LabelTagging, Message, MessageRecords, Msg,
+		Label, LabelLog, LabelRelationship, LabelTagging, Message, MessageRecords, Msg,
 		PayRecharge, Server, SopNode, SopStage, SopTask, Token, Tutorial, UsageDetail,
 		UsageStatisticDay, UsageStatisticHour, UsageStatisticMonth, UsageTotal,
 		Whatsapp, WhatsappChannel, WorkExperience, WpChatroom, WpChatroomMember, Wx,
@@ -7318,7 +7460,7 @@ type (
 		Agent, AgentBase, AliyunAvatar, AllocAgent, ApiKey, BatchMsg, Category,
 		ChatRecords, ChatSession, CompapiAsynctask, Contact, ContactField,
 		ContactFieldTemplate, CreditBalance, CreditUsage, Employee, EmployeeConfig,
-		Label, LabelRelationship, LabelTagging, Message, MessageRecords, Msg,
+		Label, LabelLog, LabelRelationship, LabelTagging, Message, MessageRecords, Msg,
 		PayRecharge, Server, SopNode, SopStage, SopTask, Token, Tutorial, UsageDetail,
 		UsageStatisticDay, UsageStatisticHour, UsageStatisticMonth, UsageTotal,
 		Whatsapp, WhatsappChannel, WorkExperience, WpChatroom, WpChatroomMember, Wx,

+ 2 - 0
ent/ent.go

@@ -26,6 +26,7 @@ import (
 	"wechat-api/ent/employee"
 	"wechat-api/ent/employeeconfig"
 	"wechat-api/ent/label"
+	"wechat-api/ent/labellog"
 	"wechat-api/ent/labelrelationship"
 	"wechat-api/ent/labeltagging"
 	"wechat-api/ent/message"
@@ -136,6 +137,7 @@ func checkColumn(table, column string) error {
 			employee.Table:             employee.ValidColumn,
 			employeeconfig.Table:       employeeconfig.ValidColumn,
 			label.Table:                label.ValidColumn,
+			labellog.Table:             labellog.ValidColumn,
 			labelrelationship.Table:    labelrelationship.ValidColumn,
 			labeltagging.Table:         labeltagging.ValidColumn,
 			message.Table:              message.ValidColumn,

+ 12 - 0
ent/hook/hook.go

@@ -224,6 +224,18 @@ func (f LabelFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error
 	return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.LabelMutation", m)
 }
 
+// The LabelLogFunc type is an adapter to allow the use of ordinary
+// function as LabelLog mutator.
+type LabelLogFunc func(context.Context, *ent.LabelLogMutation) (ent.Value, error)
+
+// Mutate calls f(ctx, m).
+func (f LabelLogFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
+	if mv, ok := m.(*ent.LabelLogMutation); ok {
+		return f(ctx, mv)
+	}
+	return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.LabelLogMutation", m)
+}
+
 // The LabelRelationshipFunc type is an adapter to allow the use of ordinary
 // function as LabelRelationship mutator.
 type LabelRelationshipFunc func(context.Context, *ent.LabelRelationshipMutation) (ent.Value, error)

+ 30 - 0
ent/intercept/intercept.go

@@ -24,6 +24,7 @@ import (
 	"wechat-api/ent/employee"
 	"wechat-api/ent/employeeconfig"
 	"wechat-api/ent/label"
+	"wechat-api/ent/labellog"
 	"wechat-api/ent/labelrelationship"
 	"wechat-api/ent/labeltagging"
 	"wechat-api/ent/message"
@@ -599,6 +600,33 @@ func (f TraverseLabel) Traverse(ctx context.Context, q ent.Query) error {
 	return fmt.Errorf("unexpected query type %T. expect *ent.LabelQuery", q)
 }
 
+// The LabelLogFunc type is an adapter to allow the use of ordinary function as a Querier.
+type LabelLogFunc func(context.Context, *ent.LabelLogQuery) (ent.Value, error)
+
+// Query calls f(ctx, q).
+func (f LabelLogFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
+	if q, ok := q.(*ent.LabelLogQuery); ok {
+		return f(ctx, q)
+	}
+	return nil, fmt.Errorf("unexpected query type %T. expect *ent.LabelLogQuery", q)
+}
+
+// The TraverseLabelLog type is an adapter to allow the use of ordinary function as Traverser.
+type TraverseLabelLog func(context.Context, *ent.LabelLogQuery) error
+
+// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
+func (f TraverseLabelLog) Intercept(next ent.Querier) ent.Querier {
+	return next
+}
+
+// Traverse calls f(ctx, q).
+func (f TraverseLabelLog) Traverse(ctx context.Context, q ent.Query) error {
+	if q, ok := q.(*ent.LabelLogQuery); ok {
+		return f(ctx, q)
+	}
+	return fmt.Errorf("unexpected query type %T. expect *ent.LabelLogQuery", q)
+}
+
 // The LabelRelationshipFunc type is an adapter to allow the use of ordinary function as a Querier.
 type LabelRelationshipFunc func(context.Context, *ent.LabelRelationshipQuery) (ent.Value, error)
 
@@ -1394,6 +1422,8 @@ func NewQuery(q ent.Query) (Query, error) {
 		return &query[*ent.EmployeeConfigQuery, predicate.EmployeeConfig, employeeconfig.OrderOption]{typ: ent.TypeEmployeeConfig, tq: q}, nil
 	case *ent.LabelQuery:
 		return &query[*ent.LabelQuery, predicate.Label, label.OrderOption]{typ: ent.TypeLabel, tq: q}, nil
+	case *ent.LabelLogQuery:
+		return &query[*ent.LabelLogQuery, predicate.LabelLog, labellog.OrderOption]{typ: ent.TypeLabelLog, tq: q}, nil
 	case *ent.LabelRelationshipQuery:
 		return &query[*ent.LabelRelationshipQuery, predicate.LabelRelationship, labelrelationship.OrderOption]{typ: ent.TypeLabelRelationship, tq: q}, nil
 	case *ent.LabelTaggingQuery:

+ 161 - 0
ent/labellog.go

@@ -0,0 +1,161 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ent
+
+import (
+	"fmt"
+	"strings"
+	"time"
+	"wechat-api/ent/labellog"
+
+	"entgo.io/ent"
+	"entgo.io/ent/dialect/sql"
+)
+
+// LabelLog is the model entity for the LabelLog schema.
+type LabelLog struct {
+	config `json:"-"`
+	// ID of the ent.
+	ID uint64 `json:"id,omitempty"`
+	// Create Time | 创建日期
+	CreatedAt time.Time `json:"created_at,omitempty"`
+	// Update Time | 修改日期
+	UpdatedAt time.Time `json:"updated_at,omitempty"`
+	// 标签名称
+	LabelName string `json:"label_name,omitempty"`
+	// 三方平台标签id
+	LabelID int `json:"label_id,omitempty"`
+	// 微信ID
+	WxID string `json:"wx_id,omitempty"`
+	// 机构 ID
+	OrganizationID uint64 `json:"organization_id,omitempty"`
+	selectValues   sql.SelectValues
+}
+
+// scanValues returns the types for scanning values from sql.Rows.
+func (*LabelLog) scanValues(columns []string) ([]any, error) {
+	values := make([]any, len(columns))
+	for i := range columns {
+		switch columns[i] {
+		case labellog.FieldID, labellog.FieldLabelID, labellog.FieldOrganizationID:
+			values[i] = new(sql.NullInt64)
+		case labellog.FieldLabelName, labellog.FieldWxID:
+			values[i] = new(sql.NullString)
+		case labellog.FieldCreatedAt, labellog.FieldUpdatedAt:
+			values[i] = new(sql.NullTime)
+		default:
+			values[i] = new(sql.UnknownType)
+		}
+	}
+	return values, nil
+}
+
+// assignValues assigns the values that were returned from sql.Rows (after scanning)
+// to the LabelLog fields.
+func (ll *LabelLog) assignValues(columns []string, values []any) error {
+	if m, n := len(values), len(columns); m < n {
+		return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
+	}
+	for i := range columns {
+		switch columns[i] {
+		case labellog.FieldID:
+			value, ok := values[i].(*sql.NullInt64)
+			if !ok {
+				return fmt.Errorf("unexpected type %T for field id", value)
+			}
+			ll.ID = uint64(value.Int64)
+		case labellog.FieldCreatedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field created_at", values[i])
+			} else if value.Valid {
+				ll.CreatedAt = value.Time
+			}
+		case labellog.FieldUpdatedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field updated_at", values[i])
+			} else if value.Valid {
+				ll.UpdatedAt = value.Time
+			}
+		case labellog.FieldLabelName:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field label_name", values[i])
+			} else if value.Valid {
+				ll.LabelName = value.String
+			}
+		case labellog.FieldLabelID:
+			if value, ok := values[i].(*sql.NullInt64); !ok {
+				return fmt.Errorf("unexpected type %T for field label_id", values[i])
+			} else if value.Valid {
+				ll.LabelID = int(value.Int64)
+			}
+		case labellog.FieldWxID:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field wx_id", values[i])
+			} else if value.Valid {
+				ll.WxID = value.String
+			}
+		case labellog.FieldOrganizationID:
+			if value, ok := values[i].(*sql.NullInt64); !ok {
+				return fmt.Errorf("unexpected type %T for field organization_id", values[i])
+			} else if value.Valid {
+				ll.OrganizationID = uint64(value.Int64)
+			}
+		default:
+			ll.selectValues.Set(columns[i], values[i])
+		}
+	}
+	return nil
+}
+
+// Value returns the ent.Value that was dynamically selected and assigned to the LabelLog.
+// This includes values selected through modifiers, order, etc.
+func (ll *LabelLog) Value(name string) (ent.Value, error) {
+	return ll.selectValues.Get(name)
+}
+
+// Update returns a builder for updating this LabelLog.
+// Note that you need to call LabelLog.Unwrap() before calling this method if this LabelLog
+// was returned from a transaction, and the transaction was committed or rolled back.
+func (ll *LabelLog) Update() *LabelLogUpdateOne {
+	return NewLabelLogClient(ll.config).UpdateOne(ll)
+}
+
+// Unwrap unwraps the LabelLog entity that was returned from a transaction after it was closed,
+// so that all future queries will be executed through the driver which created the transaction.
+func (ll *LabelLog) Unwrap() *LabelLog {
+	_tx, ok := ll.config.driver.(*txDriver)
+	if !ok {
+		panic("ent: LabelLog is not a transactional entity")
+	}
+	ll.config.driver = _tx.drv
+	return ll
+}
+
+// String implements the fmt.Stringer.
+func (ll *LabelLog) String() string {
+	var builder strings.Builder
+	builder.WriteString("LabelLog(")
+	builder.WriteString(fmt.Sprintf("id=%v, ", ll.ID))
+	builder.WriteString("created_at=")
+	builder.WriteString(ll.CreatedAt.Format(time.ANSIC))
+	builder.WriteString(", ")
+	builder.WriteString("updated_at=")
+	builder.WriteString(ll.UpdatedAt.Format(time.ANSIC))
+	builder.WriteString(", ")
+	builder.WriteString("label_name=")
+	builder.WriteString(ll.LabelName)
+	builder.WriteString(", ")
+	builder.WriteString("label_id=")
+	builder.WriteString(fmt.Sprintf("%v", ll.LabelID))
+	builder.WriteString(", ")
+	builder.WriteString("wx_id=")
+	builder.WriteString(ll.WxID)
+	builder.WriteString(", ")
+	builder.WriteString("organization_id=")
+	builder.WriteString(fmt.Sprintf("%v", ll.OrganizationID))
+	builder.WriteByte(')')
+	return builder.String()
+}
+
+// LabelLogs is a parsable slice of LabelLog.
+type LabelLogs []*LabelLog

+ 106 - 0
ent/labellog/labellog.go

@@ -0,0 +1,106 @@
+// Code generated by ent, DO NOT EDIT.
+
+package labellog
+
+import (
+	"time"
+
+	"entgo.io/ent/dialect/sql"
+)
+
+const (
+	// Label holds the string label denoting the labellog type in the database.
+	Label = "label_log"
+	// FieldID holds the string denoting the id field in the database.
+	FieldID = "id"
+	// FieldCreatedAt holds the string denoting the created_at field in the database.
+	FieldCreatedAt = "created_at"
+	// FieldUpdatedAt holds the string denoting the updated_at field in the database.
+	FieldUpdatedAt = "updated_at"
+	// FieldLabelName holds the string denoting the label_name field in the database.
+	FieldLabelName = "label_name"
+	// FieldLabelID holds the string denoting the label_id field in the database.
+	FieldLabelID = "label_id"
+	// FieldWxID holds the string denoting the wx_id field in the database.
+	FieldWxID = "wx_id"
+	// FieldOrganizationID holds the string denoting the organization_id field in the database.
+	FieldOrganizationID = "organization_id"
+	// Table holds the table name of the labellog in the database.
+	Table = "label_log"
+)
+
+// Columns holds all SQL columns for labellog fields.
+var Columns = []string{
+	FieldID,
+	FieldCreatedAt,
+	FieldUpdatedAt,
+	FieldLabelName,
+	FieldLabelID,
+	FieldWxID,
+	FieldOrganizationID,
+}
+
+// ValidColumn reports if the column name is valid (part of the table columns).
+func ValidColumn(column string) bool {
+	for i := range Columns {
+		if column == Columns[i] {
+			return true
+		}
+	}
+	return false
+}
+
+var (
+	// DefaultCreatedAt holds the default value on creation for the "created_at" field.
+	DefaultCreatedAt func() time.Time
+	// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
+	DefaultUpdatedAt func() time.Time
+	// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
+	UpdateDefaultUpdatedAt func() time.Time
+	// DefaultLabelName holds the default value on creation for the "label_name" field.
+	DefaultLabelName string
+	// DefaultLabelID holds the default value on creation for the "label_id" field.
+	DefaultLabelID int
+	// DefaultWxID holds the default value on creation for the "wx_id" field.
+	DefaultWxID string
+	// DefaultOrganizationID holds the default value on creation for the "organization_id" field.
+	DefaultOrganizationID uint64
+)
+
+// OrderOption defines the ordering options for the LabelLog queries.
+type OrderOption func(*sql.Selector)
+
+// ByID orders the results by the id field.
+func ByID(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldID, opts...).ToFunc()
+}
+
+// ByCreatedAt orders the results by the created_at field.
+func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
+}
+
+// ByUpdatedAt orders the results by the updated_at field.
+func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
+}
+
+// ByLabelName orders the results by the label_name field.
+func ByLabelName(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldLabelName, opts...).ToFunc()
+}
+
+// ByLabelID orders the results by the label_id field.
+func ByLabelID(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldLabelID, opts...).ToFunc()
+}
+
+// ByWxID orders the results by the wx_id field.
+func ByWxID(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldWxID, opts...).ToFunc()
+}
+
+// ByOrganizationID orders the results by the organization_id field.
+func ByOrganizationID(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldOrganizationID, opts...).ToFunc()
+}

+ 400 - 0
ent/labellog/where.go

@@ -0,0 +1,400 @@
+// Code generated by ent, DO NOT EDIT.
+
+package labellog
+
+import (
+	"time"
+	"wechat-api/ent/predicate"
+
+	"entgo.io/ent/dialect/sql"
+)
+
+// ID filters vertices based on their ID field.
+func ID(id uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldEQ(FieldID, id))
+}
+
+// IDEQ applies the EQ predicate on the ID field.
+func IDEQ(id uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldEQ(FieldID, id))
+}
+
+// IDNEQ applies the NEQ predicate on the ID field.
+func IDNEQ(id uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldNEQ(FieldID, id))
+}
+
+// IDIn applies the In predicate on the ID field.
+func IDIn(ids ...uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldIn(FieldID, ids...))
+}
+
+// IDNotIn applies the NotIn predicate on the ID field.
+func IDNotIn(ids ...uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldNotIn(FieldID, ids...))
+}
+
+// IDGT applies the GT predicate on the ID field.
+func IDGT(id uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldGT(FieldID, id))
+}
+
+// IDGTE applies the GTE predicate on the ID field.
+func IDGTE(id uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldGTE(FieldID, id))
+}
+
+// IDLT applies the LT predicate on the ID field.
+func IDLT(id uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldLT(FieldID, id))
+}
+
+// IDLTE applies the LTE predicate on the ID field.
+func IDLTE(id uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldLTE(FieldID, id))
+}
+
+// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
+func CreatedAt(v time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldEQ(FieldCreatedAt, v))
+}
+
+// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
+func UpdatedAt(v time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldEQ(FieldUpdatedAt, v))
+}
+
+// LabelName applies equality check predicate on the "label_name" field. It's identical to LabelNameEQ.
+func LabelName(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldEQ(FieldLabelName, v))
+}
+
+// LabelID applies equality check predicate on the "label_id" field. It's identical to LabelIDEQ.
+func LabelID(v int) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldEQ(FieldLabelID, v))
+}
+
+// WxID applies equality check predicate on the "wx_id" field. It's identical to WxIDEQ.
+func WxID(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldEQ(FieldWxID, v))
+}
+
+// OrganizationID applies equality check predicate on the "organization_id" field. It's identical to OrganizationIDEQ.
+func OrganizationID(v uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldEQ(FieldOrganizationID, v))
+}
+
+// CreatedAtEQ applies the EQ predicate on the "created_at" field.
+func CreatedAtEQ(v time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldEQ(FieldCreatedAt, v))
+}
+
+// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
+func CreatedAtNEQ(v time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldNEQ(FieldCreatedAt, v))
+}
+
+// CreatedAtIn applies the In predicate on the "created_at" field.
+func CreatedAtIn(vs ...time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldIn(FieldCreatedAt, vs...))
+}
+
+// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
+func CreatedAtNotIn(vs ...time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldNotIn(FieldCreatedAt, vs...))
+}
+
+// CreatedAtGT applies the GT predicate on the "created_at" field.
+func CreatedAtGT(v time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldGT(FieldCreatedAt, v))
+}
+
+// CreatedAtGTE applies the GTE predicate on the "created_at" field.
+func CreatedAtGTE(v time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldGTE(FieldCreatedAt, v))
+}
+
+// CreatedAtLT applies the LT predicate on the "created_at" field.
+func CreatedAtLT(v time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldLT(FieldCreatedAt, v))
+}
+
+// CreatedAtLTE applies the LTE predicate on the "created_at" field.
+func CreatedAtLTE(v time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldLTE(FieldCreatedAt, v))
+}
+
+// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
+func UpdatedAtEQ(v time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldEQ(FieldUpdatedAt, v))
+}
+
+// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
+func UpdatedAtNEQ(v time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldNEQ(FieldUpdatedAt, v))
+}
+
+// UpdatedAtIn applies the In predicate on the "updated_at" field.
+func UpdatedAtIn(vs ...time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldIn(FieldUpdatedAt, vs...))
+}
+
+// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
+func UpdatedAtNotIn(vs ...time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldNotIn(FieldUpdatedAt, vs...))
+}
+
+// UpdatedAtGT applies the GT predicate on the "updated_at" field.
+func UpdatedAtGT(v time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldGT(FieldUpdatedAt, v))
+}
+
+// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
+func UpdatedAtGTE(v time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldGTE(FieldUpdatedAt, v))
+}
+
+// UpdatedAtLT applies the LT predicate on the "updated_at" field.
+func UpdatedAtLT(v time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldLT(FieldUpdatedAt, v))
+}
+
+// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
+func UpdatedAtLTE(v time.Time) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldLTE(FieldUpdatedAt, v))
+}
+
+// LabelNameEQ applies the EQ predicate on the "label_name" field.
+func LabelNameEQ(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldEQ(FieldLabelName, v))
+}
+
+// LabelNameNEQ applies the NEQ predicate on the "label_name" field.
+func LabelNameNEQ(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldNEQ(FieldLabelName, v))
+}
+
+// LabelNameIn applies the In predicate on the "label_name" field.
+func LabelNameIn(vs ...string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldIn(FieldLabelName, vs...))
+}
+
+// LabelNameNotIn applies the NotIn predicate on the "label_name" field.
+func LabelNameNotIn(vs ...string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldNotIn(FieldLabelName, vs...))
+}
+
+// LabelNameGT applies the GT predicate on the "label_name" field.
+func LabelNameGT(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldGT(FieldLabelName, v))
+}
+
+// LabelNameGTE applies the GTE predicate on the "label_name" field.
+func LabelNameGTE(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldGTE(FieldLabelName, v))
+}
+
+// LabelNameLT applies the LT predicate on the "label_name" field.
+func LabelNameLT(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldLT(FieldLabelName, v))
+}
+
+// LabelNameLTE applies the LTE predicate on the "label_name" field.
+func LabelNameLTE(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldLTE(FieldLabelName, v))
+}
+
+// LabelNameContains applies the Contains predicate on the "label_name" field.
+func LabelNameContains(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldContains(FieldLabelName, v))
+}
+
+// LabelNameHasPrefix applies the HasPrefix predicate on the "label_name" field.
+func LabelNameHasPrefix(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldHasPrefix(FieldLabelName, v))
+}
+
+// LabelNameHasSuffix applies the HasSuffix predicate on the "label_name" field.
+func LabelNameHasSuffix(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldHasSuffix(FieldLabelName, v))
+}
+
+// LabelNameEqualFold applies the EqualFold predicate on the "label_name" field.
+func LabelNameEqualFold(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldEqualFold(FieldLabelName, v))
+}
+
+// LabelNameContainsFold applies the ContainsFold predicate on the "label_name" field.
+func LabelNameContainsFold(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldContainsFold(FieldLabelName, v))
+}
+
+// LabelIDEQ applies the EQ predicate on the "label_id" field.
+func LabelIDEQ(v int) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldEQ(FieldLabelID, v))
+}
+
+// LabelIDNEQ applies the NEQ predicate on the "label_id" field.
+func LabelIDNEQ(v int) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldNEQ(FieldLabelID, v))
+}
+
+// LabelIDIn applies the In predicate on the "label_id" field.
+func LabelIDIn(vs ...int) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldIn(FieldLabelID, vs...))
+}
+
+// LabelIDNotIn applies the NotIn predicate on the "label_id" field.
+func LabelIDNotIn(vs ...int) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldNotIn(FieldLabelID, vs...))
+}
+
+// LabelIDGT applies the GT predicate on the "label_id" field.
+func LabelIDGT(v int) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldGT(FieldLabelID, v))
+}
+
+// LabelIDGTE applies the GTE predicate on the "label_id" field.
+func LabelIDGTE(v int) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldGTE(FieldLabelID, v))
+}
+
+// LabelIDLT applies the LT predicate on the "label_id" field.
+func LabelIDLT(v int) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldLT(FieldLabelID, v))
+}
+
+// LabelIDLTE applies the LTE predicate on the "label_id" field.
+func LabelIDLTE(v int) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldLTE(FieldLabelID, v))
+}
+
+// WxIDEQ applies the EQ predicate on the "wx_id" field.
+func WxIDEQ(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldEQ(FieldWxID, v))
+}
+
+// WxIDNEQ applies the NEQ predicate on the "wx_id" field.
+func WxIDNEQ(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldNEQ(FieldWxID, v))
+}
+
+// WxIDIn applies the In predicate on the "wx_id" field.
+func WxIDIn(vs ...string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldIn(FieldWxID, vs...))
+}
+
+// WxIDNotIn applies the NotIn predicate on the "wx_id" field.
+func WxIDNotIn(vs ...string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldNotIn(FieldWxID, vs...))
+}
+
+// WxIDGT applies the GT predicate on the "wx_id" field.
+func WxIDGT(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldGT(FieldWxID, v))
+}
+
+// WxIDGTE applies the GTE predicate on the "wx_id" field.
+func WxIDGTE(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldGTE(FieldWxID, v))
+}
+
+// WxIDLT applies the LT predicate on the "wx_id" field.
+func WxIDLT(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldLT(FieldWxID, v))
+}
+
+// WxIDLTE applies the LTE predicate on the "wx_id" field.
+func WxIDLTE(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldLTE(FieldWxID, v))
+}
+
+// WxIDContains applies the Contains predicate on the "wx_id" field.
+func WxIDContains(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldContains(FieldWxID, v))
+}
+
+// WxIDHasPrefix applies the HasPrefix predicate on the "wx_id" field.
+func WxIDHasPrefix(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldHasPrefix(FieldWxID, v))
+}
+
+// WxIDHasSuffix applies the HasSuffix predicate on the "wx_id" field.
+func WxIDHasSuffix(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldHasSuffix(FieldWxID, v))
+}
+
+// WxIDEqualFold applies the EqualFold predicate on the "wx_id" field.
+func WxIDEqualFold(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldEqualFold(FieldWxID, v))
+}
+
+// WxIDContainsFold applies the ContainsFold predicate on the "wx_id" field.
+func WxIDContainsFold(v string) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldContainsFold(FieldWxID, v))
+}
+
+// OrganizationIDEQ applies the EQ predicate on the "organization_id" field.
+func OrganizationIDEQ(v uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldEQ(FieldOrganizationID, v))
+}
+
+// OrganizationIDNEQ applies the NEQ predicate on the "organization_id" field.
+func OrganizationIDNEQ(v uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldNEQ(FieldOrganizationID, v))
+}
+
+// OrganizationIDIn applies the In predicate on the "organization_id" field.
+func OrganizationIDIn(vs ...uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldIn(FieldOrganizationID, vs...))
+}
+
+// OrganizationIDNotIn applies the NotIn predicate on the "organization_id" field.
+func OrganizationIDNotIn(vs ...uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldNotIn(FieldOrganizationID, vs...))
+}
+
+// OrganizationIDGT applies the GT predicate on the "organization_id" field.
+func OrganizationIDGT(v uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldGT(FieldOrganizationID, v))
+}
+
+// OrganizationIDGTE applies the GTE predicate on the "organization_id" field.
+func OrganizationIDGTE(v uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldGTE(FieldOrganizationID, v))
+}
+
+// OrganizationIDLT applies the LT predicate on the "organization_id" field.
+func OrganizationIDLT(v uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldLT(FieldOrganizationID, v))
+}
+
+// OrganizationIDLTE applies the LTE predicate on the "organization_id" field.
+func OrganizationIDLTE(v uint64) predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldLTE(FieldOrganizationID, v))
+}
+
+// OrganizationIDIsNil applies the IsNil predicate on the "organization_id" field.
+func OrganizationIDIsNil() predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldIsNull(FieldOrganizationID))
+}
+
+// OrganizationIDNotNil applies the NotNil predicate on the "organization_id" field.
+func OrganizationIDNotNil() predicate.LabelLog {
+	return predicate.LabelLog(sql.FieldNotNull(FieldOrganizationID))
+}
+
+// And groups predicates with the AND operator between them.
+func And(predicates ...predicate.LabelLog) predicate.LabelLog {
+	return predicate.LabelLog(sql.AndPredicates(predicates...))
+}
+
+// Or groups predicates with the OR operator between them.
+func Or(predicates ...predicate.LabelLog) predicate.LabelLog {
+	return predicate.LabelLog(sql.OrPredicates(predicates...))
+}
+
+// Not applies the not operator on the given predicate.
+func Not(p predicate.LabelLog) predicate.LabelLog {
+	return predicate.LabelLog(sql.NotPredicates(p))
+}

+ 851 - 0
ent/labellog_create.go

@@ -0,0 +1,851 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ent
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"time"
+	"wechat-api/ent/labellog"
+
+	"entgo.io/ent/dialect/sql"
+	"entgo.io/ent/dialect/sql/sqlgraph"
+	"entgo.io/ent/schema/field"
+)
+
+// LabelLogCreate is the builder for creating a LabelLog entity.
+type LabelLogCreate struct {
+	config
+	mutation *LabelLogMutation
+	hooks    []Hook
+	conflict []sql.ConflictOption
+}
+
+// SetCreatedAt sets the "created_at" field.
+func (llc *LabelLogCreate) SetCreatedAt(t time.Time) *LabelLogCreate {
+	llc.mutation.SetCreatedAt(t)
+	return llc
+}
+
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
+func (llc *LabelLogCreate) SetNillableCreatedAt(t *time.Time) *LabelLogCreate {
+	if t != nil {
+		llc.SetCreatedAt(*t)
+	}
+	return llc
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (llc *LabelLogCreate) SetUpdatedAt(t time.Time) *LabelLogCreate {
+	llc.mutation.SetUpdatedAt(t)
+	return llc
+}
+
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
+func (llc *LabelLogCreate) SetNillableUpdatedAt(t *time.Time) *LabelLogCreate {
+	if t != nil {
+		llc.SetUpdatedAt(*t)
+	}
+	return llc
+}
+
+// SetLabelName sets the "label_name" field.
+func (llc *LabelLogCreate) SetLabelName(s string) *LabelLogCreate {
+	llc.mutation.SetLabelName(s)
+	return llc
+}
+
+// SetNillableLabelName sets the "label_name" field if the given value is not nil.
+func (llc *LabelLogCreate) SetNillableLabelName(s *string) *LabelLogCreate {
+	if s != nil {
+		llc.SetLabelName(*s)
+	}
+	return llc
+}
+
+// SetLabelID sets the "label_id" field.
+func (llc *LabelLogCreate) SetLabelID(i int) *LabelLogCreate {
+	llc.mutation.SetLabelID(i)
+	return llc
+}
+
+// SetNillableLabelID sets the "label_id" field if the given value is not nil.
+func (llc *LabelLogCreate) SetNillableLabelID(i *int) *LabelLogCreate {
+	if i != nil {
+		llc.SetLabelID(*i)
+	}
+	return llc
+}
+
+// SetWxID sets the "wx_id" field.
+func (llc *LabelLogCreate) SetWxID(s string) *LabelLogCreate {
+	llc.mutation.SetWxID(s)
+	return llc
+}
+
+// SetNillableWxID sets the "wx_id" field if the given value is not nil.
+func (llc *LabelLogCreate) SetNillableWxID(s *string) *LabelLogCreate {
+	if s != nil {
+		llc.SetWxID(*s)
+	}
+	return llc
+}
+
+// SetOrganizationID sets the "organization_id" field.
+func (llc *LabelLogCreate) SetOrganizationID(u uint64) *LabelLogCreate {
+	llc.mutation.SetOrganizationID(u)
+	return llc
+}
+
+// SetNillableOrganizationID sets the "organization_id" field if the given value is not nil.
+func (llc *LabelLogCreate) SetNillableOrganizationID(u *uint64) *LabelLogCreate {
+	if u != nil {
+		llc.SetOrganizationID(*u)
+	}
+	return llc
+}
+
+// SetID sets the "id" field.
+func (llc *LabelLogCreate) SetID(u uint64) *LabelLogCreate {
+	llc.mutation.SetID(u)
+	return llc
+}
+
+// Mutation returns the LabelLogMutation object of the builder.
+func (llc *LabelLogCreate) Mutation() *LabelLogMutation {
+	return llc.mutation
+}
+
+// Save creates the LabelLog in the database.
+func (llc *LabelLogCreate) Save(ctx context.Context) (*LabelLog, error) {
+	llc.defaults()
+	return withHooks(ctx, llc.sqlSave, llc.mutation, llc.hooks)
+}
+
+// SaveX calls Save and panics if Save returns an error.
+func (llc *LabelLogCreate) SaveX(ctx context.Context) *LabelLog {
+	v, err := llc.Save(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return v
+}
+
+// Exec executes the query.
+func (llc *LabelLogCreate) Exec(ctx context.Context) error {
+	_, err := llc.Save(ctx)
+	return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (llc *LabelLogCreate) ExecX(ctx context.Context) {
+	if err := llc.Exec(ctx); err != nil {
+		panic(err)
+	}
+}
+
+// defaults sets the default values of the builder before save.
+func (llc *LabelLogCreate) defaults() {
+	if _, ok := llc.mutation.CreatedAt(); !ok {
+		v := labellog.DefaultCreatedAt()
+		llc.mutation.SetCreatedAt(v)
+	}
+	if _, ok := llc.mutation.UpdatedAt(); !ok {
+		v := labellog.DefaultUpdatedAt()
+		llc.mutation.SetUpdatedAt(v)
+	}
+	if _, ok := llc.mutation.LabelName(); !ok {
+		v := labellog.DefaultLabelName
+		llc.mutation.SetLabelName(v)
+	}
+	if _, ok := llc.mutation.LabelID(); !ok {
+		v := labellog.DefaultLabelID
+		llc.mutation.SetLabelID(v)
+	}
+	if _, ok := llc.mutation.WxID(); !ok {
+		v := labellog.DefaultWxID
+		llc.mutation.SetWxID(v)
+	}
+	if _, ok := llc.mutation.OrganizationID(); !ok {
+		v := labellog.DefaultOrganizationID
+		llc.mutation.SetOrganizationID(v)
+	}
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (llc *LabelLogCreate) check() error {
+	if _, ok := llc.mutation.CreatedAt(); !ok {
+		return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "LabelLog.created_at"`)}
+	}
+	if _, ok := llc.mutation.UpdatedAt(); !ok {
+		return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "LabelLog.updated_at"`)}
+	}
+	if _, ok := llc.mutation.LabelName(); !ok {
+		return &ValidationError{Name: "label_name", err: errors.New(`ent: missing required field "LabelLog.label_name"`)}
+	}
+	if _, ok := llc.mutation.LabelID(); !ok {
+		return &ValidationError{Name: "label_id", err: errors.New(`ent: missing required field "LabelLog.label_id"`)}
+	}
+	if _, ok := llc.mutation.WxID(); !ok {
+		return &ValidationError{Name: "wx_id", err: errors.New(`ent: missing required field "LabelLog.wx_id"`)}
+	}
+	return nil
+}
+
+func (llc *LabelLogCreate) sqlSave(ctx context.Context) (*LabelLog, error) {
+	if err := llc.check(); err != nil {
+		return nil, err
+	}
+	_node, _spec := llc.createSpec()
+	if err := sqlgraph.CreateNode(ctx, llc.driver, _spec); err != nil {
+		if sqlgraph.IsConstraintError(err) {
+			err = &ConstraintError{msg: err.Error(), wrap: err}
+		}
+		return nil, err
+	}
+	if _spec.ID.Value != _node.ID {
+		id := _spec.ID.Value.(int64)
+		_node.ID = uint64(id)
+	}
+	llc.mutation.id = &_node.ID
+	llc.mutation.done = true
+	return _node, nil
+}
+
+func (llc *LabelLogCreate) createSpec() (*LabelLog, *sqlgraph.CreateSpec) {
+	var (
+		_node = &LabelLog{config: llc.config}
+		_spec = sqlgraph.NewCreateSpec(labellog.Table, sqlgraph.NewFieldSpec(labellog.FieldID, field.TypeUint64))
+	)
+	_spec.OnConflict = llc.conflict
+	if id, ok := llc.mutation.ID(); ok {
+		_node.ID = id
+		_spec.ID.Value = id
+	}
+	if value, ok := llc.mutation.CreatedAt(); ok {
+		_spec.SetField(labellog.FieldCreatedAt, field.TypeTime, value)
+		_node.CreatedAt = value
+	}
+	if value, ok := llc.mutation.UpdatedAt(); ok {
+		_spec.SetField(labellog.FieldUpdatedAt, field.TypeTime, value)
+		_node.UpdatedAt = value
+	}
+	if value, ok := llc.mutation.LabelName(); ok {
+		_spec.SetField(labellog.FieldLabelName, field.TypeString, value)
+		_node.LabelName = value
+	}
+	if value, ok := llc.mutation.LabelID(); ok {
+		_spec.SetField(labellog.FieldLabelID, field.TypeInt, value)
+		_node.LabelID = value
+	}
+	if value, ok := llc.mutation.WxID(); ok {
+		_spec.SetField(labellog.FieldWxID, field.TypeString, value)
+		_node.WxID = value
+	}
+	if value, ok := llc.mutation.OrganizationID(); ok {
+		_spec.SetField(labellog.FieldOrganizationID, field.TypeUint64, value)
+		_node.OrganizationID = value
+	}
+	return _node, _spec
+}
+
+// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
+// of the `INSERT` statement. For example:
+//
+//	client.LabelLog.Create().
+//		SetCreatedAt(v).
+//		OnConflict(
+//			// Update the row with the new values
+//			// the was proposed for insertion.
+//			sql.ResolveWithNewValues(),
+//		).
+//		// Override some of the fields with custom
+//		// update values.
+//		Update(func(u *ent.LabelLogUpsert) {
+//			SetCreatedAt(v+v).
+//		}).
+//		Exec(ctx)
+func (llc *LabelLogCreate) OnConflict(opts ...sql.ConflictOption) *LabelLogUpsertOne {
+	llc.conflict = opts
+	return &LabelLogUpsertOne{
+		create: llc,
+	}
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+//	client.LabelLog.Create().
+//		OnConflict(sql.ConflictColumns(columns...)).
+//		Exec(ctx)
+func (llc *LabelLogCreate) OnConflictColumns(columns ...string) *LabelLogUpsertOne {
+	llc.conflict = append(llc.conflict, sql.ConflictColumns(columns...))
+	return &LabelLogUpsertOne{
+		create: llc,
+	}
+}
+
+type (
+	// LabelLogUpsertOne is the builder for "upsert"-ing
+	//  one LabelLog node.
+	LabelLogUpsertOne struct {
+		create *LabelLogCreate
+	}
+
+	// LabelLogUpsert is the "OnConflict" setter.
+	LabelLogUpsert struct {
+		*sql.UpdateSet
+	}
+)
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *LabelLogUpsert) SetUpdatedAt(v time.Time) *LabelLogUpsert {
+	u.Set(labellog.FieldUpdatedAt, v)
+	return u
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *LabelLogUpsert) UpdateUpdatedAt() *LabelLogUpsert {
+	u.SetExcluded(labellog.FieldUpdatedAt)
+	return u
+}
+
+// SetLabelName sets the "label_name" field.
+func (u *LabelLogUpsert) SetLabelName(v string) *LabelLogUpsert {
+	u.Set(labellog.FieldLabelName, v)
+	return u
+}
+
+// UpdateLabelName sets the "label_name" field to the value that was provided on create.
+func (u *LabelLogUpsert) UpdateLabelName() *LabelLogUpsert {
+	u.SetExcluded(labellog.FieldLabelName)
+	return u
+}
+
+// SetLabelID sets the "label_id" field.
+func (u *LabelLogUpsert) SetLabelID(v int) *LabelLogUpsert {
+	u.Set(labellog.FieldLabelID, v)
+	return u
+}
+
+// UpdateLabelID sets the "label_id" field to the value that was provided on create.
+func (u *LabelLogUpsert) UpdateLabelID() *LabelLogUpsert {
+	u.SetExcluded(labellog.FieldLabelID)
+	return u
+}
+
+// AddLabelID adds v to the "label_id" field.
+func (u *LabelLogUpsert) AddLabelID(v int) *LabelLogUpsert {
+	u.Add(labellog.FieldLabelID, v)
+	return u
+}
+
+// SetWxID sets the "wx_id" field.
+func (u *LabelLogUpsert) SetWxID(v string) *LabelLogUpsert {
+	u.Set(labellog.FieldWxID, v)
+	return u
+}
+
+// UpdateWxID sets the "wx_id" field to the value that was provided on create.
+func (u *LabelLogUpsert) UpdateWxID() *LabelLogUpsert {
+	u.SetExcluded(labellog.FieldWxID)
+	return u
+}
+
+// SetOrganizationID sets the "organization_id" field.
+func (u *LabelLogUpsert) SetOrganizationID(v uint64) *LabelLogUpsert {
+	u.Set(labellog.FieldOrganizationID, v)
+	return u
+}
+
+// UpdateOrganizationID sets the "organization_id" field to the value that was provided on create.
+func (u *LabelLogUpsert) UpdateOrganizationID() *LabelLogUpsert {
+	u.SetExcluded(labellog.FieldOrganizationID)
+	return u
+}
+
+// AddOrganizationID adds v to the "organization_id" field.
+func (u *LabelLogUpsert) AddOrganizationID(v uint64) *LabelLogUpsert {
+	u.Add(labellog.FieldOrganizationID, v)
+	return u
+}
+
+// ClearOrganizationID clears the value of the "organization_id" field.
+func (u *LabelLogUpsert) ClearOrganizationID() *LabelLogUpsert {
+	u.SetNull(labellog.FieldOrganizationID)
+	return u
+}
+
+// UpdateNewValues updates the mutable fields using the new values that were set on create except the ID field.
+// Using this option is equivalent to using:
+//
+//	client.LabelLog.Create().
+//		OnConflict(
+//			sql.ResolveWithNewValues(),
+//			sql.ResolveWith(func(u *sql.UpdateSet) {
+//				u.SetIgnore(labellog.FieldID)
+//			}),
+//		).
+//		Exec(ctx)
+func (u *LabelLogUpsertOne) UpdateNewValues() *LabelLogUpsertOne {
+	u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
+	u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
+		if _, exists := u.create.mutation.ID(); exists {
+			s.SetIgnore(labellog.FieldID)
+		}
+		if _, exists := u.create.mutation.CreatedAt(); exists {
+			s.SetIgnore(labellog.FieldCreatedAt)
+		}
+	}))
+	return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+//	client.LabelLog.Create().
+//	    OnConflict(sql.ResolveWithIgnore()).
+//	    Exec(ctx)
+func (u *LabelLogUpsertOne) Ignore() *LabelLogUpsertOne {
+	u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
+	return u
+}
+
+// DoNothing configures the conflict_action to `DO NOTHING`.
+// Supported only by SQLite and PostgreSQL.
+func (u *LabelLogUpsertOne) DoNothing() *LabelLogUpsertOne {
+	u.create.conflict = append(u.create.conflict, sql.DoNothing())
+	return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the LabelLogCreate.OnConflict
+// documentation for more info.
+func (u *LabelLogUpsertOne) Update(set func(*LabelLogUpsert)) *LabelLogUpsertOne {
+	u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
+		set(&LabelLogUpsert{UpdateSet: update})
+	}))
+	return u
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *LabelLogUpsertOne) SetUpdatedAt(v time.Time) *LabelLogUpsertOne {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.SetUpdatedAt(v)
+	})
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *LabelLogUpsertOne) UpdateUpdatedAt() *LabelLogUpsertOne {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.UpdateUpdatedAt()
+	})
+}
+
+// SetLabelName sets the "label_name" field.
+func (u *LabelLogUpsertOne) SetLabelName(v string) *LabelLogUpsertOne {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.SetLabelName(v)
+	})
+}
+
+// UpdateLabelName sets the "label_name" field to the value that was provided on create.
+func (u *LabelLogUpsertOne) UpdateLabelName() *LabelLogUpsertOne {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.UpdateLabelName()
+	})
+}
+
+// SetLabelID sets the "label_id" field.
+func (u *LabelLogUpsertOne) SetLabelID(v int) *LabelLogUpsertOne {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.SetLabelID(v)
+	})
+}
+
+// AddLabelID adds v to the "label_id" field.
+func (u *LabelLogUpsertOne) AddLabelID(v int) *LabelLogUpsertOne {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.AddLabelID(v)
+	})
+}
+
+// UpdateLabelID sets the "label_id" field to the value that was provided on create.
+func (u *LabelLogUpsertOne) UpdateLabelID() *LabelLogUpsertOne {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.UpdateLabelID()
+	})
+}
+
+// SetWxID sets the "wx_id" field.
+func (u *LabelLogUpsertOne) SetWxID(v string) *LabelLogUpsertOne {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.SetWxID(v)
+	})
+}
+
+// UpdateWxID sets the "wx_id" field to the value that was provided on create.
+func (u *LabelLogUpsertOne) UpdateWxID() *LabelLogUpsertOne {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.UpdateWxID()
+	})
+}
+
+// SetOrganizationID sets the "organization_id" field.
+func (u *LabelLogUpsertOne) SetOrganizationID(v uint64) *LabelLogUpsertOne {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.SetOrganizationID(v)
+	})
+}
+
+// AddOrganizationID adds v to the "organization_id" field.
+func (u *LabelLogUpsertOne) AddOrganizationID(v uint64) *LabelLogUpsertOne {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.AddOrganizationID(v)
+	})
+}
+
+// UpdateOrganizationID sets the "organization_id" field to the value that was provided on create.
+func (u *LabelLogUpsertOne) UpdateOrganizationID() *LabelLogUpsertOne {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.UpdateOrganizationID()
+	})
+}
+
+// ClearOrganizationID clears the value of the "organization_id" field.
+func (u *LabelLogUpsertOne) ClearOrganizationID() *LabelLogUpsertOne {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.ClearOrganizationID()
+	})
+}
+
+// Exec executes the query.
+func (u *LabelLogUpsertOne) Exec(ctx context.Context) error {
+	if len(u.create.conflict) == 0 {
+		return errors.New("ent: missing options for LabelLogCreate.OnConflict")
+	}
+	return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *LabelLogUpsertOne) ExecX(ctx context.Context) {
+	if err := u.create.Exec(ctx); err != nil {
+		panic(err)
+	}
+}
+
+// Exec executes the UPSERT query and returns the inserted/updated ID.
+func (u *LabelLogUpsertOne) ID(ctx context.Context) (id uint64, err error) {
+	node, err := u.create.Save(ctx)
+	if err != nil {
+		return id, err
+	}
+	return node.ID, nil
+}
+
+// IDX is like ID, but panics if an error occurs.
+func (u *LabelLogUpsertOne) IDX(ctx context.Context) uint64 {
+	id, err := u.ID(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return id
+}
+
+// LabelLogCreateBulk is the builder for creating many LabelLog entities in bulk.
+type LabelLogCreateBulk struct {
+	config
+	err      error
+	builders []*LabelLogCreate
+	conflict []sql.ConflictOption
+}
+
+// Save creates the LabelLog entities in the database.
+func (llcb *LabelLogCreateBulk) Save(ctx context.Context) ([]*LabelLog, error) {
+	if llcb.err != nil {
+		return nil, llcb.err
+	}
+	specs := make([]*sqlgraph.CreateSpec, len(llcb.builders))
+	nodes := make([]*LabelLog, len(llcb.builders))
+	mutators := make([]Mutator, len(llcb.builders))
+	for i := range llcb.builders {
+		func(i int, root context.Context) {
+			builder := llcb.builders[i]
+			builder.defaults()
+			var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
+				mutation, ok := m.(*LabelLogMutation)
+				if !ok {
+					return nil, fmt.Errorf("unexpected mutation type %T", m)
+				}
+				if err := builder.check(); err != nil {
+					return nil, err
+				}
+				builder.mutation = mutation
+				var err error
+				nodes[i], specs[i] = builder.createSpec()
+				if i < len(mutators)-1 {
+					_, err = mutators[i+1].Mutate(root, llcb.builders[i+1].mutation)
+				} else {
+					spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
+					spec.OnConflict = llcb.conflict
+					// Invoke the actual operation on the latest mutation in the chain.
+					if err = sqlgraph.BatchCreate(ctx, llcb.driver, spec); err != nil {
+						if sqlgraph.IsConstraintError(err) {
+							err = &ConstraintError{msg: err.Error(), wrap: err}
+						}
+					}
+				}
+				if err != nil {
+					return nil, err
+				}
+				mutation.id = &nodes[i].ID
+				if specs[i].ID.Value != nil && nodes[i].ID == 0 {
+					id := specs[i].ID.Value.(int64)
+					nodes[i].ID = uint64(id)
+				}
+				mutation.done = true
+				return nodes[i], nil
+			})
+			for i := len(builder.hooks) - 1; i >= 0; i-- {
+				mut = builder.hooks[i](mut)
+			}
+			mutators[i] = mut
+		}(i, ctx)
+	}
+	if len(mutators) > 0 {
+		if _, err := mutators[0].Mutate(ctx, llcb.builders[0].mutation); err != nil {
+			return nil, err
+		}
+	}
+	return nodes, nil
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (llcb *LabelLogCreateBulk) SaveX(ctx context.Context) []*LabelLog {
+	v, err := llcb.Save(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return v
+}
+
+// Exec executes the query.
+func (llcb *LabelLogCreateBulk) Exec(ctx context.Context) error {
+	_, err := llcb.Save(ctx)
+	return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (llcb *LabelLogCreateBulk) ExecX(ctx context.Context) {
+	if err := llcb.Exec(ctx); err != nil {
+		panic(err)
+	}
+}
+
+// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
+// of the `INSERT` statement. For example:
+//
+//	client.LabelLog.CreateBulk(builders...).
+//		OnConflict(
+//			// Update the row with the new values
+//			// the was proposed for insertion.
+//			sql.ResolveWithNewValues(),
+//		).
+//		// Override some of the fields with custom
+//		// update values.
+//		Update(func(u *ent.LabelLogUpsert) {
+//			SetCreatedAt(v+v).
+//		}).
+//		Exec(ctx)
+func (llcb *LabelLogCreateBulk) OnConflict(opts ...sql.ConflictOption) *LabelLogUpsertBulk {
+	llcb.conflict = opts
+	return &LabelLogUpsertBulk{
+		create: llcb,
+	}
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+//	client.LabelLog.Create().
+//		OnConflict(sql.ConflictColumns(columns...)).
+//		Exec(ctx)
+func (llcb *LabelLogCreateBulk) OnConflictColumns(columns ...string) *LabelLogUpsertBulk {
+	llcb.conflict = append(llcb.conflict, sql.ConflictColumns(columns...))
+	return &LabelLogUpsertBulk{
+		create: llcb,
+	}
+}
+
+// LabelLogUpsertBulk is the builder for "upsert"-ing
+// a bulk of LabelLog nodes.
+type LabelLogUpsertBulk struct {
+	create *LabelLogCreateBulk
+}
+
+// UpdateNewValues updates the mutable fields using the new values that
+// were set on create. Using this option is equivalent to using:
+//
+//	client.LabelLog.Create().
+//		OnConflict(
+//			sql.ResolveWithNewValues(),
+//			sql.ResolveWith(func(u *sql.UpdateSet) {
+//				u.SetIgnore(labellog.FieldID)
+//			}),
+//		).
+//		Exec(ctx)
+func (u *LabelLogUpsertBulk) UpdateNewValues() *LabelLogUpsertBulk {
+	u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
+	u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
+		for _, b := range u.create.builders {
+			if _, exists := b.mutation.ID(); exists {
+				s.SetIgnore(labellog.FieldID)
+			}
+			if _, exists := b.mutation.CreatedAt(); exists {
+				s.SetIgnore(labellog.FieldCreatedAt)
+			}
+		}
+	}))
+	return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+//	client.LabelLog.Create().
+//		OnConflict(sql.ResolveWithIgnore()).
+//		Exec(ctx)
+func (u *LabelLogUpsertBulk) Ignore() *LabelLogUpsertBulk {
+	u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
+	return u
+}
+
+// DoNothing configures the conflict_action to `DO NOTHING`.
+// Supported only by SQLite and PostgreSQL.
+func (u *LabelLogUpsertBulk) DoNothing() *LabelLogUpsertBulk {
+	u.create.conflict = append(u.create.conflict, sql.DoNothing())
+	return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the LabelLogCreateBulk.OnConflict
+// documentation for more info.
+func (u *LabelLogUpsertBulk) Update(set func(*LabelLogUpsert)) *LabelLogUpsertBulk {
+	u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
+		set(&LabelLogUpsert{UpdateSet: update})
+	}))
+	return u
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *LabelLogUpsertBulk) SetUpdatedAt(v time.Time) *LabelLogUpsertBulk {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.SetUpdatedAt(v)
+	})
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *LabelLogUpsertBulk) UpdateUpdatedAt() *LabelLogUpsertBulk {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.UpdateUpdatedAt()
+	})
+}
+
+// SetLabelName sets the "label_name" field.
+func (u *LabelLogUpsertBulk) SetLabelName(v string) *LabelLogUpsertBulk {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.SetLabelName(v)
+	})
+}
+
+// UpdateLabelName sets the "label_name" field to the value that was provided on create.
+func (u *LabelLogUpsertBulk) UpdateLabelName() *LabelLogUpsertBulk {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.UpdateLabelName()
+	})
+}
+
+// SetLabelID sets the "label_id" field.
+func (u *LabelLogUpsertBulk) SetLabelID(v int) *LabelLogUpsertBulk {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.SetLabelID(v)
+	})
+}
+
+// AddLabelID adds v to the "label_id" field.
+func (u *LabelLogUpsertBulk) AddLabelID(v int) *LabelLogUpsertBulk {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.AddLabelID(v)
+	})
+}
+
+// UpdateLabelID sets the "label_id" field to the value that was provided on create.
+func (u *LabelLogUpsertBulk) UpdateLabelID() *LabelLogUpsertBulk {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.UpdateLabelID()
+	})
+}
+
+// SetWxID sets the "wx_id" field.
+func (u *LabelLogUpsertBulk) SetWxID(v string) *LabelLogUpsertBulk {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.SetWxID(v)
+	})
+}
+
+// UpdateWxID sets the "wx_id" field to the value that was provided on create.
+func (u *LabelLogUpsertBulk) UpdateWxID() *LabelLogUpsertBulk {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.UpdateWxID()
+	})
+}
+
+// SetOrganizationID sets the "organization_id" field.
+func (u *LabelLogUpsertBulk) SetOrganizationID(v uint64) *LabelLogUpsertBulk {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.SetOrganizationID(v)
+	})
+}
+
+// AddOrganizationID adds v to the "organization_id" field.
+func (u *LabelLogUpsertBulk) AddOrganizationID(v uint64) *LabelLogUpsertBulk {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.AddOrganizationID(v)
+	})
+}
+
+// UpdateOrganizationID sets the "organization_id" field to the value that was provided on create.
+func (u *LabelLogUpsertBulk) UpdateOrganizationID() *LabelLogUpsertBulk {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.UpdateOrganizationID()
+	})
+}
+
+// ClearOrganizationID clears the value of the "organization_id" field.
+func (u *LabelLogUpsertBulk) ClearOrganizationID() *LabelLogUpsertBulk {
+	return u.Update(func(s *LabelLogUpsert) {
+		s.ClearOrganizationID()
+	})
+}
+
+// Exec executes the query.
+func (u *LabelLogUpsertBulk) Exec(ctx context.Context) error {
+	if u.create.err != nil {
+		return u.create.err
+	}
+	for i, b := range u.create.builders {
+		if len(b.conflict) != 0 {
+			return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the LabelLogCreateBulk instead", i)
+		}
+	}
+	if len(u.create.conflict) == 0 {
+		return errors.New("ent: missing options for LabelLogCreateBulk.OnConflict")
+	}
+	return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *LabelLogUpsertBulk) ExecX(ctx context.Context) {
+	if err := u.create.Exec(ctx); err != nil {
+		panic(err)
+	}
+}

+ 88 - 0
ent/labellog_delete.go

@@ -0,0 +1,88 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ent
+
+import (
+	"context"
+	"wechat-api/ent/labellog"
+	"wechat-api/ent/predicate"
+
+	"entgo.io/ent/dialect/sql"
+	"entgo.io/ent/dialect/sql/sqlgraph"
+	"entgo.io/ent/schema/field"
+)
+
+// LabelLogDelete is the builder for deleting a LabelLog entity.
+type LabelLogDelete struct {
+	config
+	hooks    []Hook
+	mutation *LabelLogMutation
+}
+
+// Where appends a list predicates to the LabelLogDelete builder.
+func (lld *LabelLogDelete) Where(ps ...predicate.LabelLog) *LabelLogDelete {
+	lld.mutation.Where(ps...)
+	return lld
+}
+
+// Exec executes the deletion query and returns how many vertices were deleted.
+func (lld *LabelLogDelete) Exec(ctx context.Context) (int, error) {
+	return withHooks(ctx, lld.sqlExec, lld.mutation, lld.hooks)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (lld *LabelLogDelete) ExecX(ctx context.Context) int {
+	n, err := lld.Exec(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return n
+}
+
+func (lld *LabelLogDelete) sqlExec(ctx context.Context) (int, error) {
+	_spec := sqlgraph.NewDeleteSpec(labellog.Table, sqlgraph.NewFieldSpec(labellog.FieldID, field.TypeUint64))
+	if ps := lld.mutation.predicates; len(ps) > 0 {
+		_spec.Predicate = func(selector *sql.Selector) {
+			for i := range ps {
+				ps[i](selector)
+			}
+		}
+	}
+	affected, err := sqlgraph.DeleteNodes(ctx, lld.driver, _spec)
+	if err != nil && sqlgraph.IsConstraintError(err) {
+		err = &ConstraintError{msg: err.Error(), wrap: err}
+	}
+	lld.mutation.done = true
+	return affected, err
+}
+
+// LabelLogDeleteOne is the builder for deleting a single LabelLog entity.
+type LabelLogDeleteOne struct {
+	lld *LabelLogDelete
+}
+
+// Where appends a list predicates to the LabelLogDelete builder.
+func (lldo *LabelLogDeleteOne) Where(ps ...predicate.LabelLog) *LabelLogDeleteOne {
+	lldo.lld.mutation.Where(ps...)
+	return lldo
+}
+
+// Exec executes the deletion query.
+func (lldo *LabelLogDeleteOne) Exec(ctx context.Context) error {
+	n, err := lldo.lld.Exec(ctx)
+	switch {
+	case err != nil:
+		return err
+	case n == 0:
+		return &NotFoundError{labellog.Label}
+	default:
+		return nil
+	}
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (lldo *LabelLogDeleteOne) ExecX(ctx context.Context) {
+	if err := lldo.Exec(ctx); err != nil {
+		panic(err)
+	}
+}

+ 526 - 0
ent/labellog_query.go

@@ -0,0 +1,526 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ent
+
+import (
+	"context"
+	"fmt"
+	"math"
+	"wechat-api/ent/labellog"
+	"wechat-api/ent/predicate"
+
+	"entgo.io/ent/dialect/sql"
+	"entgo.io/ent/dialect/sql/sqlgraph"
+	"entgo.io/ent/schema/field"
+)
+
+// LabelLogQuery is the builder for querying LabelLog entities.
+type LabelLogQuery struct {
+	config
+	ctx        *QueryContext
+	order      []labellog.OrderOption
+	inters     []Interceptor
+	predicates []predicate.LabelLog
+	// intermediate query (i.e. traversal path).
+	sql  *sql.Selector
+	path func(context.Context) (*sql.Selector, error)
+}
+
+// Where adds a new predicate for the LabelLogQuery builder.
+func (llq *LabelLogQuery) Where(ps ...predicate.LabelLog) *LabelLogQuery {
+	llq.predicates = append(llq.predicates, ps...)
+	return llq
+}
+
+// Limit the number of records to be returned by this query.
+func (llq *LabelLogQuery) Limit(limit int) *LabelLogQuery {
+	llq.ctx.Limit = &limit
+	return llq
+}
+
+// Offset to start from.
+func (llq *LabelLogQuery) Offset(offset int) *LabelLogQuery {
+	llq.ctx.Offset = &offset
+	return llq
+}
+
+// Unique configures the query builder to filter duplicate records on query.
+// By default, unique is set to true, and can be disabled using this method.
+func (llq *LabelLogQuery) Unique(unique bool) *LabelLogQuery {
+	llq.ctx.Unique = &unique
+	return llq
+}
+
+// Order specifies how the records should be ordered.
+func (llq *LabelLogQuery) Order(o ...labellog.OrderOption) *LabelLogQuery {
+	llq.order = append(llq.order, o...)
+	return llq
+}
+
+// First returns the first LabelLog entity from the query.
+// Returns a *NotFoundError when no LabelLog was found.
+func (llq *LabelLogQuery) First(ctx context.Context) (*LabelLog, error) {
+	nodes, err := llq.Limit(1).All(setContextOp(ctx, llq.ctx, "First"))
+	if err != nil {
+		return nil, err
+	}
+	if len(nodes) == 0 {
+		return nil, &NotFoundError{labellog.Label}
+	}
+	return nodes[0], nil
+}
+
+// FirstX is like First, but panics if an error occurs.
+func (llq *LabelLogQuery) FirstX(ctx context.Context) *LabelLog {
+	node, err := llq.First(ctx)
+	if err != nil && !IsNotFound(err) {
+		panic(err)
+	}
+	return node
+}
+
+// FirstID returns the first LabelLog ID from the query.
+// Returns a *NotFoundError when no LabelLog ID was found.
+func (llq *LabelLogQuery) FirstID(ctx context.Context) (id uint64, err error) {
+	var ids []uint64
+	if ids, err = llq.Limit(1).IDs(setContextOp(ctx, llq.ctx, "FirstID")); err != nil {
+		return
+	}
+	if len(ids) == 0 {
+		err = &NotFoundError{labellog.Label}
+		return
+	}
+	return ids[0], nil
+}
+
+// FirstIDX is like FirstID, but panics if an error occurs.
+func (llq *LabelLogQuery) FirstIDX(ctx context.Context) uint64 {
+	id, err := llq.FirstID(ctx)
+	if err != nil && !IsNotFound(err) {
+		panic(err)
+	}
+	return id
+}
+
+// Only returns a single LabelLog entity found by the query, ensuring it only returns one.
+// Returns a *NotSingularError when more than one LabelLog entity is found.
+// Returns a *NotFoundError when no LabelLog entities are found.
+func (llq *LabelLogQuery) Only(ctx context.Context) (*LabelLog, error) {
+	nodes, err := llq.Limit(2).All(setContextOp(ctx, llq.ctx, "Only"))
+	if err != nil {
+		return nil, err
+	}
+	switch len(nodes) {
+	case 1:
+		return nodes[0], nil
+	case 0:
+		return nil, &NotFoundError{labellog.Label}
+	default:
+		return nil, &NotSingularError{labellog.Label}
+	}
+}
+
+// OnlyX is like Only, but panics if an error occurs.
+func (llq *LabelLogQuery) OnlyX(ctx context.Context) *LabelLog {
+	node, err := llq.Only(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return node
+}
+
+// OnlyID is like Only, but returns the only LabelLog ID in the query.
+// Returns a *NotSingularError when more than one LabelLog ID is found.
+// Returns a *NotFoundError when no entities are found.
+func (llq *LabelLogQuery) OnlyID(ctx context.Context) (id uint64, err error) {
+	var ids []uint64
+	if ids, err = llq.Limit(2).IDs(setContextOp(ctx, llq.ctx, "OnlyID")); err != nil {
+		return
+	}
+	switch len(ids) {
+	case 1:
+		id = ids[0]
+	case 0:
+		err = &NotFoundError{labellog.Label}
+	default:
+		err = &NotSingularError{labellog.Label}
+	}
+	return
+}
+
+// OnlyIDX is like OnlyID, but panics if an error occurs.
+func (llq *LabelLogQuery) OnlyIDX(ctx context.Context) uint64 {
+	id, err := llq.OnlyID(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return id
+}
+
+// All executes the query and returns a list of LabelLogs.
+func (llq *LabelLogQuery) All(ctx context.Context) ([]*LabelLog, error) {
+	ctx = setContextOp(ctx, llq.ctx, "All")
+	if err := llq.prepareQuery(ctx); err != nil {
+		return nil, err
+	}
+	qr := querierAll[[]*LabelLog, *LabelLogQuery]()
+	return withInterceptors[[]*LabelLog](ctx, llq, qr, llq.inters)
+}
+
+// AllX is like All, but panics if an error occurs.
+func (llq *LabelLogQuery) AllX(ctx context.Context) []*LabelLog {
+	nodes, err := llq.All(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return nodes
+}
+
+// IDs executes the query and returns a list of LabelLog IDs.
+func (llq *LabelLogQuery) IDs(ctx context.Context) (ids []uint64, err error) {
+	if llq.ctx.Unique == nil && llq.path != nil {
+		llq.Unique(true)
+	}
+	ctx = setContextOp(ctx, llq.ctx, "IDs")
+	if err = llq.Select(labellog.FieldID).Scan(ctx, &ids); err != nil {
+		return nil, err
+	}
+	return ids, nil
+}
+
+// IDsX is like IDs, but panics if an error occurs.
+func (llq *LabelLogQuery) IDsX(ctx context.Context) []uint64 {
+	ids, err := llq.IDs(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return ids
+}
+
+// Count returns the count of the given query.
+func (llq *LabelLogQuery) Count(ctx context.Context) (int, error) {
+	ctx = setContextOp(ctx, llq.ctx, "Count")
+	if err := llq.prepareQuery(ctx); err != nil {
+		return 0, err
+	}
+	return withInterceptors[int](ctx, llq, querierCount[*LabelLogQuery](), llq.inters)
+}
+
+// CountX is like Count, but panics if an error occurs.
+func (llq *LabelLogQuery) CountX(ctx context.Context) int {
+	count, err := llq.Count(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return count
+}
+
+// Exist returns true if the query has elements in the graph.
+func (llq *LabelLogQuery) Exist(ctx context.Context) (bool, error) {
+	ctx = setContextOp(ctx, llq.ctx, "Exist")
+	switch _, err := llq.FirstID(ctx); {
+	case IsNotFound(err):
+		return false, nil
+	case err != nil:
+		return false, fmt.Errorf("ent: check existence: %w", err)
+	default:
+		return true, nil
+	}
+}
+
+// ExistX is like Exist, but panics if an error occurs.
+func (llq *LabelLogQuery) ExistX(ctx context.Context) bool {
+	exist, err := llq.Exist(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return exist
+}
+
+// Clone returns a duplicate of the LabelLogQuery builder, including all associated steps. It can be
+// used to prepare common query builders and use them differently after the clone is made.
+func (llq *LabelLogQuery) Clone() *LabelLogQuery {
+	if llq == nil {
+		return nil
+	}
+	return &LabelLogQuery{
+		config:     llq.config,
+		ctx:        llq.ctx.Clone(),
+		order:      append([]labellog.OrderOption{}, llq.order...),
+		inters:     append([]Interceptor{}, llq.inters...),
+		predicates: append([]predicate.LabelLog{}, llq.predicates...),
+		// clone intermediate query.
+		sql:  llq.sql.Clone(),
+		path: llq.path,
+	}
+}
+
+// GroupBy is used to group vertices by one or more fields/columns.
+// It is often used with aggregate functions, like: count, max, mean, min, sum.
+//
+// Example:
+//
+//	var v []struct {
+//		CreatedAt time.Time `json:"created_at,omitempty"`
+//		Count int `json:"count,omitempty"`
+//	}
+//
+//	client.LabelLog.Query().
+//		GroupBy(labellog.FieldCreatedAt).
+//		Aggregate(ent.Count()).
+//		Scan(ctx, &v)
+func (llq *LabelLogQuery) GroupBy(field string, fields ...string) *LabelLogGroupBy {
+	llq.ctx.Fields = append([]string{field}, fields...)
+	grbuild := &LabelLogGroupBy{build: llq}
+	grbuild.flds = &llq.ctx.Fields
+	grbuild.label = labellog.Label
+	grbuild.scan = grbuild.Scan
+	return grbuild
+}
+
+// Select allows the selection one or more fields/columns for the given query,
+// instead of selecting all fields in the entity.
+//
+// Example:
+//
+//	var v []struct {
+//		CreatedAt time.Time `json:"created_at,omitempty"`
+//	}
+//
+//	client.LabelLog.Query().
+//		Select(labellog.FieldCreatedAt).
+//		Scan(ctx, &v)
+func (llq *LabelLogQuery) Select(fields ...string) *LabelLogSelect {
+	llq.ctx.Fields = append(llq.ctx.Fields, fields...)
+	sbuild := &LabelLogSelect{LabelLogQuery: llq}
+	sbuild.label = labellog.Label
+	sbuild.flds, sbuild.scan = &llq.ctx.Fields, sbuild.Scan
+	return sbuild
+}
+
+// Aggregate returns a LabelLogSelect configured with the given aggregations.
+func (llq *LabelLogQuery) Aggregate(fns ...AggregateFunc) *LabelLogSelect {
+	return llq.Select().Aggregate(fns...)
+}
+
+func (llq *LabelLogQuery) prepareQuery(ctx context.Context) error {
+	for _, inter := range llq.inters {
+		if inter == nil {
+			return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
+		}
+		if trv, ok := inter.(Traverser); ok {
+			if err := trv.Traverse(ctx, llq); err != nil {
+				return err
+			}
+		}
+	}
+	for _, f := range llq.ctx.Fields {
+		if !labellog.ValidColumn(f) {
+			return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
+		}
+	}
+	if llq.path != nil {
+		prev, err := llq.path(ctx)
+		if err != nil {
+			return err
+		}
+		llq.sql = prev
+	}
+	return nil
+}
+
+func (llq *LabelLogQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*LabelLog, error) {
+	var (
+		nodes = []*LabelLog{}
+		_spec = llq.querySpec()
+	)
+	_spec.ScanValues = func(columns []string) ([]any, error) {
+		return (*LabelLog).scanValues(nil, columns)
+	}
+	_spec.Assign = func(columns []string, values []any) error {
+		node := &LabelLog{config: llq.config}
+		nodes = append(nodes, node)
+		return node.assignValues(columns, values)
+	}
+	for i := range hooks {
+		hooks[i](ctx, _spec)
+	}
+	if err := sqlgraph.QueryNodes(ctx, llq.driver, _spec); err != nil {
+		return nil, err
+	}
+	if len(nodes) == 0 {
+		return nodes, nil
+	}
+	return nodes, nil
+}
+
+func (llq *LabelLogQuery) sqlCount(ctx context.Context) (int, error) {
+	_spec := llq.querySpec()
+	_spec.Node.Columns = llq.ctx.Fields
+	if len(llq.ctx.Fields) > 0 {
+		_spec.Unique = llq.ctx.Unique != nil && *llq.ctx.Unique
+	}
+	return sqlgraph.CountNodes(ctx, llq.driver, _spec)
+}
+
+func (llq *LabelLogQuery) querySpec() *sqlgraph.QuerySpec {
+	_spec := sqlgraph.NewQuerySpec(labellog.Table, labellog.Columns, sqlgraph.NewFieldSpec(labellog.FieldID, field.TypeUint64))
+	_spec.From = llq.sql
+	if unique := llq.ctx.Unique; unique != nil {
+		_spec.Unique = *unique
+	} else if llq.path != nil {
+		_spec.Unique = true
+	}
+	if fields := llq.ctx.Fields; len(fields) > 0 {
+		_spec.Node.Columns = make([]string, 0, len(fields))
+		_spec.Node.Columns = append(_spec.Node.Columns, labellog.FieldID)
+		for i := range fields {
+			if fields[i] != labellog.FieldID {
+				_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
+			}
+		}
+	}
+	if ps := llq.predicates; len(ps) > 0 {
+		_spec.Predicate = func(selector *sql.Selector) {
+			for i := range ps {
+				ps[i](selector)
+			}
+		}
+	}
+	if limit := llq.ctx.Limit; limit != nil {
+		_spec.Limit = *limit
+	}
+	if offset := llq.ctx.Offset; offset != nil {
+		_spec.Offset = *offset
+	}
+	if ps := llq.order; len(ps) > 0 {
+		_spec.Order = func(selector *sql.Selector) {
+			for i := range ps {
+				ps[i](selector)
+			}
+		}
+	}
+	return _spec
+}
+
+func (llq *LabelLogQuery) sqlQuery(ctx context.Context) *sql.Selector {
+	builder := sql.Dialect(llq.driver.Dialect())
+	t1 := builder.Table(labellog.Table)
+	columns := llq.ctx.Fields
+	if len(columns) == 0 {
+		columns = labellog.Columns
+	}
+	selector := builder.Select(t1.Columns(columns...)...).From(t1)
+	if llq.sql != nil {
+		selector = llq.sql
+		selector.Select(selector.Columns(columns...)...)
+	}
+	if llq.ctx.Unique != nil && *llq.ctx.Unique {
+		selector.Distinct()
+	}
+	for _, p := range llq.predicates {
+		p(selector)
+	}
+	for _, p := range llq.order {
+		p(selector)
+	}
+	if offset := llq.ctx.Offset; offset != nil {
+		// limit is mandatory for offset clause. We start
+		// with default value, and override it below if needed.
+		selector.Offset(*offset).Limit(math.MaxInt32)
+	}
+	if limit := llq.ctx.Limit; limit != nil {
+		selector.Limit(*limit)
+	}
+	return selector
+}
+
+// LabelLogGroupBy is the group-by builder for LabelLog entities.
+type LabelLogGroupBy struct {
+	selector
+	build *LabelLogQuery
+}
+
+// Aggregate adds the given aggregation functions to the group-by query.
+func (llgb *LabelLogGroupBy) Aggregate(fns ...AggregateFunc) *LabelLogGroupBy {
+	llgb.fns = append(llgb.fns, fns...)
+	return llgb
+}
+
+// Scan applies the selector query and scans the result into the given value.
+func (llgb *LabelLogGroupBy) Scan(ctx context.Context, v any) error {
+	ctx = setContextOp(ctx, llgb.build.ctx, "GroupBy")
+	if err := llgb.build.prepareQuery(ctx); err != nil {
+		return err
+	}
+	return scanWithInterceptors[*LabelLogQuery, *LabelLogGroupBy](ctx, llgb.build, llgb, llgb.build.inters, v)
+}
+
+func (llgb *LabelLogGroupBy) sqlScan(ctx context.Context, root *LabelLogQuery, v any) error {
+	selector := root.sqlQuery(ctx).Select()
+	aggregation := make([]string, 0, len(llgb.fns))
+	for _, fn := range llgb.fns {
+		aggregation = append(aggregation, fn(selector))
+	}
+	if len(selector.SelectedColumns()) == 0 {
+		columns := make([]string, 0, len(*llgb.flds)+len(llgb.fns))
+		for _, f := range *llgb.flds {
+			columns = append(columns, selector.C(f))
+		}
+		columns = append(columns, aggregation...)
+		selector.Select(columns...)
+	}
+	selector.GroupBy(selector.Columns(*llgb.flds...)...)
+	if err := selector.Err(); err != nil {
+		return err
+	}
+	rows := &sql.Rows{}
+	query, args := selector.Query()
+	if err := llgb.build.driver.Query(ctx, query, args, rows); err != nil {
+		return err
+	}
+	defer rows.Close()
+	return sql.ScanSlice(rows, v)
+}
+
+// LabelLogSelect is the builder for selecting fields of LabelLog entities.
+type LabelLogSelect struct {
+	*LabelLogQuery
+	selector
+}
+
+// Aggregate adds the given aggregation functions to the selector query.
+func (lls *LabelLogSelect) Aggregate(fns ...AggregateFunc) *LabelLogSelect {
+	lls.fns = append(lls.fns, fns...)
+	return lls
+}
+
+// Scan applies the selector query and scans the result into the given value.
+func (lls *LabelLogSelect) Scan(ctx context.Context, v any) error {
+	ctx = setContextOp(ctx, lls.ctx, "Select")
+	if err := lls.prepareQuery(ctx); err != nil {
+		return err
+	}
+	return scanWithInterceptors[*LabelLogQuery, *LabelLogSelect](ctx, lls.LabelLogQuery, lls, lls.inters, v)
+}
+
+func (lls *LabelLogSelect) sqlScan(ctx context.Context, root *LabelLogQuery, v any) error {
+	selector := root.sqlQuery(ctx)
+	aggregation := make([]string, 0, len(lls.fns))
+	for _, fn := range lls.fns {
+		aggregation = append(aggregation, fn(selector))
+	}
+	switch n := len(*lls.selector.flds); {
+	case n == 0 && len(aggregation) > 0:
+		selector.Select(aggregation...)
+	case n != 0 && len(aggregation) > 0:
+		selector.AppendSelect(aggregation...)
+	}
+	rows := &sql.Rows{}
+	query, args := selector.Query()
+	if err := lls.driver.Query(ctx, query, args, rows); err != nil {
+		return err
+	}
+	defer rows.Close()
+	return sql.ScanSlice(rows, v)
+}

+ 406 - 0
ent/labellog_update.go

@@ -0,0 +1,406 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ent
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"time"
+	"wechat-api/ent/labellog"
+	"wechat-api/ent/predicate"
+
+	"entgo.io/ent/dialect/sql"
+	"entgo.io/ent/dialect/sql/sqlgraph"
+	"entgo.io/ent/schema/field"
+)
+
+// LabelLogUpdate is the builder for updating LabelLog entities.
+type LabelLogUpdate struct {
+	config
+	hooks    []Hook
+	mutation *LabelLogMutation
+}
+
+// Where appends a list predicates to the LabelLogUpdate builder.
+func (llu *LabelLogUpdate) Where(ps ...predicate.LabelLog) *LabelLogUpdate {
+	llu.mutation.Where(ps...)
+	return llu
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (llu *LabelLogUpdate) SetUpdatedAt(t time.Time) *LabelLogUpdate {
+	llu.mutation.SetUpdatedAt(t)
+	return llu
+}
+
+// SetLabelName sets the "label_name" field.
+func (llu *LabelLogUpdate) SetLabelName(s string) *LabelLogUpdate {
+	llu.mutation.SetLabelName(s)
+	return llu
+}
+
+// SetNillableLabelName sets the "label_name" field if the given value is not nil.
+func (llu *LabelLogUpdate) SetNillableLabelName(s *string) *LabelLogUpdate {
+	if s != nil {
+		llu.SetLabelName(*s)
+	}
+	return llu
+}
+
+// SetLabelID sets the "label_id" field.
+func (llu *LabelLogUpdate) SetLabelID(i int) *LabelLogUpdate {
+	llu.mutation.ResetLabelID()
+	llu.mutation.SetLabelID(i)
+	return llu
+}
+
+// SetNillableLabelID sets the "label_id" field if the given value is not nil.
+func (llu *LabelLogUpdate) SetNillableLabelID(i *int) *LabelLogUpdate {
+	if i != nil {
+		llu.SetLabelID(*i)
+	}
+	return llu
+}
+
+// AddLabelID adds i to the "label_id" field.
+func (llu *LabelLogUpdate) AddLabelID(i int) *LabelLogUpdate {
+	llu.mutation.AddLabelID(i)
+	return llu
+}
+
+// SetWxID sets the "wx_id" field.
+func (llu *LabelLogUpdate) SetWxID(s string) *LabelLogUpdate {
+	llu.mutation.SetWxID(s)
+	return llu
+}
+
+// SetNillableWxID sets the "wx_id" field if the given value is not nil.
+func (llu *LabelLogUpdate) SetNillableWxID(s *string) *LabelLogUpdate {
+	if s != nil {
+		llu.SetWxID(*s)
+	}
+	return llu
+}
+
+// SetOrganizationID sets the "organization_id" field.
+func (llu *LabelLogUpdate) SetOrganizationID(u uint64) *LabelLogUpdate {
+	llu.mutation.ResetOrganizationID()
+	llu.mutation.SetOrganizationID(u)
+	return llu
+}
+
+// SetNillableOrganizationID sets the "organization_id" field if the given value is not nil.
+func (llu *LabelLogUpdate) SetNillableOrganizationID(u *uint64) *LabelLogUpdate {
+	if u != nil {
+		llu.SetOrganizationID(*u)
+	}
+	return llu
+}
+
+// AddOrganizationID adds u to the "organization_id" field.
+func (llu *LabelLogUpdate) AddOrganizationID(u int64) *LabelLogUpdate {
+	llu.mutation.AddOrganizationID(u)
+	return llu
+}
+
+// ClearOrganizationID clears the value of the "organization_id" field.
+func (llu *LabelLogUpdate) ClearOrganizationID() *LabelLogUpdate {
+	llu.mutation.ClearOrganizationID()
+	return llu
+}
+
+// Mutation returns the LabelLogMutation object of the builder.
+func (llu *LabelLogUpdate) Mutation() *LabelLogMutation {
+	return llu.mutation
+}
+
+// Save executes the query and returns the number of nodes affected by the update operation.
+func (llu *LabelLogUpdate) Save(ctx context.Context) (int, error) {
+	llu.defaults()
+	return withHooks(ctx, llu.sqlSave, llu.mutation, llu.hooks)
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (llu *LabelLogUpdate) SaveX(ctx context.Context) int {
+	affected, err := llu.Save(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return affected
+}
+
+// Exec executes the query.
+func (llu *LabelLogUpdate) Exec(ctx context.Context) error {
+	_, err := llu.Save(ctx)
+	return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (llu *LabelLogUpdate) ExecX(ctx context.Context) {
+	if err := llu.Exec(ctx); err != nil {
+		panic(err)
+	}
+}
+
+// defaults sets the default values of the builder before save.
+func (llu *LabelLogUpdate) defaults() {
+	if _, ok := llu.mutation.UpdatedAt(); !ok {
+		v := labellog.UpdateDefaultUpdatedAt()
+		llu.mutation.SetUpdatedAt(v)
+	}
+}
+
+func (llu *LabelLogUpdate) sqlSave(ctx context.Context) (n int, err error) {
+	_spec := sqlgraph.NewUpdateSpec(labellog.Table, labellog.Columns, sqlgraph.NewFieldSpec(labellog.FieldID, field.TypeUint64))
+	if ps := llu.mutation.predicates; len(ps) > 0 {
+		_spec.Predicate = func(selector *sql.Selector) {
+			for i := range ps {
+				ps[i](selector)
+			}
+		}
+	}
+	if value, ok := llu.mutation.UpdatedAt(); ok {
+		_spec.SetField(labellog.FieldUpdatedAt, field.TypeTime, value)
+	}
+	if value, ok := llu.mutation.LabelName(); ok {
+		_spec.SetField(labellog.FieldLabelName, field.TypeString, value)
+	}
+	if value, ok := llu.mutation.LabelID(); ok {
+		_spec.SetField(labellog.FieldLabelID, field.TypeInt, value)
+	}
+	if value, ok := llu.mutation.AddedLabelID(); ok {
+		_spec.AddField(labellog.FieldLabelID, field.TypeInt, value)
+	}
+	if value, ok := llu.mutation.WxID(); ok {
+		_spec.SetField(labellog.FieldWxID, field.TypeString, value)
+	}
+	if value, ok := llu.mutation.OrganizationID(); ok {
+		_spec.SetField(labellog.FieldOrganizationID, field.TypeUint64, value)
+	}
+	if value, ok := llu.mutation.AddedOrganizationID(); ok {
+		_spec.AddField(labellog.FieldOrganizationID, field.TypeUint64, value)
+	}
+	if llu.mutation.OrganizationIDCleared() {
+		_spec.ClearField(labellog.FieldOrganizationID, field.TypeUint64)
+	}
+	if n, err = sqlgraph.UpdateNodes(ctx, llu.driver, _spec); err != nil {
+		if _, ok := err.(*sqlgraph.NotFoundError); ok {
+			err = &NotFoundError{labellog.Label}
+		} else if sqlgraph.IsConstraintError(err) {
+			err = &ConstraintError{msg: err.Error(), wrap: err}
+		}
+		return 0, err
+	}
+	llu.mutation.done = true
+	return n, nil
+}
+
+// LabelLogUpdateOne is the builder for updating a single LabelLog entity.
+type LabelLogUpdateOne struct {
+	config
+	fields   []string
+	hooks    []Hook
+	mutation *LabelLogMutation
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (lluo *LabelLogUpdateOne) SetUpdatedAt(t time.Time) *LabelLogUpdateOne {
+	lluo.mutation.SetUpdatedAt(t)
+	return lluo
+}
+
+// SetLabelName sets the "label_name" field.
+func (lluo *LabelLogUpdateOne) SetLabelName(s string) *LabelLogUpdateOne {
+	lluo.mutation.SetLabelName(s)
+	return lluo
+}
+
+// SetNillableLabelName sets the "label_name" field if the given value is not nil.
+func (lluo *LabelLogUpdateOne) SetNillableLabelName(s *string) *LabelLogUpdateOne {
+	if s != nil {
+		lluo.SetLabelName(*s)
+	}
+	return lluo
+}
+
+// SetLabelID sets the "label_id" field.
+func (lluo *LabelLogUpdateOne) SetLabelID(i int) *LabelLogUpdateOne {
+	lluo.mutation.ResetLabelID()
+	lluo.mutation.SetLabelID(i)
+	return lluo
+}
+
+// SetNillableLabelID sets the "label_id" field if the given value is not nil.
+func (lluo *LabelLogUpdateOne) SetNillableLabelID(i *int) *LabelLogUpdateOne {
+	if i != nil {
+		lluo.SetLabelID(*i)
+	}
+	return lluo
+}
+
+// AddLabelID adds i to the "label_id" field.
+func (lluo *LabelLogUpdateOne) AddLabelID(i int) *LabelLogUpdateOne {
+	lluo.mutation.AddLabelID(i)
+	return lluo
+}
+
+// SetWxID sets the "wx_id" field.
+func (lluo *LabelLogUpdateOne) SetWxID(s string) *LabelLogUpdateOne {
+	lluo.mutation.SetWxID(s)
+	return lluo
+}
+
+// SetNillableWxID sets the "wx_id" field if the given value is not nil.
+func (lluo *LabelLogUpdateOne) SetNillableWxID(s *string) *LabelLogUpdateOne {
+	if s != nil {
+		lluo.SetWxID(*s)
+	}
+	return lluo
+}
+
+// SetOrganizationID sets the "organization_id" field.
+func (lluo *LabelLogUpdateOne) SetOrganizationID(u uint64) *LabelLogUpdateOne {
+	lluo.mutation.ResetOrganizationID()
+	lluo.mutation.SetOrganizationID(u)
+	return lluo
+}
+
+// SetNillableOrganizationID sets the "organization_id" field if the given value is not nil.
+func (lluo *LabelLogUpdateOne) SetNillableOrganizationID(u *uint64) *LabelLogUpdateOne {
+	if u != nil {
+		lluo.SetOrganizationID(*u)
+	}
+	return lluo
+}
+
+// AddOrganizationID adds u to the "organization_id" field.
+func (lluo *LabelLogUpdateOne) AddOrganizationID(u int64) *LabelLogUpdateOne {
+	lluo.mutation.AddOrganizationID(u)
+	return lluo
+}
+
+// ClearOrganizationID clears the value of the "organization_id" field.
+func (lluo *LabelLogUpdateOne) ClearOrganizationID() *LabelLogUpdateOne {
+	lluo.mutation.ClearOrganizationID()
+	return lluo
+}
+
+// Mutation returns the LabelLogMutation object of the builder.
+func (lluo *LabelLogUpdateOne) Mutation() *LabelLogMutation {
+	return lluo.mutation
+}
+
+// Where appends a list predicates to the LabelLogUpdate builder.
+func (lluo *LabelLogUpdateOne) Where(ps ...predicate.LabelLog) *LabelLogUpdateOne {
+	lluo.mutation.Where(ps...)
+	return lluo
+}
+
+// Select allows selecting one or more fields (columns) of the returned entity.
+// The default is selecting all fields defined in the entity schema.
+func (lluo *LabelLogUpdateOne) Select(field string, fields ...string) *LabelLogUpdateOne {
+	lluo.fields = append([]string{field}, fields...)
+	return lluo
+}
+
+// Save executes the query and returns the updated LabelLog entity.
+func (lluo *LabelLogUpdateOne) Save(ctx context.Context) (*LabelLog, error) {
+	lluo.defaults()
+	return withHooks(ctx, lluo.sqlSave, lluo.mutation, lluo.hooks)
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (lluo *LabelLogUpdateOne) SaveX(ctx context.Context) *LabelLog {
+	node, err := lluo.Save(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return node
+}
+
+// Exec executes the query on the entity.
+func (lluo *LabelLogUpdateOne) Exec(ctx context.Context) error {
+	_, err := lluo.Save(ctx)
+	return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (lluo *LabelLogUpdateOne) ExecX(ctx context.Context) {
+	if err := lluo.Exec(ctx); err != nil {
+		panic(err)
+	}
+}
+
+// defaults sets the default values of the builder before save.
+func (lluo *LabelLogUpdateOne) defaults() {
+	if _, ok := lluo.mutation.UpdatedAt(); !ok {
+		v := labellog.UpdateDefaultUpdatedAt()
+		lluo.mutation.SetUpdatedAt(v)
+	}
+}
+
+func (lluo *LabelLogUpdateOne) sqlSave(ctx context.Context) (_node *LabelLog, err error) {
+	_spec := sqlgraph.NewUpdateSpec(labellog.Table, labellog.Columns, sqlgraph.NewFieldSpec(labellog.FieldID, field.TypeUint64))
+	id, ok := lluo.mutation.ID()
+	if !ok {
+		return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "LabelLog.id" for update`)}
+	}
+	_spec.Node.ID.Value = id
+	if fields := lluo.fields; len(fields) > 0 {
+		_spec.Node.Columns = make([]string, 0, len(fields))
+		_spec.Node.Columns = append(_spec.Node.Columns, labellog.FieldID)
+		for _, f := range fields {
+			if !labellog.ValidColumn(f) {
+				return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
+			}
+			if f != labellog.FieldID {
+				_spec.Node.Columns = append(_spec.Node.Columns, f)
+			}
+		}
+	}
+	if ps := lluo.mutation.predicates; len(ps) > 0 {
+		_spec.Predicate = func(selector *sql.Selector) {
+			for i := range ps {
+				ps[i](selector)
+			}
+		}
+	}
+	if value, ok := lluo.mutation.UpdatedAt(); ok {
+		_spec.SetField(labellog.FieldUpdatedAt, field.TypeTime, value)
+	}
+	if value, ok := lluo.mutation.LabelName(); ok {
+		_spec.SetField(labellog.FieldLabelName, field.TypeString, value)
+	}
+	if value, ok := lluo.mutation.LabelID(); ok {
+		_spec.SetField(labellog.FieldLabelID, field.TypeInt, value)
+	}
+	if value, ok := lluo.mutation.AddedLabelID(); ok {
+		_spec.AddField(labellog.FieldLabelID, field.TypeInt, value)
+	}
+	if value, ok := lluo.mutation.WxID(); ok {
+		_spec.SetField(labellog.FieldWxID, field.TypeString, value)
+	}
+	if value, ok := lluo.mutation.OrganizationID(); ok {
+		_spec.SetField(labellog.FieldOrganizationID, field.TypeUint64, value)
+	}
+	if value, ok := lluo.mutation.AddedOrganizationID(); ok {
+		_spec.AddField(labellog.FieldOrganizationID, field.TypeUint64, value)
+	}
+	if lluo.mutation.OrganizationIDCleared() {
+		_spec.ClearField(labellog.FieldOrganizationID, field.TypeUint64)
+	}
+	_node = &LabelLog{config: lluo.config}
+	_spec.Assign = _node.assignValues
+	_spec.ScanValues = _node.scanValues
+	if err = sqlgraph.UpdateNode(ctx, lluo.driver, _spec); err != nil {
+		if _, ok := err.(*sqlgraph.NotFoundError); ok {
+			err = &NotFoundError{labellog.Label}
+		} else if sqlgraph.IsConstraintError(err) {
+			err = &ConstraintError{msg: err.Error(), wrap: err}
+		}
+		return nil, err
+	}
+	lluo.mutation.done = true
+	return _node, nil
+}

+ 32 - 0
ent/migrate/schema.go

@@ -566,6 +566,34 @@ var (
 			},
 		},
 	}
+	// LabelLogColumns holds the columns for the "label_log" table.
+	LabelLogColumns = []*schema.Column{
+		{Name: "id", Type: field.TypeUint64, Increment: true},
+		{Name: "created_at", Type: field.TypeTime, Comment: "Create Time | 创建日期"},
+		{Name: "updated_at", Type: field.TypeTime, Comment: "Update Time | 修改日期"},
+		{Name: "label_name", Type: field.TypeString, Comment: "标签名称", Default: ""},
+		{Name: "label_id", Type: field.TypeInt, Comment: "三方平台标签id", Default: 0},
+		{Name: "wx_id", Type: field.TypeString, Comment: "微信ID", Default: ""},
+		{Name: "organization_id", Type: field.TypeUint64, Nullable: true, Comment: "机构 ID", Default: 1},
+	}
+	// LabelLogTable holds the schema information for the "label_log" table.
+	LabelLogTable = &schema.Table{
+		Name:       "label_log",
+		Columns:    LabelLogColumns,
+		PrimaryKey: []*schema.Column{LabelLogColumns[0]},
+		Indexes: []*schema.Index{
+			{
+				Name:    "labellog_label_id",
+				Unique:  false,
+				Columns: []*schema.Column{LabelLogColumns[4]},
+			},
+			{
+				Name:    "idx_org_label",
+				Unique:  true,
+				Columns: []*schema.Column{LabelLogColumns[4], LabelLogColumns[6]},
+			},
+		},
+	}
 	// LabelRelationshipColumns holds the columns for the "label_relationship" table.
 	LabelRelationshipColumns = []*schema.Column{
 		{Name: "id", Type: field.TypeUint64, Increment: true},
@@ -1586,6 +1614,7 @@ var (
 		EmployeeTable,
 		EmployeeConfigTable,
 		LabelTable,
+		LabelLogTable,
 		LabelRelationshipTable,
 		LabelTaggingTable,
 		MessagesTable,
@@ -1674,6 +1703,9 @@ func init() {
 	LabelTable.Annotation = &entsql.Annotation{
 		Table: "label",
 	}
+	LabelLogTable.Annotation = &entsql.Annotation{
+		Table: "label_log",
+	}
 	LabelRelationshipTable.ForeignKeys[0].RefTable = ContactTable
 	LabelRelationshipTable.ForeignKeys[1].RefTable = LabelTable
 	LabelRelationshipTable.Annotation = &entsql.Annotation{

+ 696 - 0
ent/mutation.go

@@ -27,6 +27,7 @@ import (
 	"wechat-api/ent/employee"
 	"wechat-api/ent/employeeconfig"
 	"wechat-api/ent/label"
+	"wechat-api/ent/labellog"
 	"wechat-api/ent/labelrelationship"
 	"wechat-api/ent/labeltagging"
 	"wechat-api/ent/message"
@@ -88,6 +89,7 @@ const (
 	TypeEmployee             = "Employee"
 	TypeEmployeeConfig       = "EmployeeConfig"
 	TypeLabel                = "Label"
+	TypeLabelLog             = "LabelLog"
 	TypeLabelRelationship    = "LabelRelationship"
 	TypeLabelTagging         = "LabelTagging"
 	TypeMessage              = "Message"
@@ -22208,6 +22210,700 @@ func (m *LabelMutation) ResetEdge(name string) error {
 	return fmt.Errorf("unknown Label edge %s", name)
 }
 
+// LabelLogMutation represents an operation that mutates the LabelLog nodes in the graph.
+type LabelLogMutation struct {
+	config
+	op                 Op
+	typ                string
+	id                 *uint64
+	created_at         *time.Time
+	updated_at         *time.Time
+	label_name         *string
+	label_id           *int
+	addlabel_id        *int
+	wx_id              *string
+	organization_id    *uint64
+	addorganization_id *int64
+	clearedFields      map[string]struct{}
+	done               bool
+	oldValue           func(context.Context) (*LabelLog, error)
+	predicates         []predicate.LabelLog
+}
+
+var _ ent.Mutation = (*LabelLogMutation)(nil)
+
+// labellogOption allows management of the mutation configuration using functional options.
+type labellogOption func(*LabelLogMutation)
+
+// newLabelLogMutation creates new mutation for the LabelLog entity.
+func newLabelLogMutation(c config, op Op, opts ...labellogOption) *LabelLogMutation {
+	m := &LabelLogMutation{
+		config:        c,
+		op:            op,
+		typ:           TypeLabelLog,
+		clearedFields: make(map[string]struct{}),
+	}
+	for _, opt := range opts {
+		opt(m)
+	}
+	return m
+}
+
+// withLabelLogID sets the ID field of the mutation.
+func withLabelLogID(id uint64) labellogOption {
+	return func(m *LabelLogMutation) {
+		var (
+			err   error
+			once  sync.Once
+			value *LabelLog
+		)
+		m.oldValue = func(ctx context.Context) (*LabelLog, error) {
+			once.Do(func() {
+				if m.done {
+					err = errors.New("querying old values post mutation is not allowed")
+				} else {
+					value, err = m.Client().LabelLog.Get(ctx, id)
+				}
+			})
+			return value, err
+		}
+		m.id = &id
+	}
+}
+
+// withLabelLog sets the old LabelLog of the mutation.
+func withLabelLog(node *LabelLog) labellogOption {
+	return func(m *LabelLogMutation) {
+		m.oldValue = func(context.Context) (*LabelLog, error) {
+			return node, nil
+		}
+		m.id = &node.ID
+	}
+}
+
+// Client returns a new `ent.Client` from the mutation. If the mutation was
+// executed in a transaction (ent.Tx), a transactional client is returned.
+func (m LabelLogMutation) Client() *Client {
+	client := &Client{config: m.config}
+	client.init()
+	return client
+}
+
+// Tx returns an `ent.Tx` for mutations that were executed in transactions;
+// it returns an error otherwise.
+func (m LabelLogMutation) Tx() (*Tx, error) {
+	if _, ok := m.driver.(*txDriver); !ok {
+		return nil, errors.New("ent: mutation is not running in a transaction")
+	}
+	tx := &Tx{config: m.config}
+	tx.init()
+	return tx, nil
+}
+
+// SetID sets the value of the id field. Note that this
+// operation is only accepted on creation of LabelLog entities.
+func (m *LabelLogMutation) SetID(id uint64) {
+	m.id = &id
+}
+
+// ID returns the ID value in the mutation. Note that the ID is only available
+// if it was provided to the builder or after it was returned from the database.
+func (m *LabelLogMutation) ID() (id uint64, exists bool) {
+	if m.id == nil {
+		return
+	}
+	return *m.id, true
+}
+
+// IDs queries the database and returns the entity ids that match the mutation's predicate.
+// That means, if the mutation is applied within a transaction with an isolation level such
+// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
+// or updated by the mutation.
+func (m *LabelLogMutation) IDs(ctx context.Context) ([]uint64, error) {
+	switch {
+	case m.op.Is(OpUpdateOne | OpDeleteOne):
+		id, exists := m.ID()
+		if exists {
+			return []uint64{id}, nil
+		}
+		fallthrough
+	case m.op.Is(OpUpdate | OpDelete):
+		return m.Client().LabelLog.Query().Where(m.predicates...).IDs(ctx)
+	default:
+		return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
+	}
+}
+
+// SetCreatedAt sets the "created_at" field.
+func (m *LabelLogMutation) SetCreatedAt(t time.Time) {
+	m.created_at = &t
+}
+
+// CreatedAt returns the value of the "created_at" field in the mutation.
+func (m *LabelLogMutation) CreatedAt() (r time.Time, exists bool) {
+	v := m.created_at
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldCreatedAt returns the old "created_at" field's value of the LabelLog entity.
+// If the LabelLog object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *LabelLogMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldCreatedAt requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
+	}
+	return oldValue.CreatedAt, nil
+}
+
+// ResetCreatedAt resets all changes to the "created_at" field.
+func (m *LabelLogMutation) ResetCreatedAt() {
+	m.created_at = nil
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (m *LabelLogMutation) SetUpdatedAt(t time.Time) {
+	m.updated_at = &t
+}
+
+// UpdatedAt returns the value of the "updated_at" field in the mutation.
+func (m *LabelLogMutation) UpdatedAt() (r time.Time, exists bool) {
+	v := m.updated_at
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldUpdatedAt returns the old "updated_at" field's value of the LabelLog entity.
+// If the LabelLog object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *LabelLogMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldUpdatedAt requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err)
+	}
+	return oldValue.UpdatedAt, nil
+}
+
+// ResetUpdatedAt resets all changes to the "updated_at" field.
+func (m *LabelLogMutation) ResetUpdatedAt() {
+	m.updated_at = nil
+}
+
+// SetLabelName sets the "label_name" field.
+func (m *LabelLogMutation) SetLabelName(s string) {
+	m.label_name = &s
+}
+
+// LabelName returns the value of the "label_name" field in the mutation.
+func (m *LabelLogMutation) LabelName() (r string, exists bool) {
+	v := m.label_name
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldLabelName returns the old "label_name" field's value of the LabelLog entity.
+// If the LabelLog object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *LabelLogMutation) OldLabelName(ctx context.Context) (v string, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldLabelName is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldLabelName requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldLabelName: %w", err)
+	}
+	return oldValue.LabelName, nil
+}
+
+// ResetLabelName resets all changes to the "label_name" field.
+func (m *LabelLogMutation) ResetLabelName() {
+	m.label_name = nil
+}
+
+// SetLabelID sets the "label_id" field.
+func (m *LabelLogMutation) SetLabelID(i int) {
+	m.label_id = &i
+	m.addlabel_id = nil
+}
+
+// LabelID returns the value of the "label_id" field in the mutation.
+func (m *LabelLogMutation) LabelID() (r int, exists bool) {
+	v := m.label_id
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldLabelID returns the old "label_id" field's value of the LabelLog entity.
+// If the LabelLog object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *LabelLogMutation) OldLabelID(ctx context.Context) (v int, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldLabelID is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldLabelID requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldLabelID: %w", err)
+	}
+	return oldValue.LabelID, nil
+}
+
+// AddLabelID adds i to the "label_id" field.
+func (m *LabelLogMutation) AddLabelID(i int) {
+	if m.addlabel_id != nil {
+		*m.addlabel_id += i
+	} else {
+		m.addlabel_id = &i
+	}
+}
+
+// AddedLabelID returns the value that was added to the "label_id" field in this mutation.
+func (m *LabelLogMutation) AddedLabelID() (r int, exists bool) {
+	v := m.addlabel_id
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// ResetLabelID resets all changes to the "label_id" field.
+func (m *LabelLogMutation) ResetLabelID() {
+	m.label_id = nil
+	m.addlabel_id = nil
+}
+
+// SetWxID sets the "wx_id" field.
+func (m *LabelLogMutation) SetWxID(s string) {
+	m.wx_id = &s
+}
+
+// WxID returns the value of the "wx_id" field in the mutation.
+func (m *LabelLogMutation) WxID() (r string, exists bool) {
+	v := m.wx_id
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldWxID returns the old "wx_id" field's value of the LabelLog entity.
+// If the LabelLog object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *LabelLogMutation) OldWxID(ctx context.Context) (v string, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldWxID is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldWxID requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldWxID: %w", err)
+	}
+	return oldValue.WxID, nil
+}
+
+// ResetWxID resets all changes to the "wx_id" field.
+func (m *LabelLogMutation) ResetWxID() {
+	m.wx_id = nil
+}
+
+// SetOrganizationID sets the "organization_id" field.
+func (m *LabelLogMutation) SetOrganizationID(u uint64) {
+	m.organization_id = &u
+	m.addorganization_id = nil
+}
+
+// OrganizationID returns the value of the "organization_id" field in the mutation.
+func (m *LabelLogMutation) OrganizationID() (r uint64, exists bool) {
+	v := m.organization_id
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldOrganizationID returns the old "organization_id" field's value of the LabelLog entity.
+// If the LabelLog object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *LabelLogMutation) OldOrganizationID(ctx context.Context) (v uint64, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldOrganizationID is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldOrganizationID requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldOrganizationID: %w", err)
+	}
+	return oldValue.OrganizationID, nil
+}
+
+// AddOrganizationID adds u to the "organization_id" field.
+func (m *LabelLogMutation) AddOrganizationID(u int64) {
+	if m.addorganization_id != nil {
+		*m.addorganization_id += u
+	} else {
+		m.addorganization_id = &u
+	}
+}
+
+// AddedOrganizationID returns the value that was added to the "organization_id" field in this mutation.
+func (m *LabelLogMutation) AddedOrganizationID() (r int64, exists bool) {
+	v := m.addorganization_id
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// ClearOrganizationID clears the value of the "organization_id" field.
+func (m *LabelLogMutation) ClearOrganizationID() {
+	m.organization_id = nil
+	m.addorganization_id = nil
+	m.clearedFields[labellog.FieldOrganizationID] = struct{}{}
+}
+
+// OrganizationIDCleared returns if the "organization_id" field was cleared in this mutation.
+func (m *LabelLogMutation) OrganizationIDCleared() bool {
+	_, ok := m.clearedFields[labellog.FieldOrganizationID]
+	return ok
+}
+
+// ResetOrganizationID resets all changes to the "organization_id" field.
+func (m *LabelLogMutation) ResetOrganizationID() {
+	m.organization_id = nil
+	m.addorganization_id = nil
+	delete(m.clearedFields, labellog.FieldOrganizationID)
+}
+
+// Where appends a list predicates to the LabelLogMutation builder.
+func (m *LabelLogMutation) Where(ps ...predicate.LabelLog) {
+	m.predicates = append(m.predicates, ps...)
+}
+
+// WhereP appends storage-level predicates to the LabelLogMutation builder. Using this method,
+// users can use type-assertion to append predicates that do not depend on any generated package.
+func (m *LabelLogMutation) WhereP(ps ...func(*sql.Selector)) {
+	p := make([]predicate.LabelLog, len(ps))
+	for i := range ps {
+		p[i] = ps[i]
+	}
+	m.Where(p...)
+}
+
+// Op returns the operation name.
+func (m *LabelLogMutation) Op() Op {
+	return m.op
+}
+
+// SetOp allows setting the mutation operation.
+func (m *LabelLogMutation) SetOp(op Op) {
+	m.op = op
+}
+
+// Type returns the node type of this mutation (LabelLog).
+func (m *LabelLogMutation) Type() string {
+	return m.typ
+}
+
+// Fields returns all fields that were changed during this mutation. Note that in
+// order to get all numeric fields that were incremented/decremented, call
+// AddedFields().
+func (m *LabelLogMutation) Fields() []string {
+	fields := make([]string, 0, 6)
+	if m.created_at != nil {
+		fields = append(fields, labellog.FieldCreatedAt)
+	}
+	if m.updated_at != nil {
+		fields = append(fields, labellog.FieldUpdatedAt)
+	}
+	if m.label_name != nil {
+		fields = append(fields, labellog.FieldLabelName)
+	}
+	if m.label_id != nil {
+		fields = append(fields, labellog.FieldLabelID)
+	}
+	if m.wx_id != nil {
+		fields = append(fields, labellog.FieldWxID)
+	}
+	if m.organization_id != nil {
+		fields = append(fields, labellog.FieldOrganizationID)
+	}
+	return fields
+}
+
+// Field returns the value of a field with the given name. The second boolean
+// return value indicates that this field was not set, or was not defined in the
+// schema.
+func (m *LabelLogMutation) Field(name string) (ent.Value, bool) {
+	switch name {
+	case labellog.FieldCreatedAt:
+		return m.CreatedAt()
+	case labellog.FieldUpdatedAt:
+		return m.UpdatedAt()
+	case labellog.FieldLabelName:
+		return m.LabelName()
+	case labellog.FieldLabelID:
+		return m.LabelID()
+	case labellog.FieldWxID:
+		return m.WxID()
+	case labellog.FieldOrganizationID:
+		return m.OrganizationID()
+	}
+	return nil, false
+}
+
+// OldField returns the old value of the field from the database. An error is
+// returned if the mutation operation is not UpdateOne, or the query to the
+// database failed.
+func (m *LabelLogMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
+	switch name {
+	case labellog.FieldCreatedAt:
+		return m.OldCreatedAt(ctx)
+	case labellog.FieldUpdatedAt:
+		return m.OldUpdatedAt(ctx)
+	case labellog.FieldLabelName:
+		return m.OldLabelName(ctx)
+	case labellog.FieldLabelID:
+		return m.OldLabelID(ctx)
+	case labellog.FieldWxID:
+		return m.OldWxID(ctx)
+	case labellog.FieldOrganizationID:
+		return m.OldOrganizationID(ctx)
+	}
+	return nil, fmt.Errorf("unknown LabelLog field %s", name)
+}
+
+// SetField sets the value of a field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *LabelLogMutation) SetField(name string, value ent.Value) error {
+	switch name {
+	case labellog.FieldCreatedAt:
+		v, ok := value.(time.Time)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetCreatedAt(v)
+		return nil
+	case labellog.FieldUpdatedAt:
+		v, ok := value.(time.Time)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetUpdatedAt(v)
+		return nil
+	case labellog.FieldLabelName:
+		v, ok := value.(string)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetLabelName(v)
+		return nil
+	case labellog.FieldLabelID:
+		v, ok := value.(int)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetLabelID(v)
+		return nil
+	case labellog.FieldWxID:
+		v, ok := value.(string)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetWxID(v)
+		return nil
+	case labellog.FieldOrganizationID:
+		v, ok := value.(uint64)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetOrganizationID(v)
+		return nil
+	}
+	return fmt.Errorf("unknown LabelLog field %s", name)
+}
+
+// AddedFields returns all numeric fields that were incremented/decremented during
+// this mutation.
+func (m *LabelLogMutation) AddedFields() []string {
+	var fields []string
+	if m.addlabel_id != nil {
+		fields = append(fields, labellog.FieldLabelID)
+	}
+	if m.addorganization_id != nil {
+		fields = append(fields, labellog.FieldOrganizationID)
+	}
+	return fields
+}
+
+// AddedField returns the numeric value that was incremented/decremented on a field
+// with the given name. The second boolean return value indicates that this field
+// was not set, or was not defined in the schema.
+func (m *LabelLogMutation) AddedField(name string) (ent.Value, bool) {
+	switch name {
+	case labellog.FieldLabelID:
+		return m.AddedLabelID()
+	case labellog.FieldOrganizationID:
+		return m.AddedOrganizationID()
+	}
+	return nil, false
+}
+
+// AddField adds the value to the field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *LabelLogMutation) AddField(name string, value ent.Value) error {
+	switch name {
+	case labellog.FieldLabelID:
+		v, ok := value.(int)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.AddLabelID(v)
+		return nil
+	case labellog.FieldOrganizationID:
+		v, ok := value.(int64)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.AddOrganizationID(v)
+		return nil
+	}
+	return fmt.Errorf("unknown LabelLog numeric field %s", name)
+}
+
+// ClearedFields returns all nullable fields that were cleared during this
+// mutation.
+func (m *LabelLogMutation) ClearedFields() []string {
+	var fields []string
+	if m.FieldCleared(labellog.FieldOrganizationID) {
+		fields = append(fields, labellog.FieldOrganizationID)
+	}
+	return fields
+}
+
+// FieldCleared returns a boolean indicating if a field with the given name was
+// cleared in this mutation.
+func (m *LabelLogMutation) FieldCleared(name string) bool {
+	_, ok := m.clearedFields[name]
+	return ok
+}
+
+// ClearField clears the value of the field with the given name. It returns an
+// error if the field is not defined in the schema.
+func (m *LabelLogMutation) ClearField(name string) error {
+	switch name {
+	case labellog.FieldOrganizationID:
+		m.ClearOrganizationID()
+		return nil
+	}
+	return fmt.Errorf("unknown LabelLog nullable field %s", name)
+}
+
+// ResetField resets all changes in the mutation for the field with the given name.
+// It returns an error if the field is not defined in the schema.
+func (m *LabelLogMutation) ResetField(name string) error {
+	switch name {
+	case labellog.FieldCreatedAt:
+		m.ResetCreatedAt()
+		return nil
+	case labellog.FieldUpdatedAt:
+		m.ResetUpdatedAt()
+		return nil
+	case labellog.FieldLabelName:
+		m.ResetLabelName()
+		return nil
+	case labellog.FieldLabelID:
+		m.ResetLabelID()
+		return nil
+	case labellog.FieldWxID:
+		m.ResetWxID()
+		return nil
+	case labellog.FieldOrganizationID:
+		m.ResetOrganizationID()
+		return nil
+	}
+	return fmt.Errorf("unknown LabelLog field %s", name)
+}
+
+// AddedEdges returns all edge names that were set/added in this mutation.
+func (m *LabelLogMutation) AddedEdges() []string {
+	edges := make([]string, 0, 0)
+	return edges
+}
+
+// AddedIDs returns all IDs (to other nodes) that were added for the given edge
+// name in this mutation.
+func (m *LabelLogMutation) AddedIDs(name string) []ent.Value {
+	return nil
+}
+
+// RemovedEdges returns all edge names that were removed in this mutation.
+func (m *LabelLogMutation) RemovedEdges() []string {
+	edges := make([]string, 0, 0)
+	return edges
+}
+
+// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
+// the given name in this mutation.
+func (m *LabelLogMutation) RemovedIDs(name string) []ent.Value {
+	return nil
+}
+
+// ClearedEdges returns all edge names that were cleared in this mutation.
+func (m *LabelLogMutation) ClearedEdges() []string {
+	edges := make([]string, 0, 0)
+	return edges
+}
+
+// EdgeCleared returns a boolean which indicates if the edge with the given name
+// was cleared in this mutation.
+func (m *LabelLogMutation) EdgeCleared(name string) bool {
+	return false
+}
+
+// ClearEdge clears the value of the edge with the given name. It returns an error
+// if that edge is not defined in the schema.
+func (m *LabelLogMutation) ClearEdge(name string) error {
+	return fmt.Errorf("unknown LabelLog unique edge %s", name)
+}
+
+// ResetEdge resets all changes to the edge with the given name in this mutation.
+// It returns an error if the edge is not defined in the schema.
+func (m *LabelLogMutation) ResetEdge(name string) error {
+	return fmt.Errorf("unknown LabelLog edge %s", name)
+}
+
 // LabelRelationshipMutation represents an operation that mutates the LabelRelationship nodes in the graph.
 type LabelRelationshipMutation struct {
 	config

+ 82 - 0
ent/pagination.go

@@ -23,6 +23,7 @@ import (
 	"wechat-api/ent/employee"
 	"wechat-api/ent/employeeconfig"
 	"wechat-api/ent/label"
+	"wechat-api/ent/labellog"
 	"wechat-api/ent/labelrelationship"
 	"wechat-api/ent/labeltagging"
 	"wechat-api/ent/message"
@@ -1557,6 +1558,87 @@ func (l *LabelQuery) Page(
 	return ret, nil
 }
 
+type LabelLogPager struct {
+	Order  labellog.OrderOption
+	Filter func(*LabelLogQuery) (*LabelLogQuery, error)
+}
+
+// LabelLogPaginateOption enables pagination customization.
+type LabelLogPaginateOption func(*LabelLogPager)
+
+// DefaultLabelLogOrder is the default ordering of LabelLog.
+var DefaultLabelLogOrder = Desc(labellog.FieldID)
+
+func newLabelLogPager(opts []LabelLogPaginateOption) (*LabelLogPager, error) {
+	pager := &LabelLogPager{}
+	for _, opt := range opts {
+		opt(pager)
+	}
+	if pager.Order == nil {
+		pager.Order = DefaultLabelLogOrder
+	}
+	return pager, nil
+}
+
+func (p *LabelLogPager) ApplyFilter(query *LabelLogQuery) (*LabelLogQuery, error) {
+	if p.Filter != nil {
+		return p.Filter(query)
+	}
+	return query, nil
+}
+
+// LabelLogPageList is LabelLog PageList result.
+type LabelLogPageList struct {
+	List        []*LabelLog  `json:"list"`
+	PageDetails *PageDetails `json:"pageDetails"`
+}
+
+func (ll *LabelLogQuery) Page(
+	ctx context.Context, pageNum uint64, pageSize uint64, opts ...LabelLogPaginateOption,
+) (*LabelLogPageList, error) {
+
+	pager, err := newLabelLogPager(opts)
+	if err != nil {
+		return nil, err
+	}
+
+	if ll, err = pager.ApplyFilter(ll); err != nil {
+		return nil, err
+	}
+
+	ret := &LabelLogPageList{}
+
+	ret.PageDetails = &PageDetails{
+		Page: pageNum,
+		Size: pageSize,
+	}
+
+	query := ll.Clone()
+	query.ctx.Fields = nil
+	count, err := query.Count(ctx)
+
+	if err != nil {
+		return nil, err
+	}
+
+	ret.PageDetails.Total = uint64(count)
+
+	if pager.Order != nil {
+		ll = ll.Order(pager.Order)
+	} else {
+		ll = ll.Order(DefaultLabelLogOrder)
+	}
+
+	ll = ll.Offset(int((pageNum - 1) * pageSize)).Limit(int(pageSize))
+	list, err := ll.All(ctx)
+	if err != nil {
+		return nil, err
+	}
+	ret.List = list
+
+	return ret, nil
+}
+
 type LabelRelationshipPager struct {
 	Order  labelrelationship.OrderOption
 	Filter func(*LabelRelationshipQuery) (*LabelRelationshipQuery, error)

+ 3 - 0
ent/predicate/predicate.go

@@ -60,6 +60,9 @@ type EmployeeConfig func(*sql.Selector)
 // Label is the predicate function for label builders.
 type Label func(*sql.Selector)
 
+// LabelLog is the predicate function for labellog builders.
+type LabelLog func(*sql.Selector)
+
 // LabelRelationship is the predicate function for labelrelationship builders.
 type LabelRelationship func(*sql.Selector)
 

+ 32 - 0
ent/runtime/runtime.go

@@ -22,6 +22,7 @@ import (
 	"wechat-api/ent/employee"
 	"wechat-api/ent/employeeconfig"
 	"wechat-api/ent/label"
+	"wechat-api/ent/labellog"
 	"wechat-api/ent/labelrelationship"
 	"wechat-api/ent/labeltagging"
 	"wechat-api/ent/message"
@@ -902,6 +903,37 @@ func init() {
 	labelDescOrganizationID := labelFields[5].Descriptor()
 	// label.DefaultOrganizationID holds the default value on creation for the organization_id field.
 	label.DefaultOrganizationID = labelDescOrganizationID.Default.(uint64)
+	labellogMixin := schema.LabelLog{}.Mixin()
+	labellogMixinFields0 := labellogMixin[0].Fields()
+	_ = labellogMixinFields0
+	labellogFields := schema.LabelLog{}.Fields()
+	_ = labellogFields
+	// labellogDescCreatedAt is the schema descriptor for created_at field.
+	labellogDescCreatedAt := labellogMixinFields0[1].Descriptor()
+	// labellog.DefaultCreatedAt holds the default value on creation for the created_at field.
+	labellog.DefaultCreatedAt = labellogDescCreatedAt.Default.(func() time.Time)
+	// labellogDescUpdatedAt is the schema descriptor for updated_at field.
+	labellogDescUpdatedAt := labellogMixinFields0[2].Descriptor()
+	// labellog.DefaultUpdatedAt holds the default value on creation for the updated_at field.
+	labellog.DefaultUpdatedAt = labellogDescUpdatedAt.Default.(func() time.Time)
+	// labellog.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
+	labellog.UpdateDefaultUpdatedAt = labellogDescUpdatedAt.UpdateDefault.(func() time.Time)
+	// labellogDescLabelName is the schema descriptor for label_name field.
+	labellogDescLabelName := labellogFields[0].Descriptor()
+	// labellog.DefaultLabelName holds the default value on creation for the label_name field.
+	labellog.DefaultLabelName = labellogDescLabelName.Default.(string)
+	// labellogDescLabelID is the schema descriptor for label_id field.
+	labellogDescLabelID := labellogFields[1].Descriptor()
+	// labellog.DefaultLabelID holds the default value on creation for the label_id field.
+	labellog.DefaultLabelID = labellogDescLabelID.Default.(int)
+	// labellogDescWxID is the schema descriptor for wx_id field.
+	labellogDescWxID := labellogFields[2].Descriptor()
+	// labellog.DefaultWxID holds the default value on creation for the wx_id field.
+	labellog.DefaultWxID = labellogDescWxID.Default.(string)
+	// labellogDescOrganizationID is the schema descriptor for organization_id field.
+	labellogDescOrganizationID := labellogFields[3].Descriptor()
+	// labellog.DefaultOrganizationID holds the default value on creation for the organization_id field.
+	labellog.DefaultOrganizationID = labellogDescOrganizationID.Default.(uint64)
 	labelrelationshipMixin := schema.LabelRelationship{}.Mixin()
 	labelrelationshipMixinFields0 := labelrelationshipMixin[0].Fields()
 	_ = labelrelationshipMixinFields0

+ 47 - 0
ent/schema/label_log.go

@@ -0,0 +1,47 @@
+package schema
+
+import (
+	"entgo.io/ent"
+	"entgo.io/ent/dialect/entsql"
+	"entgo.io/ent/schema"
+	"entgo.io/ent/schema/field"
+	"entgo.io/ent/schema/index"
+	"github.com/suyuan32/simple-admin-common/orm/ent/mixins"
+)
+
+type LabelLog struct {
+	ent.Schema
+}
+
+func (LabelLog) Fields() []ent.Field {
+	return []ent.Field{
+		field.String("label_name").Default("").Annotations(entsql.WithComments(true)).Comment("标签名称"),
+		field.Int("label_id").Default(0).Annotations(entsql.WithComments(true)).Comment("三方平台标签id"),
+		field.String("wx_id").Default("").Annotations(entsql.WithComments(true)).Comment("微信ID"),
+		field.Uint64("organization_id").Optional().Default(1).Comment("机构 ID").Annotations(entsql.WithComments(true)),
+	}
+}
+
+func (LabelLog) Mixin() []ent.Mixin {
+	return []ent.Mixin{
+		mixins.IDMixin{},
+	}
+}
+
+func (LabelLog) Indexes() []ent.Index {
+	return []ent.Index{
+		index.Fields("label_id"),
+		index.Fields("label_id", "organization_id").Unique().StorageKey("idx_org_label"),
+	}
+}
+
+func (LabelLog) Edges() []ent.Edge {
+	return []ent.Edge{}
+}
+
+func (LabelLog) Annotations() []schema.Annotation {
+	return []schema.Annotation{
+		entsql.WithComments(true),
+		entsql.Annotation{Table: "label_log"},
+	}
+}

+ 120 - 0
ent/set_not_nil.go

@@ -4904,6 +4904,126 @@ func (l *LabelCreate) SetNotNilOrganizationID(value *uint64) *LabelCreate {
 }
 
 // set field if value's pointer is not nil.
+func (ll *LabelLogUpdate) SetNotNilUpdatedAt(value *time.Time) *LabelLogUpdate {
+	if value != nil {
+		return ll.SetUpdatedAt(*value)
+	}
+	return ll
+}
+
+// set field if value's pointer is not nil.
+func (ll *LabelLogUpdateOne) SetNotNilUpdatedAt(value *time.Time) *LabelLogUpdateOne {
+	if value != nil {
+		return ll.SetUpdatedAt(*value)
+	}
+	return ll
+}
+
+// set field if value's pointer is not nil.
+func (ll *LabelLogCreate) SetNotNilUpdatedAt(value *time.Time) *LabelLogCreate {
+	if value != nil {
+		return ll.SetUpdatedAt(*value)
+	}
+	return ll
+}
+
+// set field if value's pointer is not nil.
+func (ll *LabelLogUpdate) SetNotNilLabelName(value *string) *LabelLogUpdate {
+	if value != nil {
+		return ll.SetLabelName(*value)
+	}
+	return ll
+}
+
+// set field if value's pointer is not nil.
+func (ll *LabelLogUpdateOne) SetNotNilLabelName(value *string) *LabelLogUpdateOne {
+	if value != nil {
+		return ll.SetLabelName(*value)
+	}
+	return ll
+}
+
+// set field if value's pointer is not nil.
+func (ll *LabelLogCreate) SetNotNilLabelName(value *string) *LabelLogCreate {
+	if value != nil {
+		return ll.SetLabelName(*value)
+	}
+	return ll
+}
+
+// set field if value's pointer is not nil.
+func (ll *LabelLogUpdate) SetNotNilLabelID(value *int) *LabelLogUpdate {
+	if value != nil {
+		return ll.SetLabelID(*value)
+	}
+	return ll
+}
+
+// set field if value's pointer is not nil.
+func (ll *LabelLogUpdateOne) SetNotNilLabelID(value *int) *LabelLogUpdateOne {
+	if value != nil {
+		return ll.SetLabelID(*value)
+	}
+	return ll
+}
+
+// set field if value's pointer is not nil.
+func (ll *LabelLogCreate) SetNotNilLabelID(value *int) *LabelLogCreate {
+	if value != nil {
+		return ll.SetLabelID(*value)
+	}
+	return ll
+}
+
+// set field if value's pointer is not nil.
+func (ll *LabelLogUpdate) SetNotNilWxID(value *string) *LabelLogUpdate {
+	if value != nil {
+		return ll.SetWxID(*value)
+	}
+	return ll
+}
+
+// set field if value's pointer is not nil.
+func (ll *LabelLogUpdateOne) SetNotNilWxID(value *string) *LabelLogUpdateOne {
+	if value != nil {
+		return ll.SetWxID(*value)
+	}
+	return ll
+}
+
+// set field if value's pointer is not nil.
+func (ll *LabelLogCreate) SetNotNilWxID(value *string) *LabelLogCreate {
+	if value != nil {
+		return ll.SetWxID(*value)
+	}
+	return ll
+}
+
+// set field if value's pointer is not nil.
+func (ll *LabelLogUpdate) SetNotNilOrganizationID(value *uint64) *LabelLogUpdate {
+	if value != nil {
+		return ll.SetOrganizationID(*value)
+	}
+	return ll
+}
+
+// set field if value's pointer is not nil.
+func (ll *LabelLogUpdateOne) SetNotNilOrganizationID(value *uint64) *LabelLogUpdateOne {
+	if value != nil {
+		return ll.SetOrganizationID(*value)
+	}
+	return ll
+}
+
+// set field if value's pointer is not nil.
+func (ll *LabelLogCreate) SetNotNilOrganizationID(value *uint64) *LabelLogCreate {
+	if value != nil {
+		return ll.SetOrganizationID(*value)
+	}
+	return ll
+}
+
+// set field if value's pointer is not nil.
 func (lr *LabelRelationshipUpdate) SetNotNilUpdatedAt(value *time.Time) *LabelRelationshipUpdate {
 	if value != nil {
 		return lr.SetUpdatedAt(*value)

+ 3 - 0
ent/tx.go

@@ -50,6 +50,8 @@ type Tx struct {
 	EmployeeConfig *EmployeeConfigClient
 	// Label is the client for interacting with the Label builders.
 	Label *LabelClient
+	// LabelLog is the client for interacting with the LabelLog builders.
+	LabelLog *LabelLogClient
 	// LabelRelationship is the client for interacting with the LabelRelationship builders.
 	LabelRelationship *LabelRelationshipClient
 	// LabelTagging is the client for interacting with the LabelTagging builders.
@@ -255,6 +257,7 @@ func (tx *Tx) init() {
 	tx.Employee = NewEmployeeClient(tx.config)
 	tx.EmployeeConfig = NewEmployeeConfigClient(tx.config)
 	tx.Label = NewLabelClient(tx.config)
+	tx.LabelLog = NewLabelLogClient(tx.config)
 	tx.LabelRelationship = NewLabelRelationshipClient(tx.config)
 	tx.LabelTagging = NewLabelTaggingClient(tx.config)
 	tx.Message = NewMessageClient(tx.config)

+ 0 - 6
go.mod

@@ -48,12 +48,6 @@ require github.com/invopop/jsonschema v0.13.0
 require github.com/google/uuid v1.6.0
 
 require (
-	github.com/invopop/jsonschema v0.13.0
-	github.com/openai/openai-go v0.1.0-beta.9
-//github.com/openai/openai-go v0.1.0-alpha.62
-)
-
-require (
 	ariga.io/atlas v0.19.2 // indirect
 	filippo.io/edwards25519 v1.1.0 // indirect
 	github.com/ArtisanCloud/PowerLibs/v2 v2.0.49 // indirect

+ 4 - 0
go.sum

@@ -480,6 +480,7 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
@@ -520,6 +521,7 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
 github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
@@ -621,6 +623,8 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
 github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
 github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
 github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
+github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
+github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=

+ 3 - 1
hook/message.go

@@ -27,7 +27,7 @@ func (h *Hook) ConfigureMsgRecive(isEnable string, url string) (err error) {
 }
 
 // SendTextMsg 发送微信文本消息
-func (h *Hook) SendTextMsg(wxid, msg, wxWxid string) error {
+func (h *Hook) SendTextMsg(wxid, msg, wxWxid string, msgId int64) error {
 	if h.ServerIp == "" || h.ServerIp == "0" {
 		conn, err := h.connWorkPhone()
 		if err != nil {
@@ -73,6 +73,7 @@ func (h *Hook) SendTextMsg(wxid, msg, wxWxid string) error {
 					"ConvId":      wxid,
 					"ContentType": "Text",
 					"Content":     encodedString,
+					"TaskId":      msgId,
 				},
 			}
 		} else {
@@ -83,6 +84,7 @@ func (h *Hook) SendTextMsg(wxid, msg, wxWxid string) error {
 					"FriendId":    wxid,
 					"ContentType": "Text",
 					"Content":     encodedString,
+					"MsgId":       msgId,
 				},
 			}
 		}

+ 44 - 0
internal/handler/chat/send_text_msg_handler.go

@@ -0,0 +1,44 @@
+package chat
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/chat"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /v1/wx/sendTextMsg chat SendTextMsg
+//
+
+//
+
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: SendTextMsgReq
+//
+// Responses:
+//  200: BaseMsgResp
+
+func SendTextMsgHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.SendTextMsgReq
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := chat.NewSendTextMsgLogic(r.Context(), svcCtx)
+		resp, err := l.SendTextMsg(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 13 - 0
internal/handler/routes.go

@@ -921,6 +921,19 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
 
 	server.AddRoutes(
 		rest.WithMiddlewares(
+			[]rest.Middleware{serverCtx.OpenAuthority},
+			[]rest.Route{
+				{
+					Method:  http.MethodPost,
+					Path:    "/wx/sendTextMsg",
+					Handler: chat.SendTextMsgHandler(serverCtx),
+				},
+			}...,
+		),
+	)
+
+	server.AddRoutes(
+		rest.WithMiddlewares(
 			[]rest.Middleware{serverCtx.Authority},
 			[]rest.Route{
 				{

+ 5 - 3
internal/logic/Wxhook/send_text_msg_logic.go

@@ -2,8 +2,10 @@ package Wxhook
 
 import (
 	"context"
+	"fmt"
 	"github.com/suyuan32/simple-admin-common/enum/errorcode"
 	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"time"
 	"wechat-api/ent/predicate"
 	"wechat-api/ent/wx"
 	"wechat-api/hook"
@@ -72,9 +74,9 @@ func (l *SendTextMsgLogic) SendTextMsg(req *types.SendTextMsgReq) (resp *types.B
 	} else {
 		hookClient = hook.NewHook(privateIP, adminPort, port)
 	}
-
-	err = hookClient.SendTextMsg(*req.Wxid, *req.Msg, wxInfo.Wxid)
-
+	msgId := time.Now().UnixNano() / int64(time.Microsecond)
+	l.svcCtx.Rds.Set(l.ctx, fmt.Sprintf("MsgId_FriendId:%d", msgId), *req.Wxid, 10*time.Minute)
+	err = hookClient.SendTextMsg(*req.Wxid, *req.Msg, wxInfo.Wxid, msgId)
 	if err != nil {
 		l.Errorf("发送微信文本消息失败:%v\n", err)
 		return nil, err

+ 3 - 2
internal/logic/batch_msg/get_batch_msg_by_id_logic.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"encoding/json"
 	"github.com/zeromicro/go-zero/core/errorx"
+	"time"
 	"wechat-api/ent"
 	"wechat-api/ent/batchmsg"
 
@@ -53,7 +54,7 @@ func (l *GetBatchMsgByIdLogic) GetBatchMsgById(req *types.IDReq) (*types.BatchMs
 	}
 	labels = tagMap["contact_tag"]
 	groupLables = tagMap["group_tag"]
-
+	loc, _ := time.LoadLocation("Asia/Shanghai")
 	return &types.BatchMsgInfoResp{
 		BaseDataInfo: types.BaseDataInfo{
 			Code: 0,
@@ -79,7 +80,7 @@ func (l *GetBatchMsgByIdLogic) GetBatchMsgById(req *types.IDReq) (*types.BatchMs
 			StartTime:    pointy.GetUnixMilliPointer(data.StartTime.UnixMilli()),
 			StopTime:     pointy.GetUnixMilliPointer(data.StopTime.UnixMilli()),
 			SendTime:     pointy.GetUnixMilliPointer(data.SendTime.UnixMilli()),
-			StartTimeStr: pointy.GetPointer(data.StartTime.Format("2006-01-02 15:04:05")),
+			StartTimeStr: pointy.GetPointer(data.StartTime.In(loc).Format("2006-01-02 15:04:05")),
 			Labels:       labels,
 			GroupLabels:  groupLables,
 		},

+ 4 - 2
internal/logic/batch_msg/get_batch_msg_list_logic.go

@@ -2,6 +2,7 @@ package batch_msg
 
 import (
 	"context"
+	"time"
 	"wechat-api/ent/wx"
 
 	"wechat-api/ent/batchmsg"
@@ -69,7 +70,8 @@ func (l *GetBatchMsgListLogic) GetBatchMsgList(req *types.BatchMsgListReq) (*typ
 		batchMsgWxId = append(batchMsgWxId, v.Fromwxid)
 	}
 	batchMsgWxName := l.getBatchMsgWxName(batchMsgWxId)
-
+	//本地时区
+	loc, _ := time.LoadLocation("Asia/Shanghai")
 	for _, v := range data.List {
 		fromName, ok := batchMsgWxName[v.Fromwxid]
 		if !ok {
@@ -96,7 +98,7 @@ func (l *GetBatchMsgListLogic) GetBatchMsgList(req *types.BatchMsgListReq) (*typ
 				Ctype:        &v.Ctype,
 				StartTime:    pointy.GetUnixMilliPointer(v.StartTime.UnixMilli()),
 				StopTime:     pointy.GetUnixMilliPointer(v.StopTime.UnixMilli()),
-				StartTimeStr: pointy.GetPointer(v.StartTime.Format("2006-01-02 15:04:05")),
+				StartTimeStr: pointy.GetPointer(v.StartTime.In(loc).Format("2006-01-02 15:04:05")),
 			})
 	}
 

+ 6 - 0
internal/logic/chat/chat_completions_logic.go

@@ -39,6 +39,10 @@ type MismatchChatLogic struct {
 	ChatCompletionsLogic
 }
 
+type FormChatLogic struct {
+	ChatCompletionsLogic
+}
+
 type baseLogicWorkflow interface {
 	AppendAsyncRequest(apiKeyObj *ent.ApiKey, req *types.CompApiReq) error
 	DoSyncRequest(apiKeyObj *ent.ApiKey, req *types.CompApiReq) (*types.CompOpenApiResp, error)
@@ -149,6 +153,8 @@ func (l *ChatCompletionsLogic) getLogicWorkflow(apiKeyObj *ent.ApiKey, req *type
 		err = fmt.Errorf("api agent type not support(%d)", apiKeyObj.Edges.Agent.Type)
 	} else if req.EventType == "mismatch" {
 		wf = &MismatchChatLogic{ChatCompletionsLogic: *l}
+	} else if req.EventType == "form" {
+		wf = &FormChatLogic{ChatCompletionsLogic: *l}
 	} else {
 		wf = &FastgptChatLogic{ChatCompletionsLogic: *l}
 	}

+ 111 - 0
internal/logic/chat/send_text_msg_logic.go

@@ -0,0 +1,111 @@
+package chat
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"time"
+
+	"wechat-api/ent"
+	"wechat-api/ent/predicate"
+	"wechat-api/ent/wx"
+	"wechat-api/hook"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+	"wechat-api/internal/utils/contextkey"
+
+	"github.com/suyuan32/simple-admin-common/enum/errorcode"
+	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type SendTextMsgLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewSendTextMsgLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendTextMsgLogic {
+	return &SendTextMsgLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *SendTextMsgLogic) SendTextMsg(req *types.SendTextMsgReq) (resp *types.BaseMsgResp, err error) {
+	// todo: add your logic here and delete this line
+
+	var (
+		apiKeyObj *ent.ApiKey
+		ok        bool
+	)
+
+	//从上下文中获取鉴权中间件埋下的apiAuthInfo
+	apiKeyObj, ok = contextkey.AuthTokenInfoKey.GetValue(l.ctx)
+	if !ok {
+		return nil, errors.New("content get auth info err")
+	}
+
+	//根据wx实体的wxid查询
+	var predicates []predicate.Wx
+	if req.WxWxid != nil {
+		predicates = append(predicates, wx.WxidContains(*req.WxWxid))
+	}
+
+	wxInfo, err := l.svcCtx.DB.Wx.Query().Where(predicates...).Only(l.ctx)
+
+	//根据wx实体的主键ID查询
+	//wxInfo, err := l.svcCtx.DB.Wx.Get(l.ctx, *req.AgentWxId)
+
+	l.Infof("wxInfo = %v", wxInfo)
+
+	if err != nil {
+		l.Error("查询微信信息失败", err)
+		return
+	}
+	if wxInfo.OrganizationID != apiKeyObj.OrganizationID {
+		return nil, errors.New("OID不一致")
+	}
+
+	privateIP := ""
+	adminPort := ""
+	port := ""
+
+	if wxInfo.ServerID != 0 {
+		serverInfo, err := l.svcCtx.DB.Server.Get(l.ctx, wxInfo.ServerID)
+		if err != nil {
+			l.Error("查询服务器信息失败", err)
+			return nil, err
+		}
+		privateIP = serverInfo.PrivateIP
+		adminPort = serverInfo.AdminPort
+		port = wxInfo.Port
+	}
+
+	var ctype uint64
+	if req.Ctype != nil && *req.Ctype != 0 {
+		ctype = *req.Ctype
+	}
+
+	var hookClient *hook.Hook
+	if ctype == 3 {
+		hookClient = hook.NewWecomHook("", adminPort, port)
+	} else {
+		hookClient = hook.NewHook(privateIP, adminPort, port)
+	}
+	msgId := time.Now().UnixNano() / int64(time.Microsecond)
+	l.svcCtx.Rds.Set(l.ctx, fmt.Sprintf("MsgId_FriendId:%d", msgId), *req.Wxid, 10*time.Minute)
+	err = hookClient.SendTextMsg(*req.Wxid, *req.Msg, wxInfo.Wxid, msgId)
+
+	if err != nil {
+		l.Errorf("发送微信文本消息失败:%v\n", err)
+		return nil, err
+	}
+
+	resp = &types.BaseMsgResp{
+		Msg:  errormsg.Success,
+		Code: errorcode.OK,
+	}
+
+	return resp, nil
+}

+ 6 - 6
internal/logic/contact/get_contact_list_logic.go

@@ -58,22 +58,22 @@ func (l *GetContactListLogic) GetContactList(req *types.ContactListReq) (*types.
 	if req.WxWxid == nil || (req.WxWxid != nil && !isAdmin) {
 		predicates = append(predicates, contact.OrganizationIDEQ(organizationId))
 	}
+	if req.Status == nil {
+		predicates = append(predicates, contact.StatusEQ(*req.Status))
+	}
 
 	layout := "2006-01-02 15:04:05"
 	var startTime, endTime *time.Time
 
-	if req.StartDate != nil && *req.StartDate != "" {
-		startStr := *req.StartDate + " 00:00:00"
+	if req.SearchDate != nil && len(req.SearchDate) == 2 {
+		startStr := *req.SearchDate[0] + " 00:00:00"
 		if t, err := time.Parse(layout, startStr); err == nil {
 			//t = t.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
 			startTime = &t
 		} else {
 			l.Logger.Errorf("invalid startDate: %v", err)
 		}
-	}
-
-	if req.EndDate != nil && *req.EndDate != "" {
-		endStr := *req.EndDate + " 23:59:59"
+		endStr := *req.SearchDate[1] + " 23:59:59"
 		if t, err := time.Parse(layout, endStr); err == nil {
 			//t = t.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
 			endTime = &t

+ 1 - 1
internal/logic/contact/update_contact_logic.go

@@ -37,7 +37,7 @@ func (l *UpdateContactLogic) UpdateContact(req *types.ContactInfo) (*types.BaseM
 	if req.Cage != nil && *req.Cage > 0 {
 		cage = *req.Cage
 	}
-	err = l.svcCtx.DB.Contact.UpdateOneID(*req.Id).
+	err = tx.Contact.UpdateOneID(*req.Id).
 		Where(contact.OrganizationID(organizationId)).
 		SetNotNilStatus(req.Status).
 		SetNotNilWxWxid(req.WxWxid).

+ 16 - 0
internal/logic/fastgpt/set_token_logic.go

@@ -39,6 +39,7 @@ func (l *SetTokenLogic) SetToken(username string) (resp *types.BaseMsgResp, err
 	data, err := l.svcCtx.CoreRpc.GetUserById(context.TODO(), &core.UUIDReq{Id: claims.UserId})
 	token, err := GetToken(strconv.FormatUint(*data.DepartmentId, 10))
 	if err != nil {
+		l.clearFastGPTCookie()
 		return nil, fmt.Errorf("invalid token3")
 	}
 	//if err != nil {
@@ -67,6 +68,21 @@ func (l *SetTokenLogic) SetToken(username string) (resp *types.BaseMsgResp, err
 	return
 }
 
+func (l *SetTokenLogic) clearFastGPTCookie() {
+	cookie := &http.Cookie{
+		Name:     "fastgpt_token",
+		Value:    "",
+		Domain:   ".gkscrm.com",
+		SameSite: http.SameSiteNoneMode,
+		Secure:   true,
+		HttpOnly: false,
+		Path:     "/",
+		MaxAge:   -1, // 删除 Cookie
+	}
+	http.SetCookie(l.rw, cookie)
+	l.Logger.Info("Cleared fastgpt_token cookie due to an error")
+}
+
 // SetTokenByUserId 根据用户ID设置token。
 // 参数:
 //

+ 2 - 0
internal/logic/message_records/get_message_records_list_logic.go

@@ -48,6 +48,8 @@ func (l *GetMessageRecordsListLogic) GetMessageRecordsList(req *types.MessageRec
 	}
 	if req.Status != nil {
 		predicates = append(predicates, messagerecords.StatusEQ(*req.Status))
+	} else {
+		predicates = append(predicates, messagerecords.StatusNEQ(0))
 	}
 	data, err := l.svcCtx.DB.MessageRecords.Query().Where(predicates...).Page(l.ctx, req.Page, req.PageSize)
 

+ 48 - 0
internal/service/MessageHandlers/chatroom_push_notice.go

@@ -65,3 +65,51 @@ func (f *ChatroomPushNoticeHandler) Handler(msg *wechat_ws.MsgJsonObject) error
 	}
 	return nil
 }
+
+type ChatroomPushNoticeTypeHandler struct {
+	svcCtx *svc.ServiceContext
+}
+
+func NewChatroomPushNoticeTypeHandler(svcCtx *svc.ServiceContext) *ChatroomPushNoticeTypeHandler {
+	return &ChatroomPushNoticeTypeHandler{
+		svcCtx: svcCtx,
+	}
+}
+
+// Handle 实现 MessageHandlerStrategy 接口
+func (c *ChatroomPushNoticeTypeHandler) Handle(ctx context.Context, msg *wechat_ws.MsgJsonObject, svcCtx *svc.ServiceContext) error {
+	message := workphone.ChatRoomPushNoticeMessage{}
+	logx.Infof("msg.Message 的内容是:%s", msg.Message)
+	err := json.Unmarshal([]byte(msg.Message), &message)
+	if err != nil {
+		return err
+	}
+	// 拿到租户 id
+	wxInfo, err := svcCtx.DB.Wx.Query().
+		Where(
+			wx.WxidEQ(message.WeChatId), // Additional filter by organizationId
+		).
+		Only(ctx)
+	hookClient := hook.NewHook("", "", "")
+	for _, friend := range message.ChatRooms {
+		friendType := 2
+		_ = hookClient.RequestChatRoomInfo(friend.UserName, message.WeChatId)
+		_, err = svcCtx.DB.Contact.Create().
+			SetWxWxid(message.WeChatId).
+			SetType(friendType).
+			SetWxid(friend.UserName).
+			SetNickname(friend.NickName).
+			SetHeadimg(friend.Avatar).
+			SetOrganizationID(wxInfo.OrganizationID).
+			OnConflict().
+			UpdateNewValues().
+			SetOrganizationID(wxInfo.OrganizationID).
+			ID(ctx)
+
+		if err != nil {
+			logx.Error("Contact.Create: ", wxInfo.OrganizationID)
+			return err
+		}
+	}
+	return nil
+}

+ 120 - 0
internal/service/MessageHandlers/contact_Label_info_notice.go

@@ -0,0 +1,120 @@
+package MessageHandlers
+
+import (
+	"context"
+	"encoding/json"
+	"entgo.io/ent/dialect/sql"
+	"github.com/zeromicro/go-zero/core/logx"
+	"strconv"
+	"time"
+	"wechat-api/ent"
+	"wechat-api/ent/wx"
+	"wechat-api/internal/pkg/wechat_ws"
+	"wechat-api/internal/svc"
+	"wechat-api/workphone"
+)
+
+type ContactLabelInfoNotice struct {
+	svcCtx *svc.ServiceContext
+}
+
+func NewContactLabelInfoNotice(svcCtx *svc.ServiceContext) *ContactLabelInfoNotice {
+	return &ContactLabelInfoNotice{
+		svcCtx: svcCtx,
+	}
+}
+
+// Handle 实现 MessageHandlerStrategy 接口
+func (f *ContactLabelInfoNotice) Handle(ctx context.Context, msg *wechat_ws.MsgJsonObject, svcCtx *svc.ServiceContext) error {
+	message := workphone.ContactLabelInfoNoticeMessage{}
+	logx.Infof("msg.Message 的内容是:%s", msg.Message)
+	err := json.Unmarshal([]byte(msg.Message), &message)
+	if err != nil {
+		return err
+	}
+	// 拿到租户 id
+	wxInfo, err := svcCtx.DB.Wx.Query().
+		Where(
+			wx.WxidEQ(message.WeChatId), // Additional filter by organizationId
+		).
+		Only(ctx)
+	if err != nil {
+		return err
+	}
+	var bulkCreates []*ent.LabelLogCreate
+	var labelCreates []*ent.LabelCreate
+
+	for _, label := range message.Labels {
+
+		labelIDSet := make(map[int]struct{})
+		labelIDSet[int(label.LabelId)] = struct{}{}
+		var labelIDs []int
+		for id := range labelIDSet {
+			labelIDs = append(labelIDs, id)
+		}
+		existingMap := make(map[int]struct{})
+		// 只拼接还没插入过的 label_id + organization_id 组合
+		if _, exists := existingMap[int(label.LabelId)]; exists {
+			logx.Error("label_log---已经存在的: ", wxInfo.OrganizationID, label.LabelId)
+			continue
+		}
+		tsInt, err := strconv.ParseInt(label.CreateTime, 10, 64)
+		if err != nil {
+			logx.Errorf("时间戳转换失败: %v", err)
+			continue
+		}
+		bulkCreates = append(bulkCreates,
+			svcCtx.DB.LabelLog.Create().
+				SetLabelName(label.LabelName).
+				SetLabelID(int(label.LabelId)).
+				SetOrganizationID(wxInfo.OrganizationID).
+				SetWxID(message.WeChatId).
+				SetCreatedAt(time.Unix(tsInt/1000, 0)),
+			//SetUpdatedAt(time.Now()),
+		)
+
+		//label 主表
+		labelCreates = append(labelCreates,
+			svcCtx.DB.Label.Create().
+				//SetID(int(label.LabelId)).
+				SetName(label.LabelName).
+				SetType(1).
+				SetStatus(1).
+				SetOrganizationID(wxInfo.OrganizationID).
+				SetFrom(2).
+				SetMode(2).
+				SetConditions(string(json.RawMessage(`{}`))).
+				SetCreatedAt(time.Now()).
+				SetUpdatedAt(time.Now()),
+		)
+
+		logx.Info("数据:", label.LabelName+"-----", label.LabelId, wxInfo.OrganizationID, message.WeChatId, time.Unix(tsInt/1000, 0))
+	}
+	//批量插入labelLog
+	if len(bulkCreates) > 0 {
+		err := svcCtx.DB.LabelLog.CreateBulk(bulkCreates...).
+			OnConflict(
+				sql.ConflictColumns("label_id", "organization_id"),
+			).
+			DoNothing().
+			Exec(ctx)
+		if err != nil {
+			//logx.Error("labelLog 批量插入失败", bulkCreates)
+			//return err
+		}
+	}
+	//批量插入label
+	if len(labelCreates) > 0 {
+		err := svcCtx.DB.Label.CreateBulk(labelCreates...).
+			OnConflict(
+				sql.ConflictColumns("name", "from", "organization_id"),
+			).
+			DoNothing().
+			Exec(ctx)
+		if err != nil {
+			logx.Error("label 批量插入失败", labelCreates)
+			return err
+		}
+	}
+	return nil
+}

+ 136 - 0
internal/service/MessageHandlers/friend_push_notice.go

@@ -3,7 +3,14 @@ package MessageHandlers
 import (
 	"context"
 	"encoding/json"
+	"entgo.io/ent/dialect/sql"
 	"github.com/zeromicro/go-zero/core/logx"
+	"strconv"
+	"strings"
+	"time"
+	"wechat-api/ent"
+	"wechat-api/ent/label"
+	"wechat-api/ent/labellog"
 	"wechat-api/ent/wx"
 	"wechat-api/internal/pkg/wechat_ws"
 	"wechat-api/internal/svc"
@@ -66,3 +73,132 @@ func (f *FriendPushNoticeHandler) Handler(msg *wechat_ws.MsgJsonObject) error {
 	}
 	return nil
 }
+
+type FriendPushNoticeTypeHandler struct {
+	svcCtx *svc.ServiceContext
+}
+
+func NewFriendPushNoticeTypeHandler(svcCtx *svc.ServiceContext) *FriendPushNoticeTypeHandler {
+	return &FriendPushNoticeTypeHandler{
+		svcCtx: svcCtx,
+	}
+}
+
+// Handle 实现 MessageHandlerStrategy 接口
+func (f *FriendPushNoticeTypeHandler) Handle(ctx context.Context, msg *wechat_ws.MsgJsonObject, svcCtx *svc.ServiceContext) error {
+	message := workphone.FriendPushNoticeMessage{}
+	err := json.Unmarshal([]byte(msg.Message), &message)
+	//logx.Infof("msg.Message 的内容是:%s", msg.Message)
+	if err != nil {
+		logx.Errorf("Unmarshal.fail")
+		return err
+	}
+	// 拿到租户 id
+	wxInfo, err := svcCtx.DB.Wx.Query().
+		Where(
+			wx.WxidEQ(message.WeChatId), // Additional filter by organizationId
+		).
+		Only(ctx)
+	if err != nil {
+		return err
+	}
+	var labelRelationshipCreates []*ent.LabelRelationshipCreate
+	for _, friend := range message.Friends {
+		var friendId uint64
+		friendType := 1
+		friendId, err = svcCtx.DB.Contact.Create().
+			SetWxWxid(message.WeChatId).
+			SetType(friendType).
+			SetWxid(friend.FriendId).
+			SetAccount(friend.FriendNo).
+			SetNickname(friend.FriendNick).
+			SetMarkname(friend.Memo).
+			SetHeadimg(friend.Avatar).
+			SetOrganizationID(wxInfo.OrganizationID).
+			OnConflict().
+			UpdateNewValues().
+			SetType(friendType).
+			SetOrganizationID(wxInfo.OrganizationID).
+			ID(ctx)
+		if err != nil {
+			logx.Errorf("Contact.Create 失败, OrgID=%d, err=%v", wxInfo.OrganizationID, err)
+			return err
+		}
+		//判断friend里的labelId="1,2,3,4,5"为空就不处理了,不为空的时候就查下label表里有没有这个labelId,没有就插入,有就跳过
+		if friend.LabelIds == "" {
+			continue
+		}
+		//获取labelId,并且按照逗号去分割成数组
+		labelIdsStr := friend.LabelIds
+		var ids []int
+		ids, err = ParseCSVToIntSlice(labelIdsStr)
+		if err != nil {
+			continue
+		}
+
+		//转换成labelIds的切片,去labelLog里 用labelId in 查下一下数据。
+		//labelIds := strings.Split(labelIdsStr, ",")
+		//ids, _ = ParseCSVToIntSlice(labelIdsStr)
+
+		LabelLogs, err := svcCtx.DB.LabelLog.Query().
+			Where(labellog.LabelIDIn(ids...)).
+			All(ctx)
+
+		if err != nil || len(LabelLogs) == 0 {
+			continue
+		}
+
+		//映射本地的name + type + model + organization_id
+		currentOrgID := wxInfo.OrganizationID
+		for _, remoteLabel := range LabelLogs {
+			labelInfo, err := svcCtx.DB.Label.Query().Where(
+				label.NameEQ(remoteLabel.LabelName),
+				label.FromEQ(2),
+				label.ModeEQ(2),
+				label.OrganizationID(currentOrgID),
+			).Only(ctx)
+			if err != nil || ent.IsNotFound(err) {
+				logx.Error("label_relation.create.fail: ", wxInfo.OrganizationID)
+				continue
+			}
+			//生成批量的关系数据 待插入
+			labelRelationshipCreates = append(labelRelationshipCreates,
+				svcCtx.DB.LabelRelationship.Create().
+					//SetID(int(label.LabelId)).
+					SetOrganizationID(wxInfo.OrganizationID).
+					SetContactID(friendId).
+					SetLabelID(labelInfo.ID).
+					SetCreatedAt(time.Now()).
+					SetUpdatedAt(time.Now()),
+			)
+			//
+		}
+	}
+	if len(labelRelationshipCreates) > 0 {
+		errShip := svcCtx.DB.LabelRelationship.CreateBulk(labelRelationshipCreates...).
+			OnConflict(
+				sql.ConflictColumns("label_id", "contact_id"),
+			).DoNothing().Exec(ctx)
+		if errShip != nil {
+			logx.Error("label_relationship.create.fail: ", wxInfo.OrganizationID, labelRelationshipCreates)
+			return err
+		}
+	}
+	return nil
+}
+
+func ParseCSVToIntSlice(csv string) ([]int, error) {
+	if csv == "" {
+		return nil, nil
+	}
+	parts := strings.Split(csv, ",")
+	var result []int
+	for _, p := range parts {
+		n, err := strconv.Atoi(strings.TrimSpace(p))
+		if err != nil {
+			return nil, err
+		}
+		result = append(result, n)
+	}
+	return result, nil
+}

+ 86 - 0
internal/service/MessageHandlers/register_strategy.go

@@ -0,0 +1,86 @@
+package MessageHandlers
+
+import (
+	"context"
+	"time"
+
+	"github.com/zeromicro/go-zero/core/logx"
+	"wechat-api/internal/pkg/wechat_ws"
+	"wechat-api/internal/svc"
+)
+
+// MessageHandlerStrategy 消息处理策略接口
+type MessageHandlerStrategy interface {
+	Handle(ctx context.Context, msg *wechat_ws.MsgJsonObject, svcCtx *svc.ServiceContext) error
+}
+
+type MessageHandler struct {
+	strategies map[string]MessageHandlerStrategy
+	svcCtx     *svc.ServiceContext
+}
+
+// NewMessageHandler 创建新的消息调度器
+func NewMessageHandler(svcCtx *svc.ServiceContext) *MessageHandler {
+	return &MessageHandler{
+		strategies: make(map[string]MessageHandlerStrategy),
+		svcCtx:     svcCtx,
+	}
+}
+
+// GetWrappedHandler 返回适配后的 handler,可直接用于 ws.RegisterMessageHandler
+func (h *MessageHandler) GetWrappedHandler() func(msg *wechat_ws.MsgJsonObject) error {
+	return WrapToSimpleHandler(h.Handle)
+}
+
+// RegisterStrategy 注册单个消息处理策略
+func (h *MessageHandler) RegisterStrategy(msgType string, strategy MessageHandlerStrategy) {
+	h.strategies[msgType] = strategy
+}
+
+// RegisterStrategies 批量注册消息处理策略
+func (h *MessageHandler) RegisterStrategies() {
+	strategyMap := map[string]func(*svc.ServiceContext) MessageHandlerStrategy{
+		"FriendPushNotice": toStrategy(NewFriendPushNoticeTypeHandler),
+		//"ChatroomPushNotice":     toStrategy(NewChatroomPushNoticeTypeHandler),
+		"ContactLabelInfoNotice": toStrategy(NewContactLabelInfoNotice),
+	}
+	for msgType, strategyFunc := range strategyMap {
+		h.RegisterStrategy(msgType, strategyFunc(h.svcCtx))
+		logx.Infof("已注册消息处理策略: %s", msgType)
+	}
+}
+
+// Handle 消息分发逻辑,带 recover 容错
+func (h *MessageHandler) Handle(ctx context.Context, msg *wechat_ws.MsgJsonObject) (err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			logx.Errorf("处理消息 msgType=%s 时 panic: %v", msg.MsgType, r)
+			err = nil // 防止 panic 向上冒泡
+		}
+	}()
+
+	strategy, exists := h.strategies[msg.MsgType]
+	if !exists {
+		logx.Infof("未注册处理器的消息类型: %s,跳过处理", msg.MsgType)
+		return nil
+	}
+	return strategy.Handle(ctx, msg, h.svcCtx)
+}
+
+// toStrategy 泛型辅助转换
+func toStrategy[T MessageHandlerStrategy](fn func(*svc.ServiceContext) T) func(*svc.ServiceContext) MessageHandlerStrategy {
+	return func(svcCtx *svc.ServiceContext) MessageHandlerStrategy {
+		return fn(svcCtx)
+	}
+}
+
+// WrapToSimpleHandler 将带 context 的 handler 包装成简洁 MessageHandler 函数
+func WrapToSimpleHandler(handler func(ctx context.Context, msg *wechat_ws.MsgJsonObject) error) func(msg *wechat_ws.MsgJsonObject) error {
+	return func(msg *wechat_ws.MsgJsonObject) error {
+		// 创建默认上下文(支持自定义扩展)
+		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+		defer cancel()
+
+		return handler(ctx, msg)
+	}
+}

+ 50 - 0
internal/service/MessageHandlers/talk_to_friend_task_result_notice.go

@@ -0,0 +1,50 @@
+package MessageHandlers
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"wechat-api/ent/contact"
+	"wechat-api/internal/pkg/wechat_ws"
+	"wechat-api/internal/svc"
+	"wechat-api/workphone"
+)
+
+type TalkToFriendTaskResultNoticeHandler struct {
+	svcCtx *svc.ServiceContext
+}
+
+func NewTalkToFriendTaskResultNoticeHandler(svcCtx *svc.ServiceContext) *TalkToFriendTaskResultNoticeHandler {
+	return &TalkToFriendTaskResultNoticeHandler{
+		svcCtx: svcCtx,
+	}
+}
+
+func (f *TalkToFriendTaskResultNoticeHandler) Handler(msg *wechat_ws.MsgJsonObject) error {
+	if msg.MsgType == "TalkToFriendTaskResultNotice" {
+		message := workphone.TalkToFriendTaskResultNoticeMessage{}
+		err := json.Unmarshal([]byte(msg.Message), &message)
+		if err != nil {
+			return err
+		}
+
+		friendId, _ := f.svcCtx.Rds.Get(context.TODO(), fmt.Sprintf("MsgId_FriendId:%d", message.MsgId)).Result()
+
+		if friendId == "" {
+			return nil
+		}
+
+		if message.Code == workphone.EnumErrorCode_InternalError {
+			err = f.svcCtx.DB.Contact.
+				Update().
+				Where(contact.WxWxidEQ(message.WeChatId), contact.WxidEQ(friendId)).
+				SetStatus(2).
+				Exec(context.TODO())
+
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}

+ 2 - 2
internal/types/types.go

@@ -1076,8 +1076,8 @@ type ContactListReq struct {
 	// 内容类型:1-个微 3-企微
 	Ctype *uint64 `json:"ctype,optional"`
 	//标签搜索开始结束日期
-	StartDate *string `json:"start_date,optional"`
-	EndDate   *string `json:"end_date,optional"`
+	SearchDate []*string `json:"date,optional"`
+	Status    *uint8  `json:"status,optional"`
 }
 
 // Contact information response | Contact信息返回体

+ 3 - 0
internal/utils/compapi/base.go

@@ -65,6 +65,8 @@ func (me *Client) getClientActFace(clientType string) (clientActionFace, error)
 	switch clientType {
 	case "mismatch":
 		actFace = &MismatchClient{StdClient: StdClient{Client: me}}
+	case "form":
+		actFace = &FormClient{StdClient: StdClient{Client: me}}
 	default:
 		actFace = &FastgptClient{StdClient: StdClient{Client: me}}
 	}
@@ -106,6 +108,7 @@ func (me *Client) Callback(clientType string, callbackUrl string, params any) (m
 	resp := map[string]any{}
 	err = me.OAC.Post(me.ctx, callbackUrl, newParams, &resp)
 	if err != nil {
+		fmt.Printf("Callback Post(%s) By Params:'%s' error\n", callbackUrl, string(newParams))
 		return nil, err
 	}
 	return resp, nil

+ 3 - 0
internal/utils/compapi/config.go

@@ -23,4 +23,7 @@ var fastgptWorkIdMap = map[string]workIdInfo{
 	"GENERALIZED_KEYWORDS": {"fastgpt-yefNCWpNtzY2Qn0xtJvKuvepOP9DR7BiXQwv5BiVP5JseP2IS92gE", 3},
 	"TEST_DOUYIN_CN":       {"fastgpt-jsMmQKEM5uX7tDimT1zHlZHkBhMHRT2k61YaxyDJRZTUHehID7sG8BKXADNIU", 4},
 	"travel":               {"fastgpt-bcnfFtw1lXWdmYGOv165UVD5R1kY28tyXX8SJv8MHhrSMOgVJsuU", 5},
+	"loreal":               {"fastgpt-qqJeBEkwhgx7wR9fvGToQygmOb7FVjAbGBTFjOAMbd95InEtndke", 6},
+	"xiulike-dc":           {"fastgpt-ir9RgnKHMT9HIOnPsUCFChN15ZbW9kt1lbd5Y0ohfLw9gOz3KcPrfaZWHRB", 7},
+	"wuhanzhongxin":        {"fastgpt-jX6Gl50Ivrc7vzyD4xNlahG11cgmJ4N63QHKrntt2gQ78g31haxuAsA", 8},
 }

+ 89 - 0
internal/utils/compapi/form.go

@@ -0,0 +1,89 @@
+package compapi
+
+import (
+	"time"
+	"wechat-api/internal/types"
+
+	"github.com/openai/openai-go"
+)
+
+type FormClient struct {
+	StdClient
+}
+
+// Generate the JSON schema at initialization time
+var FormResponseSchema = GenerateSchema[FormResponse]()
+
+type FormResponse struct {
+	DataIndex string   `json:"dataIndex" jsonschema_description:"表单 id"`
+	Value     []string `json:"value" jsonschema_description:" 值列表"`
+}
+
+type FormList struct {
+	Values []FormResponse `json:"values" jsonschema_description:"表单列表"`
+}
+
+/*
+
+"response_format":{"type":"json_object"}}
+
+{"type":"json_schema",
+	"json_schema":{
+		"description":"从通话记录中提取表单","name":"keyword_schema","schema":{
+			"$schema":"https://json-schema.org/draft/2020-12/schema","additionalProperties":false,"properties":{
+			"keywords":{
+				"description":"关键词库","items":{"type":"string"},"type":"array"},"regular":{"description":"正则表达式","items":{"type":"string"},"type":"array"},"similar_reply":{"description":"类似回复","items":{"type":"string"},"type":"array"},"swear_words":{"description":"脏话列表","items":{"type":"string"},"type":"array"},"user_intent":{"description":"用户意图","type":"string"},"user_mood":{"description":"用户情绪","type":"string"}},"required":["user_intent","similar_reply","keywords","regular","user_mood","swear_words"],"type":"object"},"strict":true}}
+*/
+
+func (me *FormClient) BuildRequest(req *types.CompApiReq) error {
+	nowTime := time.Now().Format("2006-01-02 15:04:05")
+	//bytes, err := json.Marshal(req.Variables["form_data"])
+	//if err != nil {
+	//	return err
+	//}
+	//form_data := string(bytes)
+
+	//先重构message
+	newMessSlice := make([]types.StdCompMessage, 2)
+	newMessSlice[0] = types.StdCompMessage{Role: "system", Content: `# 任务
+请帮助user从通话记录中提取表单值,并返回一个JSON格式的表单值。
+
+# 背景信息
+` + nowTime + `
+
+# 返回值示例
+* 如表单类型为 input、autoComplete、textarea,返回示例:["表单值"]
+* 如表单类型为 radio、select,返回示例:["值1"]
+* 如表单类型为 checkbox,返回示例:["值1", "值2"]
+* 如表单类型为 cascader,返回示例:["一级值1", "二级值3"]
+* 如表单类型为 date,返回示例:["2025-01-01"]`}
+
+	newMessSlice[1] = types.StdCompMessage{Role: "user", Content: `# 表单数据
+` + req.Variables["form_data"] + `
+
+# 聊天记录
+` + req.Variables["chat_history"]}
+
+	//再构造ResponseFormat
+	if !IsOpenaiModel(req.Model) {
+		newMessSlice[1].Content = newMessSlice[1].Content.(string) + `
+# 请以下方的json结构输出
+[{
+    "dataIndex": str, # 表单ID
+    "value": list[str] # 表单值列表
+}]`
+		req.ResponseFormat = openai.ResponseFormatJSONObjectParam{Type: "json_object"}
+	} else {
+		schemaParam := openai.ResponseFormatJSONSchemaJSONSchemaParam{
+			Name:        "keyword_schema",
+			Description: openai.String("从通话记录中提取表单"),
+			Schema:      FormResponseSchema,
+			Strict:      openai.Bool(true),
+		}
+		req.ResponseFormat = openai.ResponseFormatJSONSchemaParam{JSONSchema: schemaParam}
+	}
+	//req.Model = oldModel
+	req.Messages = newMessSlice
+
+	return nil
+}

+ 1 - 1
proto/ContactLabelInfoNotice.proto

@@ -5,7 +5,7 @@ option go_package = "./workphone";
 message LabelInfoMessage{
     int32 LabelId = 1;
     string LabelName = 2;
-    int64 CreateTime = 3;
+    string CreateTime = 3;
 }
 message ContactLabelInfoNoticeMessage {
     string WeChatId = 1; // 商家个人微信内部全局唯一识别码

+ 1 - 1
proto/FriendPushNotice.proto

@@ -9,6 +9,6 @@ message FriendPushNoticeMessage {
   int32 Size = 3;
   int32 Count = 4;
   int32 Page = 5;
-  int64 TaskId = 6;
+  string TaskId = 6;
 }
 

+ 4 - 1
wechat.go

@@ -51,7 +51,8 @@ func main() {
 	ctx := svc.NewServiceContext(c)
 
 	handler.RegisterHandlers(server, ctx)
-
+	messageHandler := MessageHandlers.NewMessageHandler(ctx)
+	messageHandler.RegisterStrategies()
 	//个微处理程序 没有彻底完成之前不能开,和cow重复了
 	if len(ctx.WechatWs) == 0 {
 		fmt.Println("wechat ws is nil")
@@ -65,6 +66,8 @@ func main() {
 			//ws.RegisterMessageHandler(ic.OnMessage)
 			ws.RegisterMessageHandler(MessageHandlers.NewChatroomPushNoticeHandler(ctx).Handler)
 			ws.RegisterMessageHandler(MessageHandlers.NewFriendPushNoticeHandler(ctx).Handler)
+			ws.RegisterMessageHandler(messageHandler.GetWrappedHandler())
+			ws.RegisterMessageHandler(MessageHandlers.NewTalkToFriendTaskResultNoticeHandler(ctx).Handler)
 		}
 		logx.Info("注册个微处理通道~")
 	}

+ 29 - 41
workphone/ContactLabelInfoNotice.pb.go

@@ -1,7 +1,7 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.35.2
-// 	protoc        v5.28.3
+// 	protoc-gen-go v1.36.6
+// 	protoc        v3.19.4
 // source: ContactLabelInfoNotice.proto
 
 package workphone
@@ -11,6 +11,7 @@ import (
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
 	reflect "reflect"
 	sync "sync"
+	unsafe "unsafe"
 )
 
 const (
@@ -21,13 +22,12 @@ const (
 )
 
 type LabelInfoMessage struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	LabelId       int32                  `protobuf:"varint,1,opt,name=LabelId,proto3" json:"LabelId,omitempty"`
+	LabelName     string                 `protobuf:"bytes,2,opt,name=LabelName,proto3" json:"LabelName,omitempty"`
+	CreateTime    string                 `protobuf:"bytes,3,opt,name=CreateTime,proto3" json:"CreateTime,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	LabelId    int32  `protobuf:"varint,1,opt,name=LabelId,proto3" json:"LabelId,omitempty"`
-	LabelName  string `protobuf:"bytes,2,opt,name=LabelName,proto3" json:"LabelName,omitempty"`
-	CreateTime int64  `protobuf:"varint,3,opt,name=CreateTime,proto3" json:"CreateTime,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *LabelInfoMessage) Reset() {
@@ -74,20 +74,19 @@ func (x *LabelInfoMessage) GetLabelName() string {
 	return ""
 }
 
-func (x *LabelInfoMessage) GetCreateTime() int64 {
+func (x *LabelInfoMessage) GetCreateTime() string {
 	if x != nil {
 		return x.CreateTime
 	}
-	return 0
+	return ""
 }
 
 type ContactLabelInfoNoticeMessage struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	WeChatId      string                 `protobuf:"bytes,1,opt,name=WeChatId,proto3" json:"WeChatId,omitempty"` // 商家个人微信内部全局唯一识别码
+	Labels        []*LabelInfoMessage    `protobuf:"bytes,2,rep,name=Labels,proto3" json:"Labels,omitempty"`     // 标签列表
 	unknownFields protoimpl.UnknownFields
-
-	WeChatId string              `protobuf:"bytes,1,opt,name=WeChatId,proto3" json:"WeChatId,omitempty"` // 商家个人微信内部全局唯一识别码
-	Labels   []*LabelInfoMessage `protobuf:"bytes,2,rep,name=Labels,proto3" json:"Labels,omitempty"`     // 标签列表
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *ContactLabelInfoNoticeMessage) Reset() {
@@ -136,37 +135,27 @@ func (x *ContactLabelInfoNoticeMessage) GetLabels() []*LabelInfoMessage {
 
 var File_ContactLabelInfoNotice_proto protoreflect.FileDescriptor
 
-var file_ContactLabelInfoNotice_proto_rawDesc = []byte{
-	0x0a, 0x1c, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x49, 0x6e,
-	0x66, 0x6f, 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x17,
-	0x4a, 0x75, 0x62, 0x6f, 0x2e, 0x4a, 0x75, 0x4c, 0x69, 0x61, 0x6f, 0x2e, 0x49, 0x4d, 0x2e, 0x57,
-	0x78, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6a, 0x0a, 0x10, 0x4c, 0x61, 0x62, 0x65, 0x6c,
-	0x49, 0x6e, 0x66, 0x6f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x4c,
-	0x61, 0x62, 0x65, 0x6c, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x4c, 0x61,
-	0x62, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x4e, 0x61,
-	0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x4e,
-	0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d,
-	0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54,
-	0x69, 0x6d, 0x65, 0x22, 0x7e, 0x0a, 0x1d, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x4c, 0x61,
-	0x62, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x4d, 0x65, 0x73,
-	0x73, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x57, 0x65, 0x43, 0x68, 0x61, 0x74, 0x49, 0x64,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x57, 0x65, 0x43, 0x68, 0x61, 0x74, 0x49, 0x64,
-	0x12, 0x41, 0x0a, 0x06, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
-	0x32, 0x29, 0x2e, 0x4a, 0x75, 0x62, 0x6f, 0x2e, 0x4a, 0x75, 0x4c, 0x69, 0x61, 0x6f, 0x2e, 0x49,
-	0x4d, 0x2e, 0x57, 0x78, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c,
-	0x49, 0x6e, 0x66, 0x6f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x06, 0x4c, 0x61, 0x62,
-	0x65, 0x6c, 0x73, 0x42, 0x0d, 0x5a, 0x0b, 0x2e, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x70, 0x68, 0x6f,
-	0x6e, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
-}
+const file_ContactLabelInfoNotice_proto_rawDesc = "" +
+	"\n" +
+	"\x1cContactLabelInfoNotice.proto\x12\x17Jubo.JuLiao.IM.Wx.Proto\"j\n" +
+	"\x10LabelInfoMessage\x12\x18\n" +
+	"\aLabelId\x18\x01 \x01(\x05R\aLabelId\x12\x1c\n" +
+	"\tLabelName\x18\x02 \x01(\tR\tLabelName\x12\x1e\n" +
+	"\n" +
+	"CreateTime\x18\x03 \x01(\tR\n" +
+	"CreateTime\"~\n" +
+	"\x1dContactLabelInfoNoticeMessage\x12\x1a\n" +
+	"\bWeChatId\x18\x01 \x01(\tR\bWeChatId\x12A\n" +
+	"\x06Labels\x18\x02 \x03(\v2).Jubo.JuLiao.IM.Wx.Proto.LabelInfoMessageR\x06LabelsB\rZ\v./workphoneb\x06proto3"
 
 var (
 	file_ContactLabelInfoNotice_proto_rawDescOnce sync.Once
-	file_ContactLabelInfoNotice_proto_rawDescData = file_ContactLabelInfoNotice_proto_rawDesc
+	file_ContactLabelInfoNotice_proto_rawDescData []byte
 )
 
 func file_ContactLabelInfoNotice_proto_rawDescGZIP() []byte {
 	file_ContactLabelInfoNotice_proto_rawDescOnce.Do(func() {
-		file_ContactLabelInfoNotice_proto_rawDescData = protoimpl.X.CompressGZIP(file_ContactLabelInfoNotice_proto_rawDescData)
+		file_ContactLabelInfoNotice_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_ContactLabelInfoNotice_proto_rawDesc), len(file_ContactLabelInfoNotice_proto_rawDesc)))
 	})
 	return file_ContactLabelInfoNotice_proto_rawDescData
 }
@@ -194,7 +183,7 @@ func file_ContactLabelInfoNotice_proto_init() {
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
-			RawDescriptor: file_ContactLabelInfoNotice_proto_rawDesc,
+			RawDescriptor: unsafe.Slice(unsafe.StringData(file_ContactLabelInfoNotice_proto_rawDesc), len(file_ContactLabelInfoNotice_proto_rawDesc)),
 			NumEnums:      0,
 			NumMessages:   2,
 			NumExtensions: 0,
@@ -205,7 +194,6 @@ func file_ContactLabelInfoNotice_proto_init() {
 		MessageInfos:      file_ContactLabelInfoNotice_proto_msgTypes,
 	}.Build()
 	File_ContactLabelInfoNotice_proto = out.File
-	file_ContactLabelInfoNotice_proto_rawDesc = nil
 	file_ContactLabelInfoNotice_proto_goTypes = nil
 	file_ContactLabelInfoNotice_proto_depIdxs = nil
 }

+ 26 - 38
workphone/FriendPushNotice.pb.go

@@ -1,7 +1,7 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.35.2
-// 	protoc        v5.28.3
+// 	protoc-gen-go v1.36.6
+// 	protoc        v3.19.4
 // source: FriendPushNotice.proto
 
 package workphone
@@ -11,6 +11,7 @@ import (
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
 	reflect "reflect"
 	sync "sync"
+	unsafe "unsafe"
 )
 
 const (
@@ -21,16 +22,15 @@ const (
 )
 
 type FriendPushNoticeMessage struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	WeChatId      string                 `protobuf:"bytes,1,opt,name=WeChatId,proto3" json:"WeChatId,omitempty"` // 商家所属微信号
+	Friends       []*FriendMessage       `protobuf:"bytes,2,rep,name=Friends,proto3" json:"Friends,omitempty"`   // 好友信息模型 多个
+	Size          int32                  `protobuf:"varint,3,opt,name=Size,proto3" json:"Size,omitempty"`
+	Count         int32                  `protobuf:"varint,4,opt,name=Count,proto3" json:"Count,omitempty"`
+	Page          int32                  `protobuf:"varint,5,opt,name=Page,proto3" json:"Page,omitempty"`
+	TaskId        string                 `protobuf:"bytes,6,opt,name=TaskId,proto3" json:"TaskId,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	WeChatId string           `protobuf:"bytes,1,opt,name=WeChatId,proto3" json:"WeChatId,omitempty"` // 商家所属微信号
-	Friends  []*FriendMessage `protobuf:"bytes,2,rep,name=Friends,proto3" json:"Friends,omitempty"`   // 好友信息模型 多个
-	Size     int32            `protobuf:"varint,3,opt,name=Size,proto3" json:"Size,omitempty"`
-	Count    int32            `protobuf:"varint,4,opt,name=Count,proto3" json:"Count,omitempty"`
-	Page     int32            `protobuf:"varint,5,opt,name=Page,proto3" json:"Page,omitempty"`
-	TaskId   int64            `protobuf:"varint,6,opt,name=TaskId,proto3" json:"TaskId,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *FriendPushNoticeMessage) Reset() {
@@ -98,45 +98,34 @@ func (x *FriendPushNoticeMessage) GetPage() int32 {
 	return 0
 }
 
-func (x *FriendPushNoticeMessage) GetTaskId() int64 {
+func (x *FriendPushNoticeMessage) GetTaskId() string {
 	if x != nil {
 		return x.TaskId
 	}
-	return 0
+	return ""
 }
 
 var File_FriendPushNotice_proto protoreflect.FileDescriptor
 
-var file_FriendPushNotice_proto_rawDesc = []byte{
-	0x0a, 0x16, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x50, 0x75, 0x73, 0x68, 0x4e, 0x6f, 0x74, 0x69,
-	0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x17, 0x4a, 0x75, 0x62, 0x6f, 0x2e, 0x4a,
-	0x75, 0x4c, 0x69, 0x61, 0x6f, 0x2e, 0x49, 0x4d, 0x2e, 0x57, 0x78, 0x2e, 0x50, 0x72, 0x6f, 0x74,
-	0x6f, 0x1a, 0x15, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x4e, 0x6f, 0x74, 0x69,
-	0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xcd, 0x01, 0x0a, 0x17, 0x46, 0x72, 0x69,
-	0x65, 0x6e, 0x64, 0x50, 0x75, 0x73, 0x68, 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x4d, 0x65, 0x73,
-	0x73, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x57, 0x65, 0x43, 0x68, 0x61, 0x74, 0x49, 0x64,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x57, 0x65, 0x43, 0x68, 0x61, 0x74, 0x49, 0x64,
-	0x12, 0x40, 0x0a, 0x07, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
-	0x0b, 0x32, 0x26, 0x2e, 0x4a, 0x75, 0x62, 0x6f, 0x2e, 0x4a, 0x75, 0x4c, 0x69, 0x61, 0x6f, 0x2e,
-	0x49, 0x4d, 0x2e, 0x57, 0x78, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x72, 0x69, 0x65,
-	0x6e, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x07, 0x46, 0x72, 0x69, 0x65, 0x6e,
-	0x64, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05,
-	0x52, 0x04, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18,
-	0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04,
-	0x50, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x50, 0x61, 0x67, 0x65,
-	0x12, 0x16, 0x0a, 0x06, 0x54, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03,
-	0x52, 0x06, 0x54, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x42, 0x0d, 0x5a, 0x0b, 0x2e, 0x2f, 0x77, 0x6f,
-	0x72, 0x6b, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
-}
+const file_FriendPushNotice_proto_rawDesc = "" +
+	"\n" +
+	"\x16FriendPushNotice.proto\x12\x17Jubo.JuLiao.IM.Wx.Proto\x1a\x15FriendAddNotice.proto\"\xcd\x01\n" +
+	"\x17FriendPushNoticeMessage\x12\x1a\n" +
+	"\bWeChatId\x18\x01 \x01(\tR\bWeChatId\x12@\n" +
+	"\aFriends\x18\x02 \x03(\v2&.Jubo.JuLiao.IM.Wx.Proto.FriendMessageR\aFriends\x12\x12\n" +
+	"\x04Size\x18\x03 \x01(\x05R\x04Size\x12\x14\n" +
+	"\x05Count\x18\x04 \x01(\x05R\x05Count\x12\x12\n" +
+	"\x04Page\x18\x05 \x01(\x05R\x04Page\x12\x16\n" +
+	"\x06TaskId\x18\x06 \x01(\tR\x06TaskIdB\rZ\v./workphoneb\x06proto3"
 
 var (
 	file_FriendPushNotice_proto_rawDescOnce sync.Once
-	file_FriendPushNotice_proto_rawDescData = file_FriendPushNotice_proto_rawDesc
+	file_FriendPushNotice_proto_rawDescData []byte
 )
 
 func file_FriendPushNotice_proto_rawDescGZIP() []byte {
 	file_FriendPushNotice_proto_rawDescOnce.Do(func() {
-		file_FriendPushNotice_proto_rawDescData = protoimpl.X.CompressGZIP(file_FriendPushNotice_proto_rawDescData)
+		file_FriendPushNotice_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_FriendPushNotice_proto_rawDesc), len(file_FriendPushNotice_proto_rawDesc)))
 	})
 	return file_FriendPushNotice_proto_rawDescData
 }
@@ -165,7 +154,7 @@ func file_FriendPushNotice_proto_init() {
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
-			RawDescriptor: file_FriendPushNotice_proto_rawDesc,
+			RawDescriptor: unsafe.Slice(unsafe.StringData(file_FriendPushNotice_proto_rawDesc), len(file_FriendPushNotice_proto_rawDesc)),
 			NumEnums:      0,
 			NumMessages:   1,
 			NumExtensions: 0,
@@ -176,7 +165,6 @@ func file_FriendPushNotice_proto_init() {
 		MessageInfos:      file_FriendPushNotice_proto_msgTypes,
 	}.Build()
 	File_FriendPushNotice_proto = out.File
-	file_FriendPushNotice_proto_rawDesc = nil
 	file_FriendPushNotice_proto_goTypes = nil
 	file_FriendPushNotice_proto_depIdxs = nil
 }