From c89082aea9726da2a0813cb723ce9b63ca7433f5 Mon Sep 17 00:00:00 2001 From: Leufolium Date: Thu, 11 Apr 2024 19:34:51 +0800 Subject: [PATCH] by Robin at 20240411 --- api/consts/consts.go | 29 +++---- api/errcode/errcode.go | 10 +++ api/proto/zonemoment/proto/zonemoment_api.go | 17 ++++ api/proto/zonemoment/proto/zonemoment_op.go | 20 ----- app/mix/controller/init.go | 1 + app/mix/controller/zonemoment_api.go | 13 ++++ app/mix/dao/mongo.go | 77 +++++++++++-------- app/mix/service/apiservice.go | 53 +++++++++++++ .../service/apiservice_business_validation.go | 1 + app/mix/service/business_validator/auth.go | 26 +++++++ .../service/logic/zone_moment_create_times.go | 40 ++++++++++ app/mix/service/logic/zonemoment.go | 15 +++- app/mix/service/service.go | 2 + dbstruct/zone_moment_create_times.go | 6 ++ 14 files changed, 240 insertions(+), 70 deletions(-) create mode 100644 app/mix/service/logic/zone_moment_create_times.go create mode 100644 dbstruct/zone_moment_create_times.go diff --git a/api/consts/consts.go b/api/consts/consts.go index a1d3b2b2..fdfb0f99 100644 --- a/api/consts/consts.go +++ b/api/consts/consts.go @@ -35,20 +35,21 @@ const ( // apollo_config const ( - MaxPswdWrongTimesKey = "max_pswd_wrong_times" - MaxVeriCodeValidDurationKey = "max_veri_code_valid_duration" - IosKey = "ios" - AndroidKey = "android" - AccountInitKey = "account_init" - TagNumKey = "tag_num" - PlatformNumKey = "platform_num" - SupportWxIdNumKey = "support_wx_id_num" - MaxDailyVeriCodeSendTimesKey = "max_daily_veri_code_send_times" - ImageIdForUploadFail = "image_id_for_upload_fail" - VideoIdForUploadFail = "video_id_for_upload_fail" - RestrictedVisitorKey = "restricted_visitor" - MaxDailyMomentCreateTimesKey = "max_daily_moment_create_times" - DefaultMomentTextKey = "default_moment_text" + MaxPswdWrongTimesKey = "max_pswd_wrong_times" + MaxVeriCodeValidDurationKey = "max_veri_code_valid_duration" + IosKey = "ios" + AndroidKey = "android" + AccountInitKey = "account_init" + TagNumKey = "tag_num" + PlatformNumKey = "platform_num" + SupportWxIdNumKey = "support_wx_id_num" + MaxDailyVeriCodeSendTimesKey = "max_daily_veri_code_send_times" + ImageIdForUploadFail = "image_id_for_upload_fail" + VideoIdForUploadFail = "video_id_for_upload_fail" + RestrictedVisitorKey = "restricted_visitor" + MaxDailyMomentCreateTimesKey = "max_daily_moment_create_times" + DefaultMomentTextKey = "default_moment_text" + MaxDailyZoneMomentCreateTimesKey = "max_daily_zone_moment_create_times" ) // del_flag diff --git a/api/errcode/errcode.go b/api/errcode/errcode.go index 0883e838..97ee5cd3 100644 --- a/api/errcode/errcode.go +++ b/api/errcode/errcode.go @@ -192,6 +192,10 @@ var ErrCodeMsgMap = map[ErrCode]string{ ErrCodeZoneCollaboratorSrvFail: "空间协作者表服务错误", ErrCodeZoneCollaboratorNotExist: "空间协作者表不存在", + + ErrCodeZoneMomentCreateTimesSrvFail: "空间动态创建频次表服务错误", + ErrCodeZoneMomentCreateTimesNotExist: "空间动态创建频次表不存在", + ErrCodeZoneMomentCreateTimesReachedDailyUpperbound: "每天仅可发布十条空间动态", } const ( @@ -461,6 +465,12 @@ const ( ErrCodeZoneCollaboratorSrvFail ErrCode = -38001 // 空间协作者表服务错误 ErrCodeZoneCollaboratorNotExist ErrCode = -38002 // 空间协作者表不存在 + // MomentCreateTimes: 39xxx + ErrCodeZoneMomentCreateTimesSrvOk ErrCode = ErrCodeOk + ErrCodeZoneMomentCreateTimesSrvFail ErrCode = -39001 // 动态创建频次表服务错误 + ErrCodeZoneMomentCreateTimesNotExist ErrCode = -39002 // 动态创建频次表不存在 + ErrCodeZoneMomentCreateTimesReachedDailyUpperbound ErrCode = -39003 // 动态创建次数已达每日上限 + // Media: 60xxx ErrCodeMediaSrvOk ErrCode = ErrCodeOk ErrCodeMediaSrvFail ErrCode = -60001 // 媒体服务错误 diff --git a/api/proto/zonemoment/proto/zonemoment_api.go b/api/proto/zonemoment/proto/zonemoment_api.go index 67b3052f..7d8006a6 100644 --- a/api/proto/zonemoment/proto/zonemoment_api.go +++ b/api/proto/zonemoment/proto/zonemoment_api.go @@ -146,3 +146,20 @@ type ApiHeadResp struct { base.BaseResponse Data *ApiHeadData `json:"data"` } + +// op 列表-创建人 +type ApiListStatisticsByCreaterMidReq struct { + base.BaseRequest +} + +type ApiListStatisticsByCreaterMidData struct { + FreeCount int64 `json:"free_count"` + PaidCount int64 `json:"paid_count"` + RejectedCount int64 `json:"rejected_count"` + PaidLimit int64 `json:"paid_limit"` +} + +type ApiListStatisticsByCreaterMidResp struct { + base.BaseResponse + Data *ApiListStatisticsByCreaterMidData `json:"data"` +} diff --git a/api/proto/zonemoment/proto/zonemoment_op.go b/api/proto/zonemoment/proto/zonemoment_op.go index a9f062b8..835de296 100644 --- a/api/proto/zonemoment/proto/zonemoment_op.go +++ b/api/proto/zonemoment/proto/zonemoment_op.go @@ -186,23 +186,3 @@ type OpHeadResp struct { base.BaseResponse Data *OpHeadData `json:"data"` } - -// op 列表 -type OpCountByZidReq struct { - base.BaseRequest - Zid *int64 `json:"zid"` // 空间id - MType *int64 `json:"m_type"` // 媒体类型 - CType *int64 `json:"c_type"` // 付费模式 - IsIronfanVisible *int64 `json:"is_ironfan_visible"` // 是否铁粉可见 - CtUpperBound *int64 `json:"ct_upper_bound"` // 创建时间上界,闭区间 - CtLowerBound *int64 `json:"ct_lower_bound"` // 创建时间下界,开区间,可为0 - Status *int64 `json:"status"` // 状态 -} - -type OpCountByZidData struct { -} - -type OpLCountByZidResp struct { - base.BaseResponse - Data *OpListByMidData `json:"data"` -} diff --git a/app/mix/controller/init.go b/app/mix/controller/init.go index 86569ef9..e86ddf44 100644 --- a/app/mix/controller/init.go +++ b/app/mix/controller/init.go @@ -227,6 +227,7 @@ func Init(r *gin.Engine) { apiZoneMomentGroup.POST("list_by_zid", middleware.JSONParamValidator(zonemomentproto.ApiListByZidReq{}), middleware.JwtAuthenticator(), ApiGetZoneMomentListByZid) apiZoneMomentGroup.POST("thumbs_up", middleware.JSONParamValidator(zonemomentproto.ApiZoneMomentThumbsUpReq{}), middleware.JwtAuthenticator(), ApiZoneMomentThumbsUpMoment) apiZoneMomentGroup.POST("head", middleware.JSONParamValidator(zonemomentproto.ApiHeadReq{}), middleware.JwtAuthenticator(), ApiHeadZoneMoment) + apiZoneMomentGroup.POST("list_statistics_by_creater_mid", middleware.JSONParamValidator(zonemomentproto.ApiListStatisticsByCreaterMidReq{}), middleware.JwtAuthenticator(), ApiGetZoneMomentStatisticsByCreaterMid) // 空间对话 apiZoneSessionGroup := r.Group("/api/zone_session", PrepareToC()) diff --git a/app/mix/controller/zonemoment_api.go b/app/mix/controller/zonemoment_api.go index 8014adab..860a2367 100644 --- a/app/mix/controller/zonemoment_api.go +++ b/app/mix/controller/zonemoment_api.go @@ -184,3 +184,16 @@ func ApiHeadZoneMoment(ctx *gin.Context) { ReplyOk(ctx, nil) } + +func ApiGetZoneMomentStatisticsByCreaterMid(ctx *gin.Context) { + req := ctx.MustGet("client_req").(*zonemomentproto.ApiListStatisticsByCreaterMidReq) + + data, ec := service.DefaultService.ApiGetZoneMomentStatisticsByCreaterMid(ctx, req) + if ec != errcode.ErrCodeZoneMomentSrvOk { + logger.Error("ApiGetZoneMomentList fail, req: %v, ec: %v", util.ToJson(req), ec) + ReplyErrCodeMsg(ctx, ec) + return + } + + ReplyOk(ctx, data) +} diff --git a/app/mix/dao/mongo.go b/app/mix/dao/mongo.go index d25b63ae..a566419c 100644 --- a/app/mix/dao/mongo.go +++ b/app/mix/dao/mongo.go @@ -174,8 +174,9 @@ const ( DBZone = "zone" COLZone = "zone" - DBZoneMoment = "zone_moment" - COLZoneMoment = "zone_moment" + DBZoneMoment = "zone_moment" + COLZoneMoment = "zone_moment" + COLZoneMomentCreateTimes = "zone_moment_create_times" DBZoneMomentThumbsUp = "zone_moment_thumbs_up" COLZoneMomentThumbsUp = "zone_moment_thumbs_up" @@ -431,6 +432,11 @@ func (m *Mongo) getColZoneMoment() *qmgo.Collection { return m.clientMix.Database(DBZoneMoment).Collection(COLZoneMoment) } +// 私密圈动态创建频次表 +func (m *Mongo) getColZoneMomentCreateTimes() *qmgo.Collection { + return m.clientMix.Database(DBZoneMoment).Collection(COLZoneMomentCreateTimes) +} + // 私密圈点赞表 func (m *Mongo) getColZoneMomentThumbsUp() *qmgo.Collection { return m.clientMix.Database(DBZoneMomentThumbsUp).Collection(COLZoneMomentThumbsUp) @@ -4136,37 +4142,6 @@ func (m *Mongo) GetZoneMomentListByMid(ctx *gin.Context, req *zonemomentproto.Op return list, err } -func (m *Mongo) GetZoneMomentCountByZid(ctx *gin.Context, req *zonemomentproto.OpCountByZidReq) (int64, error) { - col := m.getColZoneMoment() - query := qmgo.M{ - "zid": util.DerefInt64(req.Zid), - "del_flag": 0, - } - if req.MType != nil { - query["m_type"] = util.DerefInt64(req.MType) - } - if req.CType != nil { - query["c_type"] = util.DerefInt64(req.CType) - } - if req.IsIronfanVisible != nil { - query["is_ironfan_visible"] = util.DerefInt64(req.IsIronfanVisible) - } - if req.Status != nil { - query["status"] = util.DerefInt64(req.Status) - } - ctClause := qmgo.M{} - if req.CtLowerBound != nil { - ctClause["$gt"] = util.DerefInt64(req.CtLowerBound) - } - if req.CtUpperBound != nil { - ctClause["$lte"] = util.DerefInt64(req.CtUpperBound) - } - if len(ctClause) != 0 { - query["ct"] = ctClause - } - return col.Find(ctx, query).Count() -} - func (m *Mongo) ThumbsUpZoneMoment(ctx *gin.Context, req *zonemomentproto.OpZoneMomentThumbsUpReq) (err error) { col := m.getColMoment() change := qmgo.Change{ @@ -4225,6 +4200,16 @@ func (m *Mongo) GetZoneMomentCountByMidAndCType(ctx *gin.Context, mid int64, cTy return count, err } +func (m *Mongo) GetZoneMomentCountByMidAndStatus(ctx *gin.Context, mid int64, status int64) (int64, error) { + col := m.getColZoneMoment() + query := qmgo.M{ + "mid": mid, + "status": status, + "del_flag": 0, + } + return col.Find(ctx, query).Count() +} + func (m *Mongo) UpdateZoneMomentByIdsAndStatus(ctx *gin.Context, zonemoment *dbstruct.ZoneMoment, ids []int64, status int64) error { col := m.getColZoneMoment() set := util.EntityToM(zonemoment) @@ -4591,3 +4576,29 @@ func (m *Mongo) GetZoneThirdPartnerListByTpMid(ctx *gin.Context, tp_mid int64) ( } return list, err } + +// 动态创建频次 +func (m *Mongo) GetAndUpdateZoneMomentCreateTimes(ctx *gin.Context, mid int64) (momentCreateTimes *dbstruct.ZoneMomentCreateTimes, err error) { + col := m.getColZoneMomentCreateTimes() + + change := qmgo.Change{ + Update: qmgo.M{"$inc": qmgo.M{"create_times": 1}}, + Upsert: true, + ReturnNew: false, + } + + momentCreateTimesInstance := dbstruct.ZoneMomentCreateTimes{} + if err = col.Find(ctx, qmgo.M{"_id": mid}).Apply(change, &momentCreateTimesInstance); err != nil { + logger.Error("change error : %v", err) + return + } + + return &momentCreateTimesInstance, err +} + +// 清空动态创建频次表 +func (m *Mongo) ClearZoneMomentCreateTimes(ctx *gin.Context) error { + col := m.getColZoneMomentCreateTimes() + _, err := col.RemoveAll(ctx, qmgo.M{}) + return err +} diff --git a/app/mix/service/apiservice.go b/app/mix/service/apiservice.go index 77db6a85..dfe9eaa1 100644 --- a/app/mix/service/apiservice.go +++ b/app/mix/service/apiservice.go @@ -2675,6 +2675,59 @@ func (s *Service) ApiHeadZoneMoment(ctx *gin.Context, req *zonemomentproto.ApiHe return } +func (s *Service) ApiGetZoneMomentStatisticsByCreaterMid(ctx *gin.Context, req *zonemomentproto.ApiListStatisticsByCreaterMidReq) (result *zonemomentproto.ApiListStatisticsByCreaterMidData, ec errcode.ErrCode) { + ec = errcode.ErrCodeZoneMomentSrvOk + + // 读取每日发送上限 + maxDailyZoneMomentCreateTimes, err := apollo.GetIntValue(consts.MaxDailyZoneMomentCreateTimesKey, apollo.ApolloOpts().SetNamespace("application")) + if err != nil { + logger.Error("Apollo read failed : %v", err) + ec = errcode.ErrCodeApolloReadFail + return + } + + // 免费贴个数 + freeCount, err := _DefaultZoneMoment.OpCountByMidAndCType(ctx, req.BaseRequest.Mid, consts.ZoneMomentCType_Free) + if err != nil { + logger.Error("_DefaultZoneMoment OpCountByMidAndCType fail, req: %v, err: %v", util.ToJson(req), err) + ec = errcode.ErrCodeZoneMomentSrvFail + return + } + + // 付费贴个数 + paidCount, err := _DefaultZoneMoment.OpCountByMidAndCType(ctx, req.BaseRequest.Mid, consts.ZoneMomentCType_Paid) + if err != nil { + logger.Error("_DefaultZoneMoment OpCountByMidAndCType fail, req: %v, err: %v", util.ToJson(req), err) + ec = errcode.ErrCodeZoneMomentSrvFail + return + } + + // 审核未通过个数 + rejectedCount, err := _DefaultZoneMoment.OpCountByMidAndStatus(ctx, req.BaseRequest.Mid, consts.ZoneMoment_Private) + if err != nil { + logger.Error("_DefaultZoneMoment OpCountByMidAndStatus fail, req: %v, err: %v", util.ToJson(req), err) + ec = errcode.ErrCodeZoneMomentSrvFail + return + } + + // 付费贴限额 + diff := freeCount - paidCount // 免费贴付费贴差额 + dailyLimit := int64(maxDailyZoneMomentCreateTimes) - freeCount - paidCount // 每日发帖剩余限额 + paidLimit := diff // 取两者最小值 + if dailyLimit < diff { + paidLimit = dailyLimit + } + + result = &zonemomentproto.ApiListStatisticsByCreaterMidData{ + FreeCount: freeCount, + PaidCount: paidCount, + RejectedCount: rejectedCount, + PaidLimit: paidLimit, + } + + return +} + func (s *Service) ApiZoneMomentThumbsUpMoment(ctx *gin.Context, req *zonemomentproto.ApiZoneMomentThumbsUpReq) (ec errcode.ErrCode) { ec = errcode.ErrCodeZoneMomentSrvOk diff --git a/app/mix/service/apiservice_business_validation.go b/app/mix/service/apiservice_business_validation.go index 006e588d..ad4b7ea1 100644 --- a/app/mix/service/apiservice_business_validation.go +++ b/app/mix/service/apiservice_business_validation.go @@ -562,6 +562,7 @@ func (s *Service) ApiCreateZoneMomentBusinessValidate(ctx *gin.Context, req *zon EnsureIsThisRole(consts.Streamer). EnsureSuchAccountPunishmentNotExist(req.GetBaseRequest().Mid, consts.AccountPunishment_BlockFromCreatingZoneMoment, _DefaultAccountPunishment.OpListByMidAndType). EnsureSuchAccountPunishmentNotExist(req.GetBaseRequest().Mid, pType, _DefaultAccountPunishment.OpListByMidAndType). + EnsureZoneMomentCreateTimesNotReachedDailyUpperbound(_DefaultZoneMomentCreateTimes.OpGetAndUpdate, req.GetBaseRequest().Mid). EnsureAmongZoneMomentsPaidItemsLessThanFreeItems(_DefaultZoneMoment.OpCountByMidAndCType, req.GetBaseRequest().Mid, util.DerefInt64(req.CType)). Validate(). Collect() diff --git a/app/mix/service/business_validator/auth.go b/app/mix/service/business_validator/auth.go index 3e6d7998..9b2cf84d 100644 --- a/app/mix/service/business_validator/auth.go +++ b/app/mix/service/business_validator/auth.go @@ -411,6 +411,32 @@ func (l *AuthBusinessValidator) EnsureSuchAccountPunishmentNotExist(uid int64, t return l } +func (l *AuthBusinessValidator) EnsureZoneMomentCreateTimesNotReachedDailyUpperbound(fun func(*gin.Context, int64) (*dbstruct.ZoneMomentCreateTimes, error), mid int64) *AuthBusinessValidator { + l.oplist = append(l.oplist, func() { + + // 读取每日发送上限 + maxDailyZoneMomentCreateTimes, err := apollo.GetIntValue(consts.MaxDailyZoneMomentCreateTimesKey, apollo.ApolloOpts().SetNamespace("application")) + if err != nil { + logger.Error("Apollo read failed : %v", err) + l.ec = errcode.ErrCodeApolloReadFail + return + } + + zoneMomentCreateTimes, err := fun(l.ctx, mid) + if err != nil { + l.ec = errcode.ErrCodeZoneMomentCreateTimesSrvFail + return + } + + if zoneMomentCreateTimes.CreateTimes >= int64(maxDailyZoneMomentCreateTimes) { + logger.Error("the zone moment create times of this mid has reached its daily upperbound") + l.ec = errcode.ErrCodeZoneMomentCreateTimesReachedDailyUpperbound + return + } + }) + return l +} + // 执行校验 func (a *AuthBusinessValidator) Validate() *AuthBusinessValidator { a.BusinessValidateStream.Validate() diff --git a/app/mix/service/logic/zone_moment_create_times.go b/app/mix/service/logic/zone_moment_create_times.go new file mode 100644 index 00000000..7ce18072 --- /dev/null +++ b/app/mix/service/logic/zone_moment_create_times.go @@ -0,0 +1,40 @@ +package logic + +import ( + "service/app/mix/dao" + "service/dbstruct" + "service/library/logger" + + "github.com/gin-gonic/gin" +) + +type ZoneMomentCreateTimes struct { + store *dao.Store +} + +func NewZoneMomentCreateTimes(store *dao.Store) (a *ZoneMomentCreateTimes) { + a = &ZoneMomentCreateTimes{ + store: store, + } + return +} + +func (p *ZoneMomentCreateTimes) OpGetAndUpdate(ctx *gin.Context, mid int64) (*dbstruct.ZoneMomentCreateTimes, error) { + + vericodeSendTimes, err := p.store.GetAndUpdateZoneMomentCreateTimes(ctx, mid) + if err != nil { + logger.Error("GetAndUpdateZoneMomentCreateTimes fail, err: %v", err) + return nil, err + } + + return vericodeSendTimes, nil +} + +func (p *ZoneMomentCreateTimes) OpClear(ctx *gin.Context) error { + err := p.store.ClearZoneMomentCreateTimes(ctx) + if err != nil { + logger.Error("ClearZoneMomentCreateTimes fail, err: %v", err) + return err + } + return nil +} diff --git a/app/mix/service/logic/zonemoment.go b/app/mix/service/logic/zonemoment.go index d934def4..f50cdad2 100644 --- a/app/mix/service/logic/zonemoment.go +++ b/app/mix/service/logic/zonemoment.go @@ -131,7 +131,7 @@ func (p *ZoneMoment) TryToCompleteAudit(ctx *gin.Context, zonemomentId int64) er func (p *ZoneMoment) OpCountByMidAndCType(ctx *gin.Context, mid int64, cType int64) (int64, error) { count, err := p.store.GetZoneMomentCountByMidAndCType(ctx, mid, cType) if err != nil { - logger.Error("GetZoneMomentList fail, err: %v", err) + logger.Error("GetZoneMomentCountByMidAndCType fail, err: %v", err) return 0, err } return count, nil @@ -140,7 +140,7 @@ func (p *ZoneMoment) OpCountByMidAndCType(ctx *gin.Context, mid int64, cType int func (p *ZoneMoment) OpUpdateByIdsAndStatus(ctx *gin.Context, req *zonemomentproto.OpUpdateReq, ids []int64, status int64) error { err := p.store.UpdateZoneMomentByIdsAndStatus(ctx, req.ZoneMoment, ids, status) if err != nil { - logger.Error("UpdateZoneMoment fail, err: %v", err) + logger.Error("UpdateZoneMomentByIdsAndStatus fail, err: %v", err) return err } return nil @@ -149,7 +149,7 @@ func (p *ZoneMoment) OpUpdateByIdsAndStatus(ctx *gin.Context, req *zonemomentpro func (p *ZoneMoment) GetZidsByZoneMomentIds(ctx *gin.Context, zonemomentIds []int64) ([]int64, error) { zids, err := p.store.GetZidsByZoneMomentIds(ctx, zonemomentIds) if err != nil { - logger.Error("UpdateZoneMoment fail, err: %v", err) + logger.Error("GetZidsByZoneMomentIds fail, err: %v", err) return nil, err } return zids, err @@ -163,3 +163,12 @@ func (p *ZoneMoment) OpHeadByIds(ctx *gin.Context, ids []int64, opType int64) er } return nil } + +func (p *ZoneMoment) OpCountByMidAndStatus(ctx *gin.Context, mid int64, status int64) (int64, error) { + count, err := p.store.GetZoneMomentCountByMidAndStatus(ctx, mid, status) + if err != nil { + logger.Error("GetZoneMomentCountByMidAndStatus fail, err: %v", err) + return 0, err + } + return count, nil +} diff --git a/app/mix/service/service.go b/app/mix/service/service.go index 9aa4891f..f3c9a698 100644 --- a/app/mix/service/service.go +++ b/app/mix/service/service.go @@ -118,6 +118,7 @@ var ( _DefaultZoneSession *logic.ZoneSession _DefaultZoneThirdPartner *logic.ZoneThirdPartner _DefaultZoneCollaborator *logic.ZoneCollaborator + _DefaultZoneMomentCreateTimes *logic.ZoneMomentCreateTimes ) type Service struct { @@ -204,6 +205,7 @@ func (s *Service) Init(c any) (err error) { _DefaultZoneSession = logic.NewZoneSession(store) _DefaultZoneThirdPartner = logic.NewZoneThirdPartner(store) _DefaultZoneCollaborator = logic.NewZoneCollaborator(store) + _DefaultZoneMomentCreateTimes = logic.NewZoneMomentCreateTimes(store) return } diff --git a/dbstruct/zone_moment_create_times.go b/dbstruct/zone_moment_create_times.go new file mode 100644 index 00000000..286b8721 --- /dev/null +++ b/dbstruct/zone_moment_create_times.go @@ -0,0 +1,6 @@ +package dbstruct + +type ZoneMomentCreateTimes struct { + Id int64 `json:"id" bson:"_id"` //id,主播的mid + CreateTimes int64 `json:"create_times" bson:"create_times"` //发送次数 +}