package service import ( "math" "sort" "strconv" "yitao/ecode" "yitao/model" "yitao/util" ) type RecommentService struct{} // 推荐算法模块 /*********************************************************************************/ // 获取用户个人推荐商品 func (s *RecommentService) GetPersonalHotItems(uid uint) (items []model.ItemModel, e *ecode.Ecode) { e = ecode.OK() // 在算法中处理的都是“类型”,而最后返回的是“商品” // 类型总体热度 = 该类商品在过去30天的浏览量*0.5 + 该类商品的总被购买量*0.5 // 用户喜爱程度 = 用户对该类商品的浏览量*0.4 + 用户对该类商品的购买量*0.6 // 时间指数:为解决商品冷启动问题:Wtime = 1/(1+e^(-x)) // x 为 5天 一个单位 // 类型推荐程度 = (类型总体热度*0.4 + 用户喜爱程度*0.6) * Wtime // 将类型推荐程度进行拟合,得到一个商品符合类型多的商品 // 然后在对所有商品对于所有用户的浏览量进行排序,采用类型拟合优先,类型完全一致的商品采用浏览量排序 // 因为用户会一直刷新列表 // 在前15名中随机选取5个商品,在15-27随机选取4个商品,在27-30中随机选取1个商品 // 维持三轮,如果用户三轮都不满意,那么判定用户是来找茬的,建议前端直接跳个鬼图 // 这样做可以也可以保证热门商品可能被重复推荐,让用户狠狠购买! // 该算法运行过程中计算与数据库负荷较大,用户喜爱程度缓存60秒,商品总体热度缓存600秒,单一商品浏览量缓存60秒,Wtime缓存 600 秒 // @TODO: 采用布隆过滤器,降低推荐过的商品的概率 // @TODO[IMPORTANT]: 优化 Wtime 算法!!! IMPORTANT //缓存机制,每次用户的记录都会隔60秒更新 // userHotTypes := model.RedisRemember(strconv.FormatUint(uint64(uid), 10)+"_hot_types", 60, func() interface{} { // return s.LoadUserHotTypes(uid) // }) // allHotTypes := model.RedisRemember("all_hot_types", 600, func() interface{} { // return s.LoadAllHotTypes() // }) userHotTypes := new(map[int]int) model.RedisRememberRet(strconv.FormatUint(uint64(uid), 10)+"_hot_types", 60, func() interface{} { return s.LoadUserHotTypes(uid) }, &userHotTypes) allHotTypes := new(map[int]int) model.RedisRememberRet("all_hot_types", 600, func() interface{} { return s.LoadAllHotTypes() }, &allHotTypes) // 整合 allTypes := make(map[int]int) for k, v := range *userHotTypes { allTypes[k] += v } for k, v := range *allHotTypes { allTypes[k] += v } // 在所有商品中匹配符合类型多的 // 1. 获取所有商品 var allItems []model.ItemModel var allItemsWtime = make(map[uint]float64) model.DB.Find(&allItems) // 冷启动部分:不知道可不可用,反正先用着 // 计算 Wtime // 1/(1+e^(-x)) model.RedisRememberRet("all_items_wtime", 600, func() interface{} { now := util.GetTime() for _, v := range allItems { allItemsWtime[v.ID] = 1 / (1 + math.Pow(2.7181, -float64((now-v.CreatedAt.Unix())/86400/5))) } return allItemsWtime }, &allItemsWtime) // 2. 计算商品的类型匹配度 var itemMatch = make(map[uint]int) for _, item := range allItems { for _, t := range item.Types { itemMatch[item.ID] += allTypes[t] } } for k, v := range itemMatch { itemMatch[k] = int(float64(v) * allItemsWtime[k]) } // 计算冷启动问题 // 3. 对商品进行排序 type kv struct { Key uint Value int } var sortedItems []kv for k, v := range itemMatch { sortedItems = append(sortedItems, kv{k, v}) } sort.Slice(sortedItems, func(i, j int) bool { return sortedItems[i].Value > sortedItems[j].Value }) // 4. 根据排序结果获取商品 for _, kv := range sortedItems { var item model.ItemModel model.DB.First(&item, kv.Key) items = append(items, item) } // 5. 随机选取商品 var result []model.ItemModel if len(items) > 30 { // 在前15名中随机选取5个商品 for _, i := range util.RandomSample(0, 15, 5) { result = append(result, items[i]) } // 在15-27随机选取4个商品 for _, i := range util.RandomSample(15, 27, 4) { result = append(result, items[i]) } // 在27-30中随机选取1个商品 for _, i := range util.RandomSample(27, 30, 1) { result = append(result, items[i]) } } else { result = items } return result, e } func (s *RecommentService) LoadUserHotTypes(uid uint) map[int]int { // 1. 获取用户的历史记录 var history []model.HistoryModel model.DB.Preload("Item").Where("user_id=?", uid).Find(&history) // 提取对每种商品的类型 var allTypes = make(map[int]int) for _, h := range history { for _, t := range h.Item.Types { allTypes[t] += 4 } } // 2. 获取用户的购买记录 var buyments []model.BuymentModel model.DB.Preload("Item").Preload("User").Where("user_id=?", uid).Find(&buyments) for _, b := range buyments { for _, t := range b.Item.Types { allTypes[t] += 6 } } return allTypes } func (s *RecommentService) LoadAllHotTypes() map[int]int { // 获取全部的商品浏览记录 var history []model.HistoryModel model.DB.Preload("Item").Find(&history) // 提取对每种商品的类型 var allTypes = make(map[int]int) for _, h := range history { for _, t := range h.Item.Types { allTypes[t] += 5 } } // 获取全部的商品购买记录 var buyments []model.BuymentModel model.DB.Preload("Item").Preload("User").Find(&buyments) for _, b := range buyments { for _, t := range b.Item.Types { allTypes[t] += 5 } } return allTypes }