YiTao/service/recomment_service.go

186 lines
5.5 KiB
Go
Raw Permalink Normal View History

2025-01-21 14:37:34 +08:00
package service
import (
"math"
2025-01-21 14:37:34 +08:00
"sort"
"strconv"
"yitao/ecode"
"yitao/model"
"yitao/util"
2025-01-21 14:37:34 +08:00
)
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天 一个单位
2025-01-21 14:37:34 +08:00
// 类型推荐程度 = (类型总体热度*0.4 + 用户喜爱程度*0.6) * Wtime
// 将类型推荐程度进行拟合,得到一个商品符合类型多的商品
// 然后在对所有商品对于所有用户的浏览量进行排序,采用类型拟合优先,类型完全一致的商品采用浏览量排序
// 因为用户会一直刷新列表
// 在前15名中随机选取5个商品在15-27随机选取4个商品在27-30中随机选取1个商品
// 维持三轮,如果用户三轮都不满意,那么判定用户是来找茬的,建议前端直接跳个鬼图
// 这样做可以也可以保证热门商品可能被重复推荐,让用户狠狠购买!
// 该算法运行过程中计算与数据库负荷较大用户喜爱程度缓存60秒商品总体热度缓存600秒单一商品浏览量缓存60秒Wtime缓存 600 秒
2025-01-21 14:37:34 +08:00
// @TODO: 采用布隆过滤器,降低推荐过的商品的概率
// @TODO[IMPORTANT]: 优化 Wtime 算法!!! IMPORTANT
2025-01-21 14:37:34 +08:00
//缓存机制每次用户的记录都会隔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)
2025-01-21 14:37:34 +08:00
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)
2025-01-21 14:37:34 +08:00
// 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])
}
// 计算冷启动问题
2025-01-21 14:37:34 +08:00
// 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) {
2025-01-21 14:37:34 +08:00
result = append(result, items[i])
}
// 在27-30中随机选取1个商品
for _, i := range util.RandomSample(27, 30, 1) {
2025-01-21 14:37:34 +08:00
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
}