Prechádzať zdrojové kódy

Merge branch 'feature/531-bowen-wecom' into debug

lichangdong 21 hodín pred
rodič
commit
9bb5d8b2d5
100 zmenil súbory, kde vykonal 1170 pridanie a 126 odobranie
  1. 1 1
      crontask/wx_add_friend.go
  2. 2 1
      desc/all.api
  3. 24 0
      desc/wechat/add_wechat_friend_log.api
  4. 2 2
      ent/labellog.go
  5. 1 1
      ent/labellog/labellog.go
  6. 9 9
      ent/labellog/where.go
  7. 12 12
      ent/labellog_create.go
  8. 20 20
      ent/labellog_update.go
  9. 1 1
      ent/migrate/schema.go
  10. 13 13
      ent/mutation.go
  11. 1 1
      ent/runtime/runtime.go
  12. 1 1
      ent/schema/label_log.go
  13. 3 3
      ent/set_not_nil.go
  14. 70 0
      hook/contact.go
  15. 44 0
      internal/handler/add_friend/add_friend_by_phone_handler.go
  16. 15 0
      internal/handler/routes.go
  17. 2 1
      internal/logic/Wxhook/get_friends_and_groups_logic.go
  18. 29 0
      internal/logic/add_friend/add_friend_by_phone_logic.go
  19. 347 0
      internal/pkg/wechat_ws/wecom_ws_client.go
  20. 5 5
      internal/service/MessageHandlers/contact_Label_info_notice.go
  21. 4 4
      internal/service/MessageHandlers/friend_push_notice.go
  22. 2 19
      internal/service/MessageHandlers/message_handler.go
  23. 110 26
      internal/service/MessageHandlers/task_result_notice.go
  24. 13 0
      internal/service/MessageHandlers/wechat_strategies.go
  25. 200 0
      internal/service/MessageHandlers/wecom/customer_push_notice.go
  26. 180 0
      internal/service/MessageHandlers/wecom/user_label_push_notice.go
  27. 14 0
      internal/service/MessageHandlers/wecom_strategies.go
  28. 23 6
      internal/service/addfriend/add_wechat_friend_log.go
  29. 13 0
      internal/svc/service_context.go
  30. 9 0
      internal/types/types.go
  31. 0 0
      proto/wechat/AcceptFriendAddRequestTask.proto
  32. 0 0
      proto/wechat/AccountForceOfflineNotice.proto
  33. 0 0
      proto/wechat/AccountLogoutNotice.proto
  34. 0 0
      proto/wechat/AddEmojiTask.proto
  35. 0 0
      proto/wechat/AddFriendFromPhonebookTask.proto
  36. 0 0
      proto/wechat/AddFriendInChatRoomTask.proto
  37. 0 0
      proto/wechat/AddFriendNameCardTask.proto
  38. 0 0
      proto/wechat/AddFriendNotice.proto
  39. 0 0
      proto/wechat/AddFriendWithSceneTask.proto
  40. 0 0
      proto/wechat/AddFriendsTask.proto
  41. 0 0
      proto/wechat/AgreeJoinChatRoomTask.proto
  42. 0 0
      proto/wechat/BizContactAddNotice.proto
  43. 0 0
      proto/wechat/BizContactPushNotice.proto
  44. 0 0
      proto/wechat/BizConversPushNotice.proto
  45. 0 0
      proto/wechat/CDNDownloadFileTask.proto
  46. 0 0
      proto/wechat/CDNDownloadResultNotice.proto
  47. 0 0
      proto/wechat/CallLogPushNotice.proto
  48. 0 0
      proto/wechat/ChatMsgFilePushNotice.proto
  49. 0 0
      proto/wechat/ChatMsgIdsPushNotice.proto
  50. 0 0
      proto/wechat/ChatRoomActionTask.proto
  51. 0 0
      proto/wechat/ChatRoomAddNotice.proto
  52. 0 0
      proto/wechat/ChatRoomChangedNotice.proto
  53. 0 0
      proto/wechat/ChatRoomDelNotice.proto
  54. 0 0
      proto/wechat/ChatRoomInviteApproveTask.proto
  55. 0 0
      proto/wechat/ChatRoomMembersNotice.proto
  56. 0 0
      proto/wechat/ChatRoomPushNotice.proto
  57. 0 0
      proto/wechat/CircleCommentDeleteTask.proto
  58. 0 0
      proto/wechat/CircleCommentDeleteTaskResultNotice.proto
  59. 0 0
      proto/wechat/CircleCommentNotice.proto
  60. 0 0
      proto/wechat/CircleCommentReplyTask.proto
  61. 0 0
      proto/wechat/CircleCommentReplyTaskResultNotice.proto
  62. 0 0
      proto/wechat/CircleDelNotice.proto
  63. 0 0
      proto/wechat/CircleDetailNotice.proto
  64. 0 0
      proto/wechat/CircleLikeNotice.proto
  65. 0 0
      proto/wechat/CircleLikeTask.proto
  66. 0 0
      proto/wechat/CircleMsgClearTask.proto
  67. 0 0
      proto/wechat/CircleMsgPushNotice.proto
  68. 0 0
      proto/wechat/CircleMsgReadTask.proto
  69. 0 0
      proto/wechat/CircleNewPublishNotice.proto
  70. 0 0
      proto/wechat/CirclePushNotice.proto
  71. 0 0
      proto/wechat/ClearAllChatMsgTask.proto
  72. 0 0
      proto/wechat/ConfigPushNotice.proto
  73. 0 0
      proto/wechat/ContactInfoNotice.proto
  74. 0 0
      proto/wechat/ContactLabelAddNotic.proto
  75. 0 0
      proto/wechat/ContactLabelDelNotice.proto
  76. 0 0
      proto/wechat/ContactLabelDeleteTask.proto
  77. 0 0
      proto/wechat/ContactLabelInfoNotice.proto
  78. 0 0
      proto/wechat/ContactLabelTask.proto
  79. 0 0
      proto/wechat/ContactSetLabelTask.proto
  80. 0 0
      proto/wechat/ConvDelNotice.proto
  81. 0 0
      proto/wechat/ConversationPushNotice.proto
  82. 0 0
      proto/wechat/DeleteFriendTask.proto
  83. 0 0
      proto/wechat/DeleteSNSNewsTask.proto
  84. 0 0
      proto/wechat/DeviceAuthReq.proto
  85. 0 0
      proto/wechat/DeviceAuthRsp.proto
  86. 0 0
      proto/wechat/ErrorMessage.proto
  87. 0 0
      proto/wechat/FindContactTask.proto
  88. 0 0
      proto/wechat/FindContactTaskResultNotice.proto
  89. 0 0
      proto/wechat/ForwardMessageByContentTask.proto
  90. 0 0
      proto/wechat/ForwardMessageTask.proto
  91. 0 0
      proto/wechat/ForwardMultiMessageTask.proto
  92. 0 0
      proto/wechat/FriendAddNotice.proto
  93. 0 0
      proto/wechat/FriendAddReqListNotice.proto
  94. 0 0
      proto/wechat/FriendAddReqeustNotice.proto
  95. 0 0
      proto/wechat/FriendChangeNotice.proto
  96. 0 0
      proto/wechat/FriendDelNotice.proto
  97. 0 0
      proto/wechat/FriendDetectResultNotice.proto
  98. 0 0
      proto/wechat/FriendPushNotice.proto
  99. 0 0
      proto/wechat/FriendTalkNotice.proto
  100. 0 0
      proto/wechat/GetA8KeyTask.proto

+ 1 - 1
crontask/wx_add_friend.go

@@ -104,7 +104,7 @@ func (l *CronTask) wxAddFriend() {
 				task.OwnerWxID, addCount, WX_DAILY_ADDCOUNT)
 			continue
 		}
-		ok := serv.AddNewFriend(task.OwnerWxID, task.FindContent, task.Message)
+		ok := serv.AddNewFriend(task.OwnerWxID, task.FindContent, task.Message, task.OwnerWxType)
 		l.Logger.Debugf("serv.AddNewFriend()=>%v", ok)
 	}
 

+ 2 - 1
desc/all.api

@@ -50,4 +50,5 @@ import "./wechat/fastgpt.api"
 import "./wechat/department.api"
 import "./wechat/api_key.api"
 import "./wechat/auth_login.api"
-import "./wechat/contact_field_template.api"
+import "./wechat/contact_field_template.api"
+import "./wechat/add_wechat_friend_log.api"

+ 24 - 0
desc/wechat/add_wechat_friend_log.api

@@ -0,0 +1,24 @@
+import "../base.api"
+
+type (
+    
+    //add_friend_by_phone api接口请求值
+    AddWechatFriendLogInfo {
+        Type int `json:"type"`
+        WeChatIds []string `json:"WeChatId,optional,omitempty"`
+        Phone string `json:"phone"`
+        Message string `json:"message"`
+    }
+)
+
+@server(
+    jwt: Auth
+    group: add_friend
+    middleware: Authority
+)
+
+service Wechat {
+    // 手机号加好友接口
+    @handler AddFriendByPhone
+    post /add_friend/add_friend_by_phone (AddWechatFriendLogInfo) returns (BaseMsgResp)
+} 

+ 2 - 2
ent/labellog.go

@@ -24,7 +24,7 @@ type LabelLog struct {
 	// 标签名称
 	LabelName string `json:"label_name,omitempty"`
 	// 三方平台标签id
-	LabelID int `json:"label_id,omitempty"`
+	LabelID uint64 `json:"label_id,omitempty"`
 	// 微信ID
 	WxID string `json:"wx_id,omitempty"`
 	// 机构 ID
@@ -86,7 +86,7 @@ func (ll *LabelLog) assignValues(columns []string, values []any) error {
 			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)
+				ll.LabelID = uint64(value.Int64)
 			}
 		case labellog.FieldWxID:
 			if value, ok := values[i].(*sql.NullString); !ok {

+ 1 - 1
ent/labellog/labellog.go

@@ -60,7 +60,7 @@ var (
 	// 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
+	DefaultLabelID uint64
 	// 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.

+ 9 - 9
ent/labellog/where.go

@@ -70,7 +70,7 @@ func LabelName(v string) predicate.LabelLog {
 }
 
 // LabelID applies equality check predicate on the "label_id" field. It's identical to LabelIDEQ.
-func LabelID(v int) predicate.LabelLog {
+func LabelID(v uint64) predicate.LabelLog {
 	return predicate.LabelLog(sql.FieldEQ(FieldLabelID, v))
 }
 
@@ -230,42 +230,42 @@ func LabelNameContainsFold(v string) predicate.LabelLog {
 }
 
 // LabelIDEQ applies the EQ predicate on the "label_id" field.
-func LabelIDEQ(v int) predicate.LabelLog {
+func LabelIDEQ(v uint64) 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 {
+func LabelIDNEQ(v uint64) 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 {
+func LabelIDIn(vs ...uint64) 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 {
+func LabelIDNotIn(vs ...uint64) 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 {
+func LabelIDGT(v uint64) 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 {
+func LabelIDGTE(v uint64) 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 {
+func LabelIDLT(v uint64) 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 {
+func LabelIDLTE(v uint64) predicate.LabelLog {
 	return predicate.LabelLog(sql.FieldLTE(FieldLabelID, v))
 }
 

+ 12 - 12
ent/labellog_create.go

@@ -65,15 +65,15 @@ func (llc *LabelLogCreate) SetNillableLabelName(s *string) *LabelLogCreate {
 }
 
 // SetLabelID sets the "label_id" field.
-func (llc *LabelLogCreate) SetLabelID(i int) *LabelLogCreate {
-	llc.mutation.SetLabelID(i)
+func (llc *LabelLogCreate) SetLabelID(u uint64) *LabelLogCreate {
+	llc.mutation.SetLabelID(u)
 	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)
+func (llc *LabelLogCreate) SetNillableLabelID(u *uint64) *LabelLogCreate {
+	if u != nil {
+		llc.SetLabelID(*u)
 	}
 	return llc
 }
@@ -236,7 +236,7 @@ func (llc *LabelLogCreate) createSpec() (*LabelLog, *sqlgraph.CreateSpec) {
 		_node.LabelName = value
 	}
 	if value, ok := llc.mutation.LabelID(); ok {
-		_spec.SetField(labellog.FieldLabelID, field.TypeInt, value)
+		_spec.SetField(labellog.FieldLabelID, field.TypeUint64, value)
 		_node.LabelID = value
 	}
 	if value, ok := llc.mutation.WxID(); ok {
@@ -324,7 +324,7 @@ func (u *LabelLogUpsert) UpdateLabelName() *LabelLogUpsert {
 }
 
 // SetLabelID sets the "label_id" field.
-func (u *LabelLogUpsert) SetLabelID(v int) *LabelLogUpsert {
+func (u *LabelLogUpsert) SetLabelID(v uint64) *LabelLogUpsert {
 	u.Set(labellog.FieldLabelID, v)
 	return u
 }
@@ -336,7 +336,7 @@ func (u *LabelLogUpsert) UpdateLabelID() *LabelLogUpsert {
 }
 
 // AddLabelID adds v to the "label_id" field.
-func (u *LabelLogUpsert) AddLabelID(v int) *LabelLogUpsert {
+func (u *LabelLogUpsert) AddLabelID(v uint64) *LabelLogUpsert {
 	u.Add(labellog.FieldLabelID, v)
 	return u
 }
@@ -457,14 +457,14 @@ func (u *LabelLogUpsertOne) UpdateLabelName() *LabelLogUpsertOne {
 }
 
 // SetLabelID sets the "label_id" field.
-func (u *LabelLogUpsertOne) SetLabelID(v int) *LabelLogUpsertOne {
+func (u *LabelLogUpsertOne) SetLabelID(v uint64) *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 {
+func (u *LabelLogUpsertOne) AddLabelID(v uint64) *LabelLogUpsertOne {
 	return u.Update(func(s *LabelLogUpsert) {
 		s.AddLabelID(v)
 	})
@@ -765,14 +765,14 @@ func (u *LabelLogUpsertBulk) UpdateLabelName() *LabelLogUpsertBulk {
 }
 
 // SetLabelID sets the "label_id" field.
-func (u *LabelLogUpsertBulk) SetLabelID(v int) *LabelLogUpsertBulk {
+func (u *LabelLogUpsertBulk) SetLabelID(v uint64) *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 {
+func (u *LabelLogUpsertBulk) AddLabelID(v uint64) *LabelLogUpsertBulk {
 	return u.Update(func(s *LabelLogUpsert) {
 		s.AddLabelID(v)
 	})

+ 20 - 20
ent/labellog_update.go

@@ -49,23 +49,23 @@ func (llu *LabelLogUpdate) SetNillableLabelName(s *string) *LabelLogUpdate {
 }
 
 // SetLabelID sets the "label_id" field.
-func (llu *LabelLogUpdate) SetLabelID(i int) *LabelLogUpdate {
+func (llu *LabelLogUpdate) SetLabelID(u uint64) *LabelLogUpdate {
 	llu.mutation.ResetLabelID()
-	llu.mutation.SetLabelID(i)
+	llu.mutation.SetLabelID(u)
 	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)
+func (llu *LabelLogUpdate) SetNillableLabelID(u *uint64) *LabelLogUpdate {
+	if u != nil {
+		llu.SetLabelID(*u)
 	}
 	return llu
 }
 
-// AddLabelID adds i to the "label_id" field.
-func (llu *LabelLogUpdate) AddLabelID(i int) *LabelLogUpdate {
-	llu.mutation.AddLabelID(i)
+// AddLabelID adds u to the "label_id" field.
+func (llu *LabelLogUpdate) AddLabelID(u int64) *LabelLogUpdate {
+	llu.mutation.AddLabelID(u)
 	return llu
 }
 
@@ -167,10 +167,10 @@ func (llu *LabelLogUpdate) sqlSave(ctx context.Context) (n int, err error) {
 		_spec.SetField(labellog.FieldLabelName, field.TypeString, value)
 	}
 	if value, ok := llu.mutation.LabelID(); ok {
-		_spec.SetField(labellog.FieldLabelID, field.TypeInt, value)
+		_spec.SetField(labellog.FieldLabelID, field.TypeUint64, value)
 	}
 	if value, ok := llu.mutation.AddedLabelID(); ok {
-		_spec.AddField(labellog.FieldLabelID, field.TypeInt, value)
+		_spec.AddField(labellog.FieldLabelID, field.TypeUint64, value)
 	}
 	if value, ok := llu.mutation.WxID(); ok {
 		_spec.SetField(labellog.FieldWxID, field.TypeString, value)
@@ -225,23 +225,23 @@ func (lluo *LabelLogUpdateOne) SetNillableLabelName(s *string) *LabelLogUpdateOn
 }
 
 // SetLabelID sets the "label_id" field.
-func (lluo *LabelLogUpdateOne) SetLabelID(i int) *LabelLogUpdateOne {
+func (lluo *LabelLogUpdateOne) SetLabelID(u uint64) *LabelLogUpdateOne {
 	lluo.mutation.ResetLabelID()
-	lluo.mutation.SetLabelID(i)
+	lluo.mutation.SetLabelID(u)
 	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)
+func (lluo *LabelLogUpdateOne) SetNillableLabelID(u *uint64) *LabelLogUpdateOne {
+	if u != nil {
+		lluo.SetLabelID(*u)
 	}
 	return lluo
 }
 
-// AddLabelID adds i to the "label_id" field.
-func (lluo *LabelLogUpdateOne) AddLabelID(i int) *LabelLogUpdateOne {
-	lluo.mutation.AddLabelID(i)
+// AddLabelID adds u to the "label_id" field.
+func (lluo *LabelLogUpdateOne) AddLabelID(u int64) *LabelLogUpdateOne {
+	lluo.mutation.AddLabelID(u)
 	return lluo
 }
 
@@ -373,10 +373,10 @@ func (lluo *LabelLogUpdateOne) sqlSave(ctx context.Context) (_node *LabelLog, er
 		_spec.SetField(labellog.FieldLabelName, field.TypeString, value)
 	}
 	if value, ok := lluo.mutation.LabelID(); ok {
-		_spec.SetField(labellog.FieldLabelID, field.TypeInt, value)
+		_spec.SetField(labellog.FieldLabelID, field.TypeUint64, value)
 	}
 	if value, ok := lluo.mutation.AddedLabelID(); ok {
-		_spec.AddField(labellog.FieldLabelID, field.TypeInt, value)
+		_spec.AddField(labellog.FieldLabelID, field.TypeUint64, value)
 	}
 	if value, ok := lluo.mutation.WxID(); ok {
 		_spec.SetField(labellog.FieldWxID, field.TypeString, value)

+ 1 - 1
ent/migrate/schema.go

@@ -602,7 +602,7 @@ var (
 		{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: "label_id", Type: field.TypeUint64, 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},
 	}

+ 13 - 13
ent/mutation.go

@@ -23481,8 +23481,8 @@ type LabelLogMutation struct {
 	created_at         *time.Time
 	updated_at         *time.Time
 	label_name         *string
-	label_id           *int
-	addlabel_id        *int
+	label_id           *uint64
+	addlabel_id        *int64
 	wx_id              *string
 	organization_id    *uint64
 	addorganization_id *int64
@@ -23705,13 +23705,13 @@ func (m *LabelLogMutation) ResetLabelName() {
 }
 
 // SetLabelID sets the "label_id" field.
-func (m *LabelLogMutation) SetLabelID(i int) {
-	m.label_id = &i
+func (m *LabelLogMutation) SetLabelID(u uint64) {
+	m.label_id = &u
 	m.addlabel_id = nil
 }
 
 // LabelID returns the value of the "label_id" field in the mutation.
-func (m *LabelLogMutation) LabelID() (r int, exists bool) {
+func (m *LabelLogMutation) LabelID() (r uint64, exists bool) {
 	v := m.label_id
 	if v == nil {
 		return
@@ -23722,7 +23722,7 @@ func (m *LabelLogMutation) LabelID() (r int, exists bool) {
 // 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) {
+func (m *LabelLogMutation) OldLabelID(ctx context.Context) (v uint64, err error) {
 	if !m.op.Is(OpUpdateOne) {
 		return v, errors.New("OldLabelID is only allowed on UpdateOne operations")
 	}
@@ -23736,17 +23736,17 @@ func (m *LabelLogMutation) OldLabelID(ctx context.Context) (v int, err error) {
 	return oldValue.LabelID, nil
 }
 
-// AddLabelID adds i to the "label_id" field.
-func (m *LabelLogMutation) AddLabelID(i int) {
+// AddLabelID adds u to the "label_id" field.
+func (m *LabelLogMutation) AddLabelID(u int64) {
 	if m.addlabel_id != nil {
-		*m.addlabel_id += i
+		*m.addlabel_id += u
 	} else {
-		m.addlabel_id = &i
+		m.addlabel_id = &u
 	}
 }
 
 // AddedLabelID returns the value that was added to the "label_id" field in this mutation.
-func (m *LabelLogMutation) AddedLabelID() (r int, exists bool) {
+func (m *LabelLogMutation) AddedLabelID() (r int64, exists bool) {
 	v := m.addlabel_id
 	if v == nil {
 		return
@@ -23991,7 +23991,7 @@ func (m *LabelLogMutation) SetField(name string, value ent.Value) error {
 		m.SetLabelName(v)
 		return nil
 	case labellog.FieldLabelID:
-		v, ok := value.(int)
+		v, ok := value.(uint64)
 		if !ok {
 			return fmt.Errorf("unexpected type %T for field %s", value, name)
 		}
@@ -24047,7 +24047,7 @@ func (m *LabelLogMutation) AddedField(name string) (ent.Value, bool) {
 func (m *LabelLogMutation) AddField(name string, value ent.Value) error {
 	switch name {
 	case labellog.FieldLabelID:
-		v, ok := value.(int)
+		v, ok := value.(int64)
 		if !ok {
 			return fmt.Errorf("unexpected type %T for field %s", value, name)
 		}

+ 1 - 1
ent/runtime/runtime.go

@@ -1014,7 +1014,7 @@ func init() {
 	// 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)
+	labellog.DefaultLabelID = labellogDescLabelID.Default.(uint64)
 	// 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.

+ 1 - 1
ent/schema/label_log.go

@@ -16,7 +16,7 @@ type LabelLog struct {
 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.Uint64("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)),
 	}

+ 3 - 3
ent/set_not_nil.go

@@ -5264,7 +5264,7 @@ func (ll *LabelLogCreate) SetNotNilLabelName(value *string) *LabelLogCreate {
 }
 
 // set field if value's pointer is not nil.
-func (ll *LabelLogUpdate) SetNotNilLabelID(value *int) *LabelLogUpdate {
+func (ll *LabelLogUpdate) SetNotNilLabelID(value *uint64) *LabelLogUpdate {
 	if value != nil {
 		return ll.SetLabelID(*value)
 	}
@@ -5272,7 +5272,7 @@ func (ll *LabelLogUpdate) SetNotNilLabelID(value *int) *LabelLogUpdate {
 }
 
 // set field if value's pointer is not nil.
-func (ll *LabelLogUpdateOne) SetNotNilLabelID(value *int) *LabelLogUpdateOne {
+func (ll *LabelLogUpdateOne) SetNotNilLabelID(value *uint64) *LabelLogUpdateOne {
 	if value != nil {
 		return ll.SetLabelID(*value)
 	}
@@ -5280,7 +5280,7 @@ func (ll *LabelLogUpdateOne) SetNotNilLabelID(value *int) *LabelLogUpdateOne {
 }
 
 // set field if value's pointer is not nil.
-func (ll *LabelLogCreate) SetNotNilLabelID(value *int) *LabelLogCreate {
+func (ll *LabelLogCreate) SetNotNilLabelID(value *uint64) *LabelLogCreate {
 	if value != nil {
 		return ll.SetLabelID(*value)
 	}

+ 70 - 0
hook/contact.go

@@ -7,6 +7,7 @@ import (
 	"github.com/zeromicro/go-zero/core/logx"
 	"strings"
 	"wechat-api/workphone"
+	"wechat-api/workphone/wecom"
 )
 
 // 获取标签信息
@@ -207,6 +208,38 @@ func (h *Hook) TriggerConversationPushTask(wxWxid string) error {
 	return nil
 }
 
+func (h *Hook) TriggerUserLabelTask(wxWxid string) error {
+	conn, err := h.connWorkPhone()
+	if err != nil {
+		err = fmt.Errorf("TriggerUserLabelTask failed")
+		return err
+	}
+	defer func(conn *websocket.Conn) {
+		err = conn.Close()
+		if err != nil {
+			err = fmt.Errorf("TriggerUserLabelTask failed")
+		}
+	}(conn)
+
+	message := map[string]interface{}{
+		"MsgType": "TriggerUserLabelTask",
+		"Content": map[string]interface{}{
+			"WxId": wxWxid,
+		},
+	}
+	transportMessageJSON, err := json.Marshal(message)
+	if err != nil {
+		return err
+	}
+	// 发送 JSON 消息
+	err = conn.WriteMessage(websocket.TextMessage, transportMessageJSON)
+	if err != nil {
+		return fmt.Errorf("failed to send message: %v", err)
+	}
+
+	return nil
+}
+
 func (h *Hook) AddFriendInChatRoom(ChatRoomId, wxWxid, friendId, desc string) error {
 	conn, err := h.connWorkPhone()
 	if err != nil {
@@ -348,3 +381,40 @@ func (m MessagePayload[T]) ToMap() map[string]interface{} {
 		"Content": m.Content,
 	}
 }
+
+// AddWecomCustomerFromSearchTask 函数用于创建并发送一个添加好友的任务
+// 参数:
+//
+//	ownerWxId: 发起添加好友请求的微信ID
+//	phone: 目标手机号 //qq号//微信id
+//	msg: 添加好友时附带的消息
+//	taskId: 任务ID
+//
+// 返回值:
+//
+//	成功时返回包含添加好友任务信息的消息负载和nil错误
+//	失败时返回空的消息负载和相应的错误
+func (h *Hook) AddWecomCustomerFromSearchTask(ownerWxId, phone, msg string, taskId int64) (map[string]interface{}, error) {
+	if ownerWxId == "" || phone == "" {
+		return MessagePayload[AddWecomCustomerFromSearchTask]{}.ToMap(), fmt.Errorf("invalid input parameters")
+	}
+	content := AddWecomCustomerFromSearchTask{
+		WxId:   ownerWxId,
+		Key:    phone,
+		Msg:    msg,
+		Memo:   "",
+		TaskId: taskId,
+		Labels: []string{0: ""},
+	}
+	return SendMessage(h, workphone.EnumMsgType(wecom.EnumMsgType_AddCustomerFromSearchTask), "AddCustomerFromSearchTask", content)
+}
+
+// AddWecomCustomerFromSearchTask  用于添加好友任务的消息结构
+type AddWecomCustomerFromSearchTask struct {
+	WxId   string   `json:"WxId"`
+	Key    string   `json:"Key"`
+	Msg    string   `json:"Msg"`
+	Memo   string   `json:"Memo"`
+	TaskId int64    `json:"TaskId"`
+	Labels []string `json:"Labels"`
+}

+ 44 - 0
internal/handler/add_friend/add_friend_by_phone_handler.go

@@ -0,0 +1,44 @@
+package add_friend
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/add_friend"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /add_friend/add_friend_by_phone add_friend AddFriendByPhone
+//
+// 手机号加好友接口
+//
+// 手机号加好友接口
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: AddWechatFriendLogInfo
+//
+// Responses:
+//  200: BaseMsgResp
+
+func AddFriendByPhoneHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.AddWechatFriendLogInfo
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := add_friend.NewAddFriendByPhoneLogic(r.Context(), svcCtx)
+		resp, err := l.AddFriendByPhone(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 15 - 0
internal/handler/routes.go

@@ -16,6 +16,7 @@ import (
 	WorkPhone "wechat-api/internal/handler/WorkPhone"
 	Wx "wechat-api/internal/handler/Wx"
 	Wxhook "wechat-api/internal/handler/Wxhook"
+	add_friend "wechat-api/internal/handler/add_friend"
 	agent "wechat-api/internal/handler/agent"
 	agent_base "wechat-api/internal/handler/agent_base"
 	aliyun_avatar "wechat-api/internal/handler/aliyun_avatar"
@@ -2360,4 +2361,18 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
 			},
 		},
 	)
+
+	server.AddRoutes(
+		rest.WithMiddlewares(
+			[]rest.Middleware{serverCtx.Authority},
+			[]rest.Route{
+				{
+					Method:  http.MethodPost,
+					Path:    "/add_friend/add_friend_by_phone",
+					Handler: add_friend.AddFriendByPhoneHandler(serverCtx),
+				},
+			}...,
+		),
+		rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
+	)
 }

+ 2 - 1
internal/logic/Wxhook/get_friends_and_groups_logic.go

@@ -210,6 +210,7 @@ func (l *GetFriendsAndGroupsLogic) GetFriendsAndGroups(req *types.IDReq) (resp *
 			hookClient = hook.NewWecomHook("", "", "")
 			_ = hookClient.TriggerCustomerPushTask(wxInfo.Wxid)
 			_ = hookClient.TriggerConversationPushTask(wxInfo.Wxid)
+			_ = hookClient.TriggerUserLabelTask(wxInfo.Wxid)
 		} else {
 
 			data := map[string]interface{}{
@@ -219,7 +220,7 @@ func (l *GetFriendsAndGroupsLogic) GetFriendsAndGroups(req *types.IDReq) (resp *
 				},
 			}
 			jsonStr, err := json.Marshal(data)
-			err = l.svcCtx.WechatWs["default"].SendMsg([]byte(jsonStr))
+			err = l.svcCtx.WechatWs["default"].SendMsg(jsonStr)
 			if err != nil {
 				logx.Error("syncWx: ", err)
 				return nil, err

+ 29 - 0
internal/logic/add_friend/add_friend_by_phone_logic.go

@@ -0,0 +1,29 @@
+package add_friend
+
+import (
+	"context"
+
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type AddFriendByPhoneLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewAddFriendByPhoneLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AddFriendByPhoneLogic {
+	return &AddFriendByPhoneLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *AddFriendByPhoneLogic) AddFriendByPhone(req *types.AddWechatFriendLogInfo) (resp *types.BaseMsgResp, err error) {
+	// todo: add your logic here and delete this line
+
+	return
+}

+ 347 - 0
internal/pkg/wechat_ws/wecom_ws_client.go

@@ -0,0 +1,347 @@
+package wechat_ws
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"github.com/gorilla/websocket"
+	"github.com/zeromicro/go-zero/core/logx"
+	"google.golang.org/protobuf/encoding/protojson"
+	"google.golang.org/protobuf/types/known/anypb"
+	"time"
+	"wechat-api/workphone/wecom"
+)
+
+type WecomWsClient struct {
+	Conn           *websocket.Conn
+	AccessToken    string
+	Send           chan []byte
+	Name           string
+	CTypes         string
+	MessageHandler []MessageHandler
+}
+
+func NewWecomWsClient(urlStr string, name string, ctype string) (*WecomWsClient, error) {
+	logx.Debug("实例开始")
+
+	//p, _ := url.Parse("http://127.0.0.1:7897")
+
+	var d = websocket.Dialer{
+		//Proxy: http.ProxyURL(&url.URL{
+		//	Scheme: "http", // or "https" depending on your proxy
+		//	Host:   "127.0.0.1:7897",
+		//	Path:   "/",
+		//}),
+	}
+
+	c, _, err := d.Dial(urlStr, nil)
+	if err != nil {
+		logx.Error(err)
+		return nil, err
+	}
+
+	client := &WecomWsClient{
+		Conn:           c,
+		AccessToken:    "",
+		Send:           make(chan []byte, 256),
+		Name:           name,
+		CTypes:         ctype,
+		MessageHandler: make([]MessageHandler, 0),
+	}
+
+	err = client.DeviceAuth()
+	if err != nil {
+		return nil, err
+	}
+
+	return client, nil
+}
+
+// SendMsg 立刻发送消息,发送不成功可以获取错误信息
+func (c *WecomWsClient) SendMsg(message []byte) error {
+	logx.Info("发送消息:", string(message))
+	err := c.Conn.SetWriteDeadline(time.Now().Add(writeWait))
+	if err != nil {
+		return err
+	}
+	err = c.Conn.WriteMessage(websocket.TextMessage, message)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// SendMsgByChan 通过chan通道发送消息,这样可以保证发送顺序,异步,不返回错误
+func (c *WecomWsClient) SendMsgByChan(msg []byte) {
+	c.Send <- msg
+}
+
+func (c *WecomWsClient) WritePump() {
+	ticker := time.NewTicker(pingPeriod)
+	defer func() {
+		ticker.Stop()
+		err := c.Conn.Close()
+		if err != nil {
+			return
+		}
+	}()
+	for {
+		select {
+		case message, ok := <-c.Send:
+			if !ok {
+				continue
+			}
+			_ = c.SendMsg(message)
+
+			// 以下注释 的,是把所有当前积压的消息一次性都发出去的写法,不确定是否会乱序,或服务端否支持
+			//_ = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
+			//if !ok {
+			//	_ = c.conn.WriteMessage(websocket.CloseMessage, []byte{})
+			//	return
+			//}
+			//
+			//w, err := c.conn.NextWriter(websocket.TextMessage)
+			//if err != nil {
+			//	return
+			//}
+			//_, _ = w.Write(message)
+			//
+			//// Add queued chat messages to the current websocket message.
+			//n := len(c.send)
+			//for i := 0; i < n; i++ {
+			//	_, _ = w.Write(newline)
+			//	_, _ = w.Write(<-c.send)
+			//}
+			//
+			//if err := w.Close(); err != nil {
+			//	return
+			//}
+		case <-ticker.C:
+			if c.AccessToken == "" {
+				logx.Error("accessToken is empty")
+				continue
+			}
+
+			message := map[string]interface{}{
+				"Id":          1001,
+				"MsgType":     "HeartBeatReq",
+				"AccessToken": c.AccessToken,
+				"Content": map[string]string{
+					"token": c.AccessToken,
+				},
+			}
+
+			//transportMessageJSON, err := json.Marshal(message)
+			//if err != nil {
+			//	logx.Error(err)
+			//	continue
+			//}
+			//
+			_ = c.Conn.SetWriteDeadline(time.Now().Add(writeWait))
+			//err = c.Conn.WriteMessage(websocket.TextMessage, transportMessageJSON)
+
+			err := c.Conn.WriteJSON(message)
+
+			if err != nil {
+				logx.Error(err)
+				continue
+			}
+			logx.Info("发送心跳保活~~")
+		}
+	}
+}
+
+func (c *WecomWsClient) RegisterMessageHandler(handler MessageHandler) {
+	if c == nil {
+		logx.Error("WecomWsClient is nil in RegisterMessageHandler")
+		return
+	}
+	logx.Info("Registering message handler for WecomWsClient:", c.Name)
+	c.MessageHandler = append(c.MessageHandler, handler)
+}
+
+func (c *WecomWsClient) ReadPump() {
+	defer func() {
+		err := c.Conn.Close()
+		if err != nil {
+			return
+		}
+	}()
+	//c.Conn.SetReadLimit(maxMessageSize)
+	//err := c.Conn.SetReadDeadline(time.Time{})
+	//if err != nil {
+	//	logx.Errorf("SetReadDeadline error: %v", err)
+	//	return
+	//}
+	//c.conn.SetPongHandler(func(string) error {
+	//	err := c.conn.SetReadDeadline(time.Time{})
+	//	if err != nil {
+	//		return err
+	//	}
+	//	return nil
+	//})
+	for {
+		_, message, err := c.Conn.ReadMessage()
+		if err != nil {
+			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
+				logx.Errorf("IsUnexpectedCloseError error: %v", err)
+			} else {
+				logx.Errorf("NotIsUnexpectedCloseError error: %v", err)
+			}
+			break
+		}
+		message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
+
+		logx.Debugf("message 获取的消息原型是 : %s", string(message))
+
+		var msg MsgJsonObject
+		//err = c.Conn.ReadJSON(msg)
+		err = json.Unmarshal(message, &msg)
+		if err != nil {
+			logx.Error(err)
+			continue
+		}
+		//logx.Info("message 获取的消息原型是 : ", msg)
+
+		switch msg.MsgType {
+		case "MsgReceivedAck":
+			logx.Info("心跳确认消息已收到,无需处理")
+		default:
+			if len(c.MessageHandler) > 0 {
+				for _, handler := range c.MessageHandler {
+					// todo 重启时这里会丢消息,回头需要用wg来进行平滑处理
+					go func() {
+						defer func() {
+							// 捕获panic, 防止程序崩溃
+							if r := recover(); r != nil {
+								logx.Error("Recovered in f", r)
+							}
+						}()
+						err = handler(&msg)
+						if err != nil {
+							logx.Error(err)
+						}
+					}()
+				}
+			}
+		}
+
+	}
+}
+
+// DeviceAuth2 todo 这个方法中,产生了一个@type的字段,无法通过注册.有空的时候看看,这种方式还是更优雅
+//
+//go:deprecated 已弃用,请使用 DeviceAuth 方法进行设备认证。
+func (c *WecomWsClient) DeviceAuth2() error {
+	sendMsg := &wecom.DeviceAuthReqMessage{
+		AuthType:   wecom.DeviceAuthReqMessage_InternalCode,
+		Credential: "",
+	}
+
+	content, err := anypb.New(sendMsg)
+	if err != nil {
+		logx.Error(err)
+		return err
+	}
+
+	logx.Info("content is ", content)
+
+	transportMessage := &wecom.TransportMessage{
+		Id:      int64(wecom.EnumMsgType_DeviceAuthReq),
+		MsgType: wecom.EnumMsgType_DeviceAuthReq,
+		Content: content,
+	}
+
+	transportMessageJSON, err := protojson.MarshalOptions{
+		UseProtoNames: true,
+	}.Marshal(transportMessage)
+
+	if err != nil {
+		logx.Error(err)
+		return err
+	}
+
+	logx.Info(string(transportMessageJSON))
+
+	err = c.Conn.WriteMessage(websocket.TextMessage, transportMessageJSON)
+	if err != nil {
+		logx.Error(err)
+		return err
+	}
+
+	return nil
+
+}
+
+// DeviceAuth 连接认证,使用IP白名单方式进行认证
+func (c *WecomWsClient) DeviceAuth() error {
+
+	message := map[string]interface{}{
+		"Id":          1010,
+		"AccessToken": "",
+		"MsgType":     "DeviceAuthReq",
+		"Content": map[string]interface{}{
+			"AuthType":   3,
+			"Credential": "",
+		},
+	}
+	transportMessageJSON, err := json.Marshal(message)
+	if err != nil {
+		logx.Error(err)
+		return err
+	}
+
+	err = c.Conn.WriteMessage(websocket.TextMessage, transportMessageJSON)
+	if err != nil {
+		logx.Error(err)
+		return err
+	}
+
+	for {
+		_, msgByte, err := c.Conn.ReadMessage()
+		if err != nil {
+			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
+				logx.Errorf("IsUnexpectedCloseError error: %v", err)
+			} else {
+				logx.Errorf("NotIsUnexpectedCloseError error: %v", err)
+			}
+			break
+		}
+		msgByte = bytes.TrimSpace(bytes.Replace(msgByte, newline, space, -1))
+
+		logx.Debugf("message 获取的消息原型是 : %s", string(msgByte))
+
+		var msg MsgJsonObject
+
+		err = json.Unmarshal(msgByte, &msg)
+		if err != nil {
+			logx.Error(err)
+			continue
+		}
+
+		if msg.MsgType == "Error" {
+			var errMsg wecom.ErrorMessage
+			_ = protojson.Unmarshal([]byte(msg.Message), &errMsg)
+			logx.Error("连接认证失败,请检查IP白名单是否正确", errMsg)
+			return errors.New("连接认证失败,请检查IP白名单是否正确")
+		}
+
+		if msg.MsgType != "DeviceAuthRsp" {
+			logx.Error("不是连接认证消息,丢弃~", string(msgByte))
+			continue
+		}
+
+		var deviceAuthRsp wecom.DeviceAuthRspMessage
+		err = protojson.Unmarshal([]byte(msg.Message), &deviceAuthRsp)
+		if err != nil {
+			logx.Error(err)
+			continue
+		}
+		logx.Info("连接认证成功 accessToken :", deviceAuthRsp.AccessToken)
+		c.AccessToken = deviceAuthRsp.AccessToken
+		break
+	}
+
+	return nil
+
+}

+ 5 - 5
internal/service/MessageHandlers/contact_Label_info_notice.go

@@ -44,10 +44,10 @@ func (f *ContactLabelInfoNotice) Handle(ctx context.Context, msg *wechat_ws.MsgJ
 	}
 
 	// 整理标签ID和名称列表
-	labelIDs := make([]int, 0, len(message.Labels))
+	labelIDs := make([]uint64, 0, len(message.Labels))
 	labelNameSet := make(map[string]struct{}) // 用于去重
 	for _, labelInfoMessage := range message.Labels {
-		labelIDs = append(labelIDs, int(labelInfoMessage.LabelId))
+		labelIDs = append(labelIDs, uint64(labelInfoMessage.LabelId))
 		labelNameSet[labelInfoMessage.LabelName] = struct{}{}
 	}
 
@@ -74,7 +74,7 @@ func (f *ContactLabelInfoNotice) Handle(ctx context.Context, msg *wechat_ws.MsgJ
 		return fmt.Errorf("查询 Label 失败: %w", err)
 	}
 
-	existingLabelLogMap := make(map[int]struct{})
+	existingLabelLogMap := make(map[uint64]struct{})
 	for _, log := range existingLabelLogs {
 		existingLabelLogMap[log.LabelID] = struct{}{}
 	}
@@ -96,11 +96,11 @@ func (f *ContactLabelInfoNotice) Handle(ctx context.Context, msg *wechat_ws.MsgJ
 		}
 
 		// 插入 LabelLog
-		if _, exists := existingLabelLogMap[int(labelWx.LabelId)]; !exists {
+		if _, exists := existingLabelLogMap[uint64(int(labelWx.LabelId))]; !exists {
 			bulkLabelLogs = append(bulkLabelLogs,
 				svcCtx.DB.LabelLog.Create().
 					SetLabelName(labelWx.LabelName).
-					SetLabelID(int(labelWx.LabelId)).
+					SetLabelID(uint64(int(labelWx.LabelId))).
 					SetOrganizationID(wxInfo.OrganizationID).
 					SetWxID(message.WeChatId).
 					SetCreatedAt(time.Unix(tsInt/1000, 0)),

+ 4 - 4
internal/service/MessageHandlers/friend_push_notice.go

@@ -145,7 +145,7 @@ func (f *FriendPushNoticeTypeHandler) Handle(ctx context.Context, msg *wechat_ws
 		}
 		//获取labelId,并且按照逗号去分割成数组
 		labelIdsStr := friend.LabelIds
-		var ids []int
+		var ids []uint64
 		ids, err = ParseCSVToIntSlice(labelIdsStr)
 		if err != nil {
 			logx.Infof("labelstring切割失败, labelIds=%v", labelIdsStr)
@@ -215,18 +215,18 @@ func (f *FriendPushNoticeTypeHandler) Handle(ctx context.Context, msg *wechat_ws
 	return nil
 }
 
-func ParseCSVToIntSlice(csv string) ([]int, error) {
+func ParseCSVToIntSlice(csv string) ([]uint64, error) {
 	if csv == "" {
 		return nil, nil
 	}
 	parts := strings.Split(csv, ",")
-	var result []int
+	var result []uint64
 	for _, p := range parts {
 		trimmed := strings.TrimSpace(p)
 		if trimmed == "" {
 			continue // 忽略空字符串
 		}
-		n, err := strconv.Atoi(trimmed)
+		n, err := strconv.ParseUint(trimmed, 10, 64)
 		if err != nil {
 			logx.Error("无法将 %s 转换为整数: %v", trimmed, err)
 			continue // 忽略无效字符

+ 2 - 19
internal/service/MessageHandlers/register_strategy.go → internal/service/MessageHandlers/message_handler.go

@@ -9,7 +9,6 @@ import (
 	"wechat-api/internal/svc"
 )
 
-// MessageHandlerStrategy 消息处理策略接口
 type MessageHandlerStrategy interface {
 	Handle(ctx context.Context, msg *wechat_ws.MsgJsonObject, svcCtx *svc.ServiceContext) error
 }
@@ -19,7 +18,6 @@ type MessageHandler struct {
 	svcCtx     *svc.ServiceContext
 }
 
-// NewMessageHandler 创建新的消息调度器
 func NewMessageHandler(svcCtx *svc.ServiceContext) *MessageHandler {
 	return &MessageHandler{
 		strategies: make(map[string]MessageHandlerStrategy),
@@ -27,37 +25,26 @@ func NewMessageHandler(svcCtx *svc.ServiceContext) *MessageHandler {
 	}
 }
 
-// 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),
-		"FindContactTaskResult":  toStrategy(NewFindContactTaskResultNoticeHandler),
-		"TaskResultNotice":       toStrategy(NewTaskResultNoticeHandler),
-	}
+func (h *MessageHandler) RegisterStrategies(strategyMap map[string]func(*svc.ServiceContext) MessageHandlerStrategy) {
 	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 向上冒泡
+			err = nil
 		}
 	}()
 
@@ -69,20 +56,16 @@ func (h *MessageHandler) Handle(ctx context.Context, msg *wechat_ws.MsgJsonObjec
 	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)
 	}
 }

+ 110 - 26
internal/service/MessageHandlers/task_result_notice.go

@@ -3,6 +3,7 @@ package MessageHandlers
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"github.com/zeromicro/go-zero/core/logx"
 	"strconv"
 	"time"
@@ -23,53 +24,136 @@ func NewTaskResultNoticeHandler(svcCtx *svc.ServiceContext) *TaskResultNoticeHan
 
 // Handle 实现 MessageHandlerStrategy 接口
 func (f *TaskResultNoticeHandler) Handle(ctx context.Context, msg *wechat_ws.MsgJsonObject, svcCtx *svc.ServiceContext) error {
+	logx.Infof("收到任务回执消息: %s", msg.Message)
 
-	var messageTask TaskResultNoticeMessage
-	if err := json.Unmarshal([]byte(msg.Message), &messageTask); err != nil {
-		logx.Errorf("预处理 JSON 失败: %v", err)
+	// 统一解析兼容不同结构体格式
+	common, err := ParseTaskResultMessage(msg.Message)
+	if err != nil {
+		logx.Errorf("解析任务消息失败: %v", err)
 		return err
 	}
-	// 如果 TaskId 是字符串,就转换成数字
-	var taskIdInt int64
-	if messageTask.TaskId != nil {
-		var err error
-		taskIdInt, err = strconv.ParseInt(*messageTask.TaskId, 10, 64)
-		if err != nil {
-			logx.Errorf("Unmarshal.fail")
-			return err
-		}
+
+	taskIdInt, err := common.GetTaskIdInt64()
+	if err != nil {
+		logx.Errorf("TaskId 非法,无法转换为数字: %v", err)
+		return err
 	}
 
-	if messageTask.TaskType != "AddFriendsTask" {
-		logx.Infof("不是添加好友任务的回执")
+	logx.Infof("处理任务:TaskId=%d,WeChatId=%s,TaskType=%s,Success=%v", taskIdInt, common.WeChatId, common.TaskType, common.Success)
+
+	// 只处理添加好友相关任务
+	if common.TaskType != "AddFriendsTask" && common.TaskType != "AddCustomerFromSearchTask" {
+		logx.Infof("忽略非添加好友任务类型: %s", common.TaskType)
 		return nil
 	}
+
+	// 将消息解析成通用 map 以用于存储结果
 	m, err := ParseJSONStringToMap(msg.Message)
 	if err != nil {
 		logx.Errorf("解析 JSON 失败: %v", err)
 		return err
 	}
+
+	// 更新数据库记录
 	update := svcCtx.DB.AddWechatFriendLog.Update().
-		Where(addwechatfriendlog.OwnerWxIDEQ(*messageTask.WeChatId)).
+		Where(addwechatfriendlog.OwnerWxIDEQ(common.WeChatId)).
 		Where(addwechatfriendlog.TaskIDEQ(taskIdInt)).
-		//SetFindResult(m).
 		SetAddResult(m).
 		SetUpdatedAt(time.Now().Unix())
-	if *messageTask.Success {
-		update.SetIsCanAdd(2)
+
+	if common.ErrMsg == "该用户不存在" {
+		update.SetIsCanAdd(0) // 重置添加为不能添加好友
 	}
+
+	if common.Success {
+		update.SetIsCanAdd(2) // 表示任务成功
+	}
+
 	if _, err := update.Save(ctx); err != nil {
-		logx.Errorf("更新AddWechatFriendLog-field-add-result失败: %v", err)
+		logx.Errorf("更新 AddWechatFriendLog 表失败: %v", err)
 		return err
 	}
+
 	return nil
 }
 
-type TaskResultNoticeMessage struct {
-	Success  *bool   `json:"Success,omitempty"`  // 指针,便于判断字段是否存在
-	Code     *int32  `json:"Code,omitempty"`     // 错误码
-	ErrMsg   *string `json:"ErrMsg,omitempty"`   // 错误描述
-	TaskId   *string `json:"TaskId,omitempty"`   // 任务ID(因前面问题,保持 string 类型)
-	TaskType string  `json:"TaskType,omitempty"` // 枚举类型
-	WeChatId *string `json:"WeChatId,omitempty"` // 微信号
+//type TaskResultNoticeMessage struct {
+//	Success  bool    `json:"Success,omitempty"`  // 指针,便于判断字段是否存在
+//	Code     *string `json:"Code,omitempty"`     // 错误码
+//	ErrMsg   *string `json:"ErrMsg,omitempty"`   // 错误描述
+//	TaskId   *string `json:"TaskId,omitempty"`   // 任务ID(因前面问题,保持 string 类型)
+//	TaskType string  `json:"TaskType,omitempty"` // 枚举类型
+//	WeChatId *string `json:"WeChatId,omitempty"` // 微信号
+//}
+//
+//type WecomTaskResultNoticeMessage struct {
+//	Success  bool    `json:"Success,omitempty"`  // 指针,便于判断字段是否存在
+//	Code     *string `json:"Code,omitempty"`     // 错误码
+//	ErrMsg   *string `json:"ErrMsg,omitempty"`   // 错误描述
+//	TaskId   *string `json:"TaskId,omitempty"`   // 任务ID(因前面问题,保持 string 类型)
+//	TaskType string  `json:"TaskType,omitempty"` // 枚举类型
+//	WeChatId *string `json:"WxId,omitempty"`     // 微信号
+//}
+
+type TaskResultMessageCommon struct {
+	Success  bool
+	Code     string
+	ErrMsg   string
+	TaskId   string
+	TaskType string
+	WeChatId string
+}
+
+func ParseTaskResultMessage(jsonStr string) (*TaskResultMessageCommon, error) {
+	var data map[string]interface{}
+	if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
+		return nil, fmt.Errorf("JSON 解析失败: %w", err)
+	}
+
+	var result TaskResultMessageCommon
+
+	// Success
+	if val, ok := data["Success"].(bool); ok {
+		result.Success = val
+	}
+
+	// Code
+	if val, ok := data["Code"]; ok {
+		result.Code = fmt.Sprintf("%v", val)
+	}
+
+	// ErrMsg
+	if val, ok := data["ErrMsg"]; ok {
+		result.ErrMsg = fmt.Sprintf("%v", val)
+	}
+
+	// TaskType
+	if val, ok := data["TaskType"]; ok {
+		result.TaskType = fmt.Sprintf("%v", val)
+	}
+
+	// TaskId(统一转成 string)
+	if val, ok := data["TaskId"]; ok {
+		switch v := val.(type) {
+		case string:
+			result.TaskId = v
+		case float64:
+			result.TaskId = strconv.FormatInt(int64(v), 10)
+		default:
+			result.TaskId = fmt.Sprintf("%v", v)
+		}
+	}
+
+	// WeChatId 或 WxId(统一成 string)
+	if val, ok := data["WeChatId"]; ok {
+		result.WeChatId = fmt.Sprintf("%v", val)
+	} else if val, ok := data["WxId"]; ok {
+		result.WeChatId = fmt.Sprintf("%v", val)
+	}
+
+	return &result, nil
+}
+
+func (t *TaskResultMessageCommon) GetTaskIdInt64() (int64, error) {
+	return strconv.ParseInt(t.TaskId, 10, 64)
 }

+ 13 - 0
internal/service/MessageHandlers/wechat_strategies.go

@@ -0,0 +1,13 @@
+package MessageHandlers
+
+import "wechat-api/internal/svc"
+
+func GetWechatStrategies() map[string]func(*svc.ServiceContext) MessageHandlerStrategy {
+	return map[string]func(*svc.ServiceContext) MessageHandlerStrategy{
+		"FriendPushNotice": toStrategy(NewFriendPushNoticeTypeHandler),
+		//"ChatroomPushNotice":     toStrategy(NewChatroomPushNoticeTypeHandler),
+		"ContactLabelInfoNotice": toStrategy(NewContactLabelInfoNotice),
+		"FindContactTaskResult":  toStrategy(NewFindContactTaskResultNoticeHandler),
+		"TaskResultNotice":       toStrategy(NewTaskResultNoticeHandler),
+	}
+}

+ 200 - 0
internal/service/MessageHandlers/wecom/customer_push_notice.go

@@ -0,0 +1,200 @@
+package wecom
+
+import (
+	"context"
+	"encoding/json"
+	"entgo.io/ent/dialect/sql"
+	"fmt"
+	"github.com/zeromicro/go-zero/core/logx"
+	"strconv"
+	"sync"
+	"time"
+	"wechat-api/ent"
+	"wechat-api/ent/label"
+	"wechat-api/ent/labellog"
+	"wechat-api/ent/labelrelationship"
+	"wechat-api/ent/wx"
+	"wechat-api/internal/lock"
+	"wechat-api/internal/pkg/wechat_ws"
+	"wechat-api/internal/svc"
+	"wechat-api/workphone/wecom"
+)
+
+type CustomerPushNoticeHandler struct {
+	svcCtx  *svc.ServiceContext
+	lockMap sync.Map // 微信号 -> *sync.Mutex
+}
+
+func NewCustomerPushNoticeHandler(svcCtx *svc.ServiceContext) *CustomerPushNoticeHandler {
+	return &CustomerPushNoticeHandler{
+		svcCtx: svcCtx,
+	}
+}
+
+// Handle 实现 MessageHandlerStrategy 接口
+func (f *CustomerPushNoticeHandler) Handle(ctx context.Context, msg *wechat_ws.MsgJsonObject, svcCtx *svc.ServiceContext) error {
+	message := wecom.CustomerPushNoticeMessage{}
+	err := json.Unmarshal([]byte(msg.Message), &message)
+
+	logx.Infof("msg.Message 的内容是:%s", msg.Message)
+	if err != nil {
+		logx.Errorf("Unmarshal.fail")
+		return err
+	}
+	wxInfo, err := svcCtx.DB.Wx.Query().
+		Where(
+			wx.WxidEQ(message.WxId), // Additional filter by organizationId
+		).Only(ctx)
+	if err != nil {
+		return err
+	}
+	var labelRelationshipCreates []*ent.LabelRelationshipCreate
+	var ctype uint64
+	ctype = 3
+	for _, friend := range message.Contacts {
+		var friendId uint64
+		//Wxid := strconv.FormatInt(friend.RemoteId, 10)
+
+		friendType := 1
+		if friend.Source == 2 {
+			friendType = 1
+		} else {
+			friendType = 4
+		}
+
+		list := friend.PhoneRemark
+		var phone string
+		if len(list) > 0 {
+			phone = list[0]
+		}
+		var sex int
+
+		if friend.Gender == "Male" {
+			sex = 1
+		} else if friend.Gender == "Female" {
+			sex = 2
+		} else {
+			sex = 0
+		}
+
+		//修改拉黑后的状态被重置
+		friendId, err = svcCtx.DB.Contact.Create().
+			SetWxWxid(message.WxId).
+			SetType(friendType).
+			SetWxid(friend.RemoteId).
+			SetNickname(friend.Name).
+			SetMarkname(friend.Remark).
+			SetHeadimg(friend.Avatar).
+			SetPhone(phone).
+			SetSex(sex).
+			SetCtype(ctype).
+			SetOrganizationID(wxInfo.OrganizationID).
+			OnConflict().
+			UpdateWxWxid().
+			UpdateWxid().
+			UpdateType().
+			UpdateAccount().
+			UpdateNickname().
+			UpdateMarkname().
+			UpdateHeadimg().
+			UpdateOrganizationID().
+			UpdatePhone().
+			UpdateSex().
+			UpdateCtype().
+			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 == nil || len(friend.LabelIds) == 0 {
+			logx.Infof("没有labelIds 失败, wx_wxId=%v", message.WxId)
+			continue
+		}
+
+		labelIdsStr := friend.LabelIds
+		var ids []uint64
+		ids, err = stringSliceToUint64Slice(labelIdsStr)
+		if err != nil {
+			logx.Infof("labelstring切割失败, labelIds=%v", labelIdsStr)
+			continue
+		}
+
+		LabelLogs, err := svcCtx.DB.LabelLog.Query().
+			Where(labellog.LabelIDIn(ids...)).
+			Where(labellog.WxID(message.WxId)).
+			All(ctx)
+
+		if err != nil || len(LabelLogs) == 0 {
+			logx.Infof("labelLog.Query.fail: 跳过 || 或者查询失败 组织id:%d", wxInfo.OrganizationID)
+			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.StatusEQ(remoteLabel.LabelName),
+				label.OrganizationID(currentOrgID),
+			).Only(ctx)
+			if err != nil || ent.IsNotFound(err) {
+				logx.Infof("label id %d not found.fail: ", wxInfo.OrganizationID)
+				continue
+			}
+			//labelId, err := strconv.ParseUint(strconv.FormatUint(labelInfo.ID, 10), 10, 64)
+			if err != nil {
+				fmt.Println("转换出错:", err)
+				continue
+			}
+			_, err = svcCtx.DB.LabelRelationship.Query().Where(
+				labelrelationship.LabelIDEQ(labelInfo.ID),
+				//label.StatusEQ(remoteLabel.LabelName),
+				labelrelationship.ContactIDEQ(friendId),
+			).Only(ctx)
+
+			if err != nil || ent.IsNotFound(err) {
+				labelRelationshipCreates = append(labelRelationshipCreates,
+					svcCtx.DB.LabelRelationship.Create().
+						//SetID(int(label.LabelId)).
+						SetOrganizationID(wxInfo.OrganizationID).
+						SetContactID(friendId).
+						SetStatus(1).
+						SetLabelID(labelInfo.ID).
+						SetCreatedAt(time.Now()).
+						SetUpdatedAt(time.Now()),
+				)
+			}
+		}
+	}
+	if len(labelRelationshipCreates) > 0 {
+		lock.GetWxIdLockManager().RunWithLock(message.WxId, func() {
+			errShip := svcCtx.DB.LabelRelationship.CreateBulk(labelRelationshipCreates...).
+				OnConflict(
+					sql.ConflictColumns(labelrelationship.FieldLabelID, labelrelationship.FieldContactID),
+				).DoNothing().Exec(ctx)
+			if errShip != nil {
+				logx.Error("label_relationship.create.fail: ", wxInfo.OrganizationID, labelRelationshipCreates)
+			}
+		})
+	}
+	return nil
+}
+
+func stringSliceToUint64Slice(strSlice []string) ([]uint64, error) {
+	uintSlice := make([]uint64, 0, len(strSlice))
+	for _, s := range strSlice {
+		n, err := strconv.ParseUint(s, 10, 64)
+		if err != nil {
+			fmt.Printf("转换失败: %v\n", err)
+			continue // 你可以选择跳过或直接 return 报错
+		}
+		uintSlice = append(uintSlice, n)
+	}
+	return uintSlice, nil
+}
+
+func (f *CustomerPushNoticeHandler) getWxLock(wxid string) *sync.Mutex {
+	actual, _ := f.lockMap.LoadOrStore(wxid, &sync.Mutex{})
+	return actual.(*sync.Mutex)
+}

+ 180 - 0
internal/service/MessageHandlers/wecom/user_label_push_notice.go

@@ -0,0 +1,180 @@
+package wecom
+
+import (
+	"context"
+	"encoding/json"
+	"entgo.io/ent/dialect/sql"
+	"fmt"
+	"github.com/zeromicro/go-zero/core/logx"
+	"strconv"
+	"time"
+	"wechat-api/ent"
+	"wechat-api/ent/label"
+	"wechat-api/ent/labellog"
+	"wechat-api/ent/wx"
+	"wechat-api/internal/lock"
+	"wechat-api/internal/pkg/wechat_ws"
+	"wechat-api/internal/svc"
+	"wechat-api/workphone/wecom"
+)
+
+type UserLabelPushNotice struct {
+	svcCtx *svc.ServiceContext
+}
+
+func NewUserLabelPushNotice(svcCtx *svc.ServiceContext) *UserLabelPushNotice {
+	return &UserLabelPushNotice{
+		svcCtx: svcCtx,
+	}
+}
+
+// Handle 实现 MessageHandlerStrategy 接口
+func (f *UserLabelPushNotice) Handle(ctx context.Context, msg *wechat_ws.MsgJsonObject, svcCtx *svc.ServiceContext) error {
+	var message wecom.UserLabelPushNoticeMessage
+	logx.Infof("msg.Message 的内容是:%s", msg.Message)
+	if err := json.Unmarshal([]byte(msg.Message), &message); err != nil {
+		return err
+	}
+	//WxWxId := strconv.FormatInt(message.WxId, 10) //属主微信id
+	wxInfo, err := svcCtx.DB.Wx.Query().
+		Where(wx.WxidEQ(message.WxId)).
+		Only(ctx)
+	if err != nil {
+		return err
+	}
+	// 整理标签ID和名称列表
+	labelIDs := make([]uint64, 0, len(message.LabelGroups))
+	labelNameSet := make(map[string]struct{}) // 用于去重
+	for _, labelInfoMessage := range message.LabelGroups {
+		if len(labelInfoMessage.Labels) == 0 {
+			continue
+		}
+		for _, LabelInfo := range labelInfoMessage.Labels {
+			labelID, err := strconv.ParseUint(LabelInfo.Id, 10, 64)
+			if err != nil {
+				fmt.Println("labelId 类型转成失败:", err)
+				continue
+			}
+			labelIDs = append(labelIDs, labelID)
+			labelNameSet[labelInfoMessage.Name+"-"+LabelInfo.Name] = struct{}{}
+		}
+	}
+
+	// 提前查询现有 LabelLog 和 Label
+	existingLabelLogs, err := svcCtx.DB.LabelLog.Query().
+		Where(
+			labellog.LabelIDIn(labelIDs...),
+			labellog.OrganizationID(wxInfo.OrganizationID),
+			labellog.WxID(message.WxId),
+		).
+		Select(labellog.FieldLabelID).
+		All(ctx)
+	if err != nil {
+		return fmt.Errorf("查询 LabelLog 失败: %w", err)
+	}
+	existingLabels, err := svcCtx.DB.Label.Query().
+		Where(
+			label.NameIn(keysFromMap(labelNameSet)...),
+			label.OrganizationID(wxInfo.OrganizationID),
+		).
+		Select(label.FieldName).
+		All(ctx)
+	if err != nil {
+		return fmt.Errorf("查询 Label 失败: %w", err)
+	}
+
+	existingLabelLogMap := make(map[uint64]struct{})
+	for _, log := range existingLabelLogs {
+		existingLabelLogMap[log.LabelID] = struct{}{}
+	}
+	existingLabelMap := make(map[string]struct{})
+	for _, lab := range existingLabels {
+		existingLabelMap[lab.Name] = struct{}{}
+	}
+
+	var bulkLabelLogs []*ent.LabelLogCreate
+	var bulkLabels []*ent.LabelCreate
+
+	for _, labelWx := range message.LabelGroups {
+
+		if len(labelWx.Labels) == 0 {
+			continue
+		}
+		for _, labelChildInfo := range labelWx.Labels {
+			// 时间戳处理
+			tsInt := int64(labelChildInfo.CreateTime)
+			// 插入 LabelLog
+			labelWecomId, err := StringToUint64(labelChildInfo.Id)
+			if err != nil {
+				logx.Errorf("时间戳转换失败: %v (Label ID: %d)", err, labelWx.Id)
+				continue
+			}
+			s := labelWx.Name + "-" + labelChildInfo.Name
+			if _, exists := existingLabelLogMap[labelWecomId]; !exists {
+				bulkLabelLogs = append(bulkLabelLogs,
+					svcCtx.DB.LabelLog.Create().
+						SetLabelName(s).
+						SetLabelID(labelWecomId).
+						SetOrganizationID(wxInfo.OrganizationID).
+						SetWxID(message.WxId).
+						SetCreatedAt(time.Unix(tsInt, 0)),
+				)
+			}
+			// 插入 Label
+			if _, exists := existingLabelMap[s]; !exists {
+				bulkLabels = append(bulkLabels,
+					svcCtx.DB.Label.Create().
+						SetName(s).
+						SetType(4).
+						SetStatus(1).
+						SetOrganizationID(wxInfo.OrganizationID).
+						SetFrom(3). // 标签来源:1后台创建 2个微同步 3企业微信同步
+						SetMode(1).
+						SetConditions(`{}`).
+						SetCreatedAt(time.Now()).
+						SetUpdatedAt(time.Now()),
+				)
+			}
+		}
+
+	}
+
+	lock.LockWxId(message.WxId)
+	defer lock.UnlockWxId(message.WxId)
+	// 批量插入 LabelLog
+	if len(bulkLabelLogs) > 0 {
+		err := svcCtx.DB.LabelLog.CreateBulk(bulkLabelLogs...).
+			OnConflict(sql.ConflictColumns(labellog.FieldLabelID, labellog.FieldWxID, labellog.FieldOrganizationID)).
+			DoNothing().
+			Exec(ctx)
+		if err != nil {
+			logx.Error("labelLog 批量插入失败", bulkLabelLogs)
+		}
+	}
+
+	// 批量插入 Label
+	if len(bulkLabels) > 0 {
+		err := svcCtx.DB.Label.CreateBulk(bulkLabels...).
+			OnConflict(sql.ConflictColumns(label.FieldName, label.FieldOrganizationID)).
+			DoNothing().
+			Exec(ctx)
+		if err != nil {
+			logx.Error("label 批量插入失败", bulkLabels)
+			return err
+		}
+	}
+
+	return nil
+}
+
+func keysFromMap(m map[string]struct{}) []string {
+	keys := make([]string, 0, len(m))
+	for k := range m {
+		keys = append(keys, k)
+	}
+	return keys
+}
+
+func StringToUint64(s string) (uint64, error) {
+	return strconv.ParseUint(s, 10, 64)
+}

+ 14 - 0
internal/service/MessageHandlers/wecom_strategies.go

@@ -0,0 +1,14 @@
+package MessageHandlers
+
+import (
+	"wechat-api/internal/service/MessageHandlers/wecom"
+	"wechat-api/internal/svc"
+)
+
+func GetWecomStrategies() map[string]func(*svc.ServiceContext) MessageHandlerStrategy {
+	return map[string]func(*svc.ServiceContext) MessageHandlerStrategy{
+		"TaskResultNotice":    toStrategy(NewTaskResultNoticeHandler),
+		"CustomerPushNotice":  toStrategy(wecom.NewCustomerPushNoticeHandler),
+		"UserLabelPushNotice": toStrategy(wecom.NewUserLabelPushNotice),
+	}
+}

+ 23 - 6
internal/service/addfriend/add_wechat_friend_log.go

@@ -56,18 +56,34 @@ func (l *AddWechatFriendService) FindFriendByContent(wechatId, content string) b
 // 参数  wechatId   微信ID
 // content  手机号 或者其他
 // message  备注
+// wechatType 属主微信的类型 1:个人微信 2:企业微信
 
-func (l *AddWechatFriendService) AddNewFriend(wechatId, content, message string) bool {
+func (l *AddWechatFriendService) AddNewFriend(wechatId, content, message string, wechatType int) bool {
 	//先记录请求
-	hookClient := hook.NewHook("", "", "")
+	if wechatType != 1 && wechatType != 2 {
+		return false
+	}
 	node, err := snowflake.NewNode(1) // 1 是节点 ID,根据需要设置
 	if err != nil {
 		log.Fatal(err)
+		return false
 	}
+	var (
+		hookClient *hook.Hook
+		result     map[string]interface{}
+	)
 	taskId := node.Generate()
-	result, err := hookClient.AddFriendTask(wechatId, content, message, taskId.Int64())
-
-	if err != nil {
+	switch wechatType {
+	case 1:
+		hookClient = hook.NewHook("", "", "")
+		result, err = hookClient.AddFriendTask(wechatId, content, message, taskId.Int64())
+	case 2:
+		hookClient = hook.NewWecomHook("", "", "")
+		result, err = hookClient.AddWecomCustomerFromSearchTask(wechatId, content, message, taskId.Int64())
+	default:
+		log.Println("Invalid wechat type")
+	}
+	if err == nil {
 		result["sendResult"] = "success"
 	} else {
 		result["sendResult"] = err
@@ -80,8 +96,9 @@ func (l *AddWechatFriendService) AddNewFriend(wechatId, content, message string)
 		AddTaskCount(1).
 		SetUpdatedAt(time.Now().Unix())
 	_, err = update.Save(l.ctx)
+	logx.Infof("更新 AddWechatFriendLog-field-add-result成功: %v", result)
 	if err != nil || result["sendResult"] != "success" {
-		logx.Errorf("更新 AddWechatFriendLog 失败 或者: %v", err)
+		logx.Errorf("更新 AddWechatFriendLog 失败 或者: %v,%s", err, result["sendResult"])
 		return false
 	}
 	return true //获取好友列表

+ 13 - 0
internal/svc/service_context.go

@@ -40,6 +40,7 @@ type ServiceContext struct {
 	CoreRpc       coreclient.Core
 	Rds           redis.UniversalClient
 	WechatWs      map[string]*wechat_ws.WechatWsClient
+	WecomWs       map[string]*wechat_ws.WecomWsClient
 	Cache         *collection.Cache
 	NodeID        *snowflake.Node
 	MongoModel    *mongo_model.AllMongoModel
@@ -75,6 +76,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
 	// 初始化微信ws客户端
 	// todo 现在配置是从 config.yaml中读取的,后续需要改成从数据库中读取,以便匹配不同的微信号
 	wechatWs := make(map[string]*wechat_ws.WechatWsClient)
+	wecomWs := make(map[string]*wechat_ws.WecomWsClient)
 
 	for _, ws := range c.WebSocket {
 		if ws.Type == "wechat" {
@@ -87,6 +89,16 @@ func NewServiceContext(c config.Config) *ServiceContext {
 				go client.WritePump()
 				wechatWs[ws.Name] = client
 			}
+		} else if ws.Type == "wecom" {
+			wecomClient, err := wechat_ws.NewWecomWsClient(ws.Url, ws.Name, ws.Type)
+			if err != nil {
+				logx.Error(err)
+			} else {
+				logx.Info("建立ws wecomClient成功~", ws.Name)
+				go wecomClient.ReadPump()
+				go wecomClient.WritePump()
+				wecomWs[ws.Name] = wecomClient
+			}
 		}
 	}
 
@@ -113,6 +125,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
 		CoreRpc:       coreRpc,
 		Rds:           rds,
 		WechatWs:      wechatWs,
+		WecomWs:       wecomWs,
 		Cache:         cache,
 		NodeID:        node,
 		MongoModel:    all_mongo_model,

+ 9 - 0
internal/types/types.go

@@ -4763,3 +4763,12 @@ type LoginResp struct {
 	// The log in information | 登陆返回的数据信息
 	Data LoginInfo `json:"data"`
 }
+
+// add_friend_by_phone api接口请求值
+// swagger:model AddWechatFriendLogInfo
+type AddWechatFriendLogInfo struct {
+	Type      int      `json:"type"`
+	WeChatIds []string `json:"WeChatId,optional,omitempty"`
+	Phone     string   `json:"phone"`
+	Message   string   `json:"message"`
+}

+ 0 - 0
proto/AcceptFriendAddRequestTask.proto → proto/wechat/AcceptFriendAddRequestTask.proto


+ 0 - 0
proto/AccountForceOfflineNotice.proto → proto/wechat/AccountForceOfflineNotice.proto


+ 0 - 0
proto/AccountLogoutNotice.proto → proto/wechat/AccountLogoutNotice.proto


+ 0 - 0
proto/AddEmojiTask.proto → proto/wechat/AddEmojiTask.proto


+ 0 - 0
proto/AddFriendFromPhonebookTask.proto → proto/wechat/AddFriendFromPhonebookTask.proto


+ 0 - 0
proto/AddFriendInChatRoomTask.proto → proto/wechat/AddFriendInChatRoomTask.proto


+ 0 - 0
proto/AddFriendNameCardTask.proto → proto/wechat/AddFriendNameCardTask.proto


+ 0 - 0
proto/AddFriendNotice.proto → proto/wechat/AddFriendNotice.proto


+ 0 - 0
proto/AddFriendWithSceneTask.proto → proto/wechat/AddFriendWithSceneTask.proto


+ 0 - 0
proto/AddFriendsTask.proto → proto/wechat/AddFriendsTask.proto


+ 0 - 0
proto/AgreeJoinChatRoomTask.proto → proto/wechat/AgreeJoinChatRoomTask.proto


+ 0 - 0
proto/BizContactAddNotice.proto → proto/wechat/BizContactAddNotice.proto


+ 0 - 0
proto/BizContactPushNotice.proto → proto/wechat/BizContactPushNotice.proto


+ 0 - 0
proto/BizConversPushNotice.proto → proto/wechat/BizConversPushNotice.proto


+ 0 - 0
proto/CDNDownloadFileTask.proto → proto/wechat/CDNDownloadFileTask.proto


+ 0 - 0
proto/CDNDownloadResultNotice.proto → proto/wechat/CDNDownloadResultNotice.proto


+ 0 - 0
proto/CallLogPushNotice.proto → proto/wechat/CallLogPushNotice.proto


+ 0 - 0
proto/ChatMsgFilePushNotice.proto → proto/wechat/ChatMsgFilePushNotice.proto


+ 0 - 0
proto/ChatMsgIdsPushNotice.proto → proto/wechat/ChatMsgIdsPushNotice.proto


+ 0 - 0
proto/ChatRoomActionTask.proto → proto/wechat/ChatRoomActionTask.proto


+ 0 - 0
proto/ChatRoomAddNotice.proto → proto/wechat/ChatRoomAddNotice.proto


+ 0 - 0
proto/ChatRoomChangedNotice.proto → proto/wechat/ChatRoomChangedNotice.proto


+ 0 - 0
proto/ChatRoomDelNotice.proto → proto/wechat/ChatRoomDelNotice.proto


+ 0 - 0
proto/ChatRoomInviteApproveTask.proto → proto/wechat/ChatRoomInviteApproveTask.proto


+ 0 - 0
proto/ChatRoomMembersNotice.proto → proto/wechat/ChatRoomMembersNotice.proto


+ 0 - 0
proto/ChatRoomPushNotice.proto → proto/wechat/ChatRoomPushNotice.proto


+ 0 - 0
proto/CircleCommentDeleteTask.proto → proto/wechat/CircleCommentDeleteTask.proto


+ 0 - 0
proto/CircleCommentDeleteTaskResultNotice.proto → proto/wechat/CircleCommentDeleteTaskResultNotice.proto


+ 0 - 0
proto/CircleCommentNotice.proto → proto/wechat/CircleCommentNotice.proto


+ 0 - 0
proto/CircleCommentReplyTask.proto → proto/wechat/CircleCommentReplyTask.proto


+ 0 - 0
proto/CircleCommentReplyTaskResultNotice.proto → proto/wechat/CircleCommentReplyTaskResultNotice.proto


+ 0 - 0
proto/CircleDelNotice.proto → proto/wechat/CircleDelNotice.proto


+ 0 - 0
proto/CircleDetailNotice.proto → proto/wechat/CircleDetailNotice.proto


+ 0 - 0
proto/CircleLikeNotice.proto → proto/wechat/CircleLikeNotice.proto


+ 0 - 0
proto/CircleLikeTask.proto → proto/wechat/CircleLikeTask.proto


+ 0 - 0
proto/CircleMsgClearTask.proto → proto/wechat/CircleMsgClearTask.proto


+ 0 - 0
proto/CircleMsgPushNotice.proto → proto/wechat/CircleMsgPushNotice.proto


+ 0 - 0
proto/CircleMsgReadTask.proto → proto/wechat/CircleMsgReadTask.proto


+ 0 - 0
proto/CircleNewPublishNotice.proto → proto/wechat/CircleNewPublishNotice.proto


+ 0 - 0
proto/CirclePushNotice.proto → proto/wechat/CirclePushNotice.proto


+ 0 - 0
proto/ClearAllChatMsgTask.proto → proto/wechat/ClearAllChatMsgTask.proto


+ 0 - 0
proto/ConfigPushNotice.proto → proto/wechat/ConfigPushNotice.proto


+ 0 - 0
proto/ContactInfoNotice.proto → proto/wechat/ContactInfoNotice.proto


+ 0 - 0
proto/ContactLabelAddNotic.proto → proto/wechat/ContactLabelAddNotic.proto


+ 0 - 0
proto/ContactLabelDelNotice.proto → proto/wechat/ContactLabelDelNotice.proto


+ 0 - 0
proto/ContactLabelDeleteTask.proto → proto/wechat/ContactLabelDeleteTask.proto


+ 0 - 0
proto/ContactLabelInfoNotice.proto → proto/wechat/ContactLabelInfoNotice.proto


+ 0 - 0
proto/ContactLabelTask.proto → proto/wechat/ContactLabelTask.proto


+ 0 - 0
proto/ContactSetLabelTask.proto → proto/wechat/ContactSetLabelTask.proto


+ 0 - 0
proto/ConvDelNotice.proto → proto/wechat/ConvDelNotice.proto


+ 0 - 0
proto/ConversationPushNotice.proto → proto/wechat/ConversationPushNotice.proto


+ 0 - 0
proto/DeleteFriendTask.proto → proto/wechat/DeleteFriendTask.proto


+ 0 - 0
proto/DeleteSNSNewsTask.proto → proto/wechat/DeleteSNSNewsTask.proto


+ 0 - 0
proto/DeviceAuthReq.proto → proto/wechat/DeviceAuthReq.proto


+ 0 - 0
proto/DeviceAuthRsp.proto → proto/wechat/DeviceAuthRsp.proto


+ 0 - 0
proto/ErrorMessage.proto → proto/wechat/ErrorMessage.proto


+ 0 - 0
proto/FindContactTask.proto → proto/wechat/FindContactTask.proto


+ 0 - 0
proto/FindContactTaskResultNotice.proto → proto/wechat/FindContactTaskResultNotice.proto


+ 0 - 0
proto/ForwardMessageByContentTask.proto → proto/wechat/ForwardMessageByContentTask.proto


+ 0 - 0
proto/ForwardMessageTask.proto → proto/wechat/ForwardMessageTask.proto


+ 0 - 0
proto/ForwardMultiMessageTask.proto → proto/wechat/ForwardMultiMessageTask.proto


+ 0 - 0
proto/FriendAddNotice.proto → proto/wechat/FriendAddNotice.proto


+ 0 - 0
proto/FriendAddReqListNotice.proto → proto/wechat/FriendAddReqListNotice.proto


+ 0 - 0
proto/FriendAddReqeustNotice.proto → proto/wechat/FriendAddReqeustNotice.proto


+ 0 - 0
proto/FriendChangeNotice.proto → proto/wechat/FriendChangeNotice.proto


+ 0 - 0
proto/FriendDelNotice.proto → proto/wechat/FriendDelNotice.proto


+ 0 - 0
proto/FriendDetectResultNotice.proto → proto/wechat/FriendDetectResultNotice.proto


+ 0 - 0
proto/FriendPushNotice.proto → proto/wechat/FriendPushNotice.proto


+ 0 - 0
proto/FriendTalkNotice.proto → proto/wechat/FriendTalkNotice.proto


+ 0 - 0
proto/GetA8KeyTask.proto → proto/wechat/GetA8KeyTask.proto


Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov