mirror of
https://github.com/ProudMuBai/GoFilm.git
synced 2026-02-21 01:54:42 +08:00
354 lines
13 KiB
Go
354 lines
13 KiB
Go
package model
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/redis/go-redis/v9"
|
|
"hash/fnv"
|
|
"server/config"
|
|
"server/plugin/db"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Movie 影片基本信息
|
|
type Movie struct {
|
|
Id int64 `json:"id"` // 影片ID
|
|
Name string `json:"name"` // 影片名
|
|
Cid int64 `json:"cid"` // 所属分类ID
|
|
CName string `json:"CName"` // 所属分类名称
|
|
EnName string `json:"enName"` // 英文片名
|
|
Time string `json:"time"` // 更新时间
|
|
Remarks string `json:"remarks"` // 备注 | 清晰度
|
|
PlayFrom string `json:"playFrom"` // 播放来源
|
|
}
|
|
|
|
// MovieDescriptor 影片详情介绍信息
|
|
type MovieDescriptor struct {
|
|
SubTitle string `json:"subTitle"` //子标题
|
|
CName string `json:"cName"` //分类名称
|
|
EnName string `json:"enName"` //英文名
|
|
Initial string `json:"initial"` //首字母
|
|
ClassTag string `json:"classTag"` //分类标签
|
|
Actor string `json:"actor"` //主演
|
|
Director string `json:"director"` //导演
|
|
Writer string `json:"writer"` //作者
|
|
Blurb string `json:"blurb"` //简介, 残缺,不建议使用
|
|
Remarks string `json:"remarks"` // 更新情况
|
|
ReleaseDate string `json:"releaseDate"` //上映时间
|
|
Area string `json:"area"` // 地区
|
|
Language string `json:"language"` //语言
|
|
Year string `json:"year"` //年份
|
|
State string `json:"state"` //影片状态 正片|预告...
|
|
UpdateTime string `json:"updateTime"` //更新时间
|
|
AddTime int64 `json:"addTime"` //资源添加时间戳
|
|
DbId int64 `json:"dbId"` //豆瓣id
|
|
DbScore string `json:"dbScore"` // 豆瓣评分
|
|
Content string `json:"content"` //内容简介
|
|
}
|
|
|
|
// MovieBasicInfo 影片基本信息
|
|
type MovieBasicInfo struct {
|
|
Id int64 `json:"id"` //影片Id
|
|
Cid int64 `json:"cid"` //分类ID
|
|
Pid int64 `json:"pid"` //一级分类ID
|
|
Name string `json:"name"` //片名
|
|
SubTitle string `json:"subTitle"` //子标题
|
|
CName string `json:"cName"` //分类名称
|
|
State string `json:"state"` //影片状态 正片|预告...
|
|
Picture string `json:"picture"` //简介图片
|
|
Actor string `json:"actor"` //主演
|
|
Director string `json:"director"` //导演
|
|
Blurb string `json:"blurb"` //简介, 不完整
|
|
Remarks string `json:"remarks"` // 更新情况
|
|
Area string `json:"area"` // 地区
|
|
Year string `json:"year"` //年份
|
|
}
|
|
|
|
// MovieUrlInfo 影视资源url信息
|
|
type MovieUrlInfo struct {
|
|
Episode string `json:"episode"` // 集数
|
|
Link string `json:"link"` // 播放地址
|
|
}
|
|
|
|
// MovieDetail 影片详情信息
|
|
type MovieDetail struct {
|
|
Id int64 `json:"id"` //影片Id
|
|
Cid int64 `json:"cid"` //分类ID
|
|
Pid int64 `json:"pid"` //一级分类ID
|
|
Name string `json:"name"` //片名
|
|
Picture string `json:"picture"` //简介图片
|
|
PlayFrom []string `json:"playFrom"` // 播放来源
|
|
DownFrom string `json:"DownFrom"` //下载来源 例: http
|
|
//PlaySeparator string `json:"playSeparator"` // 播放信息分隔符
|
|
PlayList [][]MovieUrlInfo `json:"playList"` //播放地址url
|
|
DownloadList [][]MovieUrlInfo `json:"downloadList"` // 下载url地址
|
|
MovieDescriptor `json:"descriptor"` //影片描述信息
|
|
}
|
|
|
|
// SaveMoves 保存影片分页请求list
|
|
func SaveMoves(list []Movie) (err error) {
|
|
// 整合数据
|
|
for _, m := range list {
|
|
//score, _ := time.ParseInLocation(time.DateTime, m.Time, time.Local)
|
|
movie, _ := json.Marshal(m)
|
|
// 以Cid为目录为集合进行存储, 便于后续搜索, 以影片id为分值进行存储 例 MovieList:Cid%d
|
|
err = db.Rdb.ZAdd(db.Cxt, fmt.Sprintf(config.MovieListInfoKey, m.Cid), redis.Z{Score: float64(m.Id), Member: movie}).Err()
|
|
}
|
|
return err
|
|
}
|
|
|
|
// AllMovieInfoKey 获取redis中所有的影视列表信息key MovieList:Cid
|
|
func AllMovieInfoKey() []string {
|
|
return db.Rdb.Keys(db.Cxt, fmt.Sprint("MovieList:Cid*")).Val()
|
|
}
|
|
|
|
// GetMovieListByKey 获取指定分类的影片列表数据
|
|
func GetMovieListByKey(key string) []string {
|
|
return db.Rdb.ZRange(db.Cxt, key, 0, -1).Val()
|
|
}
|
|
|
|
// SaveDetails 保存影片详情信息到redis中 格式: MovieDetail:Cid?:Id?
|
|
func SaveDetails(list []MovieDetail) (err error) {
|
|
// 遍历list中的信息
|
|
for _, detail := range list {
|
|
// 序列化影片详情信息
|
|
data, _ := json.Marshal(detail)
|
|
// 1. 原使用Zset存储, 但是不便于单个检索 db.Rdb.ZAdd(db.Cxt, fmt.Sprintf("%s:Cid%d", config.MovieDetailKey, detail.Cid), redis.Z{Score: float64(detail.Id), Member: member}).Err()
|
|
// 改为普通 k v 存储, k-> id关键字, v json序列化的结果
|
|
err = db.Rdb.Set(db.Cxt, fmt.Sprintf(config.MovieDetailKey, detail.Cid, detail.Id), data, config.CategoryTreeExpired).Err()
|
|
// 2. 同步保存简略信息到redis中
|
|
SaveMovieBasicInfo(detail)
|
|
// 3. 保存Search检索信息到redis, 暂时搁置
|
|
//if err == nil {
|
|
// // 转换 detail信息
|
|
// searchInfo := ConvertSearchInfo(detail)
|
|
// // 放弃redis进行检索, 多条件处理不方便
|
|
// //err = AddSearchInfo(searchInfo)
|
|
// // 只存储用于检索对应影片的关键字信息
|
|
// SearchKeyword(searchInfo)
|
|
//}
|
|
|
|
}
|
|
// 保存一份search信息到mysql, 批量存储
|
|
BatchSaveSearchInfo(list)
|
|
return err
|
|
}
|
|
|
|
// SaveMovieBasicInfo 摘取影片的详情部分信息转存为影视基本信息
|
|
func SaveMovieBasicInfo(detail MovieDetail) {
|
|
basicInfo := MovieBasicInfo{
|
|
Id: detail.Id,
|
|
Cid: detail.Cid,
|
|
Pid: detail.Pid,
|
|
Name: detail.Name,
|
|
SubTitle: detail.SubTitle,
|
|
CName: detail.CName,
|
|
State: detail.State,
|
|
Picture: detail.Picture,
|
|
Actor: detail.Actor,
|
|
Director: detail.Director,
|
|
Blurb: detail.Blurb,
|
|
Remarks: detail.Remarks,
|
|
Area: detail.Area,
|
|
Year: detail.Year,
|
|
}
|
|
data, _ := json.Marshal(basicInfo)
|
|
_ = db.Rdb.Set(db.Cxt, fmt.Sprintf(config.MovieBasicInfoKey, detail.Cid, detail.Id), data, config.CategoryTreeExpired).Err()
|
|
}
|
|
|
|
// SaveSitePlayList 仅保存播放url列表信息到当前站点
|
|
func SaveSitePlayList(siteName string, list []MovieDetail) (err error) {
|
|
// 如果list 为空则直接返回
|
|
if len(list) <= 0 {
|
|
return nil
|
|
}
|
|
res := make(map[string]string)
|
|
for _, d := range list {
|
|
if len(d.PlayList) > 0 {
|
|
data, _ := json.Marshal(d.PlayList[0])
|
|
res[HashKey(d.Name)] = string(data)
|
|
}
|
|
}
|
|
// 保存形式 key: MultipleSource:siteName Hash[hash(movieName)]list
|
|
err = db.Rdb.HMSet(db.Cxt, fmt.Sprintf(config.MultipleSiteDetail, siteName), res).Err()
|
|
return
|
|
}
|
|
|
|
// AddSearchInfo 将影片关键字信息整合后存入search 集合中
|
|
func AddSearchInfo(searchInfo SearchInfo) (err error) {
|
|
// 片名 Name 分类 CName 类别标签 classTag 地区 Area 语言 Language 年份 Year 首字母 Initial, 排序
|
|
data, _ := json.Marshal(searchInfo)
|
|
// 时间排序 score -->时间戳 DbId 排序 --> 热度, 评分排序 DbScore
|
|
err = db.Rdb.ZAdd(db.Cxt, fmt.Sprintf("%s:Pid%d", config.SearchTimeListKey, searchInfo.Pid), redis.Z{Score: float64(searchInfo.Time), Member: data}).Err()
|
|
err = db.Rdb.ZAdd(db.Cxt, fmt.Sprintf("%s:Pid%d", config.SearchScoreListKey, searchInfo.Pid), redis.Z{Score: searchInfo.Score, Member: data}).Err()
|
|
err = db.Rdb.ZAdd(db.Cxt, fmt.Sprintf("%s:Pid%d", config.SearchHeatListKey, searchInfo.Pid), redis.Z{Score: float64(searchInfo.Rank), Member: data}).Err()
|
|
// 添加搜索关键字信息
|
|
SearchKeyword(searchInfo)
|
|
return
|
|
}
|
|
|
|
// SearchKeyword 设置search关键字集合
|
|
func SearchKeyword(search SearchInfo) {
|
|
// 首先获取redis中的search 关键字信息
|
|
key := fmt.Sprintf("%s:Pid%d", config.SearchKeys, search.Pid)
|
|
keyword := db.Rdb.HGetAll(db.Cxt, key).Val()
|
|
if keyword["Year"] == "" {
|
|
currentYear := time.Now().Year()
|
|
year := ""
|
|
for i := 0; i < 12; i++ {
|
|
// 提供当前年份前推十二年的搜索
|
|
year = fmt.Sprintf("%s,%d", year, currentYear-i)
|
|
}
|
|
initial := ""
|
|
for i := 65; i <= 90; i++ {
|
|
initial = fmt.Sprintf("%s,%c", initial, i)
|
|
}
|
|
keyword = map[string]string{
|
|
//"Name": "",
|
|
"Category": "",
|
|
"Tag": "",
|
|
"Area": "",
|
|
"Language": "",
|
|
"Year": strings.Trim(year, ","),
|
|
"Initial": strings.Trim(initial, ","),
|
|
"Sort": "Time,Db,Score", // 默认,一般不修改
|
|
}
|
|
}
|
|
// 分类标签处理
|
|
if !strings.Contains(keyword["Category"], search.CName) {
|
|
keyword["Category"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Category"], search.CName), ",")
|
|
}
|
|
// 影视内容分类处理
|
|
if strings.Contains(search.ClassTag, "/") {
|
|
for _, t := range strings.Split(search.ClassTag, "/") {
|
|
if !strings.Contains(keyword["Tag"], t) {
|
|
keyword["Tag"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Tag"], t), ",")
|
|
}
|
|
}
|
|
} else if strings.Contains(search.ClassTag, ",") {
|
|
for _, t := range strings.Split(search.ClassTag, ",") {
|
|
if !strings.Contains(keyword["Tag"], t) {
|
|
keyword["Tag"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Tag"], t), ",")
|
|
}
|
|
}
|
|
} else {
|
|
if !strings.Contains(keyword["Tag"], search.ClassTag) {
|
|
keyword["Tag"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Tag"], search.ClassTag), ",")
|
|
}
|
|
}
|
|
// 如果地区中包含 / 分隔符 则先进行切分处理
|
|
if strings.Contains(search.Area, "/") {
|
|
for _, s := range strings.Split(search.Area, "/") {
|
|
if !strings.Contains(keyword["Area"], strings.TrimSpace(s)) {
|
|
keyword["Area"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Area"], s), ",")
|
|
}
|
|
}
|
|
} else if strings.Contains(search.Area, ",") {
|
|
for _, s := range strings.Split(search.Area, ",") {
|
|
if !strings.Contains(keyword["Area"], strings.TrimSpace(s)) {
|
|
keyword["Area"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Area"], s), ",")
|
|
}
|
|
}
|
|
} else {
|
|
if !strings.Contains(keyword["Area"], search.Area) {
|
|
keyword["Area"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Area"], search.Area), ",")
|
|
}
|
|
}
|
|
// 语言处理
|
|
if strings.Contains(search.Language, "/") {
|
|
for _, l := range strings.Split(search.Language, "/") {
|
|
if !strings.Contains(keyword["Language"], l) {
|
|
keyword["Language"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Language"], l), ",")
|
|
}
|
|
}
|
|
|
|
} else if strings.Contains(search.Language, ",") {
|
|
for _, l := range strings.Split(search.Language, ",") {
|
|
if !strings.Contains(keyword["Language"], l) {
|
|
keyword["Language"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Language"], l), ",")
|
|
}
|
|
}
|
|
} else {
|
|
if !strings.Contains(keyword["Language"], search.Language) {
|
|
keyword["Language"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Language"], search.Language), ",")
|
|
}
|
|
}
|
|
_ = db.Rdb.HMSet(db.Cxt, key, keyword).Err()
|
|
}
|
|
|
|
// BatchSaveSearchInfo 批量保存Search信息
|
|
func BatchSaveSearchInfo(list []MovieDetail) {
|
|
var infoList []SearchInfo
|
|
for _, v := range list {
|
|
infoList = append(infoList, ConvertSearchInfo(v))
|
|
}
|
|
// 将检索信息存入redis中做一次转存
|
|
RdbSaveSearchInfo(infoList)
|
|
|
|
// 废弃方案, 频繁大量入库容易引起主键冲突, 事务影响速率
|
|
// 批量插入时应对已存在数据进行检测, 使用mysql事务进行锁表
|
|
//BatchSave(infoList)
|
|
// 使用批量添加or更新
|
|
//BatchSaveOrUpdate(infoList)
|
|
}
|
|
|
|
// ConvertSearchInfo 将detail信息处理成 searchInfo
|
|
func ConvertSearchInfo(detail MovieDetail) SearchInfo {
|
|
score, _ := strconv.ParseFloat(detail.DbScore, 64)
|
|
stamp, _ := time.ParseInLocation(time.DateTime, detail.UpdateTime, time.Local)
|
|
year, err := strconv.ParseInt(detail.Year, 10, 64)
|
|
if err != nil {
|
|
year = 0
|
|
}
|
|
return SearchInfo{
|
|
Mid: detail.Id,
|
|
Cid: detail.Cid,
|
|
Pid: detail.Pid,
|
|
Name: detail.Name,
|
|
SubTitle: detail.SubTitle,
|
|
CName: detail.CName,
|
|
ClassTag: detail.ClassTag,
|
|
Area: detail.Area,
|
|
Language: detail.Language,
|
|
Year: year,
|
|
Initial: detail.Initial,
|
|
Score: score,
|
|
Rank: detail.DbId,
|
|
Time: stamp.Unix(),
|
|
State: detail.State,
|
|
Remarks: detail.Remarks,
|
|
// releaseDate 部分影片缺失该参数, 所以使用添加时间作为上映时间排序
|
|
ReleaseDate: detail.AddTime,
|
|
}
|
|
}
|
|
|
|
// GetBasicInfoByKey 获取Id对应的影片基本信息
|
|
func GetBasicInfoByKey(key string) MovieBasicInfo {
|
|
// 反序列化得到的结果
|
|
data := []byte(db.Rdb.Get(db.Cxt, key).Val())
|
|
basic := MovieBasicInfo{}
|
|
_ = json.Unmarshal(data, &basic)
|
|
return basic
|
|
}
|
|
|
|
// GetDetailByKey 获取影片对应的详情信息
|
|
func GetDetailByKey(key string) MovieDetail {
|
|
// 反序列化得到的结果
|
|
data := []byte(db.Rdb.Get(db.Cxt, key).Val())
|
|
detail := MovieDetail{}
|
|
_ = json.Unmarshal(data, &detail)
|
|
return detail
|
|
}
|
|
|
|
// HashKey 将字符串转化为hash值
|
|
func HashKey(str string) string {
|
|
h := fnv.New32a()
|
|
_, err := h.Write([]byte(str))
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return fmt.Sprint(h.Sum32())
|
|
}
|