소스 검색

fix:edit batch_msg API

jimmyyem 6 달 전
부모
커밋
8a431c23aa

+ 87 - 29
crontask/send_msg.go

@@ -4,17 +4,16 @@ import (
 	"encoding/json"
 	"net/url"
 	"path"
-	"strings"
 	"time"
 	"wechat-api/ent"
 	"wechat-api/ent/batchmsg"
 	"wechat-api/ent/contact"
 	"wechat-api/ent/custom_types"
-	"wechat-api/ent/label"
 	"wechat-api/ent/labelrelationship"
 	"wechat-api/ent/msg"
 	"wechat-api/ent/wx"
 	"wechat-api/hook"
+	"wechat-api/internal/utils/dberrorhandler"
 )
 
 func (l *CronTask) sendMsg() {
@@ -30,42 +29,69 @@ func (l *CronTask) sendMsg() {
 		l.Logger.Info("batch start: ", batch.BatchNo)
 		// 如果 批次 status 为 0,则先产生待发送消息
 		if batch.Status == 0 {
-			userlist := make([]*ent.Contact, 0)
-			if batch.Tag == "all" {
+			userList := make([]*ent.Contact, 0)
+			groupList := make([]*ent.Contact, 0)
+			tagMap := make(map[string][]uint64)
+			err = json.Unmarshal([]byte(batch.Tagids), &tagMap)
+			if err != nil {
+				continue
+			}
+
+			l.Logger.Infof("send_msg.go batch.Tagids = %v\n", tagMap)
+
+			var allContact, allGroup, ok bool
+			var contactTags, groupTags []uint64
+			if contactTags, ok = tagMap["contact_tag"]; ok {
+				allContact = hasAll(contactTags, 0)
+				l.Logger.Infof("contactTags=%v", contactTags)
+			}
+			if groupTags, ok = tagMap["group_tag"]; ok {
+				allGroup = hasAll(groupTags, 0)
+				l.Logger.Infof("groupTags=%v", groupTags)
+			}
+
+			var err error
+			if allContact && allGroup {
 				// 获取 contact 表中  wx_wxid 等于 req.Fromwxid 的 type 为1或2的数据
-				userlist, err = l.svcCtx.DB.Contact.Query().Where(contact.WxWxid(batch.Fromwxid), contact.TypeIn(1, 2)).All(l.ctx)
+				userList, err = l.svcCtx.DB.Contact.Query().Where(contact.WxWxid(batch.Fromwxid), contact.TypeIn(1, 2)).All(l.ctx)
 				if err != nil {
 					l.Logger.Errorf("userlist err: %v", err)
 					continue
 				}
 			} else {
-
-				tags := strings.Split(batch.Tag, ",")
-
-				// 获取 label 表中 name 为 tags的记录
-				labids, err := l.svcCtx.DB.Label.Query().Where(label.NameIn(tags...)).IDs(l.ctx)
-				if err != nil {
-					l.Logger.Errorf("labids err: %v", err)
-					continue
-				}
-				// 获取 label_relationship 表中,label_id 等于 labids 的 contact_id
-				labelrelationships, err := l.svcCtx.DB.LabelRelationship.Query().Where(labelrelationship.LabelIDIn(labids...)).All(l.ctx)
-				if err != nil {
-					l.Logger.Errorf("labelrelationships err: %v", err)
-					continue
-				}
-				contact_ids := make([]uint64, 0)
-				for _, labelrelationship := range labelrelationships {
-					contact_ids = append(contact_ids, labelrelationship.ContactID)
+				if allContact { // 所有联系人
+					// 获取 contact 表中  wx_wxid 等于 req.Fromwxid 的 type 为1的数据
+					userList, err = l.svcCtx.DB.Contact.Query().Where(contact.WxWxid(batch.Fromwxid), contact.TypeEQ(1)).All(l.ctx)
+					if err != nil {
+						l.Logger.Errorf("userList err: %v", err)
+						continue
+					}
+				} else { //获取指定标签的联系人
+					userList, err = getContactList(l, contactTags, batch.Fromwxid, 1)
+					if err != nil {
+						l.Logger.Errorf("userList err: %v", err)
+						continue
+					}
 				}
-				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.WxWxid(batch.Fromwxid), contact.IDIn(contact_ids...), contact.TypeIn(1, 2)).All(l.ctx)
+
+				if allGroup { //所有群
+					// 获取 contact 表中  wx_wxid 等于 req.Fromwxid 的 type 为2的数据
+					groupList, err = l.svcCtx.DB.Contact.Query().Where(contact.WxWxid(batch.Fromwxid), contact.TypeEQ(2)).All(l.ctx)
+					if err != nil {
+						l.Logger.Errorf("groupList err: %v", err)
+						continue
+					}
+				} else { //获取指定标签的群
+					groupList, err = getContactList(l, groupTags, batch.Fromwxid, 2)
 					if err != nil {
-						l.Logger.Errorf("userlist err: %v", err)
+						l.Logger.Errorf("groupList err: %v", err)
 						continue
 					}
 				}
+
+				if len(groupList) > 0 {
+					userList = append(userList, groupList...)
+				}
 			}
 
 			// 这里是待插入到 msg 表的数据
@@ -73,14 +99,14 @@ func (l *CronTask) sendMsg() {
 
 			// 这里是把 batch.Msg 转换为 json 数组
 			msgArray := make([]custom_types.Action, 0)
-			err := json.Unmarshal([]byte(batch.Msg), &msgArray)
+			err = json.Unmarshal([]byte(batch.Msg), &msgArray)
 			l.Logger.Infof("msgArray length= %v, err:%v", len(msgArray), err)
 			if err != nil {
 				// json 解析失败
 				msgArray = make([]custom_types.Action, 0)
 			}
 
-			for _, user := range userlist {
+			for _, user := range userList {
 				// 这里改动主要是 batch_msg 目前支持批量添加图文,导致 batch_msg 的 msg 字段为 json
 				// msg 里包括文字和图片,msgtype=1 为文字, msgtype=2 为图片
 				// 每一条文字或者图片 都是一条单独的消息
@@ -221,3 +247,35 @@ func getFileName(photoUrl string) string {
 	}
 	return path.Base(u.Path)
 }
+
+func hasAll(array []uint64, target uint64) bool {
+	for _, val := range array {
+		if val == 0 {
+			return true
+		}
+	}
+	return false
+}
+
+func getContactList(l *CronTask, 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 {
+		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.WxWxid(fromWxId), contact.IDIn(contact_ids...), contact.TypeEQ(stype)).All(l.ctx)
+		if err != nil {
+			return nil, dberrorhandler.DefaultEntError(l.Logger, err, nil)
+		}
+	}
+
+	return userList, nil
+}

+ 5 - 1
desc/wechat/batch_msg.api

@@ -40,8 +40,12 @@ type (
         // 结束时间 
         StopTime  *int64 `json:"stopTime,optional"`
 
+	    // 发送时间
+		SendTime *int64 `json:"sendTime,optional"`
+
         // 标签列表
-        Labels  []string `json:"labels,optional"`
+        Labels  []uint64 `json:"labels,optional"`
+		GroupLabels  []uint64 `json:"groupLabels,optional"`
 
 		// 标签列表
 		Type  *int32 `json:"type,optional"`

+ 24 - 2
ent/batchmsg.go

@@ -35,6 +35,8 @@ type BatchMsg struct {
 	Msg string `json:"msg,omitempty"`
 	// 发送规则 all 全部 tag1,tag2 按tag发送
 	Tag string `json:"tag,omitempty"`
+	// 要发送的tagids
+	Tagids string `json:"tagids,omitempty"`
 	// 总数
 	Total int32 `json:"total,omitempty"`
 	// 成功数量
@@ -45,6 +47,8 @@ type BatchMsg struct {
 	StartTime time.Time `json:"start_time,omitempty"`
 	// 结束时间
 	StopTime time.Time `json:"stop_time,omitempty"`
+	// 发送时间
+	SendTime time.Time `json:"send_time,omitempty"`
 	// 发送类型 1-群发消息 2-群发朋友圈
 	Type int32 `json:"type,omitempty"`
 	// organization_id | 租户ID
@@ -59,9 +63,9 @@ 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:
 			values[i] = new(sql.NullInt64)
-		case batchmsg.FieldBatchNo, batchmsg.FieldTaskName, batchmsg.FieldFromwxid, batchmsg.FieldMsg, batchmsg.FieldTag:
+		case batchmsg.FieldBatchNo, batchmsg.FieldTaskName, batchmsg.FieldFromwxid, batchmsg.FieldMsg, batchmsg.FieldTag, batchmsg.FieldTagids:
 			values[i] = new(sql.NullString)
-		case batchmsg.FieldCreatedAt, batchmsg.FieldUpdatedAt, batchmsg.FieldDeletedAt, batchmsg.FieldStartTime, batchmsg.FieldStopTime:
+		case batchmsg.FieldCreatedAt, batchmsg.FieldUpdatedAt, batchmsg.FieldDeletedAt, batchmsg.FieldStartTime, batchmsg.FieldStopTime, batchmsg.FieldSendTime:
 			values[i] = new(sql.NullTime)
 		default:
 			values[i] = new(sql.UnknownType)
@@ -138,6 +142,12 @@ func (bm *BatchMsg) assignValues(columns []string, values []any) error {
 			} else if value.Valid {
 				bm.Tag = value.String
 			}
+		case batchmsg.FieldTagids:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field tagids", values[i])
+			} else if value.Valid {
+				bm.Tagids = value.String
+			}
 		case batchmsg.FieldTotal:
 			if value, ok := values[i].(*sql.NullInt64); !ok {
 				return fmt.Errorf("unexpected type %T for field total", values[i])
@@ -168,6 +178,12 @@ func (bm *BatchMsg) assignValues(columns []string, values []any) error {
 			} else if value.Valid {
 				bm.StopTime = value.Time
 			}
+		case batchmsg.FieldSendTime:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field send_time", values[i])
+			} else if value.Valid {
+				bm.SendTime = value.Time
+			}
 		case batchmsg.FieldType:
 			if value, ok := values[i].(*sql.NullInt64); !ok {
 				return fmt.Errorf("unexpected type %T for field type", values[i])
@@ -243,6 +259,9 @@ func (bm *BatchMsg) String() string {
 	builder.WriteString("tag=")
 	builder.WriteString(bm.Tag)
 	builder.WriteString(", ")
+	builder.WriteString("tagids=")
+	builder.WriteString(bm.Tagids)
+	builder.WriteString(", ")
 	builder.WriteString("total=")
 	builder.WriteString(fmt.Sprintf("%v", bm.Total))
 	builder.WriteString(", ")
@@ -258,6 +277,9 @@ func (bm *BatchMsg) String() string {
 	builder.WriteString("stop_time=")
 	builder.WriteString(bm.StopTime.Format(time.ANSIC))
 	builder.WriteString(", ")
+	builder.WriteString("send_time=")
+	builder.WriteString(bm.SendTime.Format(time.ANSIC))
+	builder.WriteString(", ")
 	builder.WriteString("type=")
 	builder.WriteString(fmt.Sprintf("%v", bm.Type))
 	builder.WriteString(", ")

+ 16 - 0
ent/batchmsg/batchmsg.go

@@ -32,6 +32,8 @@ const (
 	FieldMsg = "msg"
 	// FieldTag holds the string denoting the tag field in the database.
 	FieldTag = "tag"
+	// FieldTagids holds the string denoting the tagids field in the database.
+	FieldTagids = "tagids"
 	// FieldTotal holds the string denoting the total field in the database.
 	FieldTotal = "total"
 	// FieldSuccess holds the string denoting the success field in the database.
@@ -42,6 +44,8 @@ const (
 	FieldStartTime = "start_time"
 	// FieldStopTime holds the string denoting the stop_time field in the database.
 	FieldStopTime = "stop_time"
+	// FieldSendTime holds the string denoting the send_time field in the database.
+	FieldSendTime = "send_time"
 	// FieldType holds the string denoting the type field in the database.
 	FieldType = "type"
 	// FieldOrganizationID holds the string denoting the organization_id field in the database.
@@ -62,11 +66,13 @@ var Columns = []string{
 	FieldFromwxid,
 	FieldMsg,
 	FieldTag,
+	FieldTagids,
 	FieldTotal,
 	FieldSuccess,
 	FieldFail,
 	FieldStartTime,
 	FieldStopTime,
+	FieldSendTime,
 	FieldType,
 	FieldOrganizationID,
 }
@@ -154,6 +160,11 @@ func ByTag(opts ...sql.OrderTermOption) OrderOption {
 	return sql.OrderByField(FieldTag, opts...).ToFunc()
 }
 
+// ByTagids orders the results by the tagids field.
+func ByTagids(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldTagids, opts...).ToFunc()
+}
+
 // ByTotal orders the results by the total field.
 func ByTotal(opts ...sql.OrderTermOption) OrderOption {
 	return sql.OrderByField(FieldTotal, opts...).ToFunc()
@@ -179,6 +190,11 @@ func ByStopTime(opts ...sql.OrderTermOption) OrderOption {
 	return sql.OrderByField(FieldStopTime, opts...).ToFunc()
 }
 
+// BySendTime orders the results by the send_time field.
+func BySendTime(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldSendTime, opts...).ToFunc()
+}
+
 // ByType orders the results by the type field.
 func ByType(opts ...sql.OrderTermOption) OrderOption {
 	return sql.OrderByField(FieldType, opts...).ToFunc()

+ 135 - 0
ent/batchmsg/where.go

@@ -99,6 +99,11 @@ func Tag(v string) predicate.BatchMsg {
 	return predicate.BatchMsg(sql.FieldEQ(FieldTag, v))
 }
 
+// Tagids applies equality check predicate on the "tagids" field. It's identical to TagidsEQ.
+func Tagids(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldEQ(FieldTagids, v))
+}
+
 // Total applies equality check predicate on the "total" field. It's identical to TotalEQ.
 func Total(v int32) predicate.BatchMsg {
 	return predicate.BatchMsg(sql.FieldEQ(FieldTotal, v))
@@ -124,6 +129,11 @@ func StopTime(v time.Time) predicate.BatchMsg {
 	return predicate.BatchMsg(sql.FieldEQ(FieldStopTime, v))
 }
 
+// SendTime applies equality check predicate on the "send_time" field. It's identical to SendTimeEQ.
+func SendTime(v time.Time) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldEQ(FieldSendTime, v))
+}
+
 // Type applies equality check predicate on the "type" field. It's identical to TypeEQ.
 func Type(v int32) predicate.BatchMsg {
 	return predicate.BatchMsg(sql.FieldEQ(FieldType, v))
@@ -689,6 +699,81 @@ func TagContainsFold(v string) predicate.BatchMsg {
 	return predicate.BatchMsg(sql.FieldContainsFold(FieldTag, v))
 }
 
+// TagidsEQ applies the EQ predicate on the "tagids" field.
+func TagidsEQ(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldEQ(FieldTagids, v))
+}
+
+// TagidsNEQ applies the NEQ predicate on the "tagids" field.
+func TagidsNEQ(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldNEQ(FieldTagids, v))
+}
+
+// TagidsIn applies the In predicate on the "tagids" field.
+func TagidsIn(vs ...string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldIn(FieldTagids, vs...))
+}
+
+// TagidsNotIn applies the NotIn predicate on the "tagids" field.
+func TagidsNotIn(vs ...string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldNotIn(FieldTagids, vs...))
+}
+
+// TagidsGT applies the GT predicate on the "tagids" field.
+func TagidsGT(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldGT(FieldTagids, v))
+}
+
+// TagidsGTE applies the GTE predicate on the "tagids" field.
+func TagidsGTE(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldGTE(FieldTagids, v))
+}
+
+// TagidsLT applies the LT predicate on the "tagids" field.
+func TagidsLT(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldLT(FieldTagids, v))
+}
+
+// TagidsLTE applies the LTE predicate on the "tagids" field.
+func TagidsLTE(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldLTE(FieldTagids, v))
+}
+
+// TagidsContains applies the Contains predicate on the "tagids" field.
+func TagidsContains(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldContains(FieldTagids, v))
+}
+
+// TagidsHasPrefix applies the HasPrefix predicate on the "tagids" field.
+func TagidsHasPrefix(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldHasPrefix(FieldTagids, v))
+}
+
+// TagidsHasSuffix applies the HasSuffix predicate on the "tagids" field.
+func TagidsHasSuffix(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldHasSuffix(FieldTagids, v))
+}
+
+// TagidsIsNil applies the IsNil predicate on the "tagids" field.
+func TagidsIsNil() predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldIsNull(FieldTagids))
+}
+
+// TagidsNotNil applies the NotNil predicate on the "tagids" field.
+func TagidsNotNil() predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldNotNull(FieldTagids))
+}
+
+// TagidsEqualFold applies the EqualFold predicate on the "tagids" field.
+func TagidsEqualFold(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldEqualFold(FieldTagids, v))
+}
+
+// TagidsContainsFold applies the ContainsFold predicate on the "tagids" field.
+func TagidsContainsFold(v string) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldContainsFold(FieldTagids, v))
+}
+
 // TotalEQ applies the EQ predicate on the "total" field.
 func TotalEQ(v int32) predicate.BatchMsg {
 	return predicate.BatchMsg(sql.FieldEQ(FieldTotal, v))
@@ -939,6 +1024,56 @@ func StopTimeNotNil() predicate.BatchMsg {
 	return predicate.BatchMsg(sql.FieldNotNull(FieldStopTime))
 }
 
+// SendTimeEQ applies the EQ predicate on the "send_time" field.
+func SendTimeEQ(v time.Time) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldEQ(FieldSendTime, v))
+}
+
+// SendTimeNEQ applies the NEQ predicate on the "send_time" field.
+func SendTimeNEQ(v time.Time) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldNEQ(FieldSendTime, v))
+}
+
+// SendTimeIn applies the In predicate on the "send_time" field.
+func SendTimeIn(vs ...time.Time) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldIn(FieldSendTime, vs...))
+}
+
+// SendTimeNotIn applies the NotIn predicate on the "send_time" field.
+func SendTimeNotIn(vs ...time.Time) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldNotIn(FieldSendTime, vs...))
+}
+
+// SendTimeGT applies the GT predicate on the "send_time" field.
+func SendTimeGT(v time.Time) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldGT(FieldSendTime, v))
+}
+
+// SendTimeGTE applies the GTE predicate on the "send_time" field.
+func SendTimeGTE(v time.Time) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldGTE(FieldSendTime, v))
+}
+
+// SendTimeLT applies the LT predicate on the "send_time" field.
+func SendTimeLT(v time.Time) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldLT(FieldSendTime, v))
+}
+
+// SendTimeLTE applies the LTE predicate on the "send_time" field.
+func SendTimeLTE(v time.Time) predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldLTE(FieldSendTime, v))
+}
+
+// SendTimeIsNil applies the IsNil predicate on the "send_time" field.
+func SendTimeIsNil() predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldIsNull(FieldSendTime))
+}
+
+// SendTimeNotNil applies the NotNil predicate on the "send_time" field.
+func SendTimeNotNil() predicate.BatchMsg {
+	return predicate.BatchMsg(sql.FieldNotNull(FieldSendTime))
+}
+
 // TypeEQ applies the EQ predicate on the "type" field.
 func TypeEQ(v int32) predicate.BatchMsg {
 	return predicate.BatchMsg(sql.FieldEQ(FieldType, v))

+ 156 - 0
ent/batchmsg_create.go

@@ -148,6 +148,20 @@ func (bmc *BatchMsgCreate) SetNillableTag(s *string) *BatchMsgCreate {
 	return bmc
 }
 
+// SetTagids sets the "tagids" field.
+func (bmc *BatchMsgCreate) SetTagids(s string) *BatchMsgCreate {
+	bmc.mutation.SetTagids(s)
+	return bmc
+}
+
+// SetNillableTagids sets the "tagids" field if the given value is not nil.
+func (bmc *BatchMsgCreate) SetNillableTagids(s *string) *BatchMsgCreate {
+	if s != nil {
+		bmc.SetTagids(*s)
+	}
+	return bmc
+}
+
 // SetTotal sets the "total" field.
 func (bmc *BatchMsgCreate) SetTotal(i int32) *BatchMsgCreate {
 	bmc.mutation.SetTotal(i)
@@ -218,6 +232,20 @@ func (bmc *BatchMsgCreate) SetNillableStopTime(t *time.Time) *BatchMsgCreate {
 	return bmc
 }
 
+// SetSendTime sets the "send_time" field.
+func (bmc *BatchMsgCreate) SetSendTime(t time.Time) *BatchMsgCreate {
+	bmc.mutation.SetSendTime(t)
+	return bmc
+}
+
+// SetNillableSendTime sets the "send_time" field if the given value is not nil.
+func (bmc *BatchMsgCreate) SetNillableSendTime(t *time.Time) *BatchMsgCreate {
+	if t != nil {
+		bmc.SetSendTime(*t)
+	}
+	return bmc
+}
+
 // SetType sets the "type" field.
 func (bmc *BatchMsgCreate) SetType(i int32) *BatchMsgCreate {
 	bmc.mutation.SetType(i)
@@ -387,6 +415,10 @@ func (bmc *BatchMsgCreate) createSpec() (*BatchMsg, *sqlgraph.CreateSpec) {
 		_spec.SetField(batchmsg.FieldTag, field.TypeString, value)
 		_node.Tag = value
 	}
+	if value, ok := bmc.mutation.Tagids(); ok {
+		_spec.SetField(batchmsg.FieldTagids, field.TypeString, value)
+		_node.Tagids = value
+	}
 	if value, ok := bmc.mutation.Total(); ok {
 		_spec.SetField(batchmsg.FieldTotal, field.TypeInt32, value)
 		_node.Total = value
@@ -407,6 +439,10 @@ func (bmc *BatchMsgCreate) createSpec() (*BatchMsg, *sqlgraph.CreateSpec) {
 		_spec.SetField(batchmsg.FieldStopTime, field.TypeTime, value)
 		_node.StopTime = value
 	}
+	if value, ok := bmc.mutation.SendTime(); ok {
+		_spec.SetField(batchmsg.FieldSendTime, field.TypeTime, value)
+		_node.SendTime = value
+	}
 	if value, ok := bmc.mutation.GetType(); ok {
 		_spec.SetField(batchmsg.FieldType, field.TypeInt32, value)
 		_node.Type = value
@@ -611,6 +647,24 @@ func (u *BatchMsgUpsert) ClearTag() *BatchMsgUpsert {
 	return u
 }
 
+// SetTagids sets the "tagids" field.
+func (u *BatchMsgUpsert) SetTagids(v string) *BatchMsgUpsert {
+	u.Set(batchmsg.FieldTagids, v)
+	return u
+}
+
+// UpdateTagids sets the "tagids" field to the value that was provided on create.
+func (u *BatchMsgUpsert) UpdateTagids() *BatchMsgUpsert {
+	u.SetExcluded(batchmsg.FieldTagids)
+	return u
+}
+
+// ClearTagids clears the value of the "tagids" field.
+func (u *BatchMsgUpsert) ClearTagids() *BatchMsgUpsert {
+	u.SetNull(batchmsg.FieldTagids)
+	return u
+}
+
 // SetTotal sets the "total" field.
 func (u *BatchMsgUpsert) SetTotal(v int32) *BatchMsgUpsert {
 	u.Set(batchmsg.FieldTotal, v)
@@ -719,6 +773,24 @@ func (u *BatchMsgUpsert) ClearStopTime() *BatchMsgUpsert {
 	return u
 }
 
+// SetSendTime sets the "send_time" field.
+func (u *BatchMsgUpsert) SetSendTime(v time.Time) *BatchMsgUpsert {
+	u.Set(batchmsg.FieldSendTime, v)
+	return u
+}
+
+// UpdateSendTime sets the "send_time" field to the value that was provided on create.
+func (u *BatchMsgUpsert) UpdateSendTime() *BatchMsgUpsert {
+	u.SetExcluded(batchmsg.FieldSendTime)
+	return u
+}
+
+// ClearSendTime clears the value of the "send_time" field.
+func (u *BatchMsgUpsert) ClearSendTime() *BatchMsgUpsert {
+	u.SetNull(batchmsg.FieldSendTime)
+	return u
+}
+
 // SetType sets the "type" field.
 func (u *BatchMsgUpsert) SetType(v int32) *BatchMsgUpsert {
 	u.Set(batchmsg.FieldType, v)
@@ -980,6 +1052,27 @@ func (u *BatchMsgUpsertOne) ClearTag() *BatchMsgUpsertOne {
 	})
 }
 
+// SetTagids sets the "tagids" field.
+func (u *BatchMsgUpsertOne) SetTagids(v string) *BatchMsgUpsertOne {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.SetTagids(v)
+	})
+}
+
+// UpdateTagids sets the "tagids" field to the value that was provided on create.
+func (u *BatchMsgUpsertOne) UpdateTagids() *BatchMsgUpsertOne {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.UpdateTagids()
+	})
+}
+
+// ClearTagids clears the value of the "tagids" field.
+func (u *BatchMsgUpsertOne) ClearTagids() *BatchMsgUpsertOne {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.ClearTagids()
+	})
+}
+
 // SetTotal sets the "total" field.
 func (u *BatchMsgUpsertOne) SetTotal(v int32) *BatchMsgUpsertOne {
 	return u.Update(func(s *BatchMsgUpsert) {
@@ -1106,6 +1199,27 @@ func (u *BatchMsgUpsertOne) ClearStopTime() *BatchMsgUpsertOne {
 	})
 }
 
+// SetSendTime sets the "send_time" field.
+func (u *BatchMsgUpsertOne) SetSendTime(v time.Time) *BatchMsgUpsertOne {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.SetSendTime(v)
+	})
+}
+
+// UpdateSendTime sets the "send_time" field to the value that was provided on create.
+func (u *BatchMsgUpsertOne) UpdateSendTime() *BatchMsgUpsertOne {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.UpdateSendTime()
+	})
+}
+
+// ClearSendTime clears the value of the "send_time" field.
+func (u *BatchMsgUpsertOne) ClearSendTime() *BatchMsgUpsertOne {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.ClearSendTime()
+	})
+}
+
 // SetType sets the "type" field.
 func (u *BatchMsgUpsertOne) SetType(v int32) *BatchMsgUpsertOne {
 	return u.Update(func(s *BatchMsgUpsert) {
@@ -1540,6 +1654,27 @@ func (u *BatchMsgUpsertBulk) ClearTag() *BatchMsgUpsertBulk {
 	})
 }
 
+// SetTagids sets the "tagids" field.
+func (u *BatchMsgUpsertBulk) SetTagids(v string) *BatchMsgUpsertBulk {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.SetTagids(v)
+	})
+}
+
+// UpdateTagids sets the "tagids" field to the value that was provided on create.
+func (u *BatchMsgUpsertBulk) UpdateTagids() *BatchMsgUpsertBulk {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.UpdateTagids()
+	})
+}
+
+// ClearTagids clears the value of the "tagids" field.
+func (u *BatchMsgUpsertBulk) ClearTagids() *BatchMsgUpsertBulk {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.ClearTagids()
+	})
+}
+
 // SetTotal sets the "total" field.
 func (u *BatchMsgUpsertBulk) SetTotal(v int32) *BatchMsgUpsertBulk {
 	return u.Update(func(s *BatchMsgUpsert) {
@@ -1666,6 +1801,27 @@ func (u *BatchMsgUpsertBulk) ClearStopTime() *BatchMsgUpsertBulk {
 	})
 }
 
+// SetSendTime sets the "send_time" field.
+func (u *BatchMsgUpsertBulk) SetSendTime(v time.Time) *BatchMsgUpsertBulk {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.SetSendTime(v)
+	})
+}
+
+// UpdateSendTime sets the "send_time" field to the value that was provided on create.
+func (u *BatchMsgUpsertBulk) UpdateSendTime() *BatchMsgUpsertBulk {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.UpdateSendTime()
+	})
+}
+
+// ClearSendTime clears the value of the "send_time" field.
+func (u *BatchMsgUpsertBulk) ClearSendTime() *BatchMsgUpsertBulk {
+	return u.Update(func(s *BatchMsgUpsert) {
+		s.ClearSendTime()
+	})
+}
+
 // SetType sets the "type" field.
 func (u *BatchMsgUpsertBulk) SetType(v int32) *BatchMsgUpsertBulk {
 	return u.Update(func(s *BatchMsgUpsert) {

+ 104 - 0
ent/batchmsg_update.go

@@ -181,6 +181,26 @@ func (bmu *BatchMsgUpdate) ClearTag() *BatchMsgUpdate {
 	return bmu
 }
 
+// SetTagids sets the "tagids" field.
+func (bmu *BatchMsgUpdate) SetTagids(s string) *BatchMsgUpdate {
+	bmu.mutation.SetTagids(s)
+	return bmu
+}
+
+// SetNillableTagids sets the "tagids" field if the given value is not nil.
+func (bmu *BatchMsgUpdate) SetNillableTagids(s *string) *BatchMsgUpdate {
+	if s != nil {
+		bmu.SetTagids(*s)
+	}
+	return bmu
+}
+
+// ClearTagids clears the value of the "tagids" field.
+func (bmu *BatchMsgUpdate) ClearTagids() *BatchMsgUpdate {
+	bmu.mutation.ClearTagids()
+	return bmu
+}
+
 // SetTotal sets the "total" field.
 func (bmu *BatchMsgUpdate) SetTotal(i int32) *BatchMsgUpdate {
 	bmu.mutation.ResetTotal()
@@ -302,6 +322,26 @@ func (bmu *BatchMsgUpdate) ClearStopTime() *BatchMsgUpdate {
 	return bmu
 }
 
+// SetSendTime sets the "send_time" field.
+func (bmu *BatchMsgUpdate) SetSendTime(t time.Time) *BatchMsgUpdate {
+	bmu.mutation.SetSendTime(t)
+	return bmu
+}
+
+// SetNillableSendTime sets the "send_time" field if the given value is not nil.
+func (bmu *BatchMsgUpdate) SetNillableSendTime(t *time.Time) *BatchMsgUpdate {
+	if t != nil {
+		bmu.SetSendTime(*t)
+	}
+	return bmu
+}
+
+// ClearSendTime clears the value of the "send_time" field.
+func (bmu *BatchMsgUpdate) ClearSendTime() *BatchMsgUpdate {
+	bmu.mutation.ClearSendTime()
+	return bmu
+}
+
 // SetType sets the "type" field.
 func (bmu *BatchMsgUpdate) SetType(i int32) *BatchMsgUpdate {
 	bmu.mutation.ResetType()
@@ -467,6 +507,12 @@ func (bmu *BatchMsgUpdate) sqlSave(ctx context.Context) (n int, err error) {
 	if bmu.mutation.TagCleared() {
 		_spec.ClearField(batchmsg.FieldTag, field.TypeString)
 	}
+	if value, ok := bmu.mutation.Tagids(); ok {
+		_spec.SetField(batchmsg.FieldTagids, field.TypeString, value)
+	}
+	if bmu.mutation.TagidsCleared() {
+		_spec.ClearField(batchmsg.FieldTagids, field.TypeString)
+	}
 	if value, ok := bmu.mutation.Total(); ok {
 		_spec.SetField(batchmsg.FieldTotal, field.TypeInt32, value)
 	}
@@ -506,6 +552,12 @@ func (bmu *BatchMsgUpdate) sqlSave(ctx context.Context) (n int, err error) {
 	if bmu.mutation.StopTimeCleared() {
 		_spec.ClearField(batchmsg.FieldStopTime, field.TypeTime)
 	}
+	if value, ok := bmu.mutation.SendTime(); ok {
+		_spec.SetField(batchmsg.FieldSendTime, field.TypeTime, value)
+	}
+	if bmu.mutation.SendTimeCleared() {
+		_spec.ClearField(batchmsg.FieldSendTime, field.TypeTime)
+	}
 	if value, ok := bmu.mutation.GetType(); ok {
 		_spec.SetField(batchmsg.FieldType, field.TypeInt32, value)
 	}
@@ -694,6 +746,26 @@ func (bmuo *BatchMsgUpdateOne) ClearTag() *BatchMsgUpdateOne {
 	return bmuo
 }
 
+// SetTagids sets the "tagids" field.
+func (bmuo *BatchMsgUpdateOne) SetTagids(s string) *BatchMsgUpdateOne {
+	bmuo.mutation.SetTagids(s)
+	return bmuo
+}
+
+// SetNillableTagids sets the "tagids" field if the given value is not nil.
+func (bmuo *BatchMsgUpdateOne) SetNillableTagids(s *string) *BatchMsgUpdateOne {
+	if s != nil {
+		bmuo.SetTagids(*s)
+	}
+	return bmuo
+}
+
+// ClearTagids clears the value of the "tagids" field.
+func (bmuo *BatchMsgUpdateOne) ClearTagids() *BatchMsgUpdateOne {
+	bmuo.mutation.ClearTagids()
+	return bmuo
+}
+
 // SetTotal sets the "total" field.
 func (bmuo *BatchMsgUpdateOne) SetTotal(i int32) *BatchMsgUpdateOne {
 	bmuo.mutation.ResetTotal()
@@ -815,6 +887,26 @@ func (bmuo *BatchMsgUpdateOne) ClearStopTime() *BatchMsgUpdateOne {
 	return bmuo
 }
 
+// SetSendTime sets the "send_time" field.
+func (bmuo *BatchMsgUpdateOne) SetSendTime(t time.Time) *BatchMsgUpdateOne {
+	bmuo.mutation.SetSendTime(t)
+	return bmuo
+}
+
+// SetNillableSendTime sets the "send_time" field if the given value is not nil.
+func (bmuo *BatchMsgUpdateOne) SetNillableSendTime(t *time.Time) *BatchMsgUpdateOne {
+	if t != nil {
+		bmuo.SetSendTime(*t)
+	}
+	return bmuo
+}
+
+// ClearSendTime clears the value of the "send_time" field.
+func (bmuo *BatchMsgUpdateOne) ClearSendTime() *BatchMsgUpdateOne {
+	bmuo.mutation.ClearSendTime()
+	return bmuo
+}
+
 // SetType sets the "type" field.
 func (bmuo *BatchMsgUpdateOne) SetType(i int32) *BatchMsgUpdateOne {
 	bmuo.mutation.ResetType()
@@ -1010,6 +1102,12 @@ func (bmuo *BatchMsgUpdateOne) sqlSave(ctx context.Context) (_node *BatchMsg, er
 	if bmuo.mutation.TagCleared() {
 		_spec.ClearField(batchmsg.FieldTag, field.TypeString)
 	}
+	if value, ok := bmuo.mutation.Tagids(); ok {
+		_spec.SetField(batchmsg.FieldTagids, field.TypeString, value)
+	}
+	if bmuo.mutation.TagidsCleared() {
+		_spec.ClearField(batchmsg.FieldTagids, field.TypeString)
+	}
 	if value, ok := bmuo.mutation.Total(); ok {
 		_spec.SetField(batchmsg.FieldTotal, field.TypeInt32, value)
 	}
@@ -1049,6 +1147,12 @@ func (bmuo *BatchMsgUpdateOne) sqlSave(ctx context.Context) (_node *BatchMsg, er
 	if bmuo.mutation.StopTimeCleared() {
 		_spec.ClearField(batchmsg.FieldStopTime, field.TypeTime)
 	}
+	if value, ok := bmuo.mutation.SendTime(); ok {
+		_spec.SetField(batchmsg.FieldSendTime, field.TypeTime, value)
+	}
+	if bmuo.mutation.SendTimeCleared() {
+		_spec.ClearField(batchmsg.FieldSendTime, field.TypeTime)
+	}
 	if value, ok := bmuo.mutation.GetType(); ok {
 		_spec.SetField(batchmsg.FieldType, field.TypeInt32, value)
 	}

+ 3 - 1
ent/migrate/schema.go

@@ -49,11 +49,13 @@ var (
 		{Name: "fromwxid", Type: field.TypeString, Nullable: true, Comment: "发送方微信ID"},
 		{Name: "msg", Type: field.TypeString, Nullable: true, Comment: "内容"},
 		{Name: "tag", Type: field.TypeString, Nullable: true, Comment: "发送规则 all 全部 tag1,tag2 按tag发送"},
+		{Name: "tagids", Type: field.TypeString, Nullable: true, Comment: "要发送的tagids"},
 		{Name: "total", Type: field.TypeInt32, Nullable: true, Comment: "总数"},
 		{Name: "success", Type: field.TypeInt32, Nullable: true, Comment: "成功数量"},
 		{Name: "fail", Type: field.TypeInt32, Nullable: true, Comment: "失败数量"},
 		{Name: "start_time", Type: field.TypeTime, Nullable: true, Comment: "开始时间"},
 		{Name: "stop_time", Type: field.TypeTime, Nullable: true, Comment: "结束时间"},
+		{Name: "send_time", Type: field.TypeTime, Nullable: true, Comment: "发送时间"},
 		{Name: "type", Type: field.TypeInt32, Nullable: true, Comment: "发送类型 1-群发消息 2-群发朋友圈"},
 		{Name: "organization_id", Type: field.TypeUint64, Comment: "organization_id | 租户ID"},
 	}
@@ -71,7 +73,7 @@ var (
 			{
 				Name:    "batchmsg_type",
 				Unique:  false,
-				Columns: []*schema.Column{BatchMsgColumns[15]},
+				Columns: []*schema.Column{BatchMsgColumns[17]},
 			},
 		},
 	}

+ 147 - 1
ent/mutation.go

@@ -1194,6 +1194,7 @@ type BatchMsgMutation struct {
 	fromwxid           *string
 	msg                *string
 	tag                *string
+	tagids             *string
 	total              *int32
 	addtotal           *int32
 	success            *int32
@@ -1202,6 +1203,7 @@ type BatchMsgMutation struct {
 	addfail            *int32
 	start_time         *time.Time
 	stop_time          *time.Time
+	send_time          *time.Time
 	_type              *int32
 	add_type           *int32
 	organization_id    *uint64
@@ -1752,6 +1754,55 @@ func (m *BatchMsgMutation) ResetTag() {
 	delete(m.clearedFields, batchmsg.FieldTag)
 }
 
+// SetTagids sets the "tagids" field.
+func (m *BatchMsgMutation) SetTagids(s string) {
+	m.tagids = &s
+}
+
+// Tagids returns the value of the "tagids" field in the mutation.
+func (m *BatchMsgMutation) Tagids() (r string, exists bool) {
+	v := m.tagids
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldTagids returns the old "tagids" 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) OldTagids(ctx context.Context) (v string, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldTagids is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldTagids requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldTagids: %w", err)
+	}
+	return oldValue.Tagids, nil
+}
+
+// ClearTagids clears the value of the "tagids" field.
+func (m *BatchMsgMutation) ClearTagids() {
+	m.tagids = nil
+	m.clearedFields[batchmsg.FieldTagids] = struct{}{}
+}
+
+// TagidsCleared returns if the "tagids" field was cleared in this mutation.
+func (m *BatchMsgMutation) TagidsCleared() bool {
+	_, ok := m.clearedFields[batchmsg.FieldTagids]
+	return ok
+}
+
+// ResetTagids resets all changes to the "tagids" field.
+func (m *BatchMsgMutation) ResetTagids() {
+	m.tagids = nil
+	delete(m.clearedFields, batchmsg.FieldTagids)
+}
+
 // SetTotal sets the "total" field.
 func (m *BatchMsgMutation) SetTotal(i int32) {
 	m.total = &i
@@ -2060,6 +2111,55 @@ func (m *BatchMsgMutation) ResetStopTime() {
 	delete(m.clearedFields, batchmsg.FieldStopTime)
 }
 
+// SetSendTime sets the "send_time" field.
+func (m *BatchMsgMutation) SetSendTime(t time.Time) {
+	m.send_time = &t
+}
+
+// SendTime returns the value of the "send_time" field in the mutation.
+func (m *BatchMsgMutation) SendTime() (r time.Time, exists bool) {
+	v := m.send_time
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldSendTime returns the old "send_time" 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) OldSendTime(ctx context.Context) (v time.Time, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldSendTime is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldSendTime requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldSendTime: %w", err)
+	}
+	return oldValue.SendTime, nil
+}
+
+// ClearSendTime clears the value of the "send_time" field.
+func (m *BatchMsgMutation) ClearSendTime() {
+	m.send_time = nil
+	m.clearedFields[batchmsg.FieldSendTime] = struct{}{}
+}
+
+// SendTimeCleared returns if the "send_time" field was cleared in this mutation.
+func (m *BatchMsgMutation) SendTimeCleared() bool {
+	_, ok := m.clearedFields[batchmsg.FieldSendTime]
+	return ok
+}
+
+// ResetSendTime resets all changes to the "send_time" field.
+func (m *BatchMsgMutation) ResetSendTime() {
+	m.send_time = nil
+	delete(m.clearedFields, batchmsg.FieldSendTime)
+}
+
 // SetType sets the "type" field.
 func (m *BatchMsgMutation) SetType(i int32) {
 	m._type = &i
@@ -2220,7 +2320,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, 16)
+	fields := make([]string, 0, 18)
 	if m.created_at != nil {
 		fields = append(fields, batchmsg.FieldCreatedAt)
 	}
@@ -2248,6 +2348,9 @@ func (m *BatchMsgMutation) Fields() []string {
 	if m.tag != nil {
 		fields = append(fields, batchmsg.FieldTag)
 	}
+	if m.tagids != nil {
+		fields = append(fields, batchmsg.FieldTagids)
+	}
 	if m.total != nil {
 		fields = append(fields, batchmsg.FieldTotal)
 	}
@@ -2263,6 +2366,9 @@ func (m *BatchMsgMutation) Fields() []string {
 	if m.stop_time != nil {
 		fields = append(fields, batchmsg.FieldStopTime)
 	}
+	if m.send_time != nil {
+		fields = append(fields, batchmsg.FieldSendTime)
+	}
 	if m._type != nil {
 		fields = append(fields, batchmsg.FieldType)
 	}
@@ -2295,6 +2401,8 @@ func (m *BatchMsgMutation) Field(name string) (ent.Value, bool) {
 		return m.Msg()
 	case batchmsg.FieldTag:
 		return m.Tag()
+	case batchmsg.FieldTagids:
+		return m.Tagids()
 	case batchmsg.FieldTotal:
 		return m.Total()
 	case batchmsg.FieldSuccess:
@@ -2305,6 +2413,8 @@ func (m *BatchMsgMutation) Field(name string) (ent.Value, bool) {
 		return m.StartTime()
 	case batchmsg.FieldStopTime:
 		return m.StopTime()
+	case batchmsg.FieldSendTime:
+		return m.SendTime()
 	case batchmsg.FieldType:
 		return m.GetType()
 	case batchmsg.FieldOrganizationID:
@@ -2336,6 +2446,8 @@ func (m *BatchMsgMutation) OldField(ctx context.Context, name string) (ent.Value
 		return m.OldMsg(ctx)
 	case batchmsg.FieldTag:
 		return m.OldTag(ctx)
+	case batchmsg.FieldTagids:
+		return m.OldTagids(ctx)
 	case batchmsg.FieldTotal:
 		return m.OldTotal(ctx)
 	case batchmsg.FieldSuccess:
@@ -2346,6 +2458,8 @@ func (m *BatchMsgMutation) OldField(ctx context.Context, name string) (ent.Value
 		return m.OldStartTime(ctx)
 	case batchmsg.FieldStopTime:
 		return m.OldStopTime(ctx)
+	case batchmsg.FieldSendTime:
+		return m.OldSendTime(ctx)
 	case batchmsg.FieldType:
 		return m.OldType(ctx)
 	case batchmsg.FieldOrganizationID:
@@ -2422,6 +2536,13 @@ func (m *BatchMsgMutation) SetField(name string, value ent.Value) error {
 		}
 		m.SetTag(v)
 		return nil
+	case batchmsg.FieldTagids:
+		v, ok := value.(string)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetTagids(v)
+		return nil
 	case batchmsg.FieldTotal:
 		v, ok := value.(int32)
 		if !ok {
@@ -2457,6 +2578,13 @@ func (m *BatchMsgMutation) SetField(name string, value ent.Value) error {
 		}
 		m.SetStopTime(v)
 		return nil
+	case batchmsg.FieldSendTime:
+		v, ok := value.(time.Time)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetSendTime(v)
+		return nil
 	case batchmsg.FieldType:
 		v, ok := value.(int32)
 		if !ok {
@@ -2597,6 +2725,9 @@ func (m *BatchMsgMutation) ClearedFields() []string {
 	if m.FieldCleared(batchmsg.FieldTag) {
 		fields = append(fields, batchmsg.FieldTag)
 	}
+	if m.FieldCleared(batchmsg.FieldTagids) {
+		fields = append(fields, batchmsg.FieldTagids)
+	}
 	if m.FieldCleared(batchmsg.FieldTotal) {
 		fields = append(fields, batchmsg.FieldTotal)
 	}
@@ -2612,6 +2743,9 @@ func (m *BatchMsgMutation) ClearedFields() []string {
 	if m.FieldCleared(batchmsg.FieldStopTime) {
 		fields = append(fields, batchmsg.FieldStopTime)
 	}
+	if m.FieldCleared(batchmsg.FieldSendTime) {
+		fields = append(fields, batchmsg.FieldSendTime)
+	}
 	if m.FieldCleared(batchmsg.FieldType) {
 		fields = append(fields, batchmsg.FieldType)
 	}
@@ -2650,6 +2784,9 @@ func (m *BatchMsgMutation) ClearField(name string) error {
 	case batchmsg.FieldTag:
 		m.ClearTag()
 		return nil
+	case batchmsg.FieldTagids:
+		m.ClearTagids()
+		return nil
 	case batchmsg.FieldTotal:
 		m.ClearTotal()
 		return nil
@@ -2665,6 +2802,9 @@ func (m *BatchMsgMutation) ClearField(name string) error {
 	case batchmsg.FieldStopTime:
 		m.ClearStopTime()
 		return nil
+	case batchmsg.FieldSendTime:
+		m.ClearSendTime()
+		return nil
 	case batchmsg.FieldType:
 		m.ClearType()
 		return nil
@@ -2703,6 +2843,9 @@ func (m *BatchMsgMutation) ResetField(name string) error {
 	case batchmsg.FieldTag:
 		m.ResetTag()
 		return nil
+	case batchmsg.FieldTagids:
+		m.ResetTagids()
+		return nil
 	case batchmsg.FieldTotal:
 		m.ResetTotal()
 		return nil
@@ -2718,6 +2861,9 @@ func (m *BatchMsgMutation) ResetField(name string) error {
 	case batchmsg.FieldStopTime:
 		m.ResetStopTime()
 		return nil
+	case batchmsg.FieldSendTime:
+		m.ResetSendTime()
+		return nil
 	case batchmsg.FieldType:
 		m.ResetType()
 		return nil

+ 1 - 1
ent/runtime/runtime.go

@@ -115,7 +115,7 @@ func init() {
 	// batchmsg.DefaultTaskName holds the default value on creation for the task_name field.
 	batchmsg.DefaultTaskName = batchmsgDescTaskName.Default.(string)
 	// batchmsgDescOrganizationID is the schema descriptor for organization_id field.
-	batchmsgDescOrganizationID := batchmsgFields[12].Descriptor()
+	batchmsgDescOrganizationID := batchmsgFields[14].Descriptor()
 	// batchmsg.OrganizationIDValidator is a validator for the "organization_id" field. It is called by the builders before save.
 	batchmsg.OrganizationIDValidator = batchmsgDescOrganizationID.Validators[0].(func(uint64) error)
 	categoryMixin := schema.Category{}.Mixin()

+ 2 - 0
ent/schema/batch_msg.go

@@ -23,11 +23,13 @@ func (BatchMsg) Fields() []ent.Field {
 		field.String("fromwxid").Optional().Comment("发送方微信ID"),
 		field.String("msg").Optional().Comment("内容"),
 		field.String("tag").Optional().Comment("发送规则 all 全部 tag1,tag2 按tag发送"),
+		field.String("tagids").Optional().Comment("要发送的tagids"),
 		field.Int32("total").Optional().Comment("总数"),
 		field.Int32("success").Optional().Comment("成功数量"),
 		field.Int32("fail").Optional().Comment("失败数量"),
 		field.Time("start_time").Optional().Comment("开始时间"),
 		field.Time("stop_time").Optional().Comment("结束时间"),
+		field.Time("send_time").Optional().Comment("发送时间"),
 		field.Int32("type").Optional().Comment("发送类型 1-群发消息 2-群发朋友圈"),
 		field.Uint64("organization_id").Positive().Comment("organization_id | 租户ID"),
 	}

+ 48 - 0
ent/set_not_nil.go

@@ -440,6 +440,30 @@ func (bm *BatchMsgCreate) SetNotNilTag(value *string) *BatchMsgCreate {
 }
 
 // set field if value's pointer is not nil.
+func (bm *BatchMsgUpdate) SetNotNilTagids(value *string) *BatchMsgUpdate {
+	if value != nil {
+		return bm.SetTagids(*value)
+	}
+	return bm
+}
+
+// set field if value's pointer is not nil.
+func (bm *BatchMsgUpdateOne) SetNotNilTagids(value *string) *BatchMsgUpdateOne {
+	if value != nil {
+		return bm.SetTagids(*value)
+	}
+	return bm
+}
+
+// set field if value's pointer is not nil.
+func (bm *BatchMsgCreate) SetNotNilTagids(value *string) *BatchMsgCreate {
+	if value != nil {
+		return bm.SetTagids(*value)
+	}
+	return bm
+}
+
+// set field if value's pointer is not nil.
 func (bm *BatchMsgUpdate) SetNotNilTotal(value *int32) *BatchMsgUpdate {
 	if value != nil {
 		return bm.SetTotal(*value)
@@ -560,6 +584,30 @@ func (bm *BatchMsgCreate) SetNotNilStopTime(value *time.Time) *BatchMsgCreate {
 }
 
 // set field if value's pointer is not nil.
+func (bm *BatchMsgUpdate) SetNotNilSendTime(value *time.Time) *BatchMsgUpdate {
+	if value != nil {
+		return bm.SetSendTime(*value)
+	}
+	return bm
+}
+
+// set field if value's pointer is not nil.
+func (bm *BatchMsgUpdateOne) SetNotNilSendTime(value *time.Time) *BatchMsgUpdateOne {
+	if value != nil {
+		return bm.SetSendTime(*value)
+	}
+	return bm
+}
+
+// set field if value's pointer is not nil.
+func (bm *BatchMsgCreate) SetNotNilSendTime(value *time.Time) *BatchMsgCreate {
+	if value != nil {
+		return bm.SetSendTime(*value)
+	}
+	return bm
+}
+
+// set field if value's pointer is not nil.
 func (bm *BatchMsgUpdate) SetNotNilType(value *int32) *BatchMsgUpdate {
 	if value != nil {
 		return bm.SetType(*value)

+ 113 - 32
internal/logic/batch_msg/create_batch_msg_logic.go

@@ -7,10 +7,10 @@ import (
 	"strings"
 	"time"
 	"wechat-api/ent/custom_types"
+	"wechat-api/ent/label"
 
 	"wechat-api/ent"
 	"wechat-api/ent/contact"
-	"wechat-api/ent/label"
 	"wechat-api/ent/labelrelationship"
 	"wechat-api/internal/svc"
 	"wechat-api/internal/types"
@@ -40,16 +40,24 @@ func (l *CreateBatchMsgLogic) CreateBatchMsg(req *types.BatchMsgInfo) (*types.Ba
 
 	var err error
 
-	all := false
-	for _, labelName := range req.Labels {
-		if strings.EqualFold(labelName, "all") || strings.EqualFold(labelName, "全部") {
-			all = true
-			tagstring := "all"
-			req.Tag = &tagstring
+	var tagstring, tag string
+	allContact, allGroup := false, false
+	tagIdArray := make([]uint64, 0)
+	for _, labelId := range req.Labels {
+		tagIdArray = append(tagIdArray, labelId)
+		if labelId == uint64(0) { //全部
+			allContact = true
+		}
+	}
+	for _, labelId := range req.GroupLabels {
+		tagIdArray = append(tagIdArray, labelId)
+		if labelId == uint64(0) { //全部
+			allGroup = true
 		}
 	}
 
 	startTime := time.Now()
+	sendNow := true
 	// req.StartTimeStr 不为nil并且不为空
 	if req.StartTimeStr != nil && *req.StartTimeStr != "" {
 		// 将 req.StartTimeStr 转换为 req.StartTime
@@ -59,6 +67,7 @@ func (l *CreateBatchMsgLogic) CreateBatchMsg(req *types.BatchMsgInfo) (*types.Ba
 			l.Logger.Errorf("时间字符串转换错误: %v", err)
 			return nil, err
 		}
+		sendNow = false
 	}
 
 	// 把 req.Msg 字符串的内容 json_decode 到 msgArray
@@ -93,47 +102,70 @@ func (l *CreateBatchMsgLogic) CreateBatchMsg(req *types.BatchMsgInfo) (*types.Ba
 		}
 	}
 
-	tagstring := strings.Join(req.Labels, ",")
-	req.Tag = &tagstring
-
-	userlist := make([]*ent.Contact, 0)
+	userList, groupList := make([]*ent.Contact, 0), make([]*ent.Contact, 0)
 
-	if all {
+	tagMap := make(map[string][]uint64)
+	if allContact && allGroup {
 		// 获取 contact 表中  wx_wxid 等于 req.Fromwxid 的 type 为1或2的数据
-		userlist, err = l.svcCtx.DB.Contact.Query().Where(contact.WxWxid(*req.Fromwxid), contact.TypeIn(1, 2)).All(l.ctx)
+		userList, err = l.svcCtx.DB.Contact.Query().Where(contact.WxWxid(*req.Fromwxid), contact.TypeIn(1, 2)).All(l.ctx)
 		if err != nil {
 			return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
 		}
-	} else {
-		// 获取 label 表中 name 为 tags的记录
-		labids, err := l.svcCtx.DB.Label.Query().Where(label.NameIn(req.Labels...)).IDs(l.ctx)
-		if err != nil {
-			return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
-		}
-		//l.Logger.Infof("CreateBatchMsgLogic labids= %v", labids)
 
-		// 获取 label_relationship 表中,label_id 等于 labids 的 contact_id
-		labelrelationships, err := l.svcCtx.DB.LabelRelationship.Query().Where(labelrelationship.LabelIDIn(labids...)).All(l.ctx)
+		tagids := make([]uint64, 0, 1)
+		tagids = append(tagids, uint64(0))
+		tagMap["contact_tag"] = tagids
+		tagMap["group_tag"] = tagids
+		tagByte, err := json.Marshal(tagMap)
 		if err != nil {
 			return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
 		}
-		contact_ids := make([]uint64, 0)
-		for _, labelrelationship := range labelrelationships {
-			contact_ids = append(contact_ids, labelrelationship.ContactID)
+		tagstring = string(tagByte)
+		tag = "全部"
+	} else {
+		if allContact { // 所有联系人
+			// 获取 contact 表中  wx_wxid 等于 req.Fromwxid 的 type 为1的数据
+			userList, err = l.svcCtx.DB.Contact.Query().Where(contact.WxWxid(*req.Fromwxid), contact.TypeEQ(1)).All(l.ctx)
+			if err != nil {
+				return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+			}
+			tag = "全部联系人"
+		} else {
+			userList, err = getContactList(l, req.Labels, *req.Fromwxid, 1)
+			if err != nil {
+				return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+			}
 		}
-		//l.Logger.Infof("CreateBatchMsgLogic contact_ids= %v", contact_ids)
 
-		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.WxWxid(*req.Fromwxid), contact.IDIn(contact_ids...), contact.TypeIn(1, 2)).All(l.ctx)
+		if allGroup { //所有群
+			// 获取 contact 表中  wx_wxid 等于 req.Fromwxid 的 type 为2的数据
+			groupList, err = l.svcCtx.DB.Contact.Query().Where(contact.WxWxid(*req.Fromwxid), contact.TypeEQ(2)).All(l.ctx)
+			if err != nil {
+				return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+			}
+			tag = "全部群"
+		} else {
+			groupList, err = getContactList(l, req.GroupLabels, *req.Fromwxid, 2)
 			if err != nil {
 				return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
 			}
-			//l.Logger.Infof("CreateBatchMsgLogic userlist= %v", userlist)
 		}
+
+		tagMap["contact_tag"] = req.Labels
+		tagMap["group_tag"] = req.GroupLabels
+		tagByte, err := json.Marshal(tagMap)
+		if err != nil {
+			return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+		}
+		tagstring = string(tagByte)
+		tagArray := getLabelListByIds(l, req.Labels, req.GroupLabels)
+		if tag != "" {
+			tagArray = append(tagArray, tag)
+		}
+		tag = strings.Join(tagArray, ",")
 	}
 	// 这里是根据userlist 和 批量消息数 获得最终待发送消息总数
-	total := int32(len(userlist)) * int32(len(msgActionList))
+	total := int32(len(userList))*int32(len(msgActionList)) + int32(len(groupList))*int32(len(msgActionList))
 
 	if total == 0 {
 		return &types.BaseMsgResp{Msg: errormsg.TargetNotFound}, nil
@@ -142,14 +174,21 @@ func (l *CreateBatchMsgLogic) CreateBatchMsg(req *types.BatchMsgInfo) (*types.Ba
 	uuid := uuidx.NewUUID()
 	batchNo := uuid.String()
 
+	var sendTime *time.Time
+	if !sendNow {
+		sendTime = &startTime
+	}
+
 	_, err = l.svcCtx.DB.BatchMsg.Create().
 		SetNotNilBatchNo(&batchNo).
 		SetNotNilFromwxid(req.Fromwxid).
 		SetNotNilMsg(req.Msg).
-		SetNotNilTag(req.Tag).
+		SetNotNilTag(&tag).
+		SetNotNilTagids(&tagstring).
 		SetTotal(total).
 		SetNotNilTaskName(req.TaskName).
 		SetNotNilStartTime(&startTime).
+		SetNillableSendTime(sendTime).
 		SetNotNilType(req.Type).
 		SetNotNilOrganizationID(&organizationId).
 		Save(l.ctx)
@@ -160,3 +199,45 @@ 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) {
+	// 获取 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.WxWxid(fromWxId), contact.IDIn(contact_ids...), contact.TypeEQ(stype)).All(l.ctx)
+		if err != nil {
+			return nil, dberrorhandler.DefaultEntError(l.Logger, err, nil)
+		}
+	}
+
+	return userList, nil
+}
+
+func getLabelListByIds(l *CreateBatchMsgLogic, 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
+}

+ 13 - 0
internal/logic/batch_msg/get_batch_msg_by_id_logic.go

@@ -2,6 +2,7 @@ package batch_msg
 
 import (
 	"context"
+	"encoding/json"
 	"github.com/zeromicro/go-zero/core/errorx"
 	"wechat-api/ent"
 	"wechat-api/ent/batchmsg"
@@ -41,6 +42,15 @@ func (l *GetBatchMsgByIdLogic) GetBatchMsgById(req *types.IDReq) (*types.BatchMs
 		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,
@@ -64,7 +74,10 @@ func (l *GetBatchMsgByIdLogic) GetBatchMsgById(req *types.IDReq) (*types.BatchMs
 			Type:         &data.Type,
 			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
 }

+ 4 - 1
internal/types/types.go

@@ -1534,8 +1534,11 @@ type BatchMsgInfo struct {
 	StartTimeStr *string `json:"startTimeStr,optional"`
 	// 结束时间
 	StopTime *int64 `json:"stopTime,optional"`
+	// 发送时间
+	SendTime *int64 `json:"sendTime,optional"`
 	// 标签列表
-	Labels []string `json:"labels,optional"`
+	Labels      []uint64 `json:"labels,optional"`
+	GroupLabels []uint64 `json:"groupLabels,optional"`
 	// 标签列表
 	Type *int32 `json:"type,optional"`
 }