service.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. package services
  2. import (
  3. "fmt"
  4. "fpdxfeed/config"
  5. "fpdxfeed/database"
  6. "fpdxfeed/helpers"
  7. "fpdxfeed/models"
  8. "github.com/gomodule/redigo/redis"
  9. "github.com/jinzhu/gorm"
  10. "log"
  11. "math"
  12. "strconv"
  13. "sync"
  14. "time"
  15. )
  16. const (
  17. FPDXGZH = "gh_b598cb7474d8"
  18. FPDXXCX = "gh_01c089b58dda"
  19. QQXCX = "1109365561"
  20. CALCPARAMS = "msy-feed-params"
  21. TablePartner = "kdgx_partner_charge_partner"
  22. TableUser = "kdgx_partner_charge_user"
  23. TableAuthKey = "kdgx_user_auth_key"
  24. TableNoticeManage = "kdgx_miniprogram_notice_manage"
  25. TableOpenid = "kddx_user_openid"
  26. TableCardState = "fpdx_card_state_list"
  27. TableMiniprogramFormid = "kdgx_charge_fpdx_miniprogram_formid"
  28. TableQQMiniForms = "kdgx_qqmini_forms"
  29. TableUserSystag = "kdgx_fpdx_user_systag"
  30. TableFeedLog = "kdgx_fpdx_feed_log"
  31. )
  32. var (
  33. wg sync.WaitGroup
  34. pt1 float64
  35. pz float64
  36. pg float64
  37. pp float64
  38. pd float64
  39. pt3 float64
  40. count int
  41. t0 int
  42. )
  43. type Service struct {
  44. config *config.Config // 配置文件
  45. conns *database.Conns // 链接
  46. debug int // 模式
  47. goLimit int // 整理卡片进程的go程粒度
  48. sleep time.Duration // 休眠时长
  49. n0 int // 时间衰减初始分
  50. dt1 float64 // 最近更新加分衰变率1
  51. dt2 float64 // 赞数衰变率2*z(x)
  52. dt3 float64 // 发布时间加分衰变率3
  53. dt4 float64 // 最近上线加分衰变率4
  54. }
  55. 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) {
  56. if 2 == debug {
  57. conns.KdDB = conns.KdDB.Debug()
  58. conns.DataDb = conns.DataDb.Debug()
  59. }
  60. service = &Service{
  61. config: conf,
  62. conns: conns,
  63. debug: debug,
  64. goLimit: goLimit,
  65. sleep: sleep,
  66. n0: n0,
  67. dt1: dt1,
  68. dt2: dt2,
  69. dt3: dt3,
  70. dt4: dt4,
  71. }
  72. return
  73. }
  74. func (s *Service) Run() {
  75. s.execTask()
  76. }
  77. // 计算所有卡片基础分
  78. func (s *Service) execTask() {
  79. t0 = int(time.Now().Unix())
  80. val, err := redis.StringMap(s.conns.Redis.Do("hgetall", CALCPARAMS))
  81. if err != nil {
  82. // 异常退出
  83. log.Fatal(fmt.Errorf("获取计算参数错误:%v", err))
  84. }
  85. pt1, err := strconv.ParseFloat(val["pt1"], 64)
  86. if err != nil {
  87. pt1 = 0.2
  88. }
  89. pz, err := strconv.ParseFloat(val["pz"], 64)
  90. if err != nil {
  91. pz = 0.5
  92. }
  93. pg, err := strconv.ParseFloat(val["pg"], 64)
  94. if err != nil {
  95. pg = 0.5
  96. }
  97. pp, err := strconv.ParseFloat(val["pp"], 64)
  98. if err != nil {
  99. pp = 0.15
  100. }
  101. pd, err := strconv.ParseFloat(val["pd"], 64)
  102. if err != nil {
  103. pd = 0.1
  104. }
  105. pt3, err := strconv.ParseFloat(val["pt3"], 64)
  106. if err != nil {
  107. pt3 = 0.2
  108. }
  109. s.conns.KdDB.Table(TablePartner).Where("is_sell=?", 1).Count(&count)
  110. if s.debug > 0 {
  111. 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)
  112. fmt.Printf("partner total:%v\n", count)
  113. }
  114. for k := 0; k < count; {
  115. wg.Add(1)
  116. go s.calc(k, count)
  117. k += s.goLimit
  118. }
  119. wg.Wait()
  120. }
  121. // 卡片是否可通达
  122. func (s *Service) canNotice(uid int, partnerId int) bool {
  123. t := int(time.Now().Unix())
  124. var authkeys []models.AuthKey
  125. authErr := s.conns.KdDB.Table(TableAuthKey).Find(&authkeys, "uid=? and auth_type in (?,?,?)", uid, "kdgx_unionid", FPDXXCX, QQXCX).Error
  126. if authErr == gorm.ErrRecordNotFound {
  127. return false
  128. }
  129. authkey := make(map[string]string)
  130. for _, auth := range authkeys {
  131. authkey[auth.AuthType] = auth.AuthKey
  132. }
  133. if kdgxUnionid, ok := authkey["kdgx_unionid"]; ok {
  134. // 分配对象公众号
  135. var openid models.Openid
  136. publicErr := s.conns.KdDB.Table(TableOpenid).Where("unionid=? and public_id = ? and subscribe = ?", kdgxUnionid, FPDXGZH, 1).Find(&openid).Error
  137. if publicErr == nil {
  138. s.conns.DataDb.Table(TableCardState).Where("partner_id=?", partnerId).Updates(map[string]interface{}{"push_fpdx": 1})
  139. return true
  140. } else {
  141. s.conns.DataDb.Table(TableCardState).Where("partner_id=?", partnerId).Updates(map[string]interface{}{"push_fpdx": 0})
  142. }
  143. }
  144. // 微信小程序
  145. if kdgxWxxcx, ok := authkey[FPDXXCX]; ok {
  146. var formid models.MiniprogramFormid
  147. 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
  148. if formErr == nil {
  149. s.conns.DataDb.Table(TableCardState).Where("partner_id=?", partnerId).Updates(map[string]interface{}{"is_mp_push": 1})
  150. return true
  151. }
  152. }
  153. // qq小程序
  154. if kdgxQQxcx, ok := authkey[QQXCX]; ok {
  155. var qqformid models.QQFormid
  156. 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
  157. if qqformErr == nil {
  158. s.conns.DataDb.Table(TableCardState).Where("partner_id=?", partnerId).Updates(map[string]interface{}{"is_mp_push": 1})
  159. return true
  160. } else {
  161. s.conns.DataDb.Table(TableCardState).Where("partner_id=?", partnerId).Updates(map[string]interface{}{"is_mp_push": 0})
  162. }
  163. }
  164. return false
  165. }
  166. func (s *Service) appNotice(user *models.User) bool {
  167. t := int(time.Now().Unix() - 86400 * 30)
  168. if user.LoginAt > t && (user.LoginAppPlatform == "ios" || user.LoginAppPlatform == "android") {
  169. return true
  170. } else {
  171. return false
  172. }
  173. }
  174. func (s *Service) calc(k, cnt int) {
  175. defer wg.Done()
  176. t := int(time.Now().Unix())
  177. // 获取需要提取的数据段
  178. limit := s.goLimit
  179. if k+s.goLimit > cnt {
  180. limit = cnt - k
  181. }
  182. var partners []models.Partner
  183. s.conns.KdDB.Table(TablePartner).Where("is_sell=1").Offset(k).Limit(limit).Find(&partners)
  184. for _, v := range partners {
  185. if s.debug > 0 {
  186. fmt.Printf(fmt.Sprintf("partner_id:%d\n", v.Id))
  187. }
  188. var user models.User
  189. s.conns.KdDB.Table(TableUser).Where("uid=?", v.Uid).Find(&user)
  190. if !s.canNotice(v.Uid, v.Id) && !s.appNotice(&user) {
  191. /* 通知不可达 */
  192. // 权重分数清空 + 上报日志
  193. s.conns.KdDB.Table(TablePartner).Where("id=?", v.Id).Updates(map[string]interface{}{"base_score": -1, "score": -1})
  194. s.conns.DataDb.Table(TableCardState).Where("partner_id=?", v.Id).Updates(map[string]interface{}{"base_score": -1})
  195. continue
  196. }
  197. // 最近更新时间
  198. t1 := float64((t - v.UpdateAt) / 3600)
  199. if v.UpdateAt == 0 {
  200. t1 = float64((t - v.CreatedAt) / 3600)
  201. }
  202. // 点赞数
  203. z := v.Praises + 1
  204. // 曝光量
  205. b := v.FeedCnt + 2
  206. if b < z {
  207. b = z * 2
  208. }
  209. // 是否有配音 v.Voice
  210. p := 60
  211. if v.Voice.String != "" && !v.Voice.Valid {
  212. p = 100
  213. }
  214. // 是否有多张图片
  215. d := 50
  216. if v.PhotoSrc.String != "" && !v.PhotoSrc.Valid {
  217. d += 10
  218. }
  219. if v.Photo1.String != "" && !v.Photo1.Valid {
  220. d += 10
  221. }
  222. if v.Photo2.String != "" && !v.Photo2.Valid {
  223. d += 10
  224. }
  225. if v.Photo3.String != "" && !v.Photo3.Valid {
  226. d += 10
  227. }
  228. if v.Photo4.String != "" && !v.Photo4.Valid {
  229. d += 10
  230. }
  231. // 最近上线
  232. t2 := float64((t - user.LoginAt) / 3600)
  233. if 720 <= t2 && 2160 > t2 {
  234. t2 = 100
  235. } else {
  236. if 2160 < t2 {
  237. // 长时间不登录 => 置为上架不推荐 + 上报日志
  238. 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})
  239. s.conns.DataDb.Table(TableCardState).Where("partner_id=?", v.Id).Updates(map[string]interface{}{"is_push_feed": 0})
  240. }
  241. }
  242. // 发布时间
  243. t3 := float64((t - v.UploadAt) / 3600)
  244. if v.UploadAt == 0 {
  245. t3 = float64((t - v.CreatedAt) / 3600)
  246. }
  247. // 最近更新时间
  248. score1 := float64(100) * math.Exp(float64(s.dt1*t1)) * pt1
  249. // 点赞/曝光
  250. score2 := float64(z/b) * 100 * pz
  251. // 获取近三天曝光量
  252. var viserCnt int
  253. s.conns.KdDB.Table(TableFeedLog).Where("created_at > ? and partner_id=?", t-86400*3, v.Id).Count(&viserCnt)
  254. // 曝光量同步到数据库
  255. s.conns.KdDB.Table(TablePartner).Where("id=?", v.Id).Updates(map[string]interface{}{"last_three_day_feed": viserCnt})
  256. // 正态分布=NORMSDIST((近3天曝光数/100))*2*100*近三天曝光数占比
  257. score3 := helpers.Normsdist(float64(viserCnt/100)) * 200 * pg
  258. // 声音
  259. score4 := float64(p) * pp
  260. // 照片
  261. score5 := float64(d) * pd
  262. // 发布时间
  263. score6 := 100 * math.Exp(float64(s.dt3*t3)) * pt3
  264. // 基础分
  265. baseScore := int((score1 + score2 + score3 + score4 + score5 + score6) * math.Exp(float64(s.dt4*t2)))
  266. if s.debug > 0 {
  267. fmt.Printf("t1=%v\tscore1=%v\n", t1, score1)
  268. fmt.Printf("z=%v;b=%v;score2=%v\n", z, b, score2)
  269. fmt.Printf("vistor=%v;score3=%v\n", viserCnt, score3)
  270. fmt.Printf("score4=%v\n", score4)
  271. fmt.Printf("score5=%v\n", score5)
  272. fmt.Printf("t3=%v;score6=%v\n", t3, score6)
  273. fmt.Printf("t2=%v;baseScore=%v\n", t2, baseScore)
  274. }
  275. // 人气值
  276. popularity := 0
  277. switch {
  278. case 0 <= baseScore && baseScore < 40:
  279. popularity = 0 + int(100/40*(baseScore-0))
  280. case 40 <= baseScore && baseScore < 50:
  281. popularity = 100 + int(100/10)*(baseScore-40)
  282. case 50 <= baseScore && baseScore < 60:
  283. popularity = 200 + int(100/10)*(baseScore-50)
  284. case 60 <= baseScore && baseScore < 70:
  285. popularity = 200 + int(100/10)*(baseScore-60)
  286. case 70 <= baseScore && baseScore < 90:
  287. popularity = 500 + int(300/10)*(baseScore-70)
  288. case 90 <= baseScore:
  289. popularity = 800 + int(400/10)*(baseScore-90)
  290. default:
  291. popularity = 0
  292. }
  293. var usersystag models.UserSystag
  294. err := s.conns.KdDB.Table(TableUserSystag).Where("uid=?", v.Uid).Find(&usersystag).Error
  295. if err != nil {
  296. usersystag.Uid = v.Uid
  297. usersystag.PopularityShareEndAt = 0
  298. }
  299. // 分享加分
  300. if usersystag.PopularityShareEndAt > t {
  301. popularity += 100
  302. }
  303. // 签到加分
  304. var userstate models.UserState
  305. err = s.conns.KdDB.Table("kdgx_fpdx_user_states").Where("uid=? and `key`='popularity_sign_end_at'", v.Uid).Find(&userstate).Error
  306. if err != nil && err != gorm.ErrRecordNotFound {
  307. value, _ := strconv.Atoi(userstate.Value)
  308. if value > t {
  309. popularity += 100
  310. }
  311. }
  312. // 超级会员加分
  313. if user.SupvipEndat > t {
  314. popularity += 100
  315. }
  316. score := 0
  317. switch {
  318. case 0 <= popularity && popularity < 100:
  319. score = 0 + int((popularity-0)/((100-0)/(40-0)))
  320. case 100 <= popularity && popularity < 200:
  321. score = 40 + int((popularity-100)/((100-0)/(50-40)))
  322. case 200 <= popularity && popularity < 300:
  323. score = 50 + int((popularity-200)/((300-200)/(60-50)))
  324. case 300 <= popularity && popularity < 500:
  325. score = 60 + int((popularity-300)/(500-300)/(70-60))
  326. case 500 <= popularity && popularity < 800:
  327. score = 70 + int((popularity-500)/(800-500)/(90-70))
  328. case 800 <= popularity && popularity < 1200:
  329. score = 90 + int((popularity-800)/(1200-800)/(120-90))
  330. default:
  331. score = 1
  332. }
  333. // 将权重和热度记录写进数据库 + 上报日志
  334. s.conns.KdDB.Table(TablePartner).Where("id=?", v.Id).Updates(map[string]interface{}{"base_score": baseScore, "score": score})
  335. s.conns.DataDb.Table(TableCardState).Where("partner_id=?", v.Id).Updates(map[string]interface{}{"base_score": baseScore})
  336. }
  337. }