Преглед на файлове

fix:whatsapp群发接口添加

jimmyyem преди 4 седмици
родител
ревизия
26d9e47d8e
променени са 40 файла, в които са добавени 2696 реда и са изтрити 36 реда
  1. 12 5
      crontask/send_msg.go
  2. 73 0
      desc/wechat/batch_msg.api
  3. 24 2
      ent/batchmsg.go
  4. 16 0
      ent/batchmsg/batchmsg.go
  5. 160 0
      ent/batchmsg/where.go
  6. 156 0
      ent/batchmsg_create.go
  7. 104 0
      ent/batchmsg_update.go
  8. 5 0
      ent/migrate/schema.go
  9. 36 3
      ent/msg.go
  10. 30 0
      ent/msg/msg.go
  11. 185 0
      ent/msg/where.go
  12. 215 0
      ent/msg_create.go
  13. 122 0
      ent/msg_update.go
  14. 343 2
      ent/mutation.go
  15. 12 0
      ent/runtime/runtime.go
  16. 2 0
      ent/schema/batch_msg.go
  17. 5 1
      ent/schema/msg.go
  18. 120 0
      ent/set_not_nil.go
  19. 2 2
      hook/aliyun/whatsapp.go
  20. 44 0
      internal/handler/batch_msg/create_whatcapp_batch_msg_handler.go
  21. 44 0
      internal/handler/batch_msg/get_whatcapp_batch_msg_handler.go
  22. 44 0
      internal/handler/batch_msg/get_whatcapp_batch_msg_history_handler.go
  23. 44 0
      internal/handler/batch_msg/get_whatcapp_batch_msg_list_handler.go
  24. 44 0
      internal/handler/batch_msg/remove_whatcapp_batch_msg_handler.go
  25. 44 0
      internal/handler/batch_msg/send_batch_msg_text_handler.go
  26. 44 0
      internal/handler/batch_msg/update_whatcapp_batch_msg_handler.go
  27. 35 0
      internal/handler/routes.go
  28. 5 5
      internal/logic/batch_msg/create_batch_msg_logic.go
  29. 279 0
      internal/logic/batch_msg/create_whatcapp_batch_msg_logic.go
  30. 5 1
      internal/logic/batch_msg/get_batch_msg_by_id_logic.go
  31. 55 0
      internal/logic/batch_msg/get_whatcapp_batch_msg_history_logic.go
  32. 82 0
      internal/logic/batch_msg/get_whatcapp_batch_msg_list_logic.go
  33. 88 0
      internal/logic/batch_msg/get_whatcapp_batch_msg_logic.go
  34. 39 0
      internal/logic/batch_msg/remove_whatcapp_batch_msg_logic.go
  35. 42 0
      internal/logic/batch_msg/send_batch_msg_text_logic.go
  36. 46 0
      internal/logic/batch_msg/update_whatcapp_batch_msg_logic.go
  37. 2 1
      internal/logic/contact/get_contact_list_logic.go
  38. 3 1
      internal/logic/contact/get_whatsapp_contact_list_logic.go
  39. 33 12
      internal/logic/contact/get_whatsapp_contact_logic.go
  40. 52 1
      internal/types/types.go

+ 12 - 5
crontask/send_msg.go

@@ -18,7 +18,11 @@ import (
 
 func (l *CronTask) sendMsg() {
 	// 获取 BatchMsg 表中 start_time 小于当前时间并且 status 为 0 或 1 的数据
-	batchList, err := l.svcCtx.DB.BatchMsg.Query().Where(batchmsg.StartTimeLT(time.Now()), batchmsg.StatusIn(0, 1)).All(l.ctx)
+	batchList, err := l.svcCtx.DB.BatchMsg.Query().Where(
+		batchmsg.StartTimeLT(time.Now()),
+		batchmsg.StatusIn(0, 1),
+		batchmsg.Ctype(1),
+	).All(l.ctx)
 	if err != nil {
 		l.Logger.Errorf("batchList err: %v", err)
 		return
@@ -68,7 +72,7 @@ func (l *CronTask) sendMsg() {
 						continue
 					}
 				} else { //获取指定标签的联系人
-					userList, err = getContactList(l, contactTags, batch.Fromwxid, 1)
+					userList, err = l.getContactList(contactTags, batch.Fromwxid, 1)
 					if err != nil {
 						l.Logger.Errorf("userList err: %v", err)
 						continue
@@ -83,7 +87,7 @@ func (l *CronTask) sendMsg() {
 						continue
 					}
 				} else { //获取指定标签的群
-					groupList, err = getContactList(l, groupTags, batch.Fromwxid, 2)
+					groupList, err = l.getContactList(groupTags, batch.Fromwxid, 2)
 					if err != nil {
 						l.Logger.Errorf("groupList err: %v", err)
 						continue
@@ -271,9 +275,12 @@ func hasAll(array []uint64, target uint64) bool {
 	return false
 }
 
-func getContactList(l *CronTask, labels []uint64, fromWxId string, stype int) ([]*ent.Contact, error) {
+func (l *CronTask) getContactList(labels []uint64, fromWxId string, stype int) ([]*ent.Contact, error) {
 	// 获取 label_relationship 表中,label_id 等于 labids 的 contact_id
-	labelrelationships, err := l.svcCtx.DB.LabelRelationship.Query().Where(labelrelationship.LabelIDIn(labels...)).All(l.ctx)
+	labelrelationships, err := l.svcCtx.DB.LabelRelationship.Query().Where(
+		labelrelationship.LabelIDIn(labels...),
+		labelrelationship.Ctype(1),
+	).All(l.ctx)
 	if err != nil {
 		return nil, dberrorhandler.DefaultEntError(l.Logger, err, nil)
 	}

+ 73 - 0
desc/wechat/batch_msg.api

@@ -52,6 +52,10 @@ type (
 
 		// 内容类型:1-微信 2-whatsapp
 		Ctype *uint64 `json:"ctype,optional"`
+		Cc *string `json:"cc,optional"`
+		Phone *string `json:"phone,optional"`
+		TemplateCode *string `json:"templateCode,optional"`
+		Lang *string `json:"lang,optional"`
     }
 
     // The response data of batch msg list | BatchMsg列表数据
@@ -97,6 +101,47 @@ type (
         // BatchMsg information | BatchMsg数据
         Data BatchMsgInfo `json:"data"`
     }
+
+	WhatsappBatchMsgListReq {
+		PageInfo
+
+		// 批次号
+		BatchNo  *string `json:"batchNo,optional"`
+
+		// 发送方微信ID
+		Phone  *string `json:"phone,optional"`
+
+		// 内容
+		Msg  *string `json:"msg,optional"`
+	}
+
+	WhatsappBatchMsgHistoryReq {
+		PageInfo
+		IDReq
+	}
+	WhatsappBatchMsgHistoryResp {
+		BaseDataInfo
+
+		Data HistoryListInfo `json:"data"`
+	}
+	HistoryListInfo {
+		BaseListInfo
+
+		Data  []HistoryInfo  `json:"data"`
+	}
+	HistoryInfo {
+		Index *uint64 `json:"index,optional"`
+		From *string `json:"from,optional"`
+		To *string `json:"to,optional"`
+		SendTime *int64 `json:"sendTime,optional"`
+		BatchNo *string `json:"batchNo,optional"`
+	}
+
+	SendMsgReq {
+		Phone *string `json:"phone"`
+		To *string `json:"to"`
+		Text *string `json:"text"`
+	}
 )
 
 @server(
@@ -129,4 +174,32 @@ service Wechat {
 	// Stop batch_msg by ID | 通过ID停止BatchMsg
 	@handler stopBatchMsg
 	post /batch_msg/stop (IDReq) returns (BaseMsgResp)
+
+	// 获取Whatsapp 批量任务列表
+	@handler getWhatcappBatchMsgList
+	post /batch_msg/getWhatcappBatchMsgList (WhatsappBatchMsgListReq) returns (BatchMsgListResp)
+
+	// 创建Whatsapp 群发任务
+	@handler createWhatcappBatchMsg
+	post /batch_msg/createWhatcappBatchMsg (BatchMsgInfo) returns (BaseMsgResp)
+
+	// 修改Whatsapp 群发任务
+	@handler updateWhatcappBatchMsg
+	post /batch_msg/updateWhatcappBatchMsg (BatchMsgInfo) returns (BaseMsgResp)
+
+	// 获取Whatsapp 群发任务详情
+	@handler getWhatcappBatchMsg
+	post /batch_msg/getWhatcappBatchMsg (IDReq) returns (BatchMsgInfoResp)
+
+	// 删除Whatsapp 群发任务
+	@handler removeWhatcappBatchMsg
+	post /batch_msg/removeWhatcappBatchMsg (IDsReq) returns (BaseMsgResp)
+
+	// 获取Whatsapp 群发任务发送记录
+	@handler getWhatcappBatchMsgHistory
+	post /batch_msg/getWhatcappBatchMsgHistory (WhatsappBatchMsgHistoryReq) returns (WhatsappBatchMsgHistoryResp)
+
+	// 发送消息
+	@handler sendBatchMsgText
+	post /batch_msg/sendBatchMsgText (SendMsgReq) returns (BaseMsgResp)
 }

+ 24 - 2
ent/batchmsg.go

@@ -54,7 +54,11 @@ type BatchMsg struct {
 	// organization_id | 租户ID
 	OrganizationID uint64 `json:"organization_id,omitempty"`
 	// 内容类型:1-微信 2-whatsapp
-	Ctype        uint64 `json:"ctype,omitempty"`
+	Ctype uint64 `json:"ctype,omitempty"`
+	// 国家区号
+	Cc string `json:"cc,omitempty"`
+	// 手机号
+	Phone        string `json:"phone,omitempty"`
 	selectValues sql.SelectValues
 }
 
@@ -65,7 +69,7 @@ func (*BatchMsg) scanValues(columns []string) ([]any, error) {
 		switch columns[i] {
 		case batchmsg.FieldID, batchmsg.FieldStatus, batchmsg.FieldTotal, batchmsg.FieldSuccess, batchmsg.FieldFail, batchmsg.FieldType, batchmsg.FieldOrganizationID, batchmsg.FieldCtype:
 			values[i] = new(sql.NullInt64)
-		case batchmsg.FieldBatchNo, batchmsg.FieldTaskName, batchmsg.FieldFromwxid, batchmsg.FieldMsg, batchmsg.FieldTag, batchmsg.FieldTagids:
+		case batchmsg.FieldBatchNo, batchmsg.FieldTaskName, batchmsg.FieldFromwxid, batchmsg.FieldMsg, batchmsg.FieldTag, batchmsg.FieldTagids, batchmsg.FieldCc, batchmsg.FieldPhone:
 			values[i] = new(sql.NullString)
 		case batchmsg.FieldCreatedAt, batchmsg.FieldUpdatedAt, batchmsg.FieldDeletedAt, batchmsg.FieldStartTime, batchmsg.FieldStopTime, batchmsg.FieldSendTime:
 			values[i] = new(sql.NullTime)
@@ -204,6 +208,18 @@ func (bm *BatchMsg) assignValues(columns []string, values []any) error {
 			} else if value.Valid {
 				bm.Ctype = uint64(value.Int64)
 			}
+		case batchmsg.FieldCc:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field cc", values[i])
+			} else if value.Valid {
+				bm.Cc = value.String
+			}
+		case batchmsg.FieldPhone:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field phone", values[i])
+			} else if value.Valid {
+				bm.Phone = value.String
+			}
 		default:
 			bm.selectValues.Set(columns[i], values[i])
 		}
@@ -296,6 +312,12 @@ func (bm *BatchMsg) String() string {
 	builder.WriteString(", ")
 	builder.WriteString("ctype=")
 	builder.WriteString(fmt.Sprintf("%v", bm.Ctype))
+	builder.WriteString(", ")
+	builder.WriteString("cc=")
+	builder.WriteString(bm.Cc)
+	builder.WriteString(", ")
+	builder.WriteString("phone=")
+	builder.WriteString(bm.Phone)
 	builder.WriteByte(')')
 	return builder.String()
 }

+ 16 - 0
ent/batchmsg/batchmsg.go

@@ -52,6 +52,10 @@ const (
 	FieldOrganizationID = "organization_id"
 	// FieldCtype holds the string denoting the ctype field in the database.
 	FieldCtype = "ctype"
+	// FieldCc holds the string denoting the cc field in the database.
+	FieldCc = "cc"
+	// FieldPhone holds the string denoting the phone field in the database.
+	FieldPhone = "phone"
 	// Table holds the table name of the batchmsg in the database.
 	Table = "batch_msg"
 )
@@ -78,6 +82,8 @@ var Columns = []string{
 	FieldType,
 	FieldOrganizationID,
 	FieldCtype,
+	FieldCc,
+	FieldPhone,
 }
 
 // ValidColumn reports if the column name is valid (part of the table columns).
@@ -214,3 +220,13 @@ func ByOrganizationID(opts ...sql.OrderTermOption) OrderOption {
 func ByCtype(opts ...sql.OrderTermOption) OrderOption {
 	return sql.OrderByField(FieldCtype, opts...).ToFunc()
 }
+
+// ByCc orders the results by the cc field.
+func ByCc(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldCc, opts...).ToFunc()
+}
+
+// ByPhone orders the results by the phone field.
+func ByPhone(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldPhone, opts...).ToFunc()
+}

+ 160 - 0
ent/batchmsg/where.go

@@ -149,6 +149,16 @@ func Ctype(v uint64) predicate.BatchMsg {
 	return predicate.BatchMsg(sql.FieldEQ(FieldCtype, v))
 }
 
+// Cc applies equality check predicate on the "cc" field. It's identical to CcEQ.
+func Cc(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldEQ(FieldCc, v))
+}
+
+// Phone applies equality check predicate on the "phone" field. It's identical to PhoneEQ.
+func Phone(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldEQ(FieldPhone, v))
+}
+
 // CreatedAtEQ applies the EQ predicate on the "created_at" field.
 func CreatedAtEQ(v time.Time) predicate.BatchMsg {
 	return predicate.BatchMsg(sql.FieldEQ(FieldCreatedAt, v))
@@ -1209,6 +1219,156 @@ func CtypeLTE(v uint64) predicate.BatchMsg {
 	return predicate.BatchMsg(sql.FieldLTE(FieldCtype, v))
 }
 
+// CcEQ applies the EQ predicate on the "cc" field.
+func CcEQ(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldEQ(FieldCc, v))
+}
+
+// CcNEQ applies the NEQ predicate on the "cc" field.
+func CcNEQ(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldNEQ(FieldCc, v))
+}
+
+// CcIn applies the In predicate on the "cc" field.
+func CcIn(vs ...string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldIn(FieldCc, vs...))
+}
+
+// CcNotIn applies the NotIn predicate on the "cc" field.
+func CcNotIn(vs ...string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldNotIn(FieldCc, vs...))
+}
+
+// CcGT applies the GT predicate on the "cc" field.
+func CcGT(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldGT(FieldCc, v))
+}
+
+// CcGTE applies the GTE predicate on the "cc" field.
+func CcGTE(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldGTE(FieldCc, v))
+}
+
+// CcLT applies the LT predicate on the "cc" field.
+func CcLT(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldLT(FieldCc, v))
+}
+
+// CcLTE applies the LTE predicate on the "cc" field.
+func CcLTE(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldLTE(FieldCc, v))
+}
+
+// CcContains applies the Contains predicate on the "cc" field.
+func CcContains(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldContains(FieldCc, v))
+}
+
+// CcHasPrefix applies the HasPrefix predicate on the "cc" field.
+func CcHasPrefix(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldHasPrefix(FieldCc, v))
+}
+
+// CcHasSuffix applies the HasSuffix predicate on the "cc" field.
+func CcHasSuffix(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldHasSuffix(FieldCc, v))
+}
+
+// CcIsNil applies the IsNil predicate on the "cc" field.
+func CcIsNil() predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldIsNull(FieldCc))
+}
+
+// CcNotNil applies the NotNil predicate on the "cc" field.
+func CcNotNil() predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldNotNull(FieldCc))
+}
+
+// CcEqualFold applies the EqualFold predicate on the "cc" field.
+func CcEqualFold(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldEqualFold(FieldCc, v))
+}
+
+// CcContainsFold applies the ContainsFold predicate on the "cc" field.
+func CcContainsFold(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldContainsFold(FieldCc, v))
+}
+
+// PhoneEQ applies the EQ predicate on the "phone" field.
+func PhoneEQ(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldEQ(FieldPhone, v))
+}
+
+// PhoneNEQ applies the NEQ predicate on the "phone" field.
+func PhoneNEQ(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldNEQ(FieldPhone, v))
+}
+
+// PhoneIn applies the In predicate on the "phone" field.
+func PhoneIn(vs ...string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldIn(FieldPhone, vs...))
+}
+
+// PhoneNotIn applies the NotIn predicate on the "phone" field.
+func PhoneNotIn(vs ...string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldNotIn(FieldPhone, vs...))
+}
+
+// PhoneGT applies the GT predicate on the "phone" field.
+func PhoneGT(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldGT(FieldPhone, v))
+}
+
+// PhoneGTE applies the GTE predicate on the "phone" field.
+func PhoneGTE(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldGTE(FieldPhone, v))
+}
+
+// PhoneLT applies the LT predicate on the "phone" field.
+func PhoneLT(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldLT(FieldPhone, v))
+}
+
+// PhoneLTE applies the LTE predicate on the "phone" field.
+func PhoneLTE(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldLTE(FieldPhone, v))
+}
+
+// PhoneContains applies the Contains predicate on the "phone" field.
+func PhoneContains(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldContains(FieldPhone, v))
+}
+
+// PhoneHasPrefix applies the HasPrefix predicate on the "phone" field.
+func PhoneHasPrefix(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldHasPrefix(FieldPhone, v))
+}
+
+// PhoneHasSuffix applies the HasSuffix predicate on the "phone" field.
+func PhoneHasSuffix(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldHasSuffix(FieldPhone, v))
+}
+
+// PhoneIsNil applies the IsNil predicate on the "phone" field.
+func PhoneIsNil() predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldIsNull(FieldPhone))
+}
+
+// PhoneNotNil applies the NotNil predicate on the "phone" field.
+func PhoneNotNil() predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldNotNull(FieldPhone))
+}
+
+// PhoneEqualFold applies the EqualFold predicate on the "phone" field.
+func PhoneEqualFold(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldEqualFold(FieldPhone, v))
+}
+
+// PhoneContainsFold applies the ContainsFold predicate on the "phone" field.
+func PhoneContainsFold(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldContainsFold(FieldPhone, v))
+}
+
 // And groups predicates with the AND operator between them.
 func And(predicates ...predicate.BatchMsg) predicate.BatchMsg {
 	return predicate.BatchMsg(sql.AndPredicates(predicates...))

+ 156 - 0
ent/batchmsg_create.go

@@ -280,6 +280,34 @@ func (bmc *BatchMsgCreate) SetNillableCtype(u *uint64) *BatchMsgCreate {
 	return bmc
 }
 
+// SetCc sets the "cc" field.
+func (bmc *BatchMsgCreate) SetCc(s string) *BatchMsgCreate {
+	bmc.mutation.SetCc(s)
+	return bmc
+}
+
+// SetNillableCc sets the "cc" field if the given value is not nil.
+func (bmc *BatchMsgCreate) SetNillableCc(s *string) *BatchMsgCreate {
+	if s != nil {
+		bmc.SetCc(*s)
+	}
+	return bmc
+}
+
+// SetPhone sets the "phone" field.
+func (bmc *BatchMsgCreate) SetPhone(s string) *BatchMsgCreate {
+	bmc.mutation.SetPhone(s)
+	return bmc
+}
+
+// SetNillablePhone sets the "phone" field if the given value is not nil.
+func (bmc *BatchMsgCreate) SetNillablePhone(s *string) *BatchMsgCreate {
+	if s != nil {
+		bmc.SetPhone(*s)
+	}
+	return bmc
+}
+
 // SetID sets the "id" field.
 func (bmc *BatchMsgCreate) SetID(u uint64) *BatchMsgCreate {
 	bmc.mutation.SetID(u)
@@ -476,6 +504,14 @@ func (bmc *BatchMsgCreate) createSpec() (*BatchMsg, *sqlgraph.CreateSpec) {
 		_spec.SetField(batchmsg.FieldCtype, field.TypeUint64, value)
 		_node.Ctype = value
 	}
+	if value, ok := bmc.mutation.Cc(); ok {
+		_spec.SetField(batchmsg.FieldCc, field.TypeString, value)
+		_node.Cc = value
+	}
+	if value, ok := bmc.mutation.Phone(); ok {
+		_spec.SetField(batchmsg.FieldPhone, field.TypeString, value)
+		_node.Phone = value
+	}
 	return _node, _spec
 }
 
@@ -876,6 +912,42 @@ func (u *BatchMsgUpsert) AddCtype(v uint64) *BatchMsgUpsert {
 	return u
 }
 
+// SetCc sets the "cc" field.
+func (u *BatchMsgUpsert) SetCc(v string) *BatchMsgUpsert {
+	u.Set(batchmsg.FieldCc, v)
+	return u
+}
+
+// UpdateCc sets the "cc" field to the value that was provided on create.
+func (u *BatchMsgUpsert) UpdateCc() *BatchMsgUpsert {
+	u.SetExcluded(batchmsg.FieldCc)
+	return u
+}
+
+// ClearCc clears the value of the "cc" field.
+func (u *BatchMsgUpsert) ClearCc() *BatchMsgUpsert {
+	u.SetNull(batchmsg.FieldCc)
+	return u
+}
+
+// SetPhone sets the "phone" field.
+func (u *BatchMsgUpsert) SetPhone(v string) *BatchMsgUpsert {
+	u.Set(batchmsg.FieldPhone, v)
+	return u
+}
+
+// UpdatePhone sets the "phone" field to the value that was provided on create.
+func (u *BatchMsgUpsert) UpdatePhone() *BatchMsgUpsert {
+	u.SetExcluded(batchmsg.FieldPhone)
+	return u
+}
+
+// ClearPhone clears the value of the "phone" field.
+func (u *BatchMsgUpsert) ClearPhone() *BatchMsgUpsert {
+	u.SetNull(batchmsg.FieldPhone)
+	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:
 //
@@ -1333,6 +1405,48 @@ func (u *BatchMsgUpsertOne) UpdateCtype() *BatchMsgUpsertOne {
 	})
 }
 
+// SetCc sets the "cc" field.
+func (u *BatchMsgUpsertOne) SetCc(v string) *BatchMsgUpsertOne {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.SetCc(v)
+	})
+}
+
+// UpdateCc sets the "cc" field to the value that was provided on create.
+func (u *BatchMsgUpsertOne) UpdateCc() *BatchMsgUpsertOne {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.UpdateCc()
+	})
+}
+
+// ClearCc clears the value of the "cc" field.
+func (u *BatchMsgUpsertOne) ClearCc() *BatchMsgUpsertOne {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.ClearCc()
+	})
+}
+
+// SetPhone sets the "phone" field.
+func (u *BatchMsgUpsertOne) SetPhone(v string) *BatchMsgUpsertOne {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.SetPhone(v)
+	})
+}
+
+// UpdatePhone sets the "phone" field to the value that was provided on create.
+func (u *BatchMsgUpsertOne) UpdatePhone() *BatchMsgUpsertOne {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.UpdatePhone()
+	})
+}
+
+// ClearPhone clears the value of the "phone" field.
+func (u *BatchMsgUpsertOne) ClearPhone() *BatchMsgUpsertOne {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.ClearPhone()
+	})
+}
+
 // Exec executes the query.
 func (u *BatchMsgUpsertOne) Exec(ctx context.Context) error {
 	if len(u.create.conflict) == 0 {
@@ -1956,6 +2070,48 @@ func (u *BatchMsgUpsertBulk) UpdateCtype() *BatchMsgUpsertBulk {
 	})
 }
 
+// SetCc sets the "cc" field.
+func (u *BatchMsgUpsertBulk) SetCc(v string) *BatchMsgUpsertBulk {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.SetCc(v)
+	})
+}
+
+// UpdateCc sets the "cc" field to the value that was provided on create.
+func (u *BatchMsgUpsertBulk) UpdateCc() *BatchMsgUpsertBulk {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.UpdateCc()
+	})
+}
+
+// ClearCc clears the value of the "cc" field.
+func (u *BatchMsgUpsertBulk) ClearCc() *BatchMsgUpsertBulk {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.ClearCc()
+	})
+}
+
+// SetPhone sets the "phone" field.
+func (u *BatchMsgUpsertBulk) SetPhone(v string) *BatchMsgUpsertBulk {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.SetPhone(v)
+	})
+}
+
+// UpdatePhone sets the "phone" field to the value that was provided on create.
+func (u *BatchMsgUpsertBulk) UpdatePhone() *BatchMsgUpsertBulk {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.UpdatePhone()
+	})
+}
+
+// ClearPhone clears the value of the "phone" field.
+func (u *BatchMsgUpsertBulk) ClearPhone() *BatchMsgUpsertBulk {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.ClearPhone()
+	})
+}
+
 // Exec executes the query.
 func (u *BatchMsgUpsertBulk) Exec(ctx context.Context) error {
 	if u.create.err != nil {

+ 104 - 0
ent/batchmsg_update.go

@@ -411,6 +411,46 @@ func (bmu *BatchMsgUpdate) AddCtype(u int64) *BatchMsgUpdate {
 	return bmu
 }
 
+// SetCc sets the "cc" field.
+func (bmu *BatchMsgUpdate) SetCc(s string) *BatchMsgUpdate {
+	bmu.mutation.SetCc(s)
+	return bmu
+}
+
+// SetNillableCc sets the "cc" field if the given value is not nil.
+func (bmu *BatchMsgUpdate) SetNillableCc(s *string) *BatchMsgUpdate {
+	if s != nil {
+		bmu.SetCc(*s)
+	}
+	return bmu
+}
+
+// ClearCc clears the value of the "cc" field.
+func (bmu *BatchMsgUpdate) ClearCc() *BatchMsgUpdate {
+	bmu.mutation.ClearCc()
+	return bmu
+}
+
+// SetPhone sets the "phone" field.
+func (bmu *BatchMsgUpdate) SetPhone(s string) *BatchMsgUpdate {
+	bmu.mutation.SetPhone(s)
+	return bmu
+}
+
+// SetNillablePhone sets the "phone" field if the given value is not nil.
+func (bmu *BatchMsgUpdate) SetNillablePhone(s *string) *BatchMsgUpdate {
+	if s != nil {
+		bmu.SetPhone(*s)
+	}
+	return bmu
+}
+
+// ClearPhone clears the value of the "phone" field.
+func (bmu *BatchMsgUpdate) ClearPhone() *BatchMsgUpdate {
+	bmu.mutation.ClearPhone()
+	return bmu
+}
+
 // Mutation returns the BatchMsgMutation object of the builder.
 func (bmu *BatchMsgUpdate) Mutation() *BatchMsgMutation {
 	return bmu.mutation
@@ -600,6 +640,18 @@ func (bmu *BatchMsgUpdate) sqlSave(ctx context.Context) (n int, err error) {
 	if value, ok := bmu.mutation.AddedCtype(); ok {
 		_spec.AddField(batchmsg.FieldCtype, field.TypeUint64, value)
 	}
+	if value, ok := bmu.mutation.Cc(); ok {
+		_spec.SetField(batchmsg.FieldCc, field.TypeString, value)
+	}
+	if bmu.mutation.CcCleared() {
+		_spec.ClearField(batchmsg.FieldCc, field.TypeString)
+	}
+	if value, ok := bmu.mutation.Phone(); ok {
+		_spec.SetField(batchmsg.FieldPhone, field.TypeString, value)
+	}
+	if bmu.mutation.PhoneCleared() {
+		_spec.ClearField(batchmsg.FieldPhone, field.TypeString)
+	}
 	if n, err = sqlgraph.UpdateNodes(ctx, bmu.driver, _spec); err != nil {
 		if _, ok := err.(*sqlgraph.NotFoundError); ok {
 			err = &NotFoundError{batchmsg.Label}
@@ -1003,6 +1055,46 @@ func (bmuo *BatchMsgUpdateOne) AddCtype(u int64) *BatchMsgUpdateOne {
 	return bmuo
 }
 
+// SetCc sets the "cc" field.
+func (bmuo *BatchMsgUpdateOne) SetCc(s string) *BatchMsgUpdateOne {
+	bmuo.mutation.SetCc(s)
+	return bmuo
+}
+
+// SetNillableCc sets the "cc" field if the given value is not nil.
+func (bmuo *BatchMsgUpdateOne) SetNillableCc(s *string) *BatchMsgUpdateOne {
+	if s != nil {
+		bmuo.SetCc(*s)
+	}
+	return bmuo
+}
+
+// ClearCc clears the value of the "cc" field.
+func (bmuo *BatchMsgUpdateOne) ClearCc() *BatchMsgUpdateOne {
+	bmuo.mutation.ClearCc()
+	return bmuo
+}
+
+// SetPhone sets the "phone" field.
+func (bmuo *BatchMsgUpdateOne) SetPhone(s string) *BatchMsgUpdateOne {
+	bmuo.mutation.SetPhone(s)
+	return bmuo
+}
+
+// SetNillablePhone sets the "phone" field if the given value is not nil.
+func (bmuo *BatchMsgUpdateOne) SetNillablePhone(s *string) *BatchMsgUpdateOne {
+	if s != nil {
+		bmuo.SetPhone(*s)
+	}
+	return bmuo
+}
+
+// ClearPhone clears the value of the "phone" field.
+func (bmuo *BatchMsgUpdateOne) ClearPhone() *BatchMsgUpdateOne {
+	bmuo.mutation.ClearPhone()
+	return bmuo
+}
+
 // Mutation returns the BatchMsgMutation object of the builder.
 func (bmuo *BatchMsgUpdateOne) Mutation() *BatchMsgMutation {
 	return bmuo.mutation
@@ -1222,6 +1314,18 @@ func (bmuo *BatchMsgUpdateOne) sqlSave(ctx context.Context) (_node *BatchMsg, er
 	if value, ok := bmuo.mutation.AddedCtype(); ok {
 		_spec.AddField(batchmsg.FieldCtype, field.TypeUint64, value)
 	}
+	if value, ok := bmuo.mutation.Cc(); ok {
+		_spec.SetField(batchmsg.FieldCc, field.TypeString, value)
+	}
+	if bmuo.mutation.CcCleared() {
+		_spec.ClearField(batchmsg.FieldCc, field.TypeString)
+	}
+	if value, ok := bmuo.mutation.Phone(); ok {
+		_spec.SetField(batchmsg.FieldPhone, field.TypeString, value)
+	}
+	if bmuo.mutation.PhoneCleared() {
+		_spec.ClearField(batchmsg.FieldPhone, field.TypeString)
+	}
 	_node = &BatchMsg{config: bmuo.config}
 	_spec.Assign = _node.assignValues
 	_spec.ScanValues = _node.scanValues

+ 5 - 0
ent/migrate/schema.go

@@ -136,6 +136,8 @@ var (
 		{Name: "type", Type: field.TypeInt32, Nullable: true, Comment: "发送类型 1-群发消息 2-群发朋友圈"},
 		{Name: "organization_id", Type: field.TypeUint64, Comment: "organization_id | 租户ID"},
 		{Name: "ctype", Type: field.TypeUint64, Comment: "内容类型:1-微信 2-whatsapp", Default: 1},
+		{Name: "cc", Type: field.TypeString, Nullable: true, Comment: "国家区号"},
+		{Name: "phone", Type: field.TypeString, Nullable: true, Comment: "手机号"},
 	}
 	// BatchMsgTable holds the schema information for the "batch_msg" table.
 	BatchMsgTable = &schema.Table{
@@ -585,6 +587,9 @@ var (
 		{Name: "msgtype", Type: field.TypeInt32, Nullable: true, Comment: "消息类型"},
 		{Name: "msg", Type: field.TypeString, Nullable: true, Comment: "消息"},
 		{Name: "batch_no", Type: field.TypeString, Nullable: true, Comment: "批次号"},
+		{Name: "ctype", Type: field.TypeUint64, Comment: "内容类型:1-微信 2-whatsapp", Default: 1},
+		{Name: "cc", Type: field.TypeString, Comment: "国家区号", Default: ""},
+		{Name: "phone", Type: field.TypeString, Comment: "手机号", Default: ""},
 	}
 	// MsgTable holds the schema information for the "msg" table.
 	MsgTable = &schema.Table{

+ 36 - 3
ent/msg.go

@@ -34,7 +34,13 @@ type Msg struct {
 	// 消息
 	Msg string `json:"msg,omitempty"`
 	// 批次号
-	BatchNo      string `json:"batch_no,omitempty"`
+	BatchNo string `json:"batch_no,omitempty"`
+	// 内容类型:1-微信 2-whatsapp
+	Ctype uint64 `json:"ctype,omitempty"`
+	// 国家区号
+	Cc string `json:"cc,omitempty"`
+	// 手机号
+	Phone        string `json:"phone,omitempty"`
 	selectValues sql.SelectValues
 }
 
@@ -43,9 +49,9 @@ func (*Msg) scanValues(columns []string) ([]any, error) {
 	values := make([]any, len(columns))
 	for i := range columns {
 		switch columns[i] {
-		case msg.FieldID, msg.FieldStatus, msg.FieldMsgtype:
+		case msg.FieldID, msg.FieldStatus, msg.FieldMsgtype, msg.FieldCtype:
 			values[i] = new(sql.NullInt64)
-		case msg.FieldFromwxid, msg.FieldToid, msg.FieldMsg, msg.FieldBatchNo:
+		case msg.FieldFromwxid, msg.FieldToid, msg.FieldMsg, msg.FieldBatchNo, msg.FieldCc, msg.FieldPhone:
 			values[i] = new(sql.NullString)
 		case msg.FieldCreatedAt, msg.FieldUpdatedAt, msg.FieldDeletedAt:
 			values[i] = new(sql.NullTime)
@@ -124,6 +130,24 @@ func (m *Msg) assignValues(columns []string, values []any) error {
 			} else if value.Valid {
 				m.BatchNo = value.String
 			}
+		case msg.FieldCtype:
+			if value, ok := values[i].(*sql.NullInt64); !ok {
+				return fmt.Errorf("unexpected type %T for field ctype", values[i])
+			} else if value.Valid {
+				m.Ctype = uint64(value.Int64)
+			}
+		case msg.FieldCc:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field cc", values[i])
+			} else if value.Valid {
+				m.Cc = value.String
+			}
+		case msg.FieldPhone:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field phone", values[i])
+			} else if value.Valid {
+				m.Phone = value.String
+			}
 		default:
 			m.selectValues.Set(columns[i], values[i])
 		}
@@ -186,6 +210,15 @@ func (m *Msg) String() string {
 	builder.WriteString(", ")
 	builder.WriteString("batch_no=")
 	builder.WriteString(m.BatchNo)
+	builder.WriteString(", ")
+	builder.WriteString("ctype=")
+	builder.WriteString(fmt.Sprintf("%v", m.Ctype))
+	builder.WriteString(", ")
+	builder.WriteString("cc=")
+	builder.WriteString(m.Cc)
+	builder.WriteString(", ")
+	builder.WriteString("phone=")
+	builder.WriteString(m.Phone)
 	builder.WriteByte(')')
 	return builder.String()
 }

+ 30 - 0
ent/msg/msg.go

@@ -32,6 +32,12 @@ const (
 	FieldMsg = "msg"
 	// FieldBatchNo holds the string denoting the batch_no field in the database.
 	FieldBatchNo = "batch_no"
+	// FieldCtype holds the string denoting the ctype field in the database.
+	FieldCtype = "ctype"
+	// FieldCc holds the string denoting the cc field in the database.
+	FieldCc = "cc"
+	// FieldPhone holds the string denoting the phone field in the database.
+	FieldPhone = "phone"
 	// Table holds the table name of the msg in the database.
 	Table = "msg"
 )
@@ -48,6 +54,9 @@ var Columns = []string{
 	FieldMsgtype,
 	FieldMsg,
 	FieldBatchNo,
+	FieldCtype,
+	FieldCc,
+	FieldPhone,
 }
 
 // ValidColumn reports if the column name is valid (part of the table columns).
@@ -74,6 +83,12 @@ var (
 	DefaultUpdatedAt func() time.Time
 	// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
 	UpdateDefaultUpdatedAt func() time.Time
+	// DefaultCtype holds the default value on creation for the "ctype" field.
+	DefaultCtype uint64
+	// DefaultCc holds the default value on creation for the "cc" field.
+	DefaultCc string
+	// DefaultPhone holds the default value on creation for the "phone" field.
+	DefaultPhone string
 )
 
 // OrderOption defines the ordering options for the Msg queries.
@@ -128,3 +143,18 @@ func ByMsg(opts ...sql.OrderTermOption) OrderOption {
 func ByBatchNo(opts ...sql.OrderTermOption) OrderOption {
 	return sql.OrderByField(FieldBatchNo, opts...).ToFunc()
 }
+
+// ByCtype orders the results by the ctype field.
+func ByCtype(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldCtype, opts...).ToFunc()
+}
+
+// ByCc orders the results by the cc field.
+func ByCc(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldCc, opts...).ToFunc()
+}
+
+// ByPhone orders the results by the phone field.
+func ByPhone(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldPhone, opts...).ToFunc()
+}

+ 185 - 0
ent/msg/where.go

@@ -99,6 +99,21 @@ func BatchNo(v string) predicate.Msg {
 	return predicate.Msg(sql.FieldEQ(FieldBatchNo, v))
 }
 
+// Ctype applies equality check predicate on the "ctype" field. It's identical to CtypeEQ.
+func Ctype(v uint64) predicate.Msg {
+	return predicate.Msg(sql.FieldEQ(FieldCtype, v))
+}
+
+// Cc applies equality check predicate on the "cc" field. It's identical to CcEQ.
+func Cc(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldEQ(FieldCc, v))
+}
+
+// Phone applies equality check predicate on the "phone" field. It's identical to PhoneEQ.
+func Phone(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldEQ(FieldPhone, v))
+}
+
 // CreatedAtEQ applies the EQ predicate on the "created_at" field.
 func CreatedAtEQ(v time.Time) predicate.Msg {
 	return predicate.Msg(sql.FieldEQ(FieldCreatedAt, v))
@@ -629,6 +644,176 @@ func BatchNoContainsFold(v string) predicate.Msg {
 	return predicate.Msg(sql.FieldContainsFold(FieldBatchNo, v))
 }
 
+// CtypeEQ applies the EQ predicate on the "ctype" field.
+func CtypeEQ(v uint64) predicate.Msg {
+	return predicate.Msg(sql.FieldEQ(FieldCtype, v))
+}
+
+// CtypeNEQ applies the NEQ predicate on the "ctype" field.
+func CtypeNEQ(v uint64) predicate.Msg {
+	return predicate.Msg(sql.FieldNEQ(FieldCtype, v))
+}
+
+// CtypeIn applies the In predicate on the "ctype" field.
+func CtypeIn(vs ...uint64) predicate.Msg {
+	return predicate.Msg(sql.FieldIn(FieldCtype, vs...))
+}
+
+// CtypeNotIn applies the NotIn predicate on the "ctype" field.
+func CtypeNotIn(vs ...uint64) predicate.Msg {
+	return predicate.Msg(sql.FieldNotIn(FieldCtype, vs...))
+}
+
+// CtypeGT applies the GT predicate on the "ctype" field.
+func CtypeGT(v uint64) predicate.Msg {
+	return predicate.Msg(sql.FieldGT(FieldCtype, v))
+}
+
+// CtypeGTE applies the GTE predicate on the "ctype" field.
+func CtypeGTE(v uint64) predicate.Msg {
+	return predicate.Msg(sql.FieldGTE(FieldCtype, v))
+}
+
+// CtypeLT applies the LT predicate on the "ctype" field.
+func CtypeLT(v uint64) predicate.Msg {
+	return predicate.Msg(sql.FieldLT(FieldCtype, v))
+}
+
+// CtypeLTE applies the LTE predicate on the "ctype" field.
+func CtypeLTE(v uint64) predicate.Msg {
+	return predicate.Msg(sql.FieldLTE(FieldCtype, v))
+}
+
+// CcEQ applies the EQ predicate on the "cc" field.
+func CcEQ(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldEQ(FieldCc, v))
+}
+
+// CcNEQ applies the NEQ predicate on the "cc" field.
+func CcNEQ(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldNEQ(FieldCc, v))
+}
+
+// CcIn applies the In predicate on the "cc" field.
+func CcIn(vs ...string) predicate.Msg {
+	return predicate.Msg(sql.FieldIn(FieldCc, vs...))
+}
+
+// CcNotIn applies the NotIn predicate on the "cc" field.
+func CcNotIn(vs ...string) predicate.Msg {
+	return predicate.Msg(sql.FieldNotIn(FieldCc, vs...))
+}
+
+// CcGT applies the GT predicate on the "cc" field.
+func CcGT(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldGT(FieldCc, v))
+}
+
+// CcGTE applies the GTE predicate on the "cc" field.
+func CcGTE(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldGTE(FieldCc, v))
+}
+
+// CcLT applies the LT predicate on the "cc" field.
+func CcLT(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldLT(FieldCc, v))
+}
+
+// CcLTE applies the LTE predicate on the "cc" field.
+func CcLTE(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldLTE(FieldCc, v))
+}
+
+// CcContains applies the Contains predicate on the "cc" field.
+func CcContains(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldContains(FieldCc, v))
+}
+
+// CcHasPrefix applies the HasPrefix predicate on the "cc" field.
+func CcHasPrefix(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldHasPrefix(FieldCc, v))
+}
+
+// CcHasSuffix applies the HasSuffix predicate on the "cc" field.
+func CcHasSuffix(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldHasSuffix(FieldCc, v))
+}
+
+// CcEqualFold applies the EqualFold predicate on the "cc" field.
+func CcEqualFold(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldEqualFold(FieldCc, v))
+}
+
+// CcContainsFold applies the ContainsFold predicate on the "cc" field.
+func CcContainsFold(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldContainsFold(FieldCc, v))
+}
+
+// PhoneEQ applies the EQ predicate on the "phone" field.
+func PhoneEQ(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldEQ(FieldPhone, v))
+}
+
+// PhoneNEQ applies the NEQ predicate on the "phone" field.
+func PhoneNEQ(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldNEQ(FieldPhone, v))
+}
+
+// PhoneIn applies the In predicate on the "phone" field.
+func PhoneIn(vs ...string) predicate.Msg {
+	return predicate.Msg(sql.FieldIn(FieldPhone, vs...))
+}
+
+// PhoneNotIn applies the NotIn predicate on the "phone" field.
+func PhoneNotIn(vs ...string) predicate.Msg {
+	return predicate.Msg(sql.FieldNotIn(FieldPhone, vs...))
+}
+
+// PhoneGT applies the GT predicate on the "phone" field.
+func PhoneGT(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldGT(FieldPhone, v))
+}
+
+// PhoneGTE applies the GTE predicate on the "phone" field.
+func PhoneGTE(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldGTE(FieldPhone, v))
+}
+
+// PhoneLT applies the LT predicate on the "phone" field.
+func PhoneLT(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldLT(FieldPhone, v))
+}
+
+// PhoneLTE applies the LTE predicate on the "phone" field.
+func PhoneLTE(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldLTE(FieldPhone, v))
+}
+
+// PhoneContains applies the Contains predicate on the "phone" field.
+func PhoneContains(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldContains(FieldPhone, v))
+}
+
+// PhoneHasPrefix applies the HasPrefix predicate on the "phone" field.
+func PhoneHasPrefix(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldHasPrefix(FieldPhone, v))
+}
+
+// PhoneHasSuffix applies the HasSuffix predicate on the "phone" field.
+func PhoneHasSuffix(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldHasSuffix(FieldPhone, v))
+}
+
+// PhoneEqualFold applies the EqualFold predicate on the "phone" field.
+func PhoneEqualFold(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldEqualFold(FieldPhone, v))
+}
+
+// PhoneContainsFold applies the ContainsFold predicate on the "phone" field.
+func PhoneContainsFold(v string) predicate.Msg {
+	return predicate.Msg(sql.FieldContainsFold(FieldPhone, v))
+}
+
 // And groups predicates with the AND operator between them.
 func And(predicates ...predicate.Msg) predicate.Msg {
 	return predicate.Msg(sql.AndPredicates(predicates...))

+ 215 - 0
ent/msg_create.go

@@ -148,6 +148,48 @@ func (mc *MsgCreate) SetNillableBatchNo(s *string) *MsgCreate {
 	return mc
 }
 
+// SetCtype sets the "ctype" field.
+func (mc *MsgCreate) SetCtype(u uint64) *MsgCreate {
+	mc.mutation.SetCtype(u)
+	return mc
+}
+
+// SetNillableCtype sets the "ctype" field if the given value is not nil.
+func (mc *MsgCreate) SetNillableCtype(u *uint64) *MsgCreate {
+	if u != nil {
+		mc.SetCtype(*u)
+	}
+	return mc
+}
+
+// SetCc sets the "cc" field.
+func (mc *MsgCreate) SetCc(s string) *MsgCreate {
+	mc.mutation.SetCc(s)
+	return mc
+}
+
+// SetNillableCc sets the "cc" field if the given value is not nil.
+func (mc *MsgCreate) SetNillableCc(s *string) *MsgCreate {
+	if s != nil {
+		mc.SetCc(*s)
+	}
+	return mc
+}
+
+// SetPhone sets the "phone" field.
+func (mc *MsgCreate) SetPhone(s string) *MsgCreate {
+	mc.mutation.SetPhone(s)
+	return mc
+}
+
+// SetNillablePhone sets the "phone" field if the given value is not nil.
+func (mc *MsgCreate) SetNillablePhone(s *string) *MsgCreate {
+	if s != nil {
+		mc.SetPhone(*s)
+	}
+	return mc
+}
+
 // SetID sets the "id" field.
 func (mc *MsgCreate) SetID(u uint64) *MsgCreate {
 	mc.mutation.SetID(u)
@@ -205,6 +247,18 @@ func (mc *MsgCreate) defaults() error {
 		v := msg.DefaultUpdatedAt()
 		mc.mutation.SetUpdatedAt(v)
 	}
+	if _, ok := mc.mutation.Ctype(); !ok {
+		v := msg.DefaultCtype
+		mc.mutation.SetCtype(v)
+	}
+	if _, ok := mc.mutation.Cc(); !ok {
+		v := msg.DefaultCc
+		mc.mutation.SetCc(v)
+	}
+	if _, ok := mc.mutation.Phone(); !ok {
+		v := msg.DefaultPhone
+		mc.mutation.SetPhone(v)
+	}
 	return nil
 }
 
@@ -216,6 +270,15 @@ func (mc *MsgCreate) check() error {
 	if _, ok := mc.mutation.UpdatedAt(); !ok {
 		return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "Msg.updated_at"`)}
 	}
+	if _, ok := mc.mutation.Ctype(); !ok {
+		return &ValidationError{Name: "ctype", err: errors.New(`ent: missing required field "Msg.ctype"`)}
+	}
+	if _, ok := mc.mutation.Cc(); !ok {
+		return &ValidationError{Name: "cc", err: errors.New(`ent: missing required field "Msg.cc"`)}
+	}
+	if _, ok := mc.mutation.Phone(); !ok {
+		return &ValidationError{Name: "phone", err: errors.New(`ent: missing required field "Msg.phone"`)}
+	}
 	return nil
 }
 
@@ -285,6 +348,18 @@ func (mc *MsgCreate) createSpec() (*Msg, *sqlgraph.CreateSpec) {
 		_spec.SetField(msg.FieldBatchNo, field.TypeString, value)
 		_node.BatchNo = value
 	}
+	if value, ok := mc.mutation.Ctype(); ok {
+		_spec.SetField(msg.FieldCtype, field.TypeUint64, value)
+		_node.Ctype = value
+	}
+	if value, ok := mc.mutation.Cc(); ok {
+		_spec.SetField(msg.FieldCc, field.TypeString, value)
+		_node.Cc = value
+	}
+	if value, ok := mc.mutation.Phone(); ok {
+		_spec.SetField(msg.FieldPhone, field.TypeString, value)
+		_node.Phone = value
+	}
 	return _node, _spec
 }
 
@@ -487,6 +562,48 @@ func (u *MsgUpsert) ClearBatchNo() *MsgUpsert {
 	return u
 }
 
+// SetCtype sets the "ctype" field.
+func (u *MsgUpsert) SetCtype(v uint64) *MsgUpsert {
+	u.Set(msg.FieldCtype, v)
+	return u
+}
+
+// UpdateCtype sets the "ctype" field to the value that was provided on create.
+func (u *MsgUpsert) UpdateCtype() *MsgUpsert {
+	u.SetExcluded(msg.FieldCtype)
+	return u
+}
+
+// AddCtype adds v to the "ctype" field.
+func (u *MsgUpsert) AddCtype(v uint64) *MsgUpsert {
+	u.Add(msg.FieldCtype, v)
+	return u
+}
+
+// SetCc sets the "cc" field.
+func (u *MsgUpsert) SetCc(v string) *MsgUpsert {
+	u.Set(msg.FieldCc, v)
+	return u
+}
+
+// UpdateCc sets the "cc" field to the value that was provided on create.
+func (u *MsgUpsert) UpdateCc() *MsgUpsert {
+	u.SetExcluded(msg.FieldCc)
+	return u
+}
+
+// SetPhone sets the "phone" field.
+func (u *MsgUpsert) SetPhone(v string) *MsgUpsert {
+	u.Set(msg.FieldPhone, v)
+	return u
+}
+
+// UpdatePhone sets the "phone" field to the value that was provided on create.
+func (u *MsgUpsert) UpdatePhone() *MsgUpsert {
+	u.SetExcluded(msg.FieldPhone)
+	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:
 //
@@ -713,6 +830,55 @@ func (u *MsgUpsertOne) ClearBatchNo() *MsgUpsertOne {
 	})
 }
 
+// SetCtype sets the "ctype" field.
+func (u *MsgUpsertOne) SetCtype(v uint64) *MsgUpsertOne {
+	return u.Update(func(s *MsgUpsert) {
+		s.SetCtype(v)
+	})
+}
+
+// AddCtype adds v to the "ctype" field.
+func (u *MsgUpsertOne) AddCtype(v uint64) *MsgUpsertOne {
+	return u.Update(func(s *MsgUpsert) {
+		s.AddCtype(v)
+	})
+}
+
+// UpdateCtype sets the "ctype" field to the value that was provided on create.
+func (u *MsgUpsertOne) UpdateCtype() *MsgUpsertOne {
+	return u.Update(func(s *MsgUpsert) {
+		s.UpdateCtype()
+	})
+}
+
+// SetCc sets the "cc" field.
+func (u *MsgUpsertOne) SetCc(v string) *MsgUpsertOne {
+	return u.Update(func(s *MsgUpsert) {
+		s.SetCc(v)
+	})
+}
+
+// UpdateCc sets the "cc" field to the value that was provided on create.
+func (u *MsgUpsertOne) UpdateCc() *MsgUpsertOne {
+	return u.Update(func(s *MsgUpsert) {
+		s.UpdateCc()
+	})
+}
+
+// SetPhone sets the "phone" field.
+func (u *MsgUpsertOne) SetPhone(v string) *MsgUpsertOne {
+	return u.Update(func(s *MsgUpsert) {
+		s.SetPhone(v)
+	})
+}
+
+// UpdatePhone sets the "phone" field to the value that was provided on create.
+func (u *MsgUpsertOne) UpdatePhone() *MsgUpsertOne {
+	return u.Update(func(s *MsgUpsert) {
+		s.UpdatePhone()
+	})
+}
+
 // Exec executes the query.
 func (u *MsgUpsertOne) Exec(ctx context.Context) error {
 	if len(u.create.conflict) == 0 {
@@ -1105,6 +1271,55 @@ func (u *MsgUpsertBulk) ClearBatchNo() *MsgUpsertBulk {
 	})
 }
 
+// SetCtype sets the "ctype" field.
+func (u *MsgUpsertBulk) SetCtype(v uint64) *MsgUpsertBulk {
+	return u.Update(func(s *MsgUpsert) {
+		s.SetCtype(v)
+	})
+}
+
+// AddCtype adds v to the "ctype" field.
+func (u *MsgUpsertBulk) AddCtype(v uint64) *MsgUpsertBulk {
+	return u.Update(func(s *MsgUpsert) {
+		s.AddCtype(v)
+	})
+}
+
+// UpdateCtype sets the "ctype" field to the value that was provided on create.
+func (u *MsgUpsertBulk) UpdateCtype() *MsgUpsertBulk {
+	return u.Update(func(s *MsgUpsert) {
+		s.UpdateCtype()
+	})
+}
+
+// SetCc sets the "cc" field.
+func (u *MsgUpsertBulk) SetCc(v string) *MsgUpsertBulk {
+	return u.Update(func(s *MsgUpsert) {
+		s.SetCc(v)
+	})
+}
+
+// UpdateCc sets the "cc" field to the value that was provided on create.
+func (u *MsgUpsertBulk) UpdateCc() *MsgUpsertBulk {
+	return u.Update(func(s *MsgUpsert) {
+		s.UpdateCc()
+	})
+}
+
+// SetPhone sets the "phone" field.
+func (u *MsgUpsertBulk) SetPhone(v string) *MsgUpsertBulk {
+	return u.Update(func(s *MsgUpsert) {
+		s.SetPhone(v)
+	})
+}
+
+// UpdatePhone sets the "phone" field to the value that was provided on create.
+func (u *MsgUpsertBulk) UpdatePhone() *MsgUpsertBulk {
+	return u.Update(func(s *MsgUpsert) {
+		s.UpdatePhone()
+	})
+}
+
 // Exec executes the query.
 func (u *MsgUpsertBulk) Exec(ctx context.Context) error {
 	if u.create.err != nil {

+ 122 - 0
ent/msg_update.go

@@ -188,6 +188,55 @@ func (mu *MsgUpdate) ClearBatchNo() *MsgUpdate {
 	return mu
 }
 
+// SetCtype sets the "ctype" field.
+func (mu *MsgUpdate) SetCtype(u uint64) *MsgUpdate {
+	mu.mutation.ResetCtype()
+	mu.mutation.SetCtype(u)
+	return mu
+}
+
+// SetNillableCtype sets the "ctype" field if the given value is not nil.
+func (mu *MsgUpdate) SetNillableCtype(u *uint64) *MsgUpdate {
+	if u != nil {
+		mu.SetCtype(*u)
+	}
+	return mu
+}
+
+// AddCtype adds u to the "ctype" field.
+func (mu *MsgUpdate) AddCtype(u int64) *MsgUpdate {
+	mu.mutation.AddCtype(u)
+	return mu
+}
+
+// SetCc sets the "cc" field.
+func (mu *MsgUpdate) SetCc(s string) *MsgUpdate {
+	mu.mutation.SetCc(s)
+	return mu
+}
+
+// SetNillableCc sets the "cc" field if the given value is not nil.
+func (mu *MsgUpdate) SetNillableCc(s *string) *MsgUpdate {
+	if s != nil {
+		mu.SetCc(*s)
+	}
+	return mu
+}
+
+// SetPhone sets the "phone" field.
+func (mu *MsgUpdate) SetPhone(s string) *MsgUpdate {
+	mu.mutation.SetPhone(s)
+	return mu
+}
+
+// SetNillablePhone sets the "phone" field if the given value is not nil.
+func (mu *MsgUpdate) SetNillablePhone(s *string) *MsgUpdate {
+	if s != nil {
+		mu.SetPhone(*s)
+	}
+	return mu
+}
+
 // Mutation returns the MsgMutation object of the builder.
 func (mu *MsgUpdate) Mutation() *MsgMutation {
 	return mu.mutation
@@ -295,6 +344,18 @@ func (mu *MsgUpdate) sqlSave(ctx context.Context) (n int, err error) {
 	if mu.mutation.BatchNoCleared() {
 		_spec.ClearField(msg.FieldBatchNo, field.TypeString)
 	}
+	if value, ok := mu.mutation.Ctype(); ok {
+		_spec.SetField(msg.FieldCtype, field.TypeUint64, value)
+	}
+	if value, ok := mu.mutation.AddedCtype(); ok {
+		_spec.AddField(msg.FieldCtype, field.TypeUint64, value)
+	}
+	if value, ok := mu.mutation.Cc(); ok {
+		_spec.SetField(msg.FieldCc, field.TypeString, value)
+	}
+	if value, ok := mu.mutation.Phone(); ok {
+		_spec.SetField(msg.FieldPhone, field.TypeString, value)
+	}
 	if n, err = sqlgraph.UpdateNodes(ctx, mu.driver, _spec); err != nil {
 		if _, ok := err.(*sqlgraph.NotFoundError); ok {
 			err = &NotFoundError{msg.Label}
@@ -475,6 +536,55 @@ func (muo *MsgUpdateOne) ClearBatchNo() *MsgUpdateOne {
 	return muo
 }
 
+// SetCtype sets the "ctype" field.
+func (muo *MsgUpdateOne) SetCtype(u uint64) *MsgUpdateOne {
+	muo.mutation.ResetCtype()
+	muo.mutation.SetCtype(u)
+	return muo
+}
+
+// SetNillableCtype sets the "ctype" field if the given value is not nil.
+func (muo *MsgUpdateOne) SetNillableCtype(u *uint64) *MsgUpdateOne {
+	if u != nil {
+		muo.SetCtype(*u)
+	}
+	return muo
+}
+
+// AddCtype adds u to the "ctype" field.
+func (muo *MsgUpdateOne) AddCtype(u int64) *MsgUpdateOne {
+	muo.mutation.AddCtype(u)
+	return muo
+}
+
+// SetCc sets the "cc" field.
+func (muo *MsgUpdateOne) SetCc(s string) *MsgUpdateOne {
+	muo.mutation.SetCc(s)
+	return muo
+}
+
+// SetNillableCc sets the "cc" field if the given value is not nil.
+func (muo *MsgUpdateOne) SetNillableCc(s *string) *MsgUpdateOne {
+	if s != nil {
+		muo.SetCc(*s)
+	}
+	return muo
+}
+
+// SetPhone sets the "phone" field.
+func (muo *MsgUpdateOne) SetPhone(s string) *MsgUpdateOne {
+	muo.mutation.SetPhone(s)
+	return muo
+}
+
+// SetNillablePhone sets the "phone" field if the given value is not nil.
+func (muo *MsgUpdateOne) SetNillablePhone(s *string) *MsgUpdateOne {
+	if s != nil {
+		muo.SetPhone(*s)
+	}
+	return muo
+}
+
 // Mutation returns the MsgMutation object of the builder.
 func (muo *MsgUpdateOne) Mutation() *MsgMutation {
 	return muo.mutation
@@ -612,6 +722,18 @@ func (muo *MsgUpdateOne) sqlSave(ctx context.Context) (_node *Msg, err error) {
 	if muo.mutation.BatchNoCleared() {
 		_spec.ClearField(msg.FieldBatchNo, field.TypeString)
 	}
+	if value, ok := muo.mutation.Ctype(); ok {
+		_spec.SetField(msg.FieldCtype, field.TypeUint64, value)
+	}
+	if value, ok := muo.mutation.AddedCtype(); ok {
+		_spec.AddField(msg.FieldCtype, field.TypeUint64, value)
+	}
+	if value, ok := muo.mutation.Cc(); ok {
+		_spec.SetField(msg.FieldCc, field.TypeString, value)
+	}
+	if value, ok := muo.mutation.Phone(); ok {
+		_spec.SetField(msg.FieldPhone, field.TypeString, value)
+	}
 	_node = &Msg{config: muo.config}
 	_spec.Assign = _node.assignValues
 	_spec.ScanValues = _node.scanValues

+ 343 - 2
ent/mutation.go

@@ -4375,6 +4375,8 @@ type BatchMsgMutation struct {
 	addorganization_id *int64
 	ctype              *uint64
 	addctype           *int64
+	cc                 *string
+	phone              *string
 	clearedFields      map[string]struct{}
 	done               bool
 	oldValue           func(context.Context) (*BatchMsg, error)
@@ -5509,6 +5511,104 @@ func (m *BatchMsgMutation) ResetCtype() {
 	m.addctype = nil
 }
 
+// SetCc sets the "cc" field.
+func (m *BatchMsgMutation) SetCc(s string) {
+	m.cc = &s
+}
+
+// Cc returns the value of the "cc" field in the mutation.
+func (m *BatchMsgMutation) Cc() (r string, exists bool) {
+	v := m.cc
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldCc returns the old "cc" field's value of the BatchMsg entity.
+// If the BatchMsg 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 *BatchMsgMutation) OldCc(ctx context.Context) (v string, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldCc is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldCc requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldCc: %w", err)
+	}
+	return oldValue.Cc, nil
+}
+
+// ClearCc clears the value of the "cc" field.
+func (m *BatchMsgMutation) ClearCc() {
+	m.cc = nil
+	m.clearedFields[batchmsg.FieldCc] = struct{}{}
+}
+
+// CcCleared returns if the "cc" field was cleared in this mutation.
+func (m *BatchMsgMutation) CcCleared() bool {
+	_, ok := m.clearedFields[batchmsg.FieldCc]
+	return ok
+}
+
+// ResetCc resets all changes to the "cc" field.
+func (m *BatchMsgMutation) ResetCc() {
+	m.cc = nil
+	delete(m.clearedFields, batchmsg.FieldCc)
+}
+
+// SetPhone sets the "phone" field.
+func (m *BatchMsgMutation) SetPhone(s string) {
+	m.phone = &s
+}
+
+// Phone returns the value of the "phone" field in the mutation.
+func (m *BatchMsgMutation) Phone() (r string, exists bool) {
+	v := m.phone
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldPhone returns the old "phone" field's value of the BatchMsg entity.
+// If the BatchMsg 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 *BatchMsgMutation) OldPhone(ctx context.Context) (v string, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldPhone is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldPhone requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldPhone: %w", err)
+	}
+	return oldValue.Phone, nil
+}
+
+// ClearPhone clears the value of the "phone" field.
+func (m *BatchMsgMutation) ClearPhone() {
+	m.phone = nil
+	m.clearedFields[batchmsg.FieldPhone] = struct{}{}
+}
+
+// PhoneCleared returns if the "phone" field was cleared in this mutation.
+func (m *BatchMsgMutation) PhoneCleared() bool {
+	_, ok := m.clearedFields[batchmsg.FieldPhone]
+	return ok
+}
+
+// ResetPhone resets all changes to the "phone" field.
+func (m *BatchMsgMutation) ResetPhone() {
+	m.phone = nil
+	delete(m.clearedFields, batchmsg.FieldPhone)
+}
+
 // Where appends a list predicates to the BatchMsgMutation builder.
 func (m *BatchMsgMutation) Where(ps ...predicate.BatchMsg) {
 	m.predicates = append(m.predicates, ps...)
@@ -5543,7 +5643,7 @@ func (m *BatchMsgMutation) Type() string {
 // order to get all numeric fields that were incremented/decremented, call
 // AddedFields().
 func (m *BatchMsgMutation) Fields() []string {
-	fields := make([]string, 0, 19)
+	fields := make([]string, 0, 21)
 	if m.created_at != nil {
 		fields = append(fields, batchmsg.FieldCreatedAt)
 	}
@@ -5601,6 +5701,12 @@ func (m *BatchMsgMutation) Fields() []string {
 	if m.ctype != nil {
 		fields = append(fields, batchmsg.FieldCtype)
 	}
+	if m.cc != nil {
+		fields = append(fields, batchmsg.FieldCc)
+	}
+	if m.phone != nil {
+		fields = append(fields, batchmsg.FieldPhone)
+	}
 	return fields
 }
 
@@ -5647,6 +5753,10 @@ func (m *BatchMsgMutation) Field(name string) (ent.Value, bool) {
 		return m.OrganizationID()
 	case batchmsg.FieldCtype:
 		return m.Ctype()
+	case batchmsg.FieldCc:
+		return m.Cc()
+	case batchmsg.FieldPhone:
+		return m.Phone()
 	}
 	return nil, false
 }
@@ -5694,6 +5804,10 @@ func (m *BatchMsgMutation) OldField(ctx context.Context, name string) (ent.Value
 		return m.OldOrganizationID(ctx)
 	case batchmsg.FieldCtype:
 		return m.OldCtype(ctx)
+	case batchmsg.FieldCc:
+		return m.OldCc(ctx)
+	case batchmsg.FieldPhone:
+		return m.OldPhone(ctx)
 	}
 	return nil, fmt.Errorf("unknown BatchMsg field %s", name)
 }
@@ -5836,6 +5950,20 @@ func (m *BatchMsgMutation) SetField(name string, value ent.Value) error {
 		}
 		m.SetCtype(v)
 		return nil
+	case batchmsg.FieldCc:
+		v, ok := value.(string)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetCc(v)
+		return nil
+	case batchmsg.FieldPhone:
+		v, ok := value.(string)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetPhone(v)
+		return nil
 	}
 	return fmt.Errorf("unknown BatchMsg field %s", name)
 }
@@ -5998,6 +6126,12 @@ func (m *BatchMsgMutation) ClearedFields() []string {
 	if m.FieldCleared(batchmsg.FieldType) {
 		fields = append(fields, batchmsg.FieldType)
 	}
+	if m.FieldCleared(batchmsg.FieldCc) {
+		fields = append(fields, batchmsg.FieldCc)
+	}
+	if m.FieldCleared(batchmsg.FieldPhone) {
+		fields = append(fields, batchmsg.FieldPhone)
+	}
 	return fields
 }
 
@@ -6057,6 +6191,12 @@ func (m *BatchMsgMutation) ClearField(name string) error {
 	case batchmsg.FieldType:
 		m.ClearType()
 		return nil
+	case batchmsg.FieldCc:
+		m.ClearCc()
+		return nil
+	case batchmsg.FieldPhone:
+		m.ClearPhone()
+		return nil
 	}
 	return fmt.Errorf("unknown BatchMsg nullable field %s", name)
 }
@@ -6122,6 +6262,12 @@ func (m *BatchMsgMutation) ResetField(name string) error {
 	case batchmsg.FieldCtype:
 		m.ResetCtype()
 		return nil
+	case batchmsg.FieldCc:
+		m.ResetCc()
+		return nil
+	case batchmsg.FieldPhone:
+		m.ResetPhone()
+		return nil
 	}
 	return fmt.Errorf("unknown BatchMsg field %s", name)
 }
@@ -20951,6 +21097,10 @@ type MsgMutation struct {
 	addmsgtype    *int32
 	msg           *string
 	batch_no      *string
+	ctype         *uint64
+	addctype      *int64
+	cc            *string
+	phone         *string
 	clearedFields map[string]struct{}
 	done          bool
 	oldValue      func(context.Context) (*Msg, error)
@@ -21518,6 +21668,134 @@ func (m *MsgMutation) ResetBatchNo() {
 	delete(m.clearedFields, msg.FieldBatchNo)
 }
 
+// SetCtype sets the "ctype" field.
+func (m *MsgMutation) SetCtype(u uint64) {
+	m.ctype = &u
+	m.addctype = nil
+}
+
+// Ctype returns the value of the "ctype" field in the mutation.
+func (m *MsgMutation) Ctype() (r uint64, exists bool) {
+	v := m.ctype
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldCtype returns the old "ctype" field's value of the Msg entity.
+// If the Msg 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 *MsgMutation) OldCtype(ctx context.Context) (v uint64, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldCtype is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldCtype requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldCtype: %w", err)
+	}
+	return oldValue.Ctype, nil
+}
+
+// AddCtype adds u to the "ctype" field.
+func (m *MsgMutation) AddCtype(u int64) {
+	if m.addctype != nil {
+		*m.addctype += u
+	} else {
+		m.addctype = &u
+	}
+}
+
+// AddedCtype returns the value that was added to the "ctype" field in this mutation.
+func (m *MsgMutation) AddedCtype() (r int64, exists bool) {
+	v := m.addctype
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// ResetCtype resets all changes to the "ctype" field.
+func (m *MsgMutation) ResetCtype() {
+	m.ctype = nil
+	m.addctype = nil
+}
+
+// SetCc sets the "cc" field.
+func (m *MsgMutation) SetCc(s string) {
+	m.cc = &s
+}
+
+// Cc returns the value of the "cc" field in the mutation.
+func (m *MsgMutation) Cc() (r string, exists bool) {
+	v := m.cc
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldCc returns the old "cc" field's value of the Msg entity.
+// If the Msg 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 *MsgMutation) OldCc(ctx context.Context) (v string, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldCc is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldCc requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldCc: %w", err)
+	}
+	return oldValue.Cc, nil
+}
+
+// ResetCc resets all changes to the "cc" field.
+func (m *MsgMutation) ResetCc() {
+	m.cc = nil
+}
+
+// SetPhone sets the "phone" field.
+func (m *MsgMutation) SetPhone(s string) {
+	m.phone = &s
+}
+
+// Phone returns the value of the "phone" field in the mutation.
+func (m *MsgMutation) Phone() (r string, exists bool) {
+	v := m.phone
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldPhone returns the old "phone" field's value of the Msg entity.
+// If the Msg 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 *MsgMutation) OldPhone(ctx context.Context) (v string, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldPhone is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldPhone requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldPhone: %w", err)
+	}
+	return oldValue.Phone, nil
+}
+
+// ResetPhone resets all changes to the "phone" field.
+func (m *MsgMutation) ResetPhone() {
+	m.phone = nil
+}
+
 // Where appends a list predicates to the MsgMutation builder.
 func (m *MsgMutation) Where(ps ...predicate.Msg) {
 	m.predicates = append(m.predicates, ps...)
@@ -21552,7 +21830,7 @@ func (m *MsgMutation) Type() string {
 // order to get all numeric fields that were incremented/decremented, call
 // AddedFields().
 func (m *MsgMutation) Fields() []string {
-	fields := make([]string, 0, 9)
+	fields := make([]string, 0, 12)
 	if m.created_at != nil {
 		fields = append(fields, msg.FieldCreatedAt)
 	}
@@ -21580,6 +21858,15 @@ func (m *MsgMutation) Fields() []string {
 	if m.batch_no != nil {
 		fields = append(fields, msg.FieldBatchNo)
 	}
+	if m.ctype != nil {
+		fields = append(fields, msg.FieldCtype)
+	}
+	if m.cc != nil {
+		fields = append(fields, msg.FieldCc)
+	}
+	if m.phone != nil {
+		fields = append(fields, msg.FieldPhone)
+	}
 	return fields
 }
 
@@ -21606,6 +21893,12 @@ func (m *MsgMutation) Field(name string) (ent.Value, bool) {
 		return m.Msg()
 	case msg.FieldBatchNo:
 		return m.BatchNo()
+	case msg.FieldCtype:
+		return m.Ctype()
+	case msg.FieldCc:
+		return m.Cc()
+	case msg.FieldPhone:
+		return m.Phone()
 	}
 	return nil, false
 }
@@ -21633,6 +21926,12 @@ func (m *MsgMutation) OldField(ctx context.Context, name string) (ent.Value, err
 		return m.OldMsg(ctx)
 	case msg.FieldBatchNo:
 		return m.OldBatchNo(ctx)
+	case msg.FieldCtype:
+		return m.OldCtype(ctx)
+	case msg.FieldCc:
+		return m.OldCc(ctx)
+	case msg.FieldPhone:
+		return m.OldPhone(ctx)
 	}
 	return nil, fmt.Errorf("unknown Msg field %s", name)
 }
@@ -21705,6 +22004,27 @@ func (m *MsgMutation) SetField(name string, value ent.Value) error {
 		}
 		m.SetBatchNo(v)
 		return nil
+	case msg.FieldCtype:
+		v, ok := value.(uint64)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetCtype(v)
+		return nil
+	case msg.FieldCc:
+		v, ok := value.(string)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetCc(v)
+		return nil
+	case msg.FieldPhone:
+		v, ok := value.(string)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetPhone(v)
+		return nil
 	}
 	return fmt.Errorf("unknown Msg field %s", name)
 }
@@ -21719,6 +22039,9 @@ func (m *MsgMutation) AddedFields() []string {
 	if m.addmsgtype != nil {
 		fields = append(fields, msg.FieldMsgtype)
 	}
+	if m.addctype != nil {
+		fields = append(fields, msg.FieldCtype)
+	}
 	return fields
 }
 
@@ -21731,6 +22054,8 @@ func (m *MsgMutation) AddedField(name string) (ent.Value, bool) {
 		return m.AddedStatus()
 	case msg.FieldMsgtype:
 		return m.AddedMsgtype()
+	case msg.FieldCtype:
+		return m.AddedCtype()
 	}
 	return nil, false
 }
@@ -21754,6 +22079,13 @@ func (m *MsgMutation) AddField(name string, value ent.Value) error {
 		}
 		m.AddMsgtype(v)
 		return nil
+	case msg.FieldCtype:
+		v, ok := value.(int64)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.AddCtype(v)
+		return nil
 	}
 	return fmt.Errorf("unknown Msg numeric field %s", name)
 }
@@ -21853,6 +22185,15 @@ func (m *MsgMutation) ResetField(name string) error {
 	case msg.FieldBatchNo:
 		m.ResetBatchNo()
 		return nil
+	case msg.FieldCtype:
+		m.ResetCtype()
+		return nil
+	case msg.FieldCc:
+		m.ResetCc()
+		return nil
+	case msg.FieldPhone:
+		m.ResetPhone()
+		return nil
 	}
 	return fmt.Errorf("unknown Msg field %s", name)
 }

+ 12 - 0
ent/runtime/runtime.go

@@ -878,6 +878,18 @@ func init() {
 	msg.DefaultUpdatedAt = msgDescUpdatedAt.Default.(func() time.Time)
 	// msg.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
 	msg.UpdateDefaultUpdatedAt = msgDescUpdatedAt.UpdateDefault.(func() time.Time)
+	// msgDescCtype is the schema descriptor for ctype field.
+	msgDescCtype := msgFields[6].Descriptor()
+	// msg.DefaultCtype holds the default value on creation for the ctype field.
+	msg.DefaultCtype = msgDescCtype.Default.(uint64)
+	// msgDescCc is the schema descriptor for cc field.
+	msgDescCc := msgFields[7].Descriptor()
+	// msg.DefaultCc holds the default value on creation for the cc field.
+	msg.DefaultCc = msgDescCc.Default.(string)
+	// msgDescPhone is the schema descriptor for phone field.
+	msgDescPhone := msgFields[8].Descriptor()
+	// msg.DefaultPhone holds the default value on creation for the phone field.
+	msg.DefaultPhone = msgDescPhone.Default.(string)
 	payrechargeMixin := schema.PayRecharge{}.Mixin()
 	payrechargeMixinHooks1 := payrechargeMixin[1].Hooks()
 	payrecharge.Hooks[0] = payrechargeMixinHooks1[0]

+ 2 - 0
ent/schema/batch_msg.go

@@ -33,6 +33,8 @@ func (BatchMsg) Fields() []ent.Field {
 		field.Int32("type").Optional().Comment("发送类型 1-群发消息 2-群发朋友圈"),
 		field.Uint64("organization_id").Positive().Comment("organization_id | 租户ID"),
 		field.Uint64("ctype").Default(1).Comment("内容类型:1-微信 2-whatsapp"),
+		field.String("cc").Optional().Comment("国家区号"),
+		field.String("phone").Optional().Comment("手机号"),
 	}
 }
 func (BatchMsg) Mixin() []ent.Mixin {

+ 5 - 1
ent/schema/msg.go

@@ -22,7 +22,11 @@ func (Msg) Fields() []ent.Field {
 		field.String("toid").Optional().Comment("接收人微信ID/群ID"),
 		field.Int32("msgtype").Optional().Comment("消息类型"),
 		field.String("msg").Optional().Comment("消息"),
-		field.String("batch_no").Optional().Comment("批次号")}
+		field.String("batch_no").Optional().Comment("批次号"),
+		field.Uint64("ctype").Default(1).Comment("内容类型:1-微信 2-whatsapp"),
+		field.String("cc").Default("").Comment("国家区号"),
+		field.String("phone").Default("").Comment("手机号"),
+	}
 }
 
 func (Msg) Mixin() []ent.Mixin {

+ 120 - 0
ent/set_not_nil.go

@@ -1304,6 +1304,54 @@ func (bm *BatchMsgCreate) SetNotNilCtype(value *uint64) *BatchMsgCreate {
 }
 
 // set field if value's pointer is not nil.
+func (bm *BatchMsgUpdate) SetNotNilCc(value *string) *BatchMsgUpdate {
+	if value != nil {
+		return bm.SetCc(*value)
+	}
+	return bm
+}
+
+// set field if value's pointer is not nil.
+func (bm *BatchMsgUpdateOne) SetNotNilCc(value *string) *BatchMsgUpdateOne {
+	if value != nil {
+		return bm.SetCc(*value)
+	}
+	return bm
+}
+
+// set field if value's pointer is not nil.
+func (bm *BatchMsgCreate) SetNotNilCc(value *string) *BatchMsgCreate {
+	if value != nil {
+		return bm.SetCc(*value)
+	}
+	return bm
+}
+
+// set field if value's pointer is not nil.
+func (bm *BatchMsgUpdate) SetNotNilPhone(value *string) *BatchMsgUpdate {
+	if value != nil {
+		return bm.SetPhone(*value)
+	}
+	return bm
+}
+
+// set field if value's pointer is not nil.
+func (bm *BatchMsgUpdateOne) SetNotNilPhone(value *string) *BatchMsgUpdateOne {
+	if value != nil {
+		return bm.SetPhone(*value)
+	}
+	return bm
+}
+
+// set field if value's pointer is not nil.
+func (bm *BatchMsgCreate) SetNotNilPhone(value *string) *BatchMsgCreate {
+	if value != nil {
+		return bm.SetPhone(*value)
+	}
+	return bm
+}
+
+// set field if value's pointer is not nil.
 func (c *CategoryUpdate) SetNotNilUpdatedAt(value *time.Time) *CategoryUpdate {
 	if value != nil {
 		return c.SetUpdatedAt(*value)
@@ -4736,6 +4784,78 @@ func (m *MsgCreate) SetNotNilBatchNo(value *string) *MsgCreate {
 }
 
 // set field if value's pointer is not nil.
+func (m *MsgUpdate) SetNotNilCtype(value *uint64) *MsgUpdate {
+	if value != nil {
+		return m.SetCtype(*value)
+	}
+	return m
+}
+
+// set field if value's pointer is not nil.
+func (m *MsgUpdateOne) SetNotNilCtype(value *uint64) *MsgUpdateOne {
+	if value != nil {
+		return m.SetCtype(*value)
+	}
+	return m
+}
+
+// set field if value's pointer is not nil.
+func (m *MsgCreate) SetNotNilCtype(value *uint64) *MsgCreate {
+	if value != nil {
+		return m.SetCtype(*value)
+	}
+	return m
+}
+
+// set field if value's pointer is not nil.
+func (m *MsgUpdate) SetNotNilCc(value *string) *MsgUpdate {
+	if value != nil {
+		return m.SetCc(*value)
+	}
+	return m
+}
+
+// set field if value's pointer is not nil.
+func (m *MsgUpdateOne) SetNotNilCc(value *string) *MsgUpdateOne {
+	if value != nil {
+		return m.SetCc(*value)
+	}
+	return m
+}
+
+// set field if value's pointer is not nil.
+func (m *MsgCreate) SetNotNilCc(value *string) *MsgCreate {
+	if value != nil {
+		return m.SetCc(*value)
+	}
+	return m
+}
+
+// set field if value's pointer is not nil.
+func (m *MsgUpdate) SetNotNilPhone(value *string) *MsgUpdate {
+	if value != nil {
+		return m.SetPhone(*value)
+	}
+	return m
+}
+
+// set field if value's pointer is not nil.
+func (m *MsgUpdateOne) SetNotNilPhone(value *string) *MsgUpdateOne {
+	if value != nil {
+		return m.SetPhone(*value)
+	}
+	return m
+}
+
+// set field if value's pointer is not nil.
+func (m *MsgCreate) SetNotNilPhone(value *string) *MsgCreate {
+	if value != nil {
+		return m.SetPhone(*value)
+	}
+	return m
+}
+
+// set field if value's pointer is not nil.
 func (pr *PayRechargeUpdate) SetNotNilUpdatedAt(value *time.Time) *PayRechargeUpdate {
 	if value != nil {
 		return pr.SetUpdatedAt(*value)

+ 2 - 2
hook/aliyun/whatsapp.go

@@ -548,13 +548,13 @@ func SendChatappMessage(stype, messageType, templateCode, lang, sfrom, sto, stex
 
 // SendBatchChatappMessage 发送批量消息
 // @see https://help.aliyun.com/zh/chatapp/developer-reference/api-cams-2020-06-06-sendchatappmassmessage
-func SendBatchChatappMessage(stype, messageType, templateCode, lang, sfrom, stext string, sto []string) (*cams20200606.SendChatappMassMessageResponse, error) {
+func SendBatchChatappMessage(templateCode, lang, sfrom string, sto []string) (*cams20200606.SendChatappMassMessageResponse, error) {
 	client, _err := CreateCamsClient()
 	if _err != nil {
 		return nil, _err
 	}
 
-	senderList := make([]*cams20200606.SendChatappMassMessageRequestSenderList, 0)
+	senderList := make([]*cams20200606.SendChatappMassMessageRequestSenderList, len(sto))
 	for _, v := range sto {
 		senderList = append(senderList, &cams20200606.SendChatappMassMessageRequestSenderList{
 			To: &v,

+ 44 - 0
internal/handler/batch_msg/create_whatcapp_batch_msg_handler.go

@@ -0,0 +1,44 @@
+package batch_msg
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/batch_msg"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /batch_msg/createWhatcappBatchMsg batch_msg CreateWhatcappBatchMsg
+//
+// 创建Whatsapp 群发任务
+//
+// 创建Whatsapp 群发任务
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: BatchMsgInfo
+//
+// Responses:
+//  200: BaseMsgResp
+
+func CreateWhatcappBatchMsgHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.BatchMsgInfo
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := batch_msg.NewCreateWhatcappBatchMsgLogic(r.Context(), svcCtx)
+		resp, err := l.CreateWhatcappBatchMsg(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 44 - 0
internal/handler/batch_msg/get_whatcapp_batch_msg_handler.go

@@ -0,0 +1,44 @@
+package batch_msg
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/batch_msg"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /batch_msg/getWhatcappBatchMsg batch_msg GetWhatcappBatchMsg
+//
+// 获取Whatsapp 群发任务详情
+//
+// 获取Whatsapp 群发任务详情
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: IDReq
+//
+// Responses:
+//  200: BatchMsgInfoResp
+
+func GetWhatcappBatchMsgHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.IDReq
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := batch_msg.NewGetWhatcappBatchMsgLogic(r.Context(), svcCtx)
+		resp, err := l.GetWhatcappBatchMsg(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 44 - 0
internal/handler/batch_msg/get_whatcapp_batch_msg_history_handler.go

@@ -0,0 +1,44 @@
+package batch_msg
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/batch_msg"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /batch_msg/getWhatcappBatchMsgHistory batch_msg GetWhatcappBatchMsgHistory
+//
+// 获取Whatsapp 群发任务发送记录
+//
+// 获取Whatsapp 群发任务发送记录
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: IDReq
+//
+// Responses:
+//  200: BaseMsgResp
+
+func GetWhatcappBatchMsgHistoryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.WhatsappBatchMsgHistoryReq
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := batch_msg.NewGetWhatcappBatchMsgHistoryLogic(r.Context(), svcCtx)
+		resp, err := l.GetWhatcappBatchMsgHistory(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 44 - 0
internal/handler/batch_msg/get_whatcapp_batch_msg_list_handler.go

@@ -0,0 +1,44 @@
+package batch_msg
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/batch_msg"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /batch_msg/getWhatcappBatchMsgList batch_msg GetWhatcappBatchMsgList
+//
+// 获取Whatsapp 批量任务列表
+//
+// 获取Whatsapp 批量任务列表
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: WhatsappBatchMsgListReq
+//
+// Responses:
+//  200: BatchMsgListResp
+
+func GetWhatcappBatchMsgListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.WhatsappBatchMsgListReq
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := batch_msg.NewGetWhatcappBatchMsgListLogic(r.Context(), svcCtx)
+		resp, err := l.GetWhatcappBatchMsgList(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 44 - 0
internal/handler/batch_msg/remove_whatcapp_batch_msg_handler.go

@@ -0,0 +1,44 @@
+package batch_msg
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/batch_msg"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /batch_msg/removeWhatcappBatchMsg batch_msg RemoveWhatcappBatchMsg
+//
+// 删除Whatsapp 群发任务
+//
+// 删除Whatsapp 群发任务
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: IDReq
+//
+// Responses:
+//  200: BaseMsgResp
+
+func RemoveWhatcappBatchMsgHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.IDsReq
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := batch_msg.NewRemoveWhatcappBatchMsgLogic(r.Context(), svcCtx)
+		resp, err := l.RemoveWhatcappBatchMsg(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 44 - 0
internal/handler/batch_msg/send_batch_msg_text_handler.go

@@ -0,0 +1,44 @@
+package batch_msg
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/batch_msg"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /batch_msg/sendBatchMsgText batch_msg SendBatchMsgText
+//
+// 发送消息
+//
+// 发送消息
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: SendMsgReq
+//
+// Responses:
+//  200: BaseMsgResp
+
+func SendBatchMsgTextHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.SendMsgReq
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := batch_msg.NewSendBatchMsgTextLogic(r.Context(), svcCtx)
+		resp, err := l.SendBatchMsgText(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 44 - 0
internal/handler/batch_msg/update_whatcapp_batch_msg_handler.go

@@ -0,0 +1,44 @@
+package batch_msg
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/batch_msg"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /batch_msg/updateWhatcappBatchMsg batch_msg UpdateWhatcappBatchMsg
+//
+// 修改Whatsapp 群发任务
+//
+// 修改Whatsapp 群发任务
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: BatchMsgInfo
+//
+// Responses:
+//  200: BaseMsgResp
+
+func UpdateWhatcappBatchMsgHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.BatchMsgInfo
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := batch_msg.NewUpdateWhatcappBatchMsgLogic(r.Context(), svcCtx)
+		resp, err := l.UpdateWhatcappBatchMsg(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 35 - 0
internal/handler/routes.go

@@ -883,6 +883,41 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
 					Path:    "/batch_msg/stop",
 					Handler: batch_msg.StopBatchMsgHandler(serverCtx),
 				},
+				{
+					Method:  http.MethodPost,
+					Path:    "/batch_msg/getWhatcappBatchMsgList",
+					Handler: batch_msg.GetWhatcappBatchMsgListHandler(serverCtx),
+				},
+				{
+					Method:  http.MethodPost,
+					Path:    "/batch_msg/createWhatcappBatchMsg",
+					Handler: batch_msg.CreateWhatcappBatchMsgHandler(serverCtx),
+				},
+				{
+					Method:  http.MethodPost,
+					Path:    "/batch_msg/updateWhatcappBatchMsg",
+					Handler: batch_msg.UpdateWhatcappBatchMsgHandler(serverCtx),
+				},
+				{
+					Method:  http.MethodPost,
+					Path:    "/batch_msg/getWhatcappBatchMsg",
+					Handler: batch_msg.GetWhatcappBatchMsgHandler(serverCtx),
+				},
+				{
+					Method:  http.MethodPost,
+					Path:    "/batch_msg/removeWhatcappBatchMsg",
+					Handler: batch_msg.RemoveWhatcappBatchMsgHandler(serverCtx),
+				},
+				{
+					Method:  http.MethodPost,
+					Path:    "/batch_msg/getWhatcappBatchMsgHistory",
+					Handler: batch_msg.GetWhatcappBatchMsgHistoryHandler(serverCtx),
+				},
+				{
+					Method:  http.MethodPost,
+					Path:    "/batch_msg/sendBatchMsgText",
+					Handler: batch_msg.SendBatchMsgTextHandler(serverCtx),
+				},
 			}...,
 		),
 		rest.WithJwt(serverCtx.Config.Auth.AccessSecret),

+ 5 - 5
internal/logic/batch_msg/create_batch_msg_logic.go

@@ -131,7 +131,7 @@ func (l *CreateBatchMsgLogic) CreateBatchMsg(req *types.BatchMsgInfo) (*types.Ba
 			}
 			tag = "全部联系人"
 		} else {
-			userList, err = getContactList(l, req.Labels, *req.Fromwxid, 1)
+			userList, err = l.getContactList(req.Labels, *req.Fromwxid, 1)
 			if err != nil {
 				return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
 			}
@@ -145,7 +145,7 @@ func (l *CreateBatchMsgLogic) CreateBatchMsg(req *types.BatchMsgInfo) (*types.Ba
 			}
 			tag = "全部群"
 		} else {
-			groupList, err = getContactList(l, req.GroupLabels, *req.Fromwxid, 2)
+			groupList, err = l.getContactList(req.GroupLabels, *req.Fromwxid, 2)
 			if err != nil {
 				return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
 			}
@@ -158,7 +158,7 @@ func (l *CreateBatchMsgLogic) CreateBatchMsg(req *types.BatchMsgInfo) (*types.Ba
 			return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
 		}
 		tagstring = string(tagByte)
-		tagArray := getLabelListByIds(l, req.Labels, req.GroupLabels)
+		tagArray := l.getLabelListByIds(req.Labels, req.GroupLabels)
 		if tag != "" {
 			tagArray = append(tagArray, tag)
 		}
@@ -201,7 +201,7 @@ func (l *CreateBatchMsgLogic) CreateBatchMsg(req *types.BatchMsgInfo) (*types.Ba
 	return &types.BaseMsgResp{Msg: errormsg.CreateSuccess}, nil
 }
 
-func getContactList(l *CreateBatchMsgLogic, labels []uint64, fromWxId string, stype int) ([]*ent.Contact, error) {
+func (l *CreateBatchMsgLogic) getContactList(labels []uint64, fromWxId string, stype int) ([]*ent.Contact, error) {
 	// 获取 label_relationship 表中,label_id 等于 labids 的 contact_id
 	labelrelationships, err := l.svcCtx.DB.LabelRelationship.Query().Where(labelrelationship.LabelIDIn(labels...)).All(l.ctx)
 	if err != nil {
@@ -224,7 +224,7 @@ func getContactList(l *CreateBatchMsgLogic, labels []uint64, fromWxId string, st
 	return userList, nil
 }
 
-func getLabelListByIds(l *CreateBatchMsgLogic, labels, groupLabels []uint64) []string {
+func (l *CreateBatchMsgLogic) getLabelListByIds(labels, groupLabels []uint64) []string {
 	result := make([]string, 0)
 	labels = append(labels, groupLabels...)
 	if len(labels) > 0 {

+ 279 - 0
internal/logic/batch_msg/create_whatcapp_batch_msg_logic.go

@@ -0,0 +1,279 @@
+package batch_msg
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"github.com/suyuan32/simple-admin-common/utils/uuidx"
+	"github.com/zeromicro/go-zero/core/errorx"
+	"strings"
+	"time"
+	"wechat-api/ent"
+	"wechat-api/ent/contact"
+	"wechat-api/ent/custom_types"
+	"wechat-api/ent/label"
+	"wechat-api/ent/labelrelationship"
+	"wechat-api/hook/aliyun"
+	"wechat-api/internal/utils/dberrorhandler"
+
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type CreateWhatcappBatchMsgLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewCreateWhatcappBatchMsgLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateWhatcappBatchMsgLogic {
+	return &CreateWhatcappBatchMsgLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *CreateWhatcappBatchMsgLogic) CreateWhatcappBatchMsg(req *types.BatchMsgInfo) (*types.BaseMsgResp, error) {
+	organizationId := l.ctx.Value("organizationId").(uint64)
+
+	var err error
+
+	var tagstring, tag string
+	allContact := false
+	tagIdArray := make([]uint64, 0)
+	for _, labelId := range req.Labels {
+		tagIdArray = append(tagIdArray, labelId)
+		if labelId == uint64(0) { //全部
+			allContact = true
+		}
+	}
+
+	startTime := time.Now()
+	sendNow := true
+
+	// 把 req.Msg 字符串的内容 json_decode 到 msgArray
+	// 然后再把每一条信息都给所有指定的用户记录到 message_records 表
+	var msgArray []custom_types.Action
+
+	if req.Msg != nil && *req.Msg != "" {
+		err = json.Unmarshal([]byte(*req.Msg), &msgArray)
+		if err != nil {
+			return nil, errors.New("解析JSON失败")
+		}
+	}
+
+	var msgActionList []custom_types.Action
+	if len(msgArray) > 0 {
+		msgActionList = make([]custom_types.Action, len(msgArray))
+		for i, msg := range msgArray {
+			if msg.Type == 1 {
+				msgActionList[i] = custom_types.Action{
+					Type:    msg.Type,
+					Content: msg.Content,
+				}
+			} else {
+				msgActionList[i] = custom_types.Action{
+					Type:    msg.Type,
+					Content: msg.Content,
+					Meta: &custom_types.Meta{
+						Filename: msg.Meta.Filename,
+					},
+				}
+			}
+		}
+	}
+
+	userList := make([]*ent.Contact, 0)
+
+	tagMap := make(map[string][]uint64)
+	if allContact {
+		// 获取 contact 表中  cc+phone 匹配的联系人
+		userList, err = l.svcCtx.DB.Contact.Query().Where(
+			contact.Phone(*req.Phone),
+			contact.Cc(*req.Cc),
+			contact.Ctype(2),
+		).All(l.ctx)
+		if err != nil {
+			return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+		}
+
+		tagids := make([]uint64, 0, 1)
+		tagids = append(tagids, uint64(0))
+		tagMap["contact_tag"] = tagids
+		tagByte, err := json.Marshal(tagMap)
+		if err != nil {
+			return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+		}
+		tagstring = string(tagByte)
+		tag = "全部"
+	} else {
+		if allContact { // 所有联系人
+			// 获取 contact 表中  wx_wxid 等于 req.Fromwxid 的 type 为1的数据
+			userList, err = l.svcCtx.DB.Contact.Query().Where(
+				contact.Cc(*req.Cc),
+				contact.Phone(*req.Phone),
+				contact.Ctype(2),
+			).All(l.ctx)
+			if err != nil {
+				return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+			}
+			tag = "全部联系人"
+		} else {
+			userList, err = l.getContactList(req.Labels, *req.Cc, *req.Phone)
+			if err != nil {
+				return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+			}
+		}
+
+		tagMap["contact_tag"] = req.Labels
+		tagByte, err := json.Marshal(tagMap)
+		if err != nil {
+			return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+		}
+		tagstring = string(tagByte)
+		tagArray := l.getLabelListByIds(req.Labels, req.GroupLabels)
+		if tag != "" {
+			tagArray = append(tagArray, tag)
+		}
+		tag = strings.Join(tagArray, ",")
+	}
+	// 这里是根据userlist 和 批量消息数 获得最终待发送消息总数
+	total := int32(len(userList)) * int32(len(msgActionList))
+	l.Logger.Infof("userList=%v user_total=%v\n", userList, total)
+	if total == 0 {
+		return &types.BaseMsgResp{Msg: "未查询到收信人,请重新选择", Code: 3}, nil
+	}
+
+	//TODO 发送批量消息
+	sto := make([]string, 0)
+	for _, v := range userList {
+		sto = append(sto, v.Cc+v.Phone)
+	}
+	fullPhone := *req.Cc + *req.Phone
+	result, err := aliyun.SendBatchChatappMessage(*req.TemplateCode, *req.Lang, fullPhone, sto)
+	if err != nil {
+		return nil, errorx.NewInvalidArgumentError(err.Error())
+	}
+	l.Logger.Infof("send_batch_chatapp_message result=%v\n", result)
+
+	batchNo := uuidx.NewUUID().String()
+
+	var sendTime *time.Time
+	if !sendNow {
+		sendTime = &startTime
+	}
+
+	tx, err := l.svcCtx.DB.Tx(context.Background())
+
+	_, err = tx.BatchMsg.Create().
+		SetNotNilBatchNo(&batchNo).
+		SetStatus(0).
+		//SetNotNilFromwxid(req.Fromwxid).
+		SetNotNilMsg(req.Msg).
+		SetNotNilTag(&tag).
+		SetNotNilPhone(req.Phone).
+		SetNotNilCc(req.Cc).
+		SetNotNilTagids(&tagstring).
+		SetTotal(total).
+		SetNotNilTaskName(req.TaskName).
+		SetNotNilStartTime(&startTime).
+		SetNillableSendTime(sendTime).
+		SetType(3).
+		SetNotNilOrganizationID(&organizationId).
+		SetCtype(2).
+		Save(l.ctx)
+
+	if err != nil {
+		_ = tx.Rollback()
+		l.Logger.Errorf("insert whatsapp batch_msg err=%v\n", err)
+		if err != nil {
+			return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+		}
+	}
+
+	msgs := make([]*ent.MsgCreate, 100)
+	for idx, user := range userList {
+		phone := user.Cc + user.Phone
+		msgRow := tx.Msg.Create().
+			SetNotNilCc(req.Cc).
+			SetNotNilPhone(req.Phone).
+			SetNotNilToid(&phone).
+			SetMsgtype(int32(1)).
+			SetNotNilMsg(req.Msg).
+			SetStatus(1).
+			SetNotNilBatchNo(&batchNo)
+
+		msgs = append(msgs, msgRow)
+		// 100条插入一次
+		if idx == 100 {
+			_, err = tx.Msg.CreateBulk(msgs...).Save(l.ctx)
+			if err != nil {
+				_ = tx.Rollback()
+				l.Logger.Errorf("msg CreateBulk err: %v", err)
+			}
+		}
+		msgs = make([]*ent.MsgCreate, 100)
+	}
+	// 不足100条插入一次
+	if len(msgs) > 0 {
+		_, err = tx.Msg.CreateBulk(msgs...).Save(l.ctx)
+		if err != nil {
+			_ = tx.Rollback()
+			l.Logger.Errorf("msg CreateBulk err: %v", err)
+		}
+	}
+
+	_ = tx.Commit()
+
+	return &types.BaseMsgResp{Msg: errormsg.CreateSuccess}, nil
+}
+
+func (l *CreateWhatcappBatchMsgLogic) getContactList(labels []uint64, cc, phone string) ([]*ent.Contact, error) {
+	// 获取 label_relationship 表中,label_id 等于 labids 的 contact_id
+	labelrelationships, err := l.svcCtx.DB.LabelRelationship.Query().Where(labelrelationship.LabelIDIn(labels...)).All(l.ctx)
+	if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, nil)
+	}
+	contact_ids := make([]uint64, 0, len(labelrelationships))
+	for _, labelrelationship := range labelrelationships {
+		contact_ids = append(contact_ids, labelrelationship.ContactID)
+	}
+	userList := make([]*ent.Contact, 0)
+
+	if len(contact_ids) > 0 {
+		// 获取 contact 表中 wx_wxid 等于 req.Fromwxid 并且 id 等于 contact_ids 并且 type 为1或2 的数据
+		userList, err = l.svcCtx.DB.Contact.Query().Where(
+			contact.Cc(cc),
+			contact.Phone(phone),
+			contact.IDIn(contact_ids...),
+			contact.Ctype(2),
+		).All(l.ctx)
+		if err != nil {
+			return nil, dberrorhandler.DefaultEntError(l.Logger, err, nil)
+		}
+	}
+
+	return userList, nil
+}
+
+func (l *CreateWhatcappBatchMsgLogic) getLabelListByIds(labels, groupLabels []uint64) []string {
+	result := make([]string, 0)
+	labels = append(labels, groupLabels...)
+	if len(labels) > 0 {
+		contacts, err := l.svcCtx.DB.Label.Query().Where(
+			label.IDIn(labels...),
+		).Select("name").All(l.ctx)
+		l.Logger.Infof("contacts=%v", contacts)
+		if err != nil {
+			return result
+		}
+		for _, val := range contacts {
+			result = append(result, val.Name)
+		}
+	}
+
+	return result
+}

+ 5 - 1
internal/logic/batch_msg/get_batch_msg_by_id_logic.go

@@ -34,7 +34,11 @@ func NewGetBatchMsgByIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *G
 func (l *GetBatchMsgByIdLogic) GetBatchMsgById(req *types.IDReq) (*types.BatchMsgInfoResp, error) {
 	organizationId := l.ctx.Value("organizationId").(uint64)
 
-	data, err := l.svcCtx.DB.BatchMsg.Query().Where(batchmsg.ID(req.Id), batchmsg.OrganizationID(organizationId)).First(l.ctx)
+	data, err := l.svcCtx.DB.BatchMsg.Query().Where(
+		batchmsg.ID(req.Id),
+		batchmsg.OrganizationID(organizationId),
+		batchmsg.Ctype(1),
+	).First(l.ctx)
 	if err != nil {
 		if ent.IsNotFound(err) {
 			return nil, errorx.NewInvalidArgumentError("群发消息不存在")

+ 55 - 0
internal/logic/batch_msg/get_whatcapp_batch_msg_history_logic.go

@@ -0,0 +1,55 @@
+package batch_msg
+
+import (
+	"context"
+	"github.com/suyuan32/simple-admin-common/utils/pointy"
+	"wechat-api/ent/batchmsg"
+	"wechat-api/ent/msg"
+	"wechat-api/internal/utils/dberrorhandler"
+
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type GetWhatcappBatchMsgHistoryLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewGetWhatcappBatchMsgHistoryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetWhatcappBatchMsgHistoryLogic {
+	return &GetWhatcappBatchMsgHistoryLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *GetWhatcappBatchMsgHistoryLogic) GetWhatcappBatchMsgHistory(req *types.WhatsappBatchMsgHistoryReq) (*types.WhatsappBatchMsgHistoryResp, error) {
+	resp := types.WhatsappBatchMsgHistoryResp{}
+
+	batch_msg, err := l.svcCtx.DB.BatchMsg.Query().Where(batchmsg.ID(req.Id)).First(l.ctx)
+	if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+	data, err := l.svcCtx.DB.Msg.Query().Where(msg.BatchNo(batch_msg.BatchNo)).Page(l.ctx, req.Page, req.PageSize)
+	if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+	resp.Data.Total = data.PageDetails.Total
+
+	index := (req.Page - 1) * (req.PageSize)
+	for seg, value := range data.List {
+		index += uint64(seg)
+		resp.Data.Data = append(resp.Data.Data, types.HistoryInfo{
+			Index:    &index,
+			BatchNo:  &value.BatchNo,
+			From:     &value.Fromwxid,
+			To:       &value.Toid,
+			SendTime: pointy.GetPointer(value.CreatedAt.UnixMilli()),
+		})
+	}
+
+	return &resp, nil
+}

+ 82 - 0
internal/logic/batch_msg/get_whatcapp_batch_msg_list_logic.go

@@ -0,0 +1,82 @@
+package batch_msg
+
+import (
+	"context"
+	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"github.com/suyuan32/simple-admin-common/utils/pointy"
+	"wechat-api/ent/batchmsg"
+	"wechat-api/ent/predicate"
+	"wechat-api/internal/utils/dberrorhandler"
+
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type GetWhatcappBatchMsgListLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewGetWhatcappBatchMsgListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetWhatcappBatchMsgListLogic {
+	return &GetWhatcappBatchMsgListLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *GetWhatcappBatchMsgListLogic) GetWhatcappBatchMsgList(req *types.WhatsappBatchMsgListReq) (*types.BatchMsgListResp, error) {
+	organizationId := l.ctx.Value("organizationId").(uint64)
+
+	var predicates []predicate.BatchMsg
+	predicates = append(predicates, batchmsg.OrganizationIDEQ(organizationId))
+	predicates = append(predicates, batchmsg.Ctype(2))
+	if req.BatchNo != nil {
+		predicates = append(predicates, batchmsg.BatchNoContains(*req.BatchNo))
+	}
+	if req.Phone != nil {
+		predicates = append(predicates, batchmsg.FromwxidContains(*req.Phone))
+	}
+	if req.Msg != nil {
+		predicates = append(predicates, batchmsg.MsgContains(*req.Msg))
+	}
+	data, err := l.svcCtx.DB.BatchMsg.Query().Where(predicates...).Page(l.ctx, req.Page, req.PageSize)
+
+	if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+	resp := &types.BatchMsgListResp{}
+	resp.Msg = errormsg.Success
+	resp.Data.Total = data.PageDetails.Total
+
+	for _, v := range data.List {
+		resp.Data.Data = append(resp.Data.Data,
+			types.BatchMsgInfo{
+				BaseIDInfo: types.BaseIDInfo{
+					Id:        &v.ID,
+					CreatedAt: pointy.GetPointer(v.CreatedAt.UnixMilli()),
+					UpdatedAt: pointy.GetPointer(v.UpdatedAt.UnixMilli()),
+				},
+				Phone:        &v.Phone,
+				Cc:           &v.Cc,
+				Status:       &v.Status,
+				BatchNo:      &v.BatchNo,
+				TaskName:     &v.TaskName,
+				Fromwxid:     &v.Fromwxid,
+				Msg:          &v.Msg,
+				Tag:          &v.Tag,
+				Total:        &v.Total,
+				Success:      &v.Success,
+				Fail:         &v.Fail,
+				Type:         &v.Type,
+				StartTime:    pointy.GetUnixMilliPointer(v.StartTime.UnixMilli()),
+				StopTime:     pointy.GetUnixMilliPointer(v.StopTime.UnixMilli()),
+				StartTimeStr: pointy.GetPointer(v.StartTime.Format("2006-01-02 15:04:05")),
+			})
+	}
+
+	return resp, nil
+}

+ 88 - 0
internal/logic/batch_msg/get_whatcapp_batch_msg_logic.go

@@ -0,0 +1,88 @@
+package batch_msg
+
+import (
+	"context"
+	"encoding/json"
+	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"github.com/suyuan32/simple-admin-common/utils/pointy"
+	"github.com/zeromicro/go-zero/core/errorx"
+	"wechat-api/ent"
+	"wechat-api/ent/batchmsg"
+	"wechat-api/internal/utils/dberrorhandler"
+
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type GetWhatcappBatchMsgLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewGetWhatcappBatchMsgLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetWhatcappBatchMsgLogic {
+	return &GetWhatcappBatchMsgLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *GetWhatcappBatchMsgLogic) GetWhatcappBatchMsg(req *types.IDReq) (*types.BatchMsgInfoResp, error) {
+	organizationId := l.ctx.Value("organizationId").(uint64)
+
+	data, err := l.svcCtx.DB.BatchMsg.Query().Where(
+		batchmsg.ID(req.Id),
+		batchmsg.OrganizationID(organizationId),
+		batchmsg.Ctype(2),
+	).First(l.ctx)
+	if err != nil {
+		if ent.IsNotFound(err) {
+			return nil, errorx.NewInvalidArgumentError("群发消息不存在")
+		}
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+	labels, groupLables := make([]uint64, 0), make([]uint64, 0)
+	tagMap := make(map[string][]uint64)
+	err = json.Unmarshal([]byte(data.Tagids), &tagMap)
+	if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+	labels = tagMap["contact_tag"]
+	groupLables = tagMap["group_tag"]
+
+	return &types.BatchMsgInfoResp{
+		BaseDataInfo: types.BaseDataInfo{
+			Code: 0,
+			Msg:  errormsg.Success,
+		},
+		Data: types.BatchMsgInfo{
+			BaseIDInfo: types.BaseIDInfo{
+				Id:        &data.ID,
+				CreatedAt: pointy.GetPointer(data.CreatedAt.UnixMilli()),
+				UpdatedAt: pointy.GetPointer(data.UpdatedAt.UnixMilli()),
+			},
+			Phone:        &data.Phone,
+			Cc:           &data.Cc,
+			Status:       &data.Status,
+			BatchNo:      &data.BatchNo,
+			TaskName:     &data.TaskName,
+			Fromwxid:     &data.Fromwxid,
+			Msg:          &data.Msg,
+			Tag:          &data.Tag,
+			Total:        &data.Total,
+			Success:      &data.Success,
+			Fail:         &data.Fail,
+			Type:         &data.Type,
+			Ctype:        &data.Ctype,
+			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")),
+			Labels:       labels,
+			GroupLabels:  groupLables,
+		},
+	}, nil
+}

+ 39 - 0
internal/logic/batch_msg/remove_whatcapp_batch_msg_logic.go

@@ -0,0 +1,39 @@
+package batch_msg
+
+import (
+	"context"
+	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"wechat-api/ent/batchmsg"
+	"wechat-api/internal/utils/dberrorhandler"
+
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type RemoveWhatcappBatchMsgLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewRemoveWhatcappBatchMsgLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RemoveWhatcappBatchMsgLogic {
+	return &RemoveWhatcappBatchMsgLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *RemoveWhatcappBatchMsgLogic) RemoveWhatcappBatchMsg(req *types.IDsReq) (*types.BaseMsgResp, error) {
+	_, err := l.svcCtx.DB.BatchMsg.Delete().Where(
+		batchmsg.IDIn(req.Ids...),
+		batchmsg.CtypeEQ(2),
+	).Exec(l.ctx)
+
+	if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+	return &types.BaseMsgResp{Msg: errormsg.DeleteSuccess}, nil
+}

+ 42 - 0
internal/logic/batch_msg/send_batch_msg_text_logic.go

@@ -0,0 +1,42 @@
+package batch_msg
+
+import (
+	"context"
+	"wechat-api/hook/aliyun"
+
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type SendBatchMsgTextLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewSendBatchMsgTextLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendBatchMsgTextLogic {
+	return &SendBatchMsgTextLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *SendBatchMsgTextLogic) SendBatchMsgText(req *types.SendMsgReq) (*types.BaseMsgResp, error) {
+	resp := types.BaseMsgResp{}
+
+	//TODO 这里是发送文本消息
+	result, err := aliyun.SendChatappMessage(
+		"message", "text", "", "zh_CN",
+		*req.Phone, *req.To, *req.Text)
+
+	if err != nil {
+		resp.Code = 1
+		resp.Msg = err.Error()
+	} else {
+		resp.Msg = *result.Body.Message
+	}
+
+	return &resp, nil
+}

+ 46 - 0
internal/logic/batch_msg/update_whatcapp_batch_msg_logic.go

@@ -0,0 +1,46 @@
+package batch_msg
+
+import (
+	"context"
+	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"github.com/suyuan32/simple-admin-common/utils/pointy"
+	"wechat-api/internal/utils/dberrorhandler"
+
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type UpdateWhatcappBatchMsgLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewUpdateWhatcappBatchMsgLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateWhatcappBatchMsgLogic {
+	return &UpdateWhatcappBatchMsgLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *UpdateWhatcappBatchMsgLogic) UpdateWhatcappBatchMsg(req *types.BatchMsgInfo) (*types.BaseMsgResp, error) {
+	err := l.svcCtx.DB.BatchMsg.UpdateOneID(*req.Id).
+		SetNotNilBatchNo(req.BatchNo).
+		SetNotNilFromwxid(req.Fromwxid).
+		SetNotNilMsg(req.Msg).
+		SetNotNilTag(req.Tag).
+		SetNotNilTotal(req.Total).
+		SetNotNilSuccess(req.Success).
+		SetNotNilFail(req.Fail).
+		SetNotNilStartTime(pointy.GetTimeMilliPointer(req.StartTime)).
+		SetNotNilStopTime(pointy.GetTimeMilliPointer(req.StopTime)).
+		Exec(l.ctx)
+
+	if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+	return &types.BaseMsgResp{Msg: errormsg.UpdateSuccess}, nil
+}

+ 2 - 1
internal/logic/contact/get_contact_list_logic.go

@@ -53,6 +53,7 @@ func (l *GetContactListLogic) GetContactList(req *types.ContactListReq) (*types.
 	organizationId := l.ctx.Value("organizationId").(uint64)
 	var predicates []predicate.Contact
 	predicates = append(predicates, contact.OrganizationIDEQ(organizationId))
+	predicates = append(predicates, contact.Ctype(1))
 	if len(req.LabelIDs) > 0 {
 		predicates = append(predicates, contact.HasContactRelationshipsWith(
 			labelrelationship.HasLabelsWith(
@@ -78,7 +79,7 @@ func (l *GetContactListLogic) GetContactList(req *types.ContactListReq) (*types.
 		predicates = append(predicates, contact.Or(contact.TypeEQ(1), contact.TypeEQ(2)))
 	}
 	data, err := l.svcCtx.DB.Contact.Query().Where(predicates...).WithContactRelationships(func(query *ent.LabelRelationshipQuery) {
-		query.WithLabels()
+		query.WithLabels().Where(labelrelationship.Ctype(1))
 	}).Page(l.ctx, req.Page, req.PageSize)
 
 	if err != nil {

+ 3 - 1
internal/logic/contact/get_whatsapp_contact_list_logic.go

@@ -50,7 +50,7 @@ func (l *GetWhatsappContactListLogic) GetWhatsappContactList(req *types.Whatsapp
 	}
 
 	data, err := l.svcCtx.DB.Contact.Query().Where(predicates...).WithContactRelationships(func(query *ent.LabelRelationshipQuery) {
-		query.WithLabels()
+		query.WithLabels().Where(labelrelationship.Ctype(2))
 	}).Page(l.ctx, req.Page, req.PageSize)
 
 	if err != nil {
@@ -82,6 +82,8 @@ func (l *GetWhatsappContactListLogic) GetWhatsappContactList(req *types.Whatsapp
 					CreatedAt: pointy.GetPointer(v.CreatedAt.UnixMilli()),
 					UpdatedAt: pointy.GetPointer(v.UpdatedAt.UnixMilli()),
 				},
+				Cc:                 &v.Cc,
+				Phone:              &v.Phone,
 				Status:             &v.Status,
 				Nickname:           &v.Nickname,
 				Markname:           &v.Markname,

+ 33 - 12
internal/logic/contact/get_whatsapp_contact_logic.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"github.com/suyuan32/simple-admin-common/msg/errormsg"
 	"github.com/suyuan32/simple-admin-common/utils/pointy"
+	"wechat-api/ent"
 	"wechat-api/ent/contact"
 	"wechat-api/internal/utils/dberrorhandler"
 
@@ -32,12 +33,29 @@ func (l *GetWhatsappContactLogic) GetWhatsappContact(req *types.IDReq) (resp *ty
 		Where(
 			contact.IDEQ(req.Id),                   // Filter by ID
 			contact.OrganizationID(organizationId), // Additional filter by organizationId
+			contact.Ctype(2),
 		).
+		WithContactRelationships(func(query *ent.LabelRelationshipQuery) {
+			query.WithLabels()
+		}).
 		Only(l.ctx)
 	if err != nil {
 		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
 	}
 
+	labelRelationships := make([]types.ContactLabelList, 0)
+	if data.Edges.ContactRelationships != nil {
+		for _, lr := range data.Edges.ContactRelationships {
+			if lr.Edges.Labels == nil {
+				continue
+			}
+			labelRelationships = append(labelRelationships, types.ContactLabelList{
+				Label: &lr.Edges.Labels.Name,
+				Value: &lr.LabelID,
+			})
+		}
+	}
+
 	return &types.ContactInfoResp{
 		BaseDataInfo: types.BaseDataInfo{
 			Code: 0,
@@ -49,18 +67,21 @@ func (l *GetWhatsappContactLogic) GetWhatsappContact(req *types.IDReq) (resp *ty
 				CreatedAt: pointy.GetPointer(data.CreatedAt.UnixMilli()),
 				UpdatedAt: pointy.GetPointer(data.UpdatedAt.UnixMilli()),
 			},
-			Markname:       &data.Markname,
-			OrganizationId: &data.OrganizationID,
-			Ctype:          &data.Ctype,
-			Cname:          &data.Cname,
-			Csex:           &data.Csex,
-			Cage:           &data.Cage,
-			Carea:          &data.Carea,
-			Cmobile:        &data.Cmobile,
-			Cbirtyday:      &data.Cbirthday,
-			Cbirtharea:     &data.Cbirtharea,
-			CidcardNo:      &data.CidcardNo,
-			Ctitle:         &data.Ctitle,
+			Phone:              &data.Phone,
+			Cc:                 &data.Cc,
+			Markname:           &data.Markname,
+			OrganizationId:     &data.OrganizationID,
+			Ctype:              &data.Ctype,
+			Cname:              &data.Cname,
+			Csex:               &data.Csex,
+			Cage:               &data.Cage,
+			Carea:              &data.Carea,
+			Cmobile:            &data.Cmobile,
+			Cbirtyday:          &data.Cbirthday,
+			Cbirtharea:         &data.Cbirtharea,
+			CidcardNo:          &data.CidcardNo,
+			Ctitle:             &data.Ctitle,
+			LabelRelationships: labelRelationships,
 		},
 	}, nil
 }

+ 52 - 1
internal/types/types.go

@@ -806,6 +806,8 @@ type ContactInfo struct {
 	Cbirtharea    *string `json:"cbirtharea,optional"`
 	CidcardNo     *string `json:"cidcardNo,optional"`
 	Ctitle        *string `json:"ctitle,optional"`
+	Cc            *string `json:"cc,optional"`
+	Phone         *string `json:"phone,optional"`
 }
 
 // The response data of label information | Label信息
@@ -1923,7 +1925,11 @@ type BatchMsgInfo struct {
 	// 标签列表
 	Type *int32 `json:"type,optional"`
 	// 内容类型:1-微信 2-whatsapp
-	Ctype *uint64 `json:"ctype,optional"`
+	Ctype        *uint64 `json:"ctype,optional"`
+	Cc           *string `json:"cc,optional"`
+	Phone        *string `json:"phone,optional"`
+	TemplateCode *string `json:"templateCode,optional"`
+	Lang         *string `json:"lang,optional"`
 }
 
 // The response data of batch msg list | BatchMsg列表数据
@@ -1966,6 +1972,51 @@ type BatchMsgInfoResp struct {
 	Data BatchMsgInfo `json:"data"`
 }
 
+// swagger:model WhatsappBatchMsgListReq
+type WhatsappBatchMsgListReq struct {
+	PageInfo
+	// 批次号
+	BatchNo *string `json:"batchNo,optional"`
+	// 发送方微信ID
+	Phone *string `json:"phone,optional"`
+	// 内容
+	Msg *string `json:"msg,optional"`
+}
+
+// swagger:model WhatsappBatchMsgHistoryReq
+type WhatsappBatchMsgHistoryReq struct {
+	PageInfo
+	IDReq
+}
+
+// swagger:model WhatsappBatchMsgHistoryResp
+type WhatsappBatchMsgHistoryResp struct {
+	BaseDataInfo
+	Data HistoryListInfo `json:"data"`
+}
+
+// swagger:model HistoryListInfo
+type HistoryListInfo struct {
+	BaseListInfo
+	Data []HistoryInfo `json:"data"`
+}
+
+// swagger:model HistoryInfo
+type HistoryInfo struct {
+	Index    *uint64 `json:"index,optional"`
+	From     *string `json:"from,optional"`
+	To       *string `json:"to,optional"`
+	SendTime *int64  `json:"sendTime,optional"`
+	BatchNo  *string `json:"batchNo,optional"`
+}
+
+// swagger:model SendMsgReq
+type SendMsgReq struct {
+	Phone *string `json:"phone"`
+	To    *string `json:"to"`
+	Text  *string `json:"text"`
+}
+
 // The data of msg information | Msg信息
 // swagger:model MsgInfo
 type MsgInfo struct {