package model import ( "encoding/json" "fmt" "github.com/redis/go-redis/v9" "gorm.io/gorm" "log" "reflect" "regexp" "server/config" "server/plugin/db" "strings" "time" ) // SearchInfo 存储用于检索的信息 type SearchInfo struct { gorm.Model Mid int64 `json:"mid" gorm:"uniqueIndex:idx_mid"` //影片ID 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集 ReleaseDate int64 `json:"releaseDate"` //上映时间 时间戳 } // Page 分页信息结构体 type Page struct { PageSize int `json:"pageSize"` // 每页大小 Current int `json:"current"` // 当前页 PageCount int `json:"pageCount"` // 总页数 Total int `json:"total"` // 总记录数 //List []interface{} `json:"list"` // 数据 } 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...) } // ScanSearchInfo 批量扫描处理详情检索信息, 返回检索信息列表和下次开始的游标 func ScanSearchInfo(cursor uint64, count int64) ([]SearchInfo, uint64) { // 1.从redis中批量扫描详情信息 list, nextCursor := db.Rdb.ZScan(db.Cxt, config.SearchInfoTemp, cursor, "*", count).Val() // 2. 处理数据 var resList []SearchInfo for i, s := range list { // 3. 判断当前是否是元素 if i%2 == 0 { info := SearchInfo{} _ = json.Unmarshal([]byte(s), &info) info.Model = gorm.Model{} resList = append(resList, info) } } return resList, nextCursor } // RemoveAll 删除所有库存数据 func RemoveAll() { // 删除redis中当前库存储的所有数据 db.Rdb.FlushDB(db.Cxt) // 删除mysql中留存的检索表 var s *SearchInfo db.Mdb.Exec(fmt.Sprintf(`drop table if exists %s`, s.TableName())) } // DelMtPlay 清空附加播放源信息 func DelMtPlay(keys []string) { db.Rdb.Del(db.Cxt, keys...) } // ================================= Spider 数据处理(mysql) ================================= // CreateSearchTable 创建存储检索信息的数据表 func CreateSearchTable() { // 1. 判断表中是否存在当前表 isExist := db.Mdb.Migrator().HasTable(&SearchInfo{}) // 如果不存在则创建表 if !isExist { err := db.Mdb.AutoMigrate(&SearchInfo{}) if err != nil { log.Println("Create Table SearchInfo Failed: ", err) } } } // 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() return } // 插入成功后输出一下成功信息 //log.Println("BatchSave SearchInfo Successful, Count: ", len(list)) tx.Commit() } // BatchSaveOrUpdate 判断数据库中是否存在对应mid的数据, 如果存在则更新, 否则插入 func BatchSaveOrUpdate(list []SearchInfo) { tx := db.Mdb.Begin() // 失败则回滚事务 //defer func() { // if r := recover(); r != nil { // tx.Rollback() // } //}() 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, ReleaseDate: info.ReleaseDate}).Error if err != nil { tx.Rollback() } } else { // 执行插入操作 if err := tx.Create(&info).Error; err != nil { tx.Rollback() } } } // 提交事务 tx.Commit() } // SaveSearchData 添加影片检索信息 func SaveSearchData(s SearchInfo) { // 先查询数据库中是否存在对应记录 isExist := SearchMovieInfo(s.Mid) // 如果不存在对应记录则 保存当前记录 if !isExist { db.Mdb.Create(&s) } } // SearchMovieInfo 通过Mid查询符合条件的数据 func SearchMovieInfo(mid int64) bool { search := SearchInfo{} db.Mdb.Where("mid", mid).First(&search) // reflect.DeepEqual(a, A{}) return !reflect.DeepEqual(search, SearchInfo{}) } // TunCateSearchTable 截断SearchInfo数据表 func TunCateSearchTable() { var searchInfo *SearchInfo err := db.Mdb.Exec(fmt.Sprint("TRUNCATE TABLE ", searchInfo.TableName())).Error if err != nil { log.Println("TRUNCATE TABLE Error: ", err) } } // ================================= 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("year DESC, 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 获取指定类别的热门影片 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 } // 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.Sprint(`%`, keyword, `%`)).Or("sub_title LIKE ?", fmt.Sprint(`%`, keyword, `%`)).Order("year DESC, update_stamp DESC").Find(&searchList) return searchList } // 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("year DESC, 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 } // 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, "") search.Name = regexp.MustCompile(`[第.{1,3}季\s~!@#$%^&*()+={}\[\]|\\:;"'<,>·.?/\-_].*`).ReplaceAllString(search.Name, "") regexp.MustCompile(`[\s~!@#$%^&*()+={}\[\]|\\:;"'<,>.?/\-_]+`) sql = fmt.Sprintf(`select * from %s where (name LIKE "%%%s%%" or sub_title LIKE "%%%[2]s%%") AND cid=%d union`, search.TableName(), search.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) // 条件拼接完成后加上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(siteName, key string) []MovieUrlInfo { data := db.Rdb.HGet(db.Cxt, fmt.Sprintf(config.MultipleSiteDetail, siteName), key).Val() var playList []MovieUrlInfo _ = json.Unmarshal([]byte(data), &playList) return playList } // 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) }