diff --git a/app/http/controller/web/animal_controller.go b/app/http/controller/web/animal_controller.go index a39732f..9c0be16 100644 --- a/app/http/controller/web/animal_controller.go +++ b/app/http/controller/web/animal_controller.go @@ -7,11 +7,13 @@ import ( "catface/app/http/validator/core/data_transfer" "catface/app/model" "catface/app/model_es" + "catface/app/model_redis" "catface/app/service/animals/curd" "catface/app/service/upload_file" "catface/app/utils/query_handler" "catface/app/utils/redis_factory" "catface/app/utils/response" + "encoding/json" "os" "path/filepath" "strconv" @@ -37,37 +39,45 @@ func (a *Animals) List(context *gin.Context) { mode := context.GetString(consts.ValidatorPrefix + "mode") // TAG prefer MODE 查询模式。 - var redis_preferCatsId []int64 var key int64 + var redis_selctedCatsId model_redis.SelectedAnimal4Prefer var animalsWithLike []model.AnimalWithLikeList if mode == consts.AnimalPreferMode { key = int64(context.GetFloat64(consts.ValidatorPrefix + "key")) + redis_selctedCatsId.Key = key redisClient := redis_factory.GetOneRedisClient() defer redisClient.ReleaseOneRedisClient() if key != 0 { - redis_preferCatsId, _ = redisClient.Int64sFromList(redisClient.Execute("lrange", key, 0, -1)) + if res, err := redisClient.String(redisClient.Execute("get", key)); err == nil { + json.Unmarshal([]byte(res), &redis_selctedCatsId) + } else { + _ = err + } } else { key = variable.SnowFlake.GetId() } - if len(redis_preferCatsId) == skip { - preferCatsId, preferCats, _ := getPreferCatsId(int(userId), num, skip, attrs) - if len(preferCatsId) > 0 { - redis_preferCatsId = append(redis_preferCatsId, preferCatsId...) - animalsWithLike = append(animalsWithLike, preferCats...) + if redis_selctedCatsId.Length() == skip { + preferCats, _ := getPreferCats(int(userId), num, attrs, &redis_selctedCatsId) + if len(preferCats) > 0 { + animalsWithLike = preferCats } - if _, err := redisClient.String(redisClient.Execute("lpush", key, redis_preferCatsId)); err != nil { + // TODO 刷新 Redis 有效期 + if value, err := json.Marshal(redis_selctedCatsId); err == nil { + if _, err := redisClient.String(redisClient.Execute("set", key, string(value))); err != nil { + // TODO + } } } } - // 计算还需要多少动物 + // 计算还需要多少毛茸茸 num -= len(animalsWithLike) - skip = max(0, skip-len(redis_preferCatsId)) + skip = max(0, skip-redis_selctedCatsId.Length()) if num > 0 { - additionalAnimals := curd.CreateAnimalsCurdFactory().List(attrs, gender, breed, sterilization, status, department, redis_preferCatsId, num, skip, int(userId)) + additionalAnimals := curd.CreateAnimalsCurdFactory().List(attrs, gender, breed, sterilization, status, department, redis_selctedCatsId.GetAllIds(), num, skip, int(userId)) // 将 additionalAnimals 整合到 animalsWithLike 的后面 animalsWithLike = append(animalsWithLike, additionalAnimals...) } @@ -82,10 +92,43 @@ func (a *Animals) List(context *gin.Context) { } } -// UPDATE 就先简单一些,主要就依靠 encounter - animal_id 来获取一个目标。 -func getPreferCatsId(userId, num, skip int, attrs string) (ids []int64, list []model.AnimalWithLikeList, err error) { - // STAGE - 1 模块一,无视过滤条件,获取路遇“过”的 id 列表;先获取 ID,然后再去查询细节信息。 - ids, err = model.CreateEncounterFactory("").EncounteredCats(userId, num, skip) +/** + * @description: 在常规条件过滤查询之前,通过一定规则获取偏好的目标。 + * @param {*} userId + * @param {int} num + * @param {string} attrs + * @param {model_redis.SelectedAnimal4Prefer} redis 使用单独的结构体,更清晰的控制中间状态。 + * @return {*} + */ +func getPreferCats(userId, num int, attrs string, redis *model_redis.SelectedAnimal4Prefer) (list []model.AnimalWithLikeList, err error) { + // STAGE #1 无视过滤条件,获取路遇“过”的 id 列表;先获取 ID,然后再去查询细节信息。 + ids, err := model.CreateEncounterFactory("").EncounteredCatsId(userId, num, redis.NumEnc(), redis.NewCatsId) + + // STAGE #2 获取近期新增的毛茸茸;只在第一次操作 && 数量不够时 启用。 + var idsNew []int64 + if !redis.PassNew() && len(ids) < num { + // 获取近期新增的毛茸茸 + newCats, _ := model.CreateAnimalFactory("").NewCatsId(3, 0) // INFO 硬编码获取最新的 3 个。 + + // 去重:默认只会在首次查询时启用,所以只需要去重 STAGE#1 的 ids。 + uniqueIds := make(map[int64]bool) + for _, id := range ids { + uniqueIds[id] = true + } + + for _, id := range newCats { + if _, ok := uniqueIds[id]; !ok { + idsNew = append(idsNew, id) + } + } + } + + // 3. 合并然后查询数据 && 处理 redis 之间的处理。 + redis.AppendEncIds(ids) + if len(idsNew) > 0 { + ids = append(ids, idsNew...) + redis.NewCatsId = idsNew + } if err == nil && len(ids) > 0 { attrsSlice := query_handler.StringToStringArray(attrs) diff --git a/app/model/animal.go b/app/model/animal.go index db74c2b..716c1aa 100644 --- a/app/model/animal.go +++ b/app/model/animal.go @@ -134,3 +134,9 @@ func (a *Animal) InsertDate(c *gin.Context) (tmp Animal, ok bool) { } return tmp, false } + +func (a *Animal) NewCatsId(num, skip int) (temp []int64, err error) { + db := a.DB.Table(a.TableName()).Limit(int(num)).Offset(int(skip)).Select("id") + err = db.Order("updated_at DESC").Find(&temp).Error + return +} diff --git a/app/model/encounter.go b/app/model/encounter.go index 6654aa7..b9fd45f 100644 --- a/app/model/encounter.go +++ b/app/model/encounter.go @@ -179,7 +179,7 @@ func (e *Encounter) ShowByIDs(ids []int64, attrs ...string) (temp []Encounter) { * @param {int} num 限制查询的数量; * @return {*} */ -func (e *Encounter) EncounteredCats(user_id, num, skip int) ([]int64, error) { +func (e *Encounter) EncounteredCatsId(user_id, num, skip int, notInIds []int64) ([]int64, error) { sql := `SELECT DISTINCT eal.animal_id FROM encounter_animal_links eal JOIN encounters e diff --git a/app/model_redis/selectedAnimal4Prefer.go b/app/model_redis/selectedAnimal4Prefer.go new file mode 100644 index 0000000..329b93b --- /dev/null +++ b/app/model_redis/selectedAnimal4Prefer.go @@ -0,0 +1,31 @@ +package model_redis + +// INFO 辅助 animal - list - prefer 模式下的查询特化。 + +type SelectedAnimal4Prefer struct { + Key int64 `json:"-"` // redis 的 key 值 + EncounteredCatsId []int64 `json:"encountered_cats_id"` // #1 对应第一阶段:近期路遇关联 + NewCatsId []int64 `json:"new_cats_id"` // #2 对应第二阶段:近期新增 +} + +func (s *SelectedAnimal4Prefer) PassNew() bool { + return s.Key != 0 +} + +func (s *SelectedAnimal4Prefer) Length() int { + return len(s.NewCatsId) + len(s.EncounteredCatsId) +} + +func (s *SelectedAnimal4Prefer) NumEnc() int { + return len(s.EncounteredCatsId) +} + +func (s *SelectedAnimal4Prefer) GetAllIds() []int64 { + return append(s.EncounteredCatsId, s.NewCatsId...) +} + +func (s *SelectedAnimal4Prefer) AppendEncIds(ids []int64) { + for _, id := range ids { + s.EncounteredCatsId = append(s.EncounteredCatsId, int64(id)) + } +} diff --git a/test/redis_test.go b/test/redis_test.go index 83e3942..2dc06aa 100644 --- a/test/redis_test.go +++ b/test/redis_test.go @@ -2,8 +2,10 @@ package test import ( "catface/app/global/variable" + "catface/app/model_redis" "catface/app/utils/redis_factory" _ "catface/bootstrap" + "encoding/json" "fmt" "testing" "time" @@ -170,3 +172,30 @@ func TestRedisList(t *testing.T) { t.Logf("单元测试通过,%v\n", res) } } + +// 测试 struct 处理 By Json +func TestStruct(t *testing.T) { + redisClient := redis_factory.GetOneRedisClient() + defer redisClient.ReleaseOneRedisClient() + + tmp := model_redis.SelectedAnimal4Prefer{ + Key: 2, + NewCatsId: []int64{1, 2}, + EncounteredCatsId: []int64{3, 4}, + } + + key := variable.SnowFlake.GetId() + value, _ := json.Marshal(tmp) + t.Log(value) + _, err := redisClient.String(redisClient.Execute("set", key, string(value))) + if err != nil { + t.Errorf("单元测试失败,%s\n", err.Error()) + } + if res, err := redisClient.String(redisClient.Execute("get", key)); err != nil { + t.Errorf("单元测试失败,%s\n", err.Error()) + } else { + var tmp2 model_redis.SelectedAnimal4Prefer + json.Unmarshal([]byte(res), &tmp2) + t.Logf("单元测试通过,%v %v %v\n", tmp2, tmp2.NewCatsId, tmp2.EncounteredCatsId) + } +}