package system import ( "encoding/json" "fmt" "github.com/redis/go-redis/v9" "gorm.io/gorm" "log" "math" "reflect" "regexp" "server/config" "server/plugin/common/param" "server/plugin/db" "strconv" "strings" "time" ) // SearchInfo 存储用于检索的信息 type SearchInfo struct { gorm.Model Mid int64 `json:"mid"` //影片ID gorm:"uniqueIndex:idx_mid" Cid int64 `json:"cid"` //分类ID Pid int64 `json:"pid"` //上级分类ID Name string `json:"name"` // 片名 SubTitle string `json:"subTitle"` // 影片子标题 CName string `json:"cName"` // 分类名称 ClassTag string `json:"classTag"` //类型标签 Area string `json:"area"` // 地区 Language string `json:"language"` // 语言 Year int64 `json:"year"` // 年份 Initial string `json:"initial"` // 首字母 Score float64 `json:"score"` //评分 UpdateStamp int64 `json:"updateStamp"` // 更新时间 Hits int64 `json:"hits"` // 热度排行 State string `json:"state"` //状态 正片|预告 Remarks string `json:"remarks"` // 完结 | 更新至x集 ReleaseStamp int64 `json:"releaseStamp"` //上映时间 时间戳 } // Tag 影片分类标签结构体 type Tag struct { Name string `json:"name"` Value interface{} `json:"value"` } func (s *SearchInfo) TableName() string { return config.SearchTableName } // ================================= Spider 数据处理(redis) ================================= // RdbSaveSearchInfo 批量保存检索信息到redis func RdbSaveSearchInfo(list []SearchInfo) { // 1.整合一下zset数据集 var members []redis.Z for _, s := range list { member, _ := json.Marshal(s) members = append(members, redis.Z{Score: float64(s.Mid), Member: member}) } // 2.批量保存到zset集合中 db.Rdb.ZAdd(db.Cxt, config.SearchInfoTemp, members...) } // FilmZero 删除所有库存数据 func FilmZero() { // 删除redis中当前库存储的所有数据 //db.Rdb.FlushDB(db.Cxt) db.Rdb.Del(db.Cxt, db.Rdb.Keys(db.Cxt, "MovieBasicInfoKey*").Val()...) db.Rdb.Del(db.Cxt, db.Rdb.Keys(db.Cxt, "MovieDetail*").Val()...) db.Rdb.Del(db.Cxt, db.Rdb.Keys(db.Cxt, "MultipleSource*").Val()...) db.Rdb.Del(db.Cxt, db.Rdb.Keys(db.Cxt, "OriginalResource*").Val()...) db.Rdb.Del(db.Cxt, db.Rdb.Keys(db.Cxt, "Search*").Val()...) // 删除mysql中留存的检索表 var s SearchInfo //db.Mdb.Exec(fmt.Sprintf(`drop table if exists %s`, s.TableName())) // 截断数据表 truncate table users if ExistSearchTable() { db.Mdb.Exec(fmt.Sprintf("TRUNCATE table %s", s.TableName())) } } // ResetSearchTable 重置Search表 func ResetSearchTable() { // 删除 Search 表 var s SearchInfo db.Mdb.Exec(fmt.Sprintf("drop table if exists %s", s.TableName())) // 重新创建 Search 表 CreateSearchTable() } // DelMtPlay 清空附加播放源信息 func DelMtPlay(keys []string) { db.Rdb.Del(db.Cxt, keys...) } /* SearchKeyword 设置search关键字集合(影片分类检索类型数据) 类型, 剧情 , 地区, 语言, 年份, 首字母, 排序 1. 在影片详情缓存到redis时将影片的相关数据进行记录, 存在相同类型则分值加一 2. 通过分值对类型进行排序类型展示到页面 */ func SaveSearchTag(search SearchInfo) { // 声明用于存储采集的影片的分类检索信息 //searchMap := make(map[string][]map[string]int) // Redis中的记录形式 Search:SearchKeys:Pid1:Title Hash // Redis中的记录形式 Search:SearchKeys:Pid1:xxx Hash // 获取redis中的searchMap key := fmt.Sprintf(config.SearchTitle, search.Pid) searchMap := db.Rdb.HGetAll(db.Cxt, key).Val() // 是否存在对应分类的map, 如果不存在则缓存一份 if len(searchMap) == 0 { searchMap = make(map[string]string) searchMap["Category"] = "类型" searchMap["Plot"] = "剧情" searchMap["Area"] = "地区" searchMap["Language"] = "语言" searchMap["Year"] = "年份" searchMap["Initial"] = "首字母" searchMap["Sort"] = "排序" db.Rdb.HMSet(db.Cxt, key, searchMap) } // 对searchMap中的各个类型进行处理 for k, _ := range searchMap { tagKey := fmt.Sprintf(config.SearchTag, search.Pid, k) tagCount := db.Rdb.ZCard(db.Cxt, tagKey).Val() switch k { case "Category": // 获取 Category 数据, 如果不存在则缓存一份 if tagCount == 0 { for _, t := range GetChildrenTree(search.Pid) { db.Rdb.ZAdd(db.Cxt, fmt.Sprintf(config.SearchTag, search.Pid, k), redis.Z{Score: float64(-t.Id), Member: fmt.Sprintf("%v:%v", t.Name, t.Id)}) } } case "Year": // 获取 Year 数据, 如果不存在则缓存一份 if tagCount == 0 { currentYear := time.Now().Year() for i := 0; i < 12; i++ { db.Rdb.ZAdd(db.Cxt, fmt.Sprintf(config.SearchTag, search.Pid, k), redis.Z{Score: float64(currentYear - i), Member: fmt.Sprintf("%v:%v", currentYear-i, currentYear-i)}) } } case "Initial": // 如果不存在 首字母 Tag 数据, 则缓存一份 if tagCount == 0 { for i := 65; i <= 90; i++ { db.Rdb.ZAdd(db.Cxt, fmt.Sprintf(config.SearchTag, search.Pid, k), redis.Z{Score: float64(90 - i), Member: fmt.Sprintf("%c:%c", i, i)}) } } case "Sort": if tagCount == 0 { tags := []redis.Z{ {3, "时间排序:update_stamp"}, {2, "人气排序:hits"}, {1, "评分排序:score"}, {0, "最新上映:release_stamp"}, } db.Rdb.ZAdd(db.Cxt, fmt.Sprintf(config.SearchTag, search.Pid, k), tags...) } case "Plot": HandleSearchTags(search.ClassTag, tagKey) case "Area": HandleSearchTags(search.Area, tagKey) case "Language": HandleSearchTags(search.Language, tagKey) default: break } } } func HandleSearchTags(preTags string, k string) { // 先处理字符串中的空白符 然后对处理前的tag字符串进行分割 preTags = regexp.MustCompile(`[\s\n\r]+`).ReplaceAllString(preTags, "") f := func(sep string) { for _, t := range strings.Split(preTags, sep) { // 获取 tag对应的score score := db.Rdb.ZScore(db.Cxt, k, fmt.Sprintf("%v:%v", t, t)).Val() // 在原score的基础上+1 重新存入redis中 db.Rdb.ZAdd(db.Cxt, k, redis.Z{Score: score + 1, Member: fmt.Sprintf("%v:%v", t, t)}) } } switch { case strings.Contains(preTags, "/"): f("/") case strings.Contains(preTags, ","): f(",") case strings.Contains(preTags, ","): f(",") case strings.Contains(preTags, "、"): f("、") default: // 获取 tag对应的score if len(preTags) == 0 { // 如果没有 tag信息则不进行缓存 //db.Rdb.ZAdd(db.Cxt, k, redis.Z{Score: 0, Member: fmt.Sprintf("%v:%v", "未知", "未知")}) } else if preTags == "其它" { db.Rdb.ZAdd(db.Cxt, k, redis.Z{Score: 0, Member: fmt.Sprintf("%v:%v", preTags, preTags)}) } else { score := db.Rdb.ZScore(db.Cxt, k, fmt.Sprintf("%v:%v", preTags, preTags)).Val() db.Rdb.ZAdd(db.Cxt, k, redis.Z{Score: score + 1, Member: fmt.Sprintf("%v:%v", preTags, preTags)}) } } } func BatchHandleSearchTag(infos ...SearchInfo) { for _, info := range infos { SaveSearchTag(info) } } // ================================= Spider 数据处理(mysql) ================================= // CreateSearchTable 创建存储检索信息的数据表 func CreateSearchTable() { // 如果不存在则创建表 if !ExistSearchTable() { err := db.Mdb.AutoMigrate(&SearchInfo{}) if err != nil { log.Println("Create Table SearchInfo Failed: ", err) } } } // ExistSearchTable 是否存在Search Table func ExistSearchTable() bool { // 1. 判断表中是否存在当前表 return db.Mdb.Migrator().HasTable(&SearchInfo{}) } // AddSearchIndex search表中数据保存完毕后 将常用字段添加索引提高查询效率 func AddSearchIndex() { var s SearchInfo tableName := s.TableName() // 添加索引 db.Mdb.Exec(fmt.Sprintf("CREATE UNIQUE INDEX idx_mid ON %s (mid)", tableName)) db.Mdb.Exec(fmt.Sprintf("CREATE INDEX idx_time ON %s (update_stamp DESC)", tableName)) db.Mdb.Exec(fmt.Sprintf("CREATE INDEX idx_hits ON %s (hits DESC)", tableName)) db.Mdb.Exec(fmt.Sprintf("CREATE INDEX idx_score ON %s (score DESC)", tableName)) db.Mdb.Exec(fmt.Sprintf("CREATE INDEX idx_release ON %s (release_stamp DESC)", tableName)) db.Mdb.Exec(fmt.Sprintf("CREATE INDEX idx_year ON %s (year DESC)", tableName)) } // BatchSave 批量保存影片search信息 func BatchSave(list []SearchInfo) { tx := db.Mdb.Begin() // 防止程序异常终止 defer func() { if r := recover(); r != nil { tx.Rollback() } }() if err := tx.CreateInBatches(list, len(list)).Error; err != nil { // 插入失败则回滚事务, 重新进行插入 tx.Rollback() } // 保存成功后将相应tag数据缓存到redis中 BatchHandleSearchTag(list...) tx.Commit() } // BatchSaveOrUpdate 判断数据库中是否存在对应mid的数据, 如果存在则更新, 否则插入 func BatchSaveOrUpdate(list []SearchInfo) { tx := db.Mdb.Begin() for _, info := range list { var count int64 // 通过当前影片id 对应的记录数 tx.Model(&SearchInfo{}).Where("mid", info.Mid).Count(&count) // 如果存在对应数据则进行更新, 否则保存相应数据 if count > 0 { // 记录已经存在则执行更新部分内容 err := tx.Model(&SearchInfo{}).Where("mid", info.Mid).Updates(SearchInfo{UpdateStamp: info.UpdateStamp, Hits: info.Hits, State: info.State, Remarks: info.Remarks, Score: info.Score, ReleaseStamp: info.ReleaseStamp}).Error if err != nil { tx.Rollback() } } else { // 执行插入操作 if err := tx.Create(&info).Error; err != nil { tx.Rollback() } // 插入成功后保存一份tag信息到redis中 BatchHandleSearchTag(info) } } // 提交事务 tx.Commit() } // SaveSearchInfo 添加影片检索信息 func SaveSearchInfo(s SearchInfo) error { // 先查询数据库中是否存在对应记录 // 如果不存在对应记录则 保存当前记录 tx := db.Mdb.Begin() if !ExistSearchInfo(s.Mid) { // 执行插入操作 if err := tx.Create(&s).Error; err != nil { tx.Rollback() return err } // 执行添加操作时保存一份tag信息 BatchHandleSearchTag(s) } else { // 如果已经存在当前记录则将当前记录进行更新 err := tx.Model(&SearchInfo{}).Where("mid", s.Mid).Updates(SearchInfo{UpdateStamp: s.UpdateStamp, Hits: s.Hits, State: s.State, Remarks: s.Remarks, Score: s.Score, ReleaseStamp: s.ReleaseStamp}).Error if err != nil { tx.Rollback() return err } } // 提交事务 tx.Commit() return nil } // ExistSearchInfo 通过Mid查询是否存在影片的检索信息 func ExistSearchInfo(mid int64) bool { var count int64 db.Mdb.Model(&SearchInfo{}).Where("mid", mid).Count(&count) return count > 0 } // TunCateSearchTable 截断SearchInfo数据表 func TunCateSearchTable() { var searchInfo SearchInfo err := db.Mdb.Exec(fmt.Sprintf("TRUNCATE TABLE %s", searchInfo.TableName())).Error if err != nil { log.Println("TRUNCATE TABLE Error: ", err) } } // SyncSearchInfo 同步影片检索信息 func SyncSearchInfo(model int) { switch model { case 0: // 重置Search表, (恢复为初始状态, 未添加索引) ResetSearchTable() // 批量添加 SearchInfo SearchInfoToMdb(model) // 保存完所有 SearchInfo 后添加字段索引 AddSearchIndex() case 1: // 批量更新或添加 SearchInfoToMdb(model) } } // SearchInfoToMdb 扫描redis中的检索信息, 并批量存入mysql (model 执行模式 0-清空并保存 || 1-更新) func SearchInfoToMdb(model int) { // 获取集合中的元素数量, 如果集合中没有元素则直接返回 count := db.Rdb.ZCard(db.Cxt, config.SearchInfoTemp).Val() if count <= 0 { return } // 1.从redis中批量扫描详情信息 list := db.Rdb.ZPopMax(db.Cxt, config.SearchInfoTemp, config.MaxScanCount).Val() // 如果扫描到的信息为空则直接退出 if len(list) <= 0 { return } // 2. 处理数据 var sl []SearchInfo for _, s := range list { // 解析详情数据 info := SearchInfo{} _ = json.Unmarshal([]byte(s.Member.(string)), &info) sl = append(sl, info) } // 通过model执行对应的保存方法 switch model { case 0: // 批量添加 SearchInfo BatchSave(sl) case 1: // 批量更新或添加 BatchSaveOrUpdate(sl) } // 如果 SearchInfoTemp 依然存在数据, 则递归执行 SearchInfoToMdb(model) } // ================================= API 数据接口信息处理 ================================= // GetMovieListByPid 通过Pid 分类ID 获取对应影片的数据信息 func GetMovieListByPid(pid int64, page *Page) []MovieBasicInfo { // 返回分页参数 var count int64 db.Mdb.Model(&SearchInfo{}).Where("pid", pid).Count(&count) page.Total = int(count) page.PageCount = int((page.Total + page.PageSize - 1) / page.PageSize) // 进行具体的信息查询 var s []SearchInfo if err := db.Mdb.Limit(page.PageSize).Offset((page.Current-1)*page.PageSize).Where("pid", pid).Order("update_stamp DESC").Find(&s).Error; err != nil { log.Println(err) return nil } // 通过影片ID去redis中获取id对应数据信息 var list []MovieBasicInfo for _, v := range s { // 通过key搜索指定的影片信息 , MovieDetail:Cid6:Id15441 list = append(list, GetBasicInfoByKey(fmt.Sprintf(config.MovieBasicInfoKey, v.Cid, v.Mid))) } return list } // GetMovieListByCid 通过Cid查找对应的影片分页数据, 不适合GetMovieListByPid 糅合 func GetMovieListByCid(cid int64, page *Page) []MovieBasicInfo { // 返回分页参数 var count int64 db.Mdb.Model(&SearchInfo{}).Where("cid", cid).Count(&count) page.Total = int(count) page.PageCount = int((page.Total + page.PageSize - 1) / page.PageSize) // 进行具体的信息查询 var s []SearchInfo if err := db.Mdb.Limit(page.PageSize).Offset((page.Current-1)*page.PageSize).Where("cid", cid).Order("update_stamp DESC").Find(&s).Error; err != nil { log.Println(err) return nil } // 通过影片ID去redis中获取id对应数据信息 var list []MovieBasicInfo for _, v := range s { // 通过key搜索指定的影片信息 , MovieDetail:Cid6:Id15441 list = append(list, GetBasicInfoByKey(fmt.Sprintf(config.MovieBasicInfoKey, v.Cid, v.Mid))) } return list } // GetHotMovieByPid 获取Pid指定类别的热门影片 func GetHotMovieByPid(pid int64, page *Page) []SearchInfo { // 返回分页参数 //var count int64 //db.Mdb.Model(&SearchInfo{}).Where("pid", pid).Count(&count) //page.Total = int(count) //page.PageCount = int((page.Total + page.PageSize - 1) / page.PageSize) // 进行具体的信息查询 var s []SearchInfo // 当前时间偏移一个月 t := time.Now().AddDate(0, -1, 0).Unix() if err := db.Mdb.Limit(page.PageSize).Offset((page.Current-1)*page.PageSize).Where("pid=? AND update_stamp > ?", pid, t).Order(" year DESC, hits DESC").Find(&s).Error; err != nil { log.Println(err) return nil } return s } // GetHotMovieByCid 获取当前分类下的热门影片 func GetHotMovieByCid(cid int64, page *Page) []SearchInfo { // 返回分页参数 //var count int64 //db.Mdb.Model(&SearchInfo{}).Where("pid", pid).Count(&count) //page.Total = int(count) //page.PageCount = int((page.Total + page.PageSize - 1) / page.PageSize) // 进行具体的信息查询 var s []SearchInfo // 当前时间偏移一个月 t := time.Now().AddDate(0, -1, 0).Unix() if err := db.Mdb.Limit(page.PageSize).Offset((page.Current-1)*page.PageSize).Where("cid=? AND update_stamp > ?", cid, t).Order(" year DESC, hits DESC").Find(&s).Error; err != nil { log.Println(err) return nil } return s } // SearchFilmKeyword 通过关键字搜索库存中满足条件的影片名 func SearchFilmKeyword(keyword string, page *Page) []SearchInfo { var searchList []SearchInfo // 1. 先统计搜索满足条件的数据量 var count int64 db.Mdb.Model(&SearchInfo{}).Where("name LIKE ?", fmt.Sprint(`%`, keyword, `%`)).Or("sub_title LIKE ?", fmt.Sprint(`%`, keyword, `%`)).Count(&count) page.Total = int(count) page.PageCount = int((page.Total + page.PageSize - 1) / page.PageSize) // 2. 获取满足条件的数据 db.Mdb.Limit(page.PageSize).Offset((page.Current-1)*page.PageSize). Where("name LIKE ?", fmt.Sprintf(`%%%s%%`, keyword)).Or("sub_title LIKE ?", fmt.Sprintf(`%%%s%%`, keyword)).Order("year DESC, update_stamp DESC").Find(&searchList) return searchList } // GetRelateMovieBasicInfo GetRelateMovie 根据SearchInfo获取相关影片 func GetRelateMovieBasicInfo(search SearchInfo, page *Page) []MovieBasicInfo { /* 根据当前影片信息匹配相关的影片 1. 分类Cid, 2. 如果影片名称含有第x季 则根据影片名进行模糊匹配 3. class_tag 剧情内容匹配, 切分后使用 or 进行匹配 4. area 地区 5. 语言 Language */ // sql 拼接查询条件 sql := "" // 优先进行名称相似匹配 //search.Name = regexp.MustCompile("第.{1,3}季").ReplaceAllString(search.Name, "") name := regexp.MustCompile(`(第.{1,3}季.*)|([0-9]{1,3})|(剧场版)|(\s\S*$)|(之.*)|([\p{P}\p{S}].*)`).ReplaceAllString(search.Name, "") // 如果处理后的影片名称依旧没有改变 且具有一定长度 则截取部分内容作为搜索条件 if len(name) == len(search.Name) && len(name) > 10 { // 中文字符需截取3的倍数,否则可能乱码 name = name[:int(math.Ceil(float64(len(name))/5)*3)] } sql = fmt.Sprintf(`select * from %s where (name LIKE "%%%s%%" or sub_title LIKE "%%%[2]s%%") AND cid=%d AND search.deleted_at IS NULL union`, search.TableName(), name, search.Cid) // 执行后续匹配内容, 匹配结果过少,减少过滤条件 //sql = fmt.Sprintf(`%s select * from %s where cid=%d AND area="%s" AND language="%s" AND`, sql, search.TableName(), search.Cid, search.Area, search.Language) // 添加其他相似匹配规则 sql = fmt.Sprintf(`%s (select * from %s where cid=%d AND `, sql, search.TableName(), search.Cid) // 根据剧情标签查找相似影片, classTag 使用的分隔符为 , | / // 首先去除 classTag 中包含的所有空格 search.ClassTag = strings.ReplaceAll(search.ClassTag, " ", "") // 如果 classTag 中包含分割符则进行拆分匹配 if strings.Contains(search.ClassTag, ",") { s := "(" for _, t := range strings.Split(search.ClassTag, ",") { s = fmt.Sprintf(`%s class_tag like "%%%s%%" OR`, s, t) } sql = fmt.Sprintf("%s %s)", sql, strings.TrimSuffix(s, "OR")) } else if strings.Contains(search.ClassTag, "/") { s := "(" for _, t := range strings.Split(search.ClassTag, "/") { s = fmt.Sprintf(`%s class_tag like "%%%s%%" OR`, s, t) } sql = fmt.Sprintf("%s %s)", sql, strings.TrimSuffix(s, "OR")) } else { sql = fmt.Sprintf(`%s class_tag like "%%%s%%"`, sql, search.ClassTag) } // 除名称外的相似影片使用随机排序 //sql = fmt.Sprintf("%s ORDER BY RAND() limit %d,%d)", sql, page.Current, page.PageSize) sql = fmt.Sprintf("%s AND search.deleted_at IS NULL limit %d,%d)", sql, page.Current, page.PageSize) // 条件拼接完成后加上limit参数 sql = fmt.Sprintf("(%s) limit %d,%d", sql, page.Current, page.PageSize) // 执行sql var list []SearchInfo db.Mdb.Raw(sql).Scan(&list) // 根据list 获取对应的BasicInfo var basicList []MovieBasicInfo for _, s := range list { // 通过key获取对应的影片基本数据 basicList = append(basicList, GetBasicInfoByKey(fmt.Sprintf(config.MovieBasicInfoKey, s.Cid, s.Mid))) } return basicList } // GetMultiplePlay 通过影片名hash值匹配播放源 func GetMultiplePlay(siteId, key string) []MovieUrlInfo { data := db.Rdb.HGet(db.Cxt, fmt.Sprintf(config.MultipleSiteDetail, siteId), key).Val() var playList []MovieUrlInfo _ = json.Unmarshal([]byte(data), &playList) return playList } // GetSearchTag 通过影片分类 Pid 返回对应分类的tag信息 func GetSearchTag(pid int64) map[string]interface{} { // 整合searchTag相关内容 res := make(map[string]interface{}) titles := db.Rdb.HGetAll(db.Cxt, fmt.Sprintf(config.SearchTitle, pid)).Val() res["titles"] = titles // 处理单一分类的数据格式 tagMap := make(map[string]interface{}) for t, _ := range titles { tagMap[t] = HandleTagStr(t, GetTagsByTitle(pid, t)...) } res["tags"] = tagMap // 分类列表展示的顺序 res["sortList"] = []string{"Category", "Plot", "Area", "Language", "Year", "Sort"} return res } // GetTagsByTitle 返回Pid和title对应的用于检索的tag func GetTagsByTitle(pid int64, t string) []string { // 通过 k 获取对应的 tag , 并以score进行排序 var tags []string // 过滤分类tag switch t { case "Category": //tags = db.Rdb.ZRevRange(db.Cxt, fmt.Sprintf(config.SearchTag, pid, t), 0, -1).Val() // 获取所有展示的子分类 for _, c := range GetChildrenTree(pid) { if c.Show { tags = append(tags, fmt.Sprintf("%s:%d", c.Name, c.Id)) } } case "Plot": tags = db.Rdb.ZRevRange(db.Cxt, fmt.Sprintf(config.SearchTag, pid, t), 0, 10).Val() case "Area": tags = db.Rdb.ZRevRange(db.Cxt, fmt.Sprintf(config.SearchTag, pid, t), 0, 11).Val() case "Language": tags = db.Rdb.ZRevRange(db.Cxt, fmt.Sprintf(config.SearchTag, pid, t), 0, 6).Val() case "Year", "Initial", "Sort": tags = db.Rdb.ZRevRange(db.Cxt, fmt.Sprintf(config.SearchTag, pid, t), 0, -1).Val() default: break } return tags } // HandleTagStr 处理tag数据格式 func HandleTagStr(title string, tags ...string) []map[string]string { var r []map[string]string if !strings.EqualFold(title, "Sort") { r = append(r, map[string]string{ "Name": "全部", "Value": "", }) } for _, t := range tags { if sl := strings.Split(t, ":"); len(sl) > 0 { r = append(r, map[string]string{ "Name": sl[0], "Value": sl[1], }) } } if !strings.EqualFold(title, "Sort") && !strings.EqualFold(title, "Year") && !strings.EqualFold(title, "Category") { r = append(r, map[string]string{ "Name": "其它", "Value": "其它", }) } return r } // GetSearchInfosByTags 查询满足searchTag条件的影片分页数据 func GetSearchInfosByTags(st SearchTagsVO, page *Page) []SearchInfo { // 准备查询语句的条件 qw := db.Mdb.Model(&SearchInfo{}) // 通过searchTags的非空属性值, 拼接对应的查询条件 t := reflect.TypeOf(st) v := reflect.ValueOf(st) for i := 0; i < t.NumField(); i++ { // 如果字段值不为空 value := v.Field(i).Interface() if !param.IsEmpty(value) { // 如果value是 其它 则进行特殊处理 var ts []string if v, flag := value.(string); flag && strings.EqualFold(v, "其它") { for _, s := range GetTagsByTitle(st.Pid, t.Field(i).Name) { ts = append(ts, strings.Split(s, ":")[1]) } } k := strings.ToLower(t.Field(i).Name) switch k { case "pid", "cid", "year": qw = qw.Where(fmt.Sprintf("%s = ?", k), value) case "area", "language": if strings.EqualFold(value.(string), "其它") { qw = qw.Where(fmt.Sprintf("%s NOT IN ?", k), ts) break } qw = qw.Where(fmt.Sprintf("%s = ?", k), value) case "plot": if strings.EqualFold(value.(string), "其它") { for _, t := range ts { qw = qw.Where("class_tag NOT LIKE ?", fmt.Sprintf("%%%v%%", t)) } break } qw = qw.Where("class_tag LIKE ?", fmt.Sprintf("%%%v%%", value)) case "sort": if strings.EqualFold(value.(string), "release_stamp") { qw.Order(fmt.Sprintf("year DESC ,%v DESC", value)) break } qw.Order(fmt.Sprintf("%v DESC", value)) default: break } } } // 返回分页参数 GetPage(qw, page) // 查询具体的searchInfo 分页数据 var sl []SearchInfo if err := qw.Limit(page.PageSize).Offset((page.Current - 1) * page.PageSize).Find(&sl).Error; err != nil { log.Println(err) return nil } return sl } // GetMovieListBySort 通过排序类型返回对应的影片基本信息 func GetMovieListBySort(t int, pid int64, page *Page) []MovieBasicInfo { var sl []SearchInfo qw := db.Mdb.Model(&SearchInfo{}).Where("pid", pid).Limit(page.PageSize).Offset((page.Current) - 10*page.PageSize) // 针对不同排序类型返回对应的分页数据 switch t { case 0: // 最新上映 (上映时间) qw.Order("release_stamp DESC") case 1: // 排行榜 (暂定为热度排行) qw.Order("hits DESC") case 2: // 最近更新 (更新时间) qw.Order("update_stamp DESC") } if err := qw.Find(&sl).Error; err != nil { log.Println(err) return nil } return GetBasicInfoBySearchInfos(sl...) } // ================================= Manage 管理后台 ================================= // GetSearchPage 获取影片检索分页数据 func GetSearchPage(s SearchVo) []SearchInfo { // 构建 query查询条件 query := db.Mdb.Model(&SearchInfo{}) // 如果参数不为空则追加对应查询条件 if s.Name != "" { query = query.Where("name LIKE ?", fmt.Sprintf("%%%s%%", s.Name)) } // 分类ID为负数则默认不追加该条件 if s.Cid > 0 { query = query.Where("cid = ?", s.Cid) } else if s.Pid > 0 { query = query.Where("pid = ?", s.Pid) } if s.Plot != "" { query = query.Where("class_tag LIKE ?", fmt.Sprintf("%%%s%%", s.Plot)) } if s.Area != "" { query = query.Where("area = ?", s.Area) } if s.Language != "" { query = query.Where("language = ?", s.Language) } if int(s.Year) > time.Now().Year()-12 { query = query.Where("year = ?", s.Year) } switch s.Remarks { case "完结": query = query.Where("remarks IN ?", []string{"完结", "HD"}) case "": default: query = query.Not(map[string]interface{}{"remarks": []string{"完结", "HD"}}) } if s.BeginTime > 0 { query = query.Where("update_stamp >= ? ", s.BeginTime) } if s.EndTime > 0 { query = query.Where("update_stamp <= ? ", s.EndTime) } // 返回分页参数 GetPage(query, s.Paging) // 查询具体的数据 var sl []SearchInfo if err := query.Limit(s.Paging.PageSize).Offset((s.Paging.Current - 1) * s.Paging.PageSize).Find(&sl).Error; err != nil { log.Println(err) return nil } return sl } // GetSearchOptions 获取全部影片的检索标签信息 func GetSearchOptions(pid int64) map[string]interface{} { // 整合searchTag相关内容 titles := db.Rdb.HGetAll(db.Cxt, fmt.Sprintf(config.SearchTitle, pid)).Val() // 处理单一分类的数据格式 tagMap := make(map[string]interface{}) for t, _ := range titles { switch t { // 只获取对应几个类型的标签 case "Plot", "Area", "Language", "Year": tagMap[t] = HandleTagStr(t, GetTagsByTitle(pid, t)...) default: } } return tagMap } // GetSearchInfoById 查询id对应的检索信息 func GetSearchInfoById(id int64) *SearchInfo { s := SearchInfo{} if err := db.Mdb.First(&s, id).Error; err != nil { log.Println(err) return nil } return &s } // DelFilmSearch 删除影片检索信息, (不影响后续更新, 逻辑删除) func DelFilmSearch(id int64) error { // 通过检索id对影片检索信息进行删除 if err := db.Mdb.Delete(&SearchInfo{}, id).Error; err != nil { log.Println(err) return err } return nil } // ShieldFilmSearch 删除所属分类下的所有影片检索信息 func ShieldFilmSearch(cid int64) error { // 通过检索id对影片检索信息进行删除 if err := db.Mdb.Where("cid = ?", cid).Delete(&SearchInfo{}).Error; err != nil { log.Println(err) return err } return nil } // RecoverFilmSearch 恢复所属分类下的影片检索信息状态 func RecoverFilmSearch(cid int64) error { // 通过检索id对影片检索信息进行删除 if err := db.Mdb.Model(&SearchInfo{}).Unscoped().Where("cid = ?", cid).Update("deleted_at", nil).Error; err != nil { log.Println(err) return err } return nil } // ================================= 接口数据缓存 ================================= // DataCache API请求 数据缓存 func DataCache(key string, data map[string]interface{}) { val, _ := json.Marshal(data) db.Rdb.Set(db.Cxt, key, val, time.Minute*30) } // GetCacheData 获取API接口的缓存数据 func GetCacheData(key string) map[string]interface{} { data := make(map[string]interface{}) val, err := db.Rdb.Get(db.Cxt, key).Result() if err != nil || len(val) <= 0 { return nil } _ = json.Unmarshal([]byte(val), &data) return data } // RemoveCache 删除数据缓存 func RemoveCache(key string) { db.Rdb.Del(db.Cxt, key) } // ================================= OpenApi请求处理 ================================= func FindFilmIds(params map[string]string, page *Page) ([]int64, error) { var ids []int64 query := db.Mdb.Model(&SearchInfo{}).Select("mid") for k, v := range params { // 如果 v 为空则直接 continue if len(v) <= 0 { continue } switch k { case "t": if cid, err := strconv.ParseInt(v, 10, 64); err == nil { query = query.Where("cid = ?", cid) } case "wd": query = query.Where("name like ?", fmt.Sprintf("%%%s%%", v)) case "h": if h, err := strconv.ParseInt(v, 10, 64); err == nil { query = query.Where("update_stamp >= ?", time.Now().Unix()-h*3600) } } } // 返回分页参数 var count int64 query.Count(&count) page.Total = int(count) page.PageCount = int(page.Total+page.PageSize-1) / page.PageSize // 返回满足条件的ids err := query.Limit(page.PageSize).Offset(page.Current - 1).Order("update_stamp DESC").Find(&ids).Error return ids, err }