YiTao/service/recomment_service.go

186 lines
5.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}