package services import ( "fmt" "fpdxfeed/config" "fpdxfeed/database" "fpdxfeed/helpers" "fpdxfeed/models" "github.com/gomodule/redigo/redis" "github.com/jinzhu/gorm" "log" "math" "strconv" "sync" "time" ) const ( FPDXGZH = "gh_b598cb7474d8" FPDXXCX = "gh_01c089b58dda" QQXCX = "1109365561" CALCPARAMS = "msy-feed-params" TablePartner = "kdgx_partner_charge_partner" TableUser = "kdgx_partner_charge_user" TableAuthKey = "kdgx_user_auth_key" TableNoticeManage = "kdgx_miniprogram_notice_manage" TableOpenid = "kddx_user_openid" TableCardState = "fpdx_card_state_list" TableMiniprogramFormid = "kdgx_charge_fpdx_miniprogram_formid" TableQQMiniForms = "kdgx_qqmini_forms" TableUserSystag = "kdgx_fpdx_user_systag" TableFeedLog = "kdgx_fpdx_feed_log" ) var ( wg sync.WaitGroup pt1 float64 pz float64 pg float64 pp float64 pd float64 pt3 float64 count int t0 int ) type Service struct { config *config.Config // 配置文件 conns *database.Conns // 链接 debug int // 模式 goLimit int // 整理卡片进程的go程粒度 sleep time.Duration // 休眠时长 n0 int // 时间衰减初始分 dt1 float64 // 最近更新加分衰变率1 dt2 float64 // 赞数衰变率2*z(x) dt3 float64 // 发布时间加分衰变率3 dt4 float64 // 最近上线加分衰变率4 } func NewService(conf *config.Config, conns *database.Conns, debug int, goLimit int, sleep time.Duration, n0 int, dt1 float64, dt2 float64, dt3 float64, dt4 float64) (service *Service, err error) { if 2 == debug { conns.KdDB = conns.KdDB.Debug() conns.DataDb = conns.DataDb.Debug() } service = &Service{ config: conf, conns: conns, debug: debug, goLimit: goLimit, sleep: sleep, n0: n0, dt1: dt1, dt2: dt2, dt3: dt3, dt4: dt4, } return } func (s *Service) Run() { s.execTask() } // 计算所有卡片基础分 func (s *Service) execTask() { t0 = int(time.Now().Unix()) val, err := redis.StringMap(s.conns.Redis.Do("hgetall", CALCPARAMS)) if err != nil { // 异常退出 log.Fatal(fmt.Errorf("获取计算参数错误:%v", err)) } pt1, err := strconv.ParseFloat(val["pt1"], 64) if err != nil { pt1 = 0.2 } pz, err := strconv.ParseFloat(val["pz"], 64) if err != nil { pz = 0.5 } pg, err := strconv.ParseFloat(val["pg"], 64) if err != nil { pg = 0.5 } pp, err := strconv.ParseFloat(val["pp"], 64) if err != nil { pp = 0.15 } pd, err := strconv.ParseFloat(val["pd"], 64) if err != nil { pd = 0.1 } pt3, err := strconv.ParseFloat(val["pt3"], 64) if err != nil { pt3 = 0.2 } s.conns.KdDB.Table(TablePartner).Where("is_sell=?", 1).Count(&count) if s.debug > 0 { fmt.Printf("calc params: N0:%v, DT1:%f, DT2:%f, DT3:%f, DT4:%f, pt1:%v, pz:%v, pg:%v, pp:%v, pd:%v, pt3:%v\n", s.n0, s.dt1, s.dt2, s.dt3, s.dt4, pt1, pz, pg, pp, pd, pt3) fmt.Printf("partner total:%v\n", count) } for k := 0; k < count; { wg.Add(1) go s.calc(k, count) k += s.goLimit } wg.Wait() } // 卡片是否可通达 func (s *Service) canNotice(uid int, partnerId int) bool { t := int(time.Now().Unix()) var authkeys []models.AuthKey authErr := s.conns.KdDB.Table(TableAuthKey).Find(&authkeys, "uid=? and auth_type in (?,?,?)", uid, "kdgx_unionid", FPDXXCX, QQXCX).Error if authErr == gorm.ErrRecordNotFound { return false } authkey := make(map[string]string) for _, auth := range authkeys { authkey[auth.AuthType] = auth.AuthKey } if kdgxUnionid, ok := authkey["kdgx_unionid"]; ok { // 分配对象公众号 var openid models.Openid publicErr := s.conns.KdDB.Table(TableOpenid).Where("unionid=? and public_id = ? and subscribe = ?", kdgxUnionid, FPDXGZH, 1).Find(&openid).Error if publicErr == nil { s.conns.DataDb.Table(TableCardState).Where("partner_id=?", partnerId).Updates(map[string]interface{}{"push_fpdx": 1}) return true } else { s.conns.DataDb.Table(TableCardState).Where("partner_id=?", partnerId).Updates(map[string]interface{}{"push_fpdx": 0}) } } // 微信小程序 if kdgxWxxcx, ok := authkey[FPDXXCX]; ok { var formid models.MiniprogramFormid formErr := s.conns.KdDB.Table(TableMiniprogramFormid).Where("openid=? and public_id=? and state=? and created_at > ?", kdgxWxxcx, FPDXXCX, 0, t-86000*7).Find(&formid).Error if formErr == nil { s.conns.DataDb.Table(TableCardState).Where("partner_id=?", partnerId).Updates(map[string]interface{}{"is_mp_push": 1}) return true } } // qq小程序 if kdgxQQxcx, ok := authkey[QQXCX]; ok { var qqformid models.QQFormid qqformErr := s.conns.KdDB.Table(TableQQMiniForms).Where("openid=? and appid=? and send_at=? and created_at > ?", kdgxQQxcx, QQXCX, 0, t-86000*7).Find(&qqformid).Error if qqformErr == nil { s.conns.DataDb.Table(TableCardState).Where("partner_id=?", partnerId).Updates(map[string]interface{}{"is_mp_push": 1}) return true } else { s.conns.DataDb.Table(TableCardState).Where("partner_id=?", partnerId).Updates(map[string]interface{}{"is_mp_push": 0}) } } return false } func (s *Service) appNotice(user *models.User) bool { t := int(time.Now().Unix() - 86400 * 30) if user.LoginAt > t && (user.LoginAppPlatform == "ios" || user.LoginAppPlatform == "android") { return true } else { return false } } func (s *Service) calc(k, cnt int) { defer wg.Done() t := int(time.Now().Unix()) // 获取需要提取的数据段 limit := s.goLimit if k+s.goLimit > cnt { limit = cnt - k } var partners []models.Partner s.conns.KdDB.Table(TablePartner).Where("is_sell=1").Offset(k).Limit(limit).Find(&partners) for _, v := range partners { if s.debug > 0 { fmt.Printf(fmt.Sprintf("partner_id:%d\n", v.Id)) } var user models.User s.conns.KdDB.Table(TableUser).Where("uid=?", v.Uid).Find(&user) if !s.canNotice(v.Uid, v.Id) && !s.appNotice(&user) { /* 通知不可达 */ // 权重分数清空 + 上报日志 s.conns.KdDB.Table(TablePartner).Where("id=?", v.Id).Updates(map[string]interface{}{"base_score": -1, "score": -1}) s.conns.DataDb.Table(TableCardState).Where("partner_id=?", v.Id).Updates(map[string]interface{}{"base_score": -1}) continue } // 最近更新时间 t1 := float64((t - v.UpdateAt) / 3600) if v.UpdateAt == 0 { t1 = float64((t - v.CreatedAt) / 3600) } // 点赞数 z := v.Praises + 1 // 曝光量 b := v.FeedCnt + 2 if b < z { b = z * 2 } // 是否有配音 v.Voice p := 60 if v.Voice.String != "" && !v.Voice.Valid { p = 100 } // 是否有多张图片 d := 50 if v.PhotoSrc.String != "" && !v.PhotoSrc.Valid { d += 10 } if v.Photo1.String != "" && !v.Photo1.Valid { d += 10 } if v.Photo2.String != "" && !v.Photo2.Valid { d += 10 } if v.Photo3.String != "" && !v.Photo3.Valid { d += 10 } if v.Photo4.String != "" && !v.Photo4.Valid { d += 10 } // 最近上线 t2 := float64((t - user.LoginAt) / 3600) if 720 <= t2 && 2160 > t2 { t2 = 100 } else { if 2160 < t2 { // 长时间不登录 => 置为上架不推荐 + 上报日志 s.conns.KdDB.Table(TablePartner).Where("id=? and is_push_feed=?", v.Id, 1).Updates(map[string]interface{}{"is_push_feed": 0, "long_no_login_tag": 1}) s.conns.DataDb.Table(TableCardState).Where("partner_id=?", v.Id).Updates(map[string]interface{}{"is_push_feed": 0}) } } // 发布时间 t3 := float64((t - v.UploadAt) / 3600) if v.UploadAt == 0 { t3 = float64((t - v.CreatedAt) / 3600) } // 最近更新时间 score1 := float64(100) * math.Exp(float64(s.dt1*t1)) * pt1 // 点赞/曝光 score2 := float64(z/b) * 100 * pz // 获取近三天曝光量 var viserCnt int s.conns.KdDB.Table(TableFeedLog).Where("created_at > ? and partner_id=?", t-86400*3, v.Id).Count(&viserCnt) // 曝光量同步到数据库 s.conns.KdDB.Table(TablePartner).Where("id=?", v.Id).Updates(map[string]interface{}{"last_three_day_feed": viserCnt}) // 正态分布=NORMSDIST((近3天曝光数/100))*2*100*近三天曝光数占比 score3 := helpers.Normsdist(float64(viserCnt/100)) * 200 * pg // 声音 score4 := float64(p) * pp // 照片 score5 := float64(d) * pd // 发布时间 score6 := 100 * math.Exp(float64(s.dt3*t3)) * pt3 // 基础分 baseScore := int((score1 + score2 + score3 + score4 + score5 + score6) * math.Exp(float64(s.dt4*t2))) if s.debug > 0 { fmt.Printf("t1=%v\tscore1=%v\n", t1, score1) fmt.Printf("z=%v;b=%v;score2=%v\n", z, b, score2) fmt.Printf("vistor=%v;score3=%v\n", viserCnt, score3) fmt.Printf("score4=%v\n", score4) fmt.Printf("score5=%v\n", score5) fmt.Printf("t3=%v;score6=%v\n", t3, score6) fmt.Printf("t2=%v;baseScore=%v\n", t2, baseScore) } // 人气值 popularity := 0 switch { case 0 <= baseScore && baseScore < 40: popularity = 0 + int(100/40*(baseScore-0)) case 40 <= baseScore && baseScore < 50: popularity = 100 + int(100/10)*(baseScore-40) case 50 <= baseScore && baseScore < 60: popularity = 200 + int(100/10)*(baseScore-50) case 60 <= baseScore && baseScore < 70: popularity = 200 + int(100/10)*(baseScore-60) case 70 <= baseScore && baseScore < 90: popularity = 500 + int(300/10)*(baseScore-70) case 90 <= baseScore: popularity = 800 + int(400/10)*(baseScore-90) default: popularity = 0 } var usersystag models.UserSystag err := s.conns.KdDB.Table(TableUserSystag).Where("uid=?", v.Uid).Find(&usersystag).Error if err != nil { usersystag.Uid = v.Uid usersystag.PopularityShareEndAt = 0 } // 分享加分 if usersystag.PopularityShareEndAt > t { popularity += 100 } // 签到加分 var userstate models.UserState err = s.conns.KdDB.Table("kdgx_fpdx_user_states").Where("uid=? and `key`='popularity_sign_end_at'", v.Uid).Find(&userstate).Error if err != nil && err != gorm.ErrRecordNotFound { value, _ := strconv.Atoi(userstate.Value) if value > t { popularity += 100 } } // 超级会员加分 if user.SupvipEndat > t { popularity += 100 } score := 0 switch { case 0 <= popularity && popularity < 100: score = 0 + int((popularity-0)/((100-0)/(40-0))) case 100 <= popularity && popularity < 200: score = 40 + int((popularity-100)/((100-0)/(50-40))) case 200 <= popularity && popularity < 300: score = 50 + int((popularity-200)/((300-200)/(60-50))) case 300 <= popularity && popularity < 500: score = 60 + int((popularity-300)/(500-300)/(70-60)) case 500 <= popularity && popularity < 800: score = 70 + int((popularity-500)/(800-500)/(90-70)) case 800 <= popularity && popularity < 1200: score = 90 + int((popularity-800)/(1200-800)/(120-90)) default: score = 1 } // 将权重和热度记录写进数据库 + 上报日志 s.conns.KdDB.Table(TablePartner).Where("id=?", v.Id).Updates(map[string]interface{}{"base_score": baseScore, "score": score}) s.conns.DataDb.Table(TableCardState).Where("partner_id=?", v.Id).Updates(map[string]interface{}{"base_score": baseScore}) } }