mirror of
https://github.com/ProudMuBai/GoFilm.git
synced 2026-02-21 01:54:42 +08:00
add BAM
This commit is contained in:
59
server/model/system/Categories.go
Normal file
59
server/model/system/Categories.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"server/config"
|
||||
"server/plugin/db"
|
||||
)
|
||||
|
||||
// Category 分类信息
|
||||
type Category struct {
|
||||
Id int64 `json:"id"` // 分类ID
|
||||
Pid int64 `json:"pid"` // 父级分类ID
|
||||
Name string `json:"name"` // 分类名称
|
||||
Show bool `json:"show"` // 是否展示
|
||||
}
|
||||
|
||||
// CategoryTree 分类信息树形结构
|
||||
type CategoryTree struct {
|
||||
*Category
|
||||
Children []*CategoryTree `json:"children"` // 子分类信息
|
||||
}
|
||||
|
||||
// 影视分类展示树形结构
|
||||
|
||||
// SaveCategoryTree 保存影片分类信息
|
||||
func SaveCategoryTree(tree *CategoryTree) error {
|
||||
data, _ := json.Marshal(tree)
|
||||
return db.Rdb.Set(db.Cxt, config.CategoryTreeKey, data, config.CategoryTreeExpired).Err()
|
||||
}
|
||||
|
||||
// GetCategoryTree 获取影片分类信息
|
||||
func GetCategoryTree() CategoryTree {
|
||||
data := db.Rdb.Get(db.Cxt, config.CategoryTreeKey).Val()
|
||||
tree := CategoryTree{}
|
||||
_ = json.Unmarshal([]byte(data), &tree)
|
||||
return tree
|
||||
}
|
||||
|
||||
// ExistsCategoryTree 查询分类信息是否存在
|
||||
func ExistsCategoryTree() bool {
|
||||
exists, err := db.Rdb.Exists(db.Cxt, config.CategoryTreeKey).Result()
|
||||
if err != nil {
|
||||
log.Println("ExistsCategoryTree Error", err)
|
||||
}
|
||||
return exists == 1
|
||||
}
|
||||
|
||||
// GetChildrenTree 根据影片Id获取对应分类的子分类信息
|
||||
func GetChildrenTree(id int64) []*CategoryTree {
|
||||
tree := GetCategoryTree()
|
||||
for _, t := range tree.Children {
|
||||
if t.Id == id {
|
||||
return t.Children
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
176
server/model/system/CollectSource.go
Normal file
176
server/model/system/CollectSource.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"log"
|
||||
"server/config"
|
||||
"server/plugin/common/util"
|
||||
"server/plugin/db"
|
||||
)
|
||||
|
||||
/*
|
||||
影视采集站点信息
|
||||
*/
|
||||
|
||||
type SourceGrade int
|
||||
|
||||
const (
|
||||
MasterCollect SourceGrade = iota
|
||||
SlaveCollect
|
||||
)
|
||||
|
||||
type CollectResultModel int
|
||||
|
||||
const (
|
||||
JsonResult CollectResultModel = iota
|
||||
XmlResult
|
||||
)
|
||||
|
||||
type ResourceType int
|
||||
|
||||
func (rt ResourceType) GetActionType() string {
|
||||
var ac string = ""
|
||||
switch rt {
|
||||
case CollectVideo:
|
||||
ac = "detail"
|
||||
case CollectArticle:
|
||||
ac = "article"
|
||||
case CollectActor:
|
||||
ac = "actor"
|
||||
case CollectRole:
|
||||
ac = "role"
|
||||
case CollectWebSite:
|
||||
ac = "web"
|
||||
default:
|
||||
ac = "detail"
|
||||
}
|
||||
return ac
|
||||
}
|
||||
|
||||
const (
|
||||
CollectVideo = iota
|
||||
CollectArticle
|
||||
CollectActor
|
||||
CollectRole
|
||||
CollectWebSite
|
||||
)
|
||||
|
||||
// FilmSource 影视站点信息保存结构体
|
||||
type FilmSource struct {
|
||||
Id string `json:"id"` // 唯一ID
|
||||
Name string `json:"name"` // 采集站点备注名
|
||||
Uri string `json:"uri"` // 采集链接
|
||||
ResultModel CollectResultModel `json:"resultModel"` // 接口返回类型, json || xml
|
||||
Grade SourceGrade `json:"grade"` // 采集站等级 主站点 || 附属站
|
||||
SyncPictures bool `json:"syncPictures"` // 是否同步图片到服务器
|
||||
CollectType ResourceType `json:"collectType"` // 采集资源类型
|
||||
State bool `json:"state"` // 是否启用
|
||||
}
|
||||
|
||||
// SaveCollectSourceList 保存采集站Api列表
|
||||
func SaveCollectSourceList(list []FilmSource) error {
|
||||
var zl []redis.Z
|
||||
for _, v := range list {
|
||||
m, _ := json.Marshal(v)
|
||||
zl = append(zl, redis.Z{Score: float64(v.Grade), Member: m})
|
||||
}
|
||||
return db.Rdb.ZAdd(db.Cxt, config.FilmSourceListKey, zl...).Err()
|
||||
}
|
||||
|
||||
// GetCollectSourceList 获取采集站API列表
|
||||
func GetCollectSourceList() []FilmSource {
|
||||
l, err := db.Rdb.ZRange(db.Cxt, config.FilmSourceListKey, 0, -1).Result()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil
|
||||
}
|
||||
return getCollectSource(l)
|
||||
}
|
||||
|
||||
// GetCollectSourceListByGrade 返回指定类型的采集Api信息 Master | Slave
|
||||
func GetCollectSourceListByGrade(grade SourceGrade) []FilmSource {
|
||||
s := fmt.Sprintf("%d", grade)
|
||||
zl, err := db.Rdb.ZRangeByScore(db.Cxt, config.FilmSourceListKey, &redis.ZRangeBy{Max: s, Min: s}).Result()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil
|
||||
}
|
||||
return getCollectSource(zl)
|
||||
}
|
||||
|
||||
// FindCollectSourceById 通过Id标识获取对应的资源站信息
|
||||
func FindCollectSourceById(id string) *FilmSource {
|
||||
for _, v := range GetCollectSourceList() {
|
||||
if v.Id == id {
|
||||
return &v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 将 []string 转化为 []FilmSourceApi
|
||||
func getCollectSource(sl []string) []FilmSource {
|
||||
var l []FilmSource
|
||||
for _, s := range sl {
|
||||
f := FilmSource{}
|
||||
_ = json.Unmarshal([]byte(s), &f)
|
||||
l = append(l, f)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// DelCollectResource 通过Id删除对应的采集站点信息
|
||||
func DelCollectResource(id string) {
|
||||
for _, v := range GetCollectSourceList() {
|
||||
if v.Id == id {
|
||||
data, _ := json.Marshal(v)
|
||||
db.Rdb.ZRem(db.Cxt, config.FilmSourceListKey, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddCollectSource 添加采集站信息
|
||||
func AddCollectSource(s FilmSource) error {
|
||||
for _, v := range GetCollectSourceList() {
|
||||
if v.Uri == s.Uri {
|
||||
return errors.New("当前采集站点信息已存在, 请勿重复添加")
|
||||
}
|
||||
}
|
||||
// 生成一个短uuid
|
||||
s.Id = util.GenerateSalt()
|
||||
data, _ := json.Marshal(s)
|
||||
return db.Rdb.ZAddNX(db.Cxt, config.FilmSourceListKey, redis.Z{Score: float64(s.Grade), Member: data}).Err()
|
||||
}
|
||||
|
||||
// UpdateCollectSource 更新采集站信息
|
||||
func UpdateCollectSource(s FilmSource) error {
|
||||
for _, v := range GetCollectSourceList() {
|
||||
if v.Id != s.Id && v.Uri == s.Uri {
|
||||
return errors.New("当前采集站链接已存在其他站点中, 请勿重复添加")
|
||||
} else if v.Id == s.Id {
|
||||
// 删除当前旧的采集信息
|
||||
DelCollectResource(s.Id)
|
||||
// 将新的采集信息存入list中
|
||||
data, _ := json.Marshal(s)
|
||||
db.Rdb.ZAdd(db.Cxt, config.FilmSourceListKey, redis.Z{Score: float64(s.Grade), Member: data})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearAllCollectSource 删除所有采集站信息
|
||||
func ClearAllCollectSource() {
|
||||
db.Rdb.Del(db.Cxt, config.FilmSourceListKey)
|
||||
}
|
||||
|
||||
// ExistCollectSourceList 查询是否已经存在站点list相关数据
|
||||
func ExistCollectSourceList() bool {
|
||||
if db.Rdb.Exists(db.Cxt, config.FilmSourceListKey).Val() == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
}
|
||||
70
server/model/system/Crontab.go
Normal file
70
server/model/system/Crontab.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/robfig/cron/v3"
|
||||
"server/config"
|
||||
"server/plugin/db"
|
||||
)
|
||||
|
||||
/*
|
||||
定时任务持久化
|
||||
*/
|
||||
|
||||
// FilmCollectTask 影视采集任务
|
||||
type FilmCollectTask struct {
|
||||
Id string `json:"id"` // 唯一标识uid
|
||||
Ids []string `json:"ids"` // 采集站id列表
|
||||
Cid cron.EntryID `json:"cid"` // 定时任务Id
|
||||
Time int `json:"time"` // 采集时长, 最新x小时更新的内容
|
||||
Spec string `json:"spec"` // 执行周期 cron表达式
|
||||
Model int `json:"model"` // 任务类型, 0 - 自动更新已启用站点 || 1 - 更新Ids中的资源站数据
|
||||
State bool `json:"state"` // 状态 开启 | 禁用
|
||||
Remark string `json:"remark"` // 任务备注信息
|
||||
}
|
||||
|
||||
// SaveFilmTask 保存影视采集任务信息 {EntryId:FilmCollectTask}
|
||||
func SaveFilmTask(t FilmCollectTask) {
|
||||
data, _ := json.Marshal(t)
|
||||
db.Rdb.HSet(db.Cxt, config.FilmCrontabKey, t.Id, data)
|
||||
}
|
||||
|
||||
// GetAllFilmTask 获取所有的任务信息
|
||||
func GetAllFilmTask() []FilmCollectTask {
|
||||
var tl []FilmCollectTask
|
||||
tMap := db.Rdb.HGetAll(db.Cxt, config.FilmCrontabKey).Val()
|
||||
for _, v := range tMap {
|
||||
var t = FilmCollectTask{}
|
||||
_ = json.Unmarshal([]byte(v), &t)
|
||||
tl = append(tl, t)
|
||||
}
|
||||
return tl
|
||||
}
|
||||
|
||||
// GetFilmTaskById 通过Id获取当前任务信息
|
||||
func GetFilmTaskById(id string) (FilmCollectTask, error) {
|
||||
var ft = FilmCollectTask{}
|
||||
// 如果Id对应的task不存在则返回错误信息
|
||||
if !db.Rdb.HExists(db.Cxt, config.FilmCrontabKey, id).Val() {
|
||||
return ft, errors.New(" The task does not exist ")
|
||||
}
|
||||
data := db.Rdb.HGet(db.Cxt, config.FilmCrontabKey, id).Val()
|
||||
err := json.Unmarshal([]byte(data), &ft)
|
||||
return ft, err
|
||||
}
|
||||
|
||||
// UpdateFilmTask 更新定时任务信息(直接覆盖Id对应的定时任务信息) -- 后续待调整
|
||||
func UpdateFilmTask(t FilmCollectTask) {
|
||||
SaveFilmTask(t)
|
||||
}
|
||||
|
||||
// DelFilmTask 通过Id删除对应的定时任务信息
|
||||
func DelFilmTask(id string) {
|
||||
db.Rdb.HDel(db.Cxt, config.FilmCrontabKey, id)
|
||||
}
|
||||
|
||||
// ExistTask 是否存在定时任务相关信息
|
||||
func ExistTask() bool {
|
||||
return db.Rdb.Exists(db.Cxt, config.FilmCrontabKey).Val() == 1
|
||||
}
|
||||
160
server/model/system/FileUpload.go
Normal file
160
server/model/system/FileUpload.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
"log"
|
||||
"regexp"
|
||||
"server/config"
|
||||
"server/plugin/common/util"
|
||||
"server/plugin/db"
|
||||
)
|
||||
|
||||
// Picture 图片信息对象
|
||||
type Picture struct {
|
||||
gorm.Model
|
||||
Link string `json:"link"` // 图片链接
|
||||
Uid int `json:"uid"` // 上传人ID
|
||||
RelevanceId int64 `json:"relevanceId"` // 关联资源ID
|
||||
PicType int `json:"picType"` // 图片类型 (0 影片封面, 1 用户头像)
|
||||
PicUid string `json:"picUid"` // 图片唯一标识, 通常为文件名
|
||||
//Size int `json:"size"` // 图片大小
|
||||
}
|
||||
|
||||
// VirtualPicture 采集入站,待同步的图片信息
|
||||
type VirtualPicture struct {
|
||||
Id int64 `json:"id"`
|
||||
Link string `json:"link"`
|
||||
}
|
||||
|
||||
//------------------------------------------------本地图库------------------------------------------------
|
||||
|
||||
// TableName 设置图片存储表的表名
|
||||
func (p *Picture) TableName() string {
|
||||
return config.PictureTableName
|
||||
}
|
||||
|
||||
// CreatePictureTable 创建图片关联信息存储表
|
||||
func CreatePictureTable() {
|
||||
// 如果不存在则创建表 并设置自增ID初始值为10000
|
||||
if !ExistPictureTable() {
|
||||
err := db.Mdb.AutoMigrate(&Picture{})
|
||||
if err != nil {
|
||||
log.Println("Create Table Picture Failed: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ExistPictureTable 是否存在Picture表
|
||||
func ExistPictureTable() bool {
|
||||
// 1. 判断表中是否存在当前表
|
||||
return db.Mdb.Migrator().HasTable(&Picture{})
|
||||
}
|
||||
|
||||
// SaveGallery 保存图片关联信息
|
||||
func SaveGallery(p Picture) {
|
||||
db.Mdb.Create(&p)
|
||||
}
|
||||
|
||||
// ExistPictureByRid 查找图片信息是否存在
|
||||
func ExistPictureByRid(rid int64) bool {
|
||||
var count int64
|
||||
db.Mdb.Model(&Picture{}).Where("relevance_id = ?", rid).Count(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
// GetPictureByRid 通过关联的资源id获取对应的图片信息
|
||||
func GetPictureByRid(rid int64) Picture {
|
||||
var p Picture
|
||||
db.Mdb.Where("relevance_id = ?", rid).First(&p)
|
||||
return p
|
||||
}
|
||||
|
||||
func GetPicturePage(page *Page) []Picture {
|
||||
var pl []Picture
|
||||
query := db.Mdb.Model(&Picture{})
|
||||
// 获取分页相关参数
|
||||
GetPage(query, page)
|
||||
// 获取分页数据
|
||||
if err := query.Limit(page.PageSize).Offset((page.Current - 1) * page.PageSize).Find(&pl).Error; err != nil {
|
||||
log.Println(err)
|
||||
return nil
|
||||
}
|
||||
return pl
|
||||
}
|
||||
|
||||
//------------------------------------------------图片同步------------------------------------------------
|
||||
|
||||
// SaveVirtualPic 保存待同步的图片信息
|
||||
func SaveVirtualPic(pl []VirtualPicture) error {
|
||||
// 保存对应的
|
||||
var zl []redis.Z
|
||||
for _, p := range pl {
|
||||
// 首先查询 Gallery 表中是否存在当前ID对应的图片信息, 如果不存在则保存
|
||||
if !ExistPictureByRid(p.Id) {
|
||||
m, _ := json.Marshal(p)
|
||||
zl = append(zl, redis.Z{Score: float64(p.Id), Member: m})
|
||||
}
|
||||
}
|
||||
return db.Rdb.ZAdd(db.Cxt, config.VirtualPictureKey, zl...).Err()
|
||||
}
|
||||
|
||||
// SyncFilmPicture 同步新采集入栈还未同步的图片
|
||||
func SyncFilmPicture() {
|
||||
// 扫描待同步图片的信息, 每次扫描count条
|
||||
sl, cursor := db.Rdb.ZScan(db.Cxt, config.VirtualPictureKey, 0, "*", config.MaxScanCount).Val()
|
||||
if len(sl) <= 0 {
|
||||
return
|
||||
}
|
||||
// 获取 VirtualPicture
|
||||
for i, s := range sl {
|
||||
if i%2 == 0 {
|
||||
// 获取图片信息
|
||||
vp := VirtualPicture{}
|
||||
_ = json.Unmarshal([]byte(s), &vp)
|
||||
// 删除已经取出的数据
|
||||
db.Rdb.ZRem(db.Cxt, config.VirtualPictureKey, []byte(s))
|
||||
// 将图片同步到服务器
|
||||
fileName, err := util.SaveOnlineFile(vp.Link, config.FilmPictureUploadDir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// 完成同步后将图片信息保存到 Gallery 中
|
||||
SaveGallery(Picture{
|
||||
Link: fmt.Sprint(config.FilmPictureAccess, fileName),
|
||||
Uid: config.UserIdInitialVal,
|
||||
RelevanceId: vp.Id,
|
||||
PicType: 0,
|
||||
PicUid: regexp.MustCompile(`\.[^.]+$`).ReplaceAllString(fileName, ""),
|
||||
})
|
||||
}
|
||||
}
|
||||
// 如果 cursor != 0 则继续递归执行
|
||||
if cursor > 0 {
|
||||
SyncFilmPicture()
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceDetailPic 将影片详情中的图片地址替换为自己的
|
||||
func ReplaceDetailPic(d *MovieDetail) {
|
||||
// 查询影片对应的本地图片信息
|
||||
if ExistPictureByRid(d.Id) {
|
||||
// 如果存在关联的本地图片, 则查询对应的图片信息
|
||||
p := GetPictureByRid(d.Id)
|
||||
// 替换采集站的图片链接为本地链接
|
||||
d.Picture = p.Link
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceBasicDetailPic 替换影片基本数据中的封面图为本地图片
|
||||
func ReplaceBasicDetailPic(d *MovieBasicInfo) {
|
||||
// 查询影片对应的本地图片信息
|
||||
if ExistPictureByRid(d.Id) {
|
||||
// 如果存在关联的本地图片, 则查询对应的图片信息
|
||||
p := GetPictureByRid(d.Id)
|
||||
// 替换采集站的图片链接为本地链接
|
||||
d.Picture = p.Link
|
||||
}
|
||||
}
|
||||
89
server/model/system/Jwt.go
Normal file
89
server/model/system/Jwt.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"log"
|
||||
"server/config"
|
||||
"server/plugin/common/util"
|
||||
"server/plugin/db"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UserClaims struct {
|
||||
UserID uint `json:"userID"`
|
||||
UserName string `json:"userName"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// GenToken 生成token
|
||||
func GenToken(userId uint, userName string) (string, error) {
|
||||
uc := UserClaims{
|
||||
UserID: userId,
|
||||
UserName: userName,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Issuer: config.Issuer,
|
||||
Subject: userName,
|
||||
Audience: jwt.ClaimStrings{"Auth_All"},
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(config.AuthTokenExpires * time.Hour)),
|
||||
NotBefore: jwt.NewNumericDate(time.Now().Add(-10 * time.Second)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
ID: util.GenerateSalt(),
|
||||
},
|
||||
}
|
||||
priKey, err := util.ParsePriKeyBytes([]byte(config.PrivateKey))
|
||||
token, err := jwt.NewWithClaims(jwt.SigningMethodRS256, uc).SignedString(priKey)
|
||||
return token, err
|
||||
}
|
||||
|
||||
// ParseToken 解析token
|
||||
func ParseToken(tokenStr string) (*UserClaims, error) {
|
||||
token, err := jwt.ParseWithClaims(tokenStr, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
pub, err := util.ParsePubKeyBytes([]byte(config.PublicKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pub, nil
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||
claims, _ := token.Claims.(*UserClaims)
|
||||
return claims, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// 验证token是否有效
|
||||
if !token.Valid {
|
||||
return nil, errors.New("token is invalid")
|
||||
}
|
||||
// 解析token中的claims内容
|
||||
claims, ok := token.Claims.(*UserClaims)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid claim type error")
|
||||
}
|
||||
return claims, err
|
||||
}
|
||||
|
||||
// RefreshToken 刷新 token
|
||||
|
||||
// SaveUserToken 将用户登录成功后的token字符串存放到redis中
|
||||
func SaveUserToken(token string, userId uint) error {
|
||||
// 设置redis中token的过期时间为 token过期时间后的7天
|
||||
return db.Rdb.Set(db.Cxt, fmt.Sprintf(config.UserTokenKey, userId), token, (config.AuthTokenExpires+7*24)*time.Hour).Err()
|
||||
}
|
||||
|
||||
// GetUserTokenById 从redis中获取指定userId对应的token
|
||||
func GetUserTokenById(userId uint) string {
|
||||
token, err := db.Rdb.Get(db.Cxt, fmt.Sprintf(config.UserTokenKey, userId)).Result()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return ""
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// ClearUserToken 清楚指定id的用户的登录信息
|
||||
func ClearUserToken(userId uint) error {
|
||||
return db.Rdb.Del(db.Cxt, fmt.Sprintf(config.UserTokenKey, userId)).Err()
|
||||
}
|
||||
36
server/model/system/Manage.go
Normal file
36
server/model/system/Manage.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"server/config"
|
||||
"server/plugin/db"
|
||||
)
|
||||
|
||||
type BasicConfig struct {
|
||||
SiteName string `json:"siteName"` // 网站名称
|
||||
Domain string `json:"domain"` // 网站域名
|
||||
Logo string `json:"logo"` // 网站logo
|
||||
Keyword string `json:"keyword"` // seo关键字
|
||||
Describe string `json:"describe"` // 网站描述信息
|
||||
State bool `json:"state"` // 网站状态 开启 || 关闭
|
||||
Hint string `json:"hint"` // 网站关闭提示
|
||||
}
|
||||
|
||||
// ------------------------------------------------------ Redis ------------------------------------------------------
|
||||
|
||||
// SaveSiteBasic 保存网站基本配置信息
|
||||
func SaveSiteBasic(c BasicConfig) error {
|
||||
data, _ := json.Marshal(c)
|
||||
return db.Rdb.Set(db.Cxt, config.SiteConfigBasic, data, config.ManageConfigExpired).Err()
|
||||
}
|
||||
|
||||
// GetSiteBasic 获取网站基本配置信息
|
||||
func GetSiteBasic() BasicConfig {
|
||||
c := BasicConfig{}
|
||||
data := db.Rdb.Get(db.Cxt, config.SiteConfigBasic).Val()
|
||||
if err := json.Unmarshal([]byte(data), &c); err != nil {
|
||||
log.Println("GetSiteBasic Err", err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
320
server/model/system/Movies.go
Normal file
320
server/model/system/Movies.go
Normal file
@@ -0,0 +1,320 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"hash/fnv"
|
||||
"regexp"
|
||||
"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"` // 豆瓣评分
|
||||
Hits int64 `json:"hits"` //影片热度
|
||||
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"` //影片描述信息
|
||||
}
|
||||
|
||||
// ===================================Redis数据交互========================================================
|
||||
|
||||
// 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 tag redis中
|
||||
if err == nil {
|
||||
// 转换 detail信息
|
||||
searchInfo := ConvertSearchInfo(detail)
|
||||
// 只存储用于检索对应影片的关键字信息
|
||||
SaveSearchTag(searchInfo)
|
||||
}
|
||||
|
||||
}
|
||||
// 保存一份search信息到mysql, 批量存储
|
||||
BatchSaveSearchInfo(list)
|
||||
return err
|
||||
}
|
||||
|
||||
// SaveDetail 保存单部影片信息
|
||||
func SaveDetail(detail MovieDetail) (err error) {
|
||||
// 序列化影片详情信息
|
||||
data, _ := json.Marshal(detail)
|
||||
// 保存影片信息到Redis
|
||||
err = db.Rdb.Set(db.Cxt, fmt.Sprintf(config.MovieDetailKey, detail.Cid, detail.Id), data, config.CategoryTreeExpired).Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 2. 同步保存简略信息到redis中
|
||||
SaveMovieBasicInfo(detail)
|
||||
// 转换 detail信息
|
||||
searchInfo := ConvertSearchInfo(detail)
|
||||
// 3. 保存 Search tag redis中
|
||||
// 只存储用于检索对应影片的关键字信息
|
||||
SaveSearchTag(searchInfo)
|
||||
// 保存影片检索信息到searchTable
|
||||
err = SaveSearchInfo(searchInfo)
|
||||
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])
|
||||
// 不保存电影解说类
|
||||
if strings.Contains(d.CName, "解说") {
|
||||
continue
|
||||
}
|
||||
// 如果DbId不为0, 则以dbID作为key进行hash额外存储一次
|
||||
if d.DbId != 0 {
|
||||
res[GenerateHashKey(d.DbId)] = string(data)
|
||||
}
|
||||
res[GenerateHashKey(d.Name)] = string(data)
|
||||
}
|
||||
}
|
||||
// 如果结果不为空,则将数据保存到redis中
|
||||
if len(res) > 0 {
|
||||
// 保存形式 key: MultipleSource:siteName Hash[hash(movieName)]list
|
||||
err = db.Rdb.HMSet(db.Cxt, fmt.Sprintf(config.MultipleSiteDetail, siteName), res).Err()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// BatchSaveSearchInfo 批量保存Search信息
|
||||
func BatchSaveSearchInfo(list []MovieDetail) {
|
||||
var infoList []SearchInfo
|
||||
for _, v := range list {
|
||||
infoList = append(infoList, ConvertSearchInfo(v))
|
||||
}
|
||||
// 将检索信息存入redis中做一次转存
|
||||
RdbSaveSearchInfo(infoList)
|
||||
}
|
||||
|
||||
// ConvertSearchInfo 将detail信息处理成 searchInfo
|
||||
func ConvertSearchInfo(detail MovieDetail) SearchInfo {
|
||||
score, _ := strconv.ParseFloat(detail.DbScore, 64)
|
||||
stamp, _ := time.ParseInLocation(time.DateTime, detail.UpdateTime, time.Local)
|
||||
// detail中的年份信息并不准确, 因此采用 ReleaseDate中的年份
|
||||
year, err := strconv.ParseInt(regexp.MustCompile(`[1-9][0-9]{3}`).FindString(detail.ReleaseDate), 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,
|
||||
Hits: detail.Hits,
|
||||
UpdateStamp: stamp.Unix(),
|
||||
State: detail.State,
|
||||
Remarks: detail.Remarks,
|
||||
// ReleaseDate 部分影片缺失该参数, 所以使用添加时间作为上映时间排序
|
||||
ReleaseStamp: detail.AddTime,
|
||||
}
|
||||
}
|
||||
|
||||
// GetBasicInfoByKey 获取Id对应的影片基本信息
|
||||
func GetBasicInfoByKey(key string) MovieBasicInfo {
|
||||
// 反序列化得到的结果
|
||||
data := []byte(db.Rdb.Get(db.Cxt, key).Val())
|
||||
basic := MovieBasicInfo{}
|
||||
_ = json.Unmarshal(data, &basic)
|
||||
// 执行本地图片匹配
|
||||
ReplaceBasicDetailPic(&basic)
|
||||
return basic
|
||||
}
|
||||
|
||||
// GetDetailByKey 获取影片对应的详情信息
|
||||
func GetDetailByKey(key string) MovieDetail {
|
||||
// 反序列化得到的结果
|
||||
data := []byte(db.Rdb.Get(db.Cxt, key).Val())
|
||||
detail := MovieDetail{}
|
||||
_ = json.Unmarshal(data, &detail)
|
||||
|
||||
// 执行本地图片匹配
|
||||
ReplaceDetailPic(&detail)
|
||||
return detail
|
||||
}
|
||||
|
||||
// GetBasicInfoBySearchInfos 通过searchInfo 获取影片的基本信息
|
||||
func GetBasicInfoBySearchInfos(infos ...SearchInfo) []MovieBasicInfo {
|
||||
var list []MovieBasicInfo
|
||||
for _, s := range infos {
|
||||
data := []byte(db.Rdb.Get(db.Cxt, fmt.Sprintf(config.MovieBasicInfoKey, s.Cid, s.Mid)).Val())
|
||||
basic := MovieBasicInfo{}
|
||||
_ = json.Unmarshal(data, &basic)
|
||||
|
||||
// 执行本地图片匹配
|
||||
ReplaceBasicDetailPic(&basic)
|
||||
list = append(list, basic)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
/*
|
||||
对附属播放源入库时的name|dbID进行处理,保证唯一性
|
||||
1. 去除name中的所有空格
|
||||
2. 去除name中含有的别名~.*~
|
||||
3. 去除name首尾的标点符号
|
||||
4. 将处理完成后的name转化为hash值作为存储时的key
|
||||
*/
|
||||
// GenerateHashKey 存储播放源信息时对影片名称进行处理, 提高各站点间同一影片的匹配度
|
||||
func GenerateHashKey[K string | ~int | int64](key K) string {
|
||||
mName := fmt.Sprint(key)
|
||||
//1. 去除name中的所有空格
|
||||
mName = regexp.MustCompile(`\s`).ReplaceAllString(mName, "")
|
||||
//2. 去除name中含有的别名~.*~
|
||||
mName = regexp.MustCompile(`~.*~$`).ReplaceAllString(mName, "")
|
||||
//3. 去除name首尾的标点符号
|
||||
mName = regexp.MustCompile(`^[[:punct:]]+|[[:punct:]]+$`).ReplaceAllString(mName, "")
|
||||
// 部分站点包含 动画版, 特殊别名 等字符, 需进行删除
|
||||
//mName = regexp.MustCompile(`动画版`).ReplaceAllString(mName, "")
|
||||
mName = regexp.MustCompile(`季.*`).ReplaceAllString(mName, "季")
|
||||
//4. 将处理完成后的name转化为hash值作为存储时的key
|
||||
h := fnv.New32a()
|
||||
_, err := h.Write([]byte(mName))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprint(h.Sum32())
|
||||
}
|
||||
|
||||
// ============================采集方案.v1 遗留==================================================
|
||||
|
||||
// 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()
|
||||
}
|
||||
76
server/model/system/Response.go
Normal file
76
server/model/system/Response.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
/*
|
||||
对 http response 做简单的封装
|
||||
*/
|
||||
|
||||
const (
|
||||
SUCCESS = 0
|
||||
FAILED = -1
|
||||
)
|
||||
|
||||
// Response http返回数据结构体
|
||||
type Response struct {
|
||||
Code int `json:"code"` // 状态 ok | failed
|
||||
Data any `json:"data"` // 数据
|
||||
Msg string `json:"msg"` // 提示信息
|
||||
//Count int `json:"count"` // 内容长度
|
||||
}
|
||||
|
||||
// PagingData 分页基本数据通用格式
|
||||
type PagingData struct {
|
||||
List []any `json:"list"`
|
||||
Paging Page `json:"paging"`
|
||||
}
|
||||
|
||||
// Page 分页信息结构体
|
||||
type Page struct {
|
||||
PageSize int `json:"pageSize"` // 每页大小
|
||||
Current int `json:"current"` // 当前页
|
||||
PageCount int `json:"pageCount"` // 总页数
|
||||
Total int `json:"total"` // 总记录数
|
||||
//List []interface{} `json:"list"` // 数据
|
||||
}
|
||||
|
||||
// Result 构建response返回数据结构
|
||||
func Result(code int, data any, msg string, c *gin.Context) {
|
||||
c.JSON(http.StatusOK, Response{
|
||||
Code: code,
|
||||
Data: data,
|
||||
Msg: msg,
|
||||
})
|
||||
}
|
||||
|
||||
// Success 成功响应 数据 + 成功提示
|
||||
func Success(data any, message string, c *gin.Context) {
|
||||
Result(SUCCESS, data, message, c)
|
||||
}
|
||||
|
||||
// SuccessOnlyMsg 成功响应, 只返回成功信息
|
||||
func SuccessOnlyMsg(message string, c *gin.Context) {
|
||||
Result(SUCCESS, nil, message, c)
|
||||
}
|
||||
|
||||
// Failed 响应失败 只返回错误信息
|
||||
func Failed(message string, c *gin.Context) {
|
||||
Result(FAILED, nil, message, c)
|
||||
}
|
||||
|
||||
// FailedWithData 返回错误信息以及必要数据
|
||||
func FailedWithData(data any, message string, c *gin.Context) {
|
||||
Result(FAILED, data, message, c)
|
||||
}
|
||||
|
||||
// GetPage 获取分页相关数据
|
||||
func GetPage(db *gorm.DB, page *Page) {
|
||||
var count int64
|
||||
db.Count(&count)
|
||||
page.Total = int(count)
|
||||
page.PageCount = int((page.Total + page.PageSize - 1) / page.PageSize)
|
||||
}
|
||||
82
server/model/system/ResponseJson.go
Normal file
82
server/model/system/ResponseJson.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package system
|
||||
|
||||
/*
|
||||
量子资源JSON解析
|
||||
*/
|
||||
|
||||
// ClassInfo class 分类数据
|
||||
type ClassInfo struct {
|
||||
Id int64 `json:"type_id"` //分类ID
|
||||
Pid int64 `json:"type_pid"` //上级分类ID
|
||||
Name string `json:"type_name"` //分类名称
|
||||
}
|
||||
|
||||
// MovieInfo 影片数据
|
||||
type MovieInfo struct {
|
||||
Id int64 `json:"vod_id"` // 影片ID
|
||||
Name string `json:"vod_name"` // 影片名
|
||||
Cid int64 `json:"type_id"` // 所属分类ID
|
||||
CName string `json:"type_name"` // 所属分类名称
|
||||
EnName string `json:"vod_en"` // 英文片名
|
||||
Time string `json:"vod_time"` // 更新时间
|
||||
Remarks string `json:"vod_remarks"` // 备注 | 清晰度
|
||||
PlayFrom string `json:"vod_play_from"` // 播放来源
|
||||
}
|
||||
|
||||
// MovieListInfo 影视列表响应数据
|
||||
type MovieListInfo struct {
|
||||
Code int64 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Page any `json:"page"`
|
||||
PageCount int64 `json:"pagecount"`
|
||||
Limit string `json:"limit"`
|
||||
Total int64 `json:"total"`
|
||||
List []MovieInfo `json:"list"`
|
||||
Class []ClassInfo `json:"class"`
|
||||
}
|
||||
|
||||
// MovieDetailInfo 影片详情数据 (只保留需要的部分)
|
||||
type MovieDetailInfo struct {
|
||||
Id int64 `json:"vod_id"` //影片Id
|
||||
Cid int64 `json:"type_id"` //分类ID
|
||||
Pid int64 `json:"type_id_1"` //一级分类ID
|
||||
Name string `json:"vod_name"` //片名
|
||||
SubTitle string `json:"vod_sub"` //子标题
|
||||
CName string `json:"type_name"` //分类名称
|
||||
EnName string `json:"vod_en"` //英文名
|
||||
Initial string `json:"vod_letter"` //首字母
|
||||
ClassTag string `json:"vod_class"` //分类标签
|
||||
Pic string `json:"vod_pic"` //简介图片
|
||||
Actor string `json:"vod_actor"` //主演
|
||||
Director string `json:"vod_director"` //导演
|
||||
Writer string `json:"vod_writer"` //作者
|
||||
Blurb string `json:"vod_blurb"` //简介, 残缺,不建议使用
|
||||
Remarks string `json:"vod_remarks"` // 更新情况
|
||||
PubDate string `json:"vod_pubdate"` //上映时间
|
||||
Area string `json:"vod_area"` // 地区
|
||||
Language string `json:"vod_lang"` //语言
|
||||
Year string `json:"vod_year"` //年份
|
||||
State string `json:"vod_state"` //影片状态 正片|预告...
|
||||
UpdateTime string `json:"vod_time"` //更新时间
|
||||
AddTime int64 `json:"vod_time_add"` //资源添加时间戳
|
||||
DbId int64 `json:"vod_douban_id"` //豆瓣id
|
||||
DbScore string `json:"vod_douban_score"` // 豆瓣评分
|
||||
Hits int64 `json:"vod_hits"` // 总热度
|
||||
Content string `json:"vod_content"` //内容简介
|
||||
PlayFrom string `json:"vod_play_from"` // 播放来源
|
||||
PlaySeparator string `json:"vod_play_note"` // 播放信息分隔符
|
||||
PlayUrl string `json:"vod_play_url"` //播放地址url
|
||||
DownFrom string `json:"vod_down_from"` //下载来源 例: http
|
||||
DownUrl string `json:"vod_down_url"` // 下载url地址
|
||||
}
|
||||
|
||||
// DetailListInfo 影视详情信息
|
||||
type DetailListInfo struct {
|
||||
Code int64 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Page any `json:"page"`
|
||||
PageCount int64 `json:"pagecount"`
|
||||
Limit string `json:"limit"`
|
||||
Total int64 `json:"total"`
|
||||
List []MovieDetailInfo `json:"list"`
|
||||
}
|
||||
817
server/model/system/Search.go
Normal file
817
server/model/system/Search.go
Normal file
@@ -0,0 +1,817 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
return
|
||||
}
|
||||
// 保存成功后将相应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.Sprint("TRUNCATE TABLE ", 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) {
|
||||
// 1.从redis中批量扫描详情信息
|
||||
list, cursor := db.Rdb.ZScan(db.Cxt, config.SearchInfoTemp, 0, "*", config.MaxScanCount).Val()
|
||||
// 如果扫描到的信息为空则直接退出
|
||||
if len(list) <= 0 {
|
||||
return
|
||||
}
|
||||
// 2. 处理数据
|
||||
var sl []SearchInfo
|
||||
for i, s := range list {
|
||||
// 3. 判断当前是否是元素
|
||||
if i%2 == 0 {
|
||||
info := SearchInfo{}
|
||||
_ = json.Unmarshal([]byte(s), &info)
|
||||
info.Model = gorm.Model{}
|
||||
// 获取完则删除元素, 避免重复保存
|
||||
db.Rdb.ZRem(db.Cxt, config.SearchInfoTemp, []byte(s))
|
||||
sl = append(sl, info)
|
||||
}
|
||||
}
|
||||
//
|
||||
switch model {
|
||||
case 0:
|
||||
// 批量添加 SearchInfo
|
||||
BatchSave(sl)
|
||||
case 1:
|
||||
// 批量更新或添加
|
||||
BatchSaveOrUpdate(sl)
|
||||
}
|
||||
// 如果 SearchInfoTemp 依然存在数据, 则递归执行
|
||||
if cursor > 0 {
|
||||
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("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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 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)
|
||||
// 条件拼接完成后加上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
|
||||
}
|
||||
|
||||
// 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()
|
||||
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("year DESC, release_stamp DESC")
|
||||
case 1:
|
||||
// 排行榜 (暂定为热度排行)
|
||||
qw.Order("year DESC, hits DESC")
|
||||
case 2:
|
||||
// 最近更新 (更新时间)
|
||||
qw.Order("year DESC, update_stamp DESC")
|
||||
}
|
||||
if err := qw.Find(&sl).Error; err != nil {
|
||||
log.Println(err)
|
||||
return nil
|
||||
}
|
||||
return GetBasicInfoBySearchInfos(sl...)
|
||||
|
||||
}
|
||||
|
||||
// ================================= Manage 管理后台 =================================
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ================================= 接口数据缓存 =================================
|
||||
|
||||
// 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
|
||||
}
|
||||
94
server/model/system/User.go
Normal file
94
server/model/system/User.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gorm.io/gorm"
|
||||
"log"
|
||||
"server/config"
|
||||
"server/plugin/common/util"
|
||||
"server/plugin/db"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
UserName string `json:"userName"` // 用户名
|
||||
Password string `json:"password"` // 密码
|
||||
Salt string `json:"salt"` // 盐值
|
||||
Email string `json:"email"` // 邮箱
|
||||
Gender int `json:"gender"` // 性别
|
||||
NickName string `json:"nickName"` // 昵称
|
||||
Avatar string `json:"avatar"` // 头像
|
||||
Status int `json:"status"` // 状态
|
||||
Reserve1 string `json:"reserve1"` // 预留字段 3
|
||||
Reserve2 string `json:"reserve2"` // 预留字段 2
|
||||
Reserve3 string `json:"reserve3"` // 预留字段 1
|
||||
//LastLongTime time.Time `json:"LastLongTime"` // 最后登录时间
|
||||
}
|
||||
|
||||
// TableName 设置user表的表名
|
||||
func (u *User) TableName() string {
|
||||
return config.UserTableName
|
||||
}
|
||||
|
||||
// CreateUserTable 创建存储检索信息的数据表
|
||||
func CreateUserTable() {
|
||||
var u = &User{}
|
||||
// 如果不存在则创建表 并设置自增ID初始值为10000
|
||||
if !ExistUserTable() {
|
||||
err := db.Mdb.AutoMigrate(u)
|
||||
db.Mdb.Exec(fmt.Sprintf("alter table %s auto_Increment=%d", u.TableName(), config.UserIdInitialVal))
|
||||
if err != nil {
|
||||
log.Println("Create Table SearchInfo Failed: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ExistUserTable 判断表中是否存在User表
|
||||
func ExistUserTable() bool {
|
||||
return db.Mdb.Migrator().HasTable(&User{})
|
||||
}
|
||||
|
||||
// InitAdminAccount 初始化admin用户密码
|
||||
func InitAdminAccount() {
|
||||
// 先查询是否已经存在admin用户信息, 存在则直接退出
|
||||
user := GetUserByNameOrEmail("admin")
|
||||
if user != nil {
|
||||
return
|
||||
}
|
||||
// 不存在管理员用户则进行初始化创建
|
||||
u := &User{
|
||||
UserName: "admin",
|
||||
Password: "admin",
|
||||
Salt: util.GenerateSalt(),
|
||||
Email: "administrator@gmail.com",
|
||||
Gender: 2,
|
||||
NickName: "Zero",
|
||||
Avatar: "empty",
|
||||
Status: 0,
|
||||
}
|
||||
|
||||
u.Password = util.PasswordEncrypt(u.Password, u.Salt)
|
||||
db.Mdb.Create(u)
|
||||
}
|
||||
|
||||
// GetUserByNameOrEmail 查询 username || email 对应的账户信息
|
||||
func GetUserByNameOrEmail(userName string) *User {
|
||||
var u *User
|
||||
if err := db.Mdb.Where("user_name = ? OR email = ?", userName, userName).First(&u).Error; err != nil {
|
||||
log.Println(err)
|
||||
return nil
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
func GetUserById(id uint) User {
|
||||
var user = User{Model: gorm.Model{ID: id}}
|
||||
db.Mdb.First(&user)
|
||||
return user
|
||||
}
|
||||
|
||||
// UpdateUserInfo 更新用户信息
|
||||
func UpdateUserInfo(u User) {
|
||||
// 值更新允许修改的部分字段, 零值会在更新时被自动忽略
|
||||
db.Mdb.Model(&u).Updates(User{Password: u.Password, Email: u.Email, NickName: u.NickName, Status: u.Status})
|
||||
}
|
||||
102
server/model/system/VirtualObject.go
Normal file
102
server/model/system/VirtualObject.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package system
|
||||
|
||||
type SearchTagsVO struct {
|
||||
Pid int64 `json:"pid"`
|
||||
Cid int64 `json:"cid"`
|
||||
Plot string `json:"plot"`
|
||||
Area string `json:"area"`
|
||||
Language string `json:"language"`
|
||||
Year int64 `json:"year"`
|
||||
Sort string `json:"sort"`
|
||||
}
|
||||
|
||||
// FilmCronVo 影视更新任务请求参数
|
||||
type FilmCronVo struct {
|
||||
Ids []string `json:"ids"` // 定时任务关联的资源站Id
|
||||
Time int `json:"time"` // 更新最近几小时内更新的影片
|
||||
Spec string `json:"spec"` // cron表达式
|
||||
Model int `json:"model"` // 任务类型, 0 - 自动更新已启用站点 || 1 - 更新Ids中的资源站数据
|
||||
State bool `json:"state"` // 任务状态 开启 | 关闭
|
||||
Remark string `json:"remark"` // 备注信息
|
||||
}
|
||||
|
||||
// CronTaskVo 定时任务数据response
|
||||
type CronTaskVo struct {
|
||||
FilmCollectTask
|
||||
PreV string `json:"preV"` // 上次执行时间
|
||||
Next string `json:"next"` // 下次执行时间
|
||||
}
|
||||
|
||||
// FilmTaskOptions 影视采集任务添加时需要的options
|
||||
type FilmTaskOptions struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// CollectParams 数据采集所需要的参数
|
||||
type CollectParams struct {
|
||||
Id string `json:"id"` // 资源站id
|
||||
Ids []string `json:"ids"` // 资源站id列表
|
||||
Time int `json:"time"` // 采集时长
|
||||
Batch bool `json:"batch"` // 是否批量执行
|
||||
}
|
||||
|
||||
// SearchVo 影片信息搜索参数
|
||||
type SearchVo struct {
|
||||
Name string `json:"name"` // 影片名
|
||||
Pid int64 `json:"pid"` // 一级分类ID
|
||||
Cid int64 `json:"cid"` // 二级分类ID
|
||||
Plot string `json:"plot"` // 剧情
|
||||
Area string `json:"area"` // 地区
|
||||
Language string `json:"language"` // 语言
|
||||
Year int64 `json:"year"` // 年份
|
||||
//Score int64 `json:"score"` // 评分
|
||||
Remarks string `json:"remarks"` // 完结 | 未完结
|
||||
BeginTime int64 `json:"beginTime"` // 更新时间戳起始值
|
||||
EndTime int64 `json:"endTime"` // 更新时间戳结束值
|
||||
Paging *Page `json:"paging"` // 分页参数
|
||||
}
|
||||
|
||||
// FilmDetailVo 添加影片对象
|
||||
type FilmDetailVo 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
|
||||
PlayLink string `json:"playLink"` //播放地址url
|
||||
DownloadLink string `json:"downloadLink"` // 下载url地址
|
||||
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"` //作者
|
||||
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 string `json:"addTime"` //资源添加时间戳
|
||||
DbId int64 `json:"dbId"` //豆瓣id
|
||||
DbScore string `json:"dbScore"` // 豆瓣评分
|
||||
Hits int64 `json:"hits"` //影片热度
|
||||
Content string `json:"content"` //内容简介
|
||||
}
|
||||
|
||||
// UserInfoVo 用户信息返回对象
|
||||
type UserInfoVo struct {
|
||||
Id uint `json:"id"`
|
||||
UserName string `json:"userName"` // 用户名
|
||||
Email string `json:"email"` // 邮箱
|
||||
Gender int `json:"gender"` // 性别
|
||||
NickName string `json:"nickName"` // 昵称
|
||||
Avatar string `json:"avatar"` // 头像
|
||||
Status int `json:"status"` // 状态
|
||||
}
|
||||
Reference in New Issue
Block a user