mirror of
https://github.com/ProudMuBai/GoFilm.git
synced 2026-03-14 01:57:32 +08:00
ABS v1 test
This commit is contained in:
@@ -110,11 +110,10 @@ const (
|
||||
|
||||
//mysql服务配置信息 root:root 设置mysql账户的用户名和密码
|
||||
|
||||
MysqlDsn = "root:MuBai0916$@(mysql:3306)/FilmSite?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
|
||||
// Mysql连接信息
|
||||
MysqlDsn = "root:root@(mysql:3306)/FilmSite?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
// Redis连接信息
|
||||
RedisAddr = `redis:6379`
|
||||
RedisPassword = `root`
|
||||
RedisPassword = `MuBai0916$`
|
||||
RedisDBNo = 0
|
||||
)
|
||||
|
||||
19
film/server/config/PrivideConfig.go
Normal file
19
film/server/config/PrivideConfig.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
/*
|
||||
对外开放API相关配置
|
||||
*/
|
||||
|
||||
const (
|
||||
// ResourceExpired API所需要的资源有效期
|
||||
ResourceExpired = time.Hour * 24 * 90
|
||||
// OriginalFilmDetailKey 采集时原始数据存储key
|
||||
OriginalFilmDetailKey = "OriginalResource:FilmDetail:Id%d"
|
||||
FilmClassKey = "OriginalResource:FilmClass"
|
||||
PlayForm = "gfm3u8"
|
||||
PlayFormCloud = "gofilm"
|
||||
PlayFormAll = "gofilm$$$gfmu38"
|
||||
RssVersion = "5.1"
|
||||
)
|
||||
@@ -18,10 +18,7 @@ const (
|
||||
func Index(c *gin.Context) {
|
||||
// 获取首页所需数据
|
||||
data := logic.IL.IndexPage()
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"data": data,
|
||||
})
|
||||
system.Success(data, "首页数据获取成功", c)
|
||||
}
|
||||
|
||||
// CategoriesInfo 分类信息获取
|
||||
@@ -47,54 +44,50 @@ func FilmDetail(c *gin.Context) {
|
||||
// 获取请求参数
|
||||
id, err := strconv.Atoi(c.Query("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "请求异常,暂无影片信息!!!",
|
||||
})
|
||||
system.Failed("请求异常,影片请求参数异常!!!", c)
|
||||
return
|
||||
}
|
||||
// 获取影片详情信息
|
||||
detail := logic.IL.GetFilmDetail(id)
|
||||
// 获取相关推荐影片数据
|
||||
page := system.Page{Current: 0, PageSize: 14}
|
||||
relateMovie := logic.IL.RelateMovie(detail, &page)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"data": gin.H{
|
||||
"detail": detail,
|
||||
"relate": relateMovie,
|
||||
},
|
||||
})
|
||||
relateMovie := logic.IL.RelateMovie(detail.MovieDetail, &page)
|
||||
system.Success(gin.H{
|
||||
"detail": detail,
|
||||
"relate": relateMovie,
|
||||
}, "影片详情信息获取成功", c)
|
||||
}
|
||||
|
||||
// FilmPlayInfo 影视播放页数据
|
||||
func FilmPlayInfo(c *gin.Context) {
|
||||
// 获取请求参数
|
||||
id, err := strconv.Atoi(c.DefaultQuery("id", "0"))
|
||||
playFrom, err := strconv.Atoi(c.DefaultQuery("playFrom", "0"))
|
||||
playFrom := c.DefaultQuery("playFrom", "")
|
||||
episode, err := strconv.Atoi(c.DefaultQuery("episode", "0"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "请求异常,暂无影片信息!!!",
|
||||
})
|
||||
system.Failed("请求异常,暂无影片信息!!!", c)
|
||||
return
|
||||
}
|
||||
// 获取影片详情信息
|
||||
detail := logic.IL.GetFilmDetail(id)
|
||||
// 获取当前影片播放信息
|
||||
var currentPlay system.MovieUrlInfo
|
||||
for _, v := range detail.List {
|
||||
if v.Id == playFrom {
|
||||
currentPlay = v.LinkList[episode]
|
||||
}
|
||||
}
|
||||
|
||||
// 推荐影片信息
|
||||
page := system.Page{Current: 0, PageSize: 14}
|
||||
relateMovie := logic.IL.RelateMovie(detail, &page)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"data": gin.H{
|
||||
"detail": detail,
|
||||
"current": detail.PlayList[playFrom][episode],
|
||||
"currentPlayFrom": playFrom,
|
||||
"currentEpisode": episode,
|
||||
"relate": relateMovie,
|
||||
},
|
||||
})
|
||||
relateMovie := logic.IL.RelateMovie(detail.MovieDetail, &page)
|
||||
system.Success(gin.H{
|
||||
"detail": detail,
|
||||
"current": currentPlay,
|
||||
"currentPlayFrom": playFrom,
|
||||
"currentEpisode": episode,
|
||||
"relate": relateMovie,
|
||||
}, "影片播放信息获取成功", c)
|
||||
}
|
||||
|
||||
// SearchFilm 通过片名模糊匹配库存中的信息
|
||||
@@ -187,53 +180,3 @@ func FilmClassify(c *gin.Context) {
|
||||
"page": page,
|
||||
})
|
||||
}
|
||||
|
||||
// FilmCategory 获取指定分类的影片分页数据,(已弃用)
|
||||
func FilmCategory(c *gin.Context) {
|
||||
// 1.1 首先获取Cid 二级分类id是否存在
|
||||
cidStr := c.DefaultQuery("cid", "")
|
||||
// 1.2 如果pid也不存在直接返回错误信息
|
||||
pidStr := c.DefaultQuery("pid", "")
|
||||
if pidStr == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "缺少分类信息",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 1.3 获取pid对应的分类信息
|
||||
pid, _ := strconv.ParseInt(pidStr, 10, 64)
|
||||
category := logic.IL.GetPidCategory(pid)
|
||||
|
||||
// 2 设置分页信息
|
||||
currentStr := c.DefaultQuery("current", "1")
|
||||
current, _ := strconv.Atoi(currentStr)
|
||||
page := system.Page{PageSize: 49, Current: current}
|
||||
// 2.1 如果不存在cid则根据Pid进行查询
|
||||
if cidStr == "" {
|
||||
// 2.2 如果存在pid则根据pid进行查找
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"data": gin.H{
|
||||
"list": logic.IL.GetFilmCategory(pid, "pid", &page),
|
||||
"category": category,
|
||||
"search": logic.IL.SearchTags(pid),
|
||||
},
|
||||
"page": page,
|
||||
})
|
||||
return
|
||||
}
|
||||
// 2.2 如果存在cid 则根据具体的cid去查询数据
|
||||
cid, _ := strconv.ParseInt(cidStr, 10, 64)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"data": gin.H{
|
||||
"list": logic.IL.GetFilmCategory(cid, "cid", &page),
|
||||
"category": category,
|
||||
"search": logic.IL.SearchTags(pid),
|
||||
},
|
||||
"page": page,
|
||||
})
|
||||
|
||||
// 获取请求参数
|
||||
}
|
||||
|
||||
@@ -9,10 +9,9 @@ require (
|
||||
github.com/redis/go-redis/v9 v9.0.2
|
||||
github.com/robfig/cron/v3 v3.0.0
|
||||
gorm.io/driver/mysql v1.4.7
|
||||
gorm.io/gorm v1.24.6
|
||||
gorm.io/gorm v1.25.5
|
||||
)
|
||||
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.5.1 // indirect
|
||||
github.com/andybalholm/cascadia v1.2.0 // indirect
|
||||
|
||||
@@ -207,6 +207,8 @@ gorm.io/driver/mysql v1.4.7/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8o
|
||||
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/gorm v1.24.6 h1:wy98aq9oFEetsc4CAbKD2SoBCdMzsbSIvSUUFJuHi5s=
|
||||
gorm.io/gorm v1.24.6/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
@@ -67,15 +67,16 @@ func (i *IndexLogic) IndexPage() map[string]interface{} {
|
||||
}
|
||||
|
||||
// GetFilmDetail 影片详情信息页面处理
|
||||
func (i *IndexLogic) GetFilmDetail(id int) system.MovieDetail {
|
||||
func (i *IndexLogic) GetFilmDetail(id int) system.MovieDetailVo {
|
||||
// 通过Id 获取影片search信息
|
||||
search := system.SearchInfo{}
|
||||
db.Mdb.Where("mid", id).First(&search)
|
||||
// 获取redis中的完整影视信息 MovieDetail:Cid11:Id24676
|
||||
movieDetail := system.GetDetailByKey(fmt.Sprintf(config.MovieDetailKey, search.Cid, search.Mid))
|
||||
var res = system.MovieDetailVo{MovieDetail: movieDetail}
|
||||
//查找其他站点是否存在影片对应的播放源
|
||||
multipleSource(&movieDetail)
|
||||
return movieDetail
|
||||
res.List = multipleSource(&movieDetail)
|
||||
return res
|
||||
}
|
||||
|
||||
// GetCategoryInfo 分类信息获取, 组装导航栏需要的信息
|
||||
@@ -183,7 +184,11 @@ func (i *IndexLogic) SearchTags(pid int64) map[string]interface{} {
|
||||
2. 仅对主站点影片name进行映射关系处理并将结果添加到map中
|
||||
例如: xxx第一季 xxx
|
||||
*/
|
||||
func multipleSource(detail *system.MovieDetail) {
|
||||
func multipleSource(detail *system.MovieDetail) []system.PlayLinkVo {
|
||||
// 生成多站点的播放源信息
|
||||
master := system.GetCollectSourceListByGrade(system.MasterCollect)
|
||||
var playList = []system.PlayLinkVo{{master[0].Id, master[0].Name, detail.PlayList[0]}}
|
||||
|
||||
// 整合多播放源, 初始化存储key map
|
||||
names := make(map[string]int)
|
||||
// 1. 判断detail的dbId是否存在, 存在则添加到names中作为匹配条件
|
||||
@@ -206,18 +211,21 @@ func multipleSource(detail *system.MovieDetail) {
|
||||
names[system.GenerateHashKey(v)] = 0
|
||||
}
|
||||
}
|
||||
// 遍历站点列表
|
||||
// 遍历所有附属站点列表
|
||||
sc := system.GetCollectSourceListByGrade(system.SlaveCollect)
|
||||
for _, s := range sc {
|
||||
for k, _ := range names {
|
||||
pl := system.GetMultiplePlay(s.Name, k)
|
||||
pl := system.GetMultiplePlay(s.Id, k)
|
||||
if len(pl) > 0 {
|
||||
// 如果当前站点已经匹配到数据则直接退出当前循环
|
||||
detail.PlayList = append(detail.PlayList, pl)
|
||||
//detail.PlayList = append(detail.PlayList, pl)
|
||||
playList = append(playList, system.PlayLinkVo{Id: s.Id, Name: s.Name, LinkList: pl})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return playList
|
||||
}
|
||||
|
||||
// GetFilmsByTags 通过searchTag 返回满足条件的分页影片信息
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"server/model/system"
|
||||
"server/plugin/SystemInit"
|
||||
"server/plugin/db"
|
||||
"server/plugin/spider"
|
||||
"server/router"
|
||||
"time"
|
||||
)
|
||||
@@ -31,10 +30,6 @@ func main() {
|
||||
}
|
||||
|
||||
func start() {
|
||||
// 开启前先判断是否需要执行Spider
|
||||
//ExecSpider()
|
||||
// web服务启动后开启定时任务, 用于定期更新资源
|
||||
//spider.RegularUpdateMovie()
|
||||
|
||||
// 启动前先执行数据库内容的初始化工作
|
||||
DefaultDataInit()
|
||||
@@ -43,21 +38,14 @@ func start() {
|
||||
_ = r.Run(fmt.Sprintf(":%s", config.ListenerPort))
|
||||
}
|
||||
|
||||
func ExecSpider() {
|
||||
// 判断分类信息是否存在
|
||||
isStart := system.ExistsCategoryTree()
|
||||
// 如果分类信息不存在则进行一次完整爬取
|
||||
if !isStart {
|
||||
DefaultDataInit()
|
||||
spider.StartSpider()
|
||||
func DefaultDataInit() {
|
||||
// 如果系统中不存在用户表则进行初始化
|
||||
if !system.ExistUserTable() {
|
||||
// 初始化影视来源列表信息
|
||||
SystemInit.SpiderInit()
|
||||
// 初始化数据库相关数据
|
||||
SystemInit.TableInIt()
|
||||
// 初始化网站基本配置信息
|
||||
SystemInit.BasicConfigInit()
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultDataInit() {
|
||||
// 初始化影视来源列表信息
|
||||
SystemInit.SpiderInit()
|
||||
// 初始化数据库相关数据
|
||||
SystemInit.TableInIt()
|
||||
// 初始化网站基本配置信息
|
||||
SystemInit.BasicConfigInit()
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ type FilmSource struct {
|
||||
SyncPictures bool `json:"syncPictures"` // 是否同步图片到服务器
|
||||
CollectType ResourceType `json:"collectType"` // 采集资源类型
|
||||
State bool `json:"state"` // 是否启用
|
||||
Interval int `json:"interval"` // 采集时间间隔 单位/ms
|
||||
}
|
||||
|
||||
// SaveCollectSourceList 保存采集站Api列表
|
||||
|
||||
@@ -89,52 +89,59 @@ func GetPicturePage(page *Page) []Picture {
|
||||
|
||||
// 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})
|
||||
}
|
||||
//if !ExistPictureByRid(p.Id) {
|
||||
// m, _ := json.Marshal(p)
|
||||
// zl = append(zl, redis.Z{Score: float64(p.Id), Member: m})
|
||||
//}
|
||||
|
||||
// 只要开启图片同步则将图片信息存入待同步图片信息集合中, 是否同步图片交由真正同步到本地时进行决断
|
||||
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 := db.Rdb.ZCard(db.Cxt, config.VirtualPictureKey).Val()
|
||||
if count <= 0 {
|
||||
return
|
||||
}
|
||||
// 扫描待同步图片的信息, 每次扫描count条
|
||||
sl, cursor := db.Rdb.ZScan(db.Cxt, config.VirtualPictureKey, 0, "*", config.MaxScanCount).Val()
|
||||
sl := db.Rdb.ZPopMax(db.Cxt, config.VirtualPictureKey, 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, ""),
|
||||
})
|
||||
for _, s := range sl {
|
||||
// 获取图片信息
|
||||
vp := VirtualPicture{}
|
||||
_ = json.Unmarshal([]byte(s.Member.(string)), &vp)
|
||||
// 判断当前影片是否已经同步过图片, 如果已经同步则直接跳过后续逻辑
|
||||
if ExistPictureByRid(vp.Id) {
|
||||
continue
|
||||
}
|
||||
// 将图片同步到服务器中
|
||||
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()
|
||||
}
|
||||
// 递归执行直到图片暂存信息为空
|
||||
SyncFilmPicture()
|
||||
}
|
||||
|
||||
// ReplaceDetailPic 将影片详情中的图片地址替换为自己的
|
||||
|
||||
@@ -160,7 +160,7 @@ func SaveMovieBasicInfo(detail MovieDetail) {
|
||||
}
|
||||
|
||||
// SaveSitePlayList 仅保存播放url列表信息到当前站点
|
||||
func SaveSitePlayList(siteName string, list []MovieDetail) (err error) {
|
||||
func SaveSitePlayList(id string, list []MovieDetail) (err error) {
|
||||
// 如果list 为空则直接返回
|
||||
if len(list) <= 0 {
|
||||
return nil
|
||||
@@ -183,7 +183,7 @@ func SaveSitePlayList(siteName string, list []MovieDetail) (err error) {
|
||||
// 如果结果不为空,则将数据保存到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()
|
||||
err = db.Rdb.HMSet(db.Cxt, fmt.Sprintf(config.MultipleSiteDetail, id), res).Err()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
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"`
|
||||
}
|
||||
@@ -261,7 +261,6 @@ func BatchSave(list []SearchInfo) {
|
||||
if err := tx.CreateInBatches(list, len(list)).Error; err != nil {
|
||||
// 插入失败则回滚事务, 重新进行插入
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
// 保存成功后将相应tag数据缓存到redis中
|
||||
BatchHandleSearchTag(list...)
|
||||
@@ -357,26 +356,26 @@ func SyncSearchInfo(model int) {
|
||||
|
||||
// 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, cursor := db.Rdb.ZScan(db.Cxt, config.SearchInfoTemp, 0, "*", config.MaxScanCount).Val()
|
||||
list := db.Rdb.ZPopMax(db.Cxt, config.SearchInfoTemp, 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)
|
||||
}
|
||||
for _, s := range list {
|
||||
// 解析详情数据
|
||||
info := SearchInfo{}
|
||||
_ = json.Unmarshal([]byte(s.Member.(string)), &info)
|
||||
sl = append(sl, info)
|
||||
}
|
||||
//
|
||||
// 通过model执行对应的保存方法
|
||||
switch model {
|
||||
case 0:
|
||||
// 批量添加 SearchInfo
|
||||
@@ -386,9 +385,7 @@ func SearchInfoToMdb(model int) {
|
||||
BatchSaveOrUpdate(sl)
|
||||
}
|
||||
// 如果 SearchInfoTemp 依然存在数据, 则递归执行
|
||||
if cursor > 0 {
|
||||
SearchInfoToMdb(model)
|
||||
}
|
||||
SearchInfoToMdb(model)
|
||||
}
|
||||
|
||||
// ================================= API 数据接口信息处理 =================================
|
||||
@@ -533,8 +530,8 @@ func GetRelateMovieBasicInfo(search SearchInfo, page *Page) []MovieBasicInfo {
|
||||
}
|
||||
|
||||
// GetMultiplePlay 通过影片名hash值匹配播放源
|
||||
func GetMultiplePlay(siteName, key string) []MovieUrlInfo {
|
||||
data := db.Rdb.HGet(db.Cxt, fmt.Sprintf(config.MultipleSiteDetail, siteName), key).Val()
|
||||
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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package system
|
||||
|
||||
// SearchTagsVO 搜索标签请求参数
|
||||
type SearchTagsVO struct {
|
||||
Pid int64 `json:"pid"`
|
||||
Cid int64 `json:"cid"`
|
||||
@@ -100,3 +101,15 @@ type UserInfoVo struct {
|
||||
Avatar string `json:"avatar"` // 头像
|
||||
Status int `json:"status"` // 状态
|
||||
}
|
||||
|
||||
// PlayLinkVo 多站点播放链接数据列表
|
||||
type PlayLinkVo struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
LinkList []MovieUrlInfo `json:"linkList"`
|
||||
}
|
||||
|
||||
type MovieDetailVo struct {
|
||||
MovieDetail
|
||||
List []PlayLinkVo `json:"list"`
|
||||
}
|
||||
|
||||
@@ -21,12 +21,12 @@ func FilmSourceInit() {
|
||||
return
|
||||
}
|
||||
var l []system.FilmSource = []system.FilmSource{
|
||||
{Id: util.GenerateSalt(), Name: "HD(lzBk)", Uri: `https://cj.lzcaiji.com/api.php/provide/vod/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false,CollectType:system.CollectVideo, State: false},
|
||||
// {Id: util.GenerateSalt(), Name: "HD(lz)", Uri: `https://cj.lziapi.com/api.php/provide/vod/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: true},
|
||||
{Id: util.GenerateSalt(), Name: "HD(bf)", Uri: `https://bfzyapi.com/api.php/provide/vod/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: true},
|
||||
{Id: util.GenerateSalt(), Name: "HD(lzBk)", Uri: `https://cj.lzcaiji.com/api.php/provide/vod/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: true},
|
||||
{Id: util.GenerateSalt(), Name: "HD(sn)", Uri: `https://suoniapi.com/api.php/provide/vod/from/snm3u8/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: true},
|
||||
{Id: util.GenerateSalt(), Name: "HD(bf)", Uri: `https://bfzyapi.com/api.php/provide/vod/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: true, Interval: 1600},
|
||||
{Id: util.GenerateSalt(), Name: "HD(ff)", Uri: `http://cj.ffzyapi.com/api.php/provide/vod/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: true},
|
||||
{Id: util.GenerateSalt(), Name: "HD(kk)", Uri: `https://kuaikan-api.com/api.php/provide/vod/from/kuaikan/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: true},
|
||||
{Id: util.GenerateSalt(), Name: "HD(sn)", Uri: `https://suoniapi.com/api.php/provide/vod/from/snm3u8/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: true},
|
||||
//{Id: util.GenerateSalt(), Name: "HD(lz)", Uri: `https://cj.lziapi.com/api.php/provide/vod/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: true},
|
||||
//{Id: util.GenerateSalt(), Name: "HD(fs)", Uri: `https://www.feisuzyapi.com/api.php/provide/vod/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: true},
|
||||
//{Id: util.GenerateSalt(), Name: "HD(bfApp)", Uri: `http://app.bfzyapi.com/api.php/provide/vod/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: true},
|
||||
//Id: util.GenerateSalt(), {Name: "HD(bfBk)", Uri: `http://by.bfzyapi.com/api.php/provide/vod/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false,CollectType:system.CollectVideo, State: false},
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package dp
|
||||
|
||||
import (
|
||||
"server/model/system"
|
||||
)
|
||||
|
||||
// =================Spider数据处理=======================
|
||||
|
||||
// CategoryTree 组装树形菜单
|
||||
func CategoryTree(list []system.ClassInfo) *system.CategoryTree {
|
||||
// 遍历所有分类进行树形结构组装
|
||||
tree := &system.CategoryTree{Category: &system.Category{Id: 0, Pid: -1, Name: "分类信息"}}
|
||||
temp := make(map[int64]*system.CategoryTree)
|
||||
temp[tree.Id] = tree
|
||||
|
||||
for _, c := range list {
|
||||
// 判断当前节点ID是否存在于 temp中
|
||||
category, ok := temp[c.Id]
|
||||
if ok {
|
||||
// 将当前节点信息保存
|
||||
category.Category = &system.Category{Id: c.Id, Pid: c.Pid, Name: c.Name}
|
||||
} else {
|
||||
// 如果不存在则将当前分类存放到 temp中
|
||||
category = &system.CategoryTree{Category: &system.Category{Id: c.Id, Pid: c.Pid, Name: c.Name}}
|
||||
temp[c.Id] = category
|
||||
}
|
||||
// 根据 pid获取父节点信息
|
||||
parent, ok := temp[category.Pid]
|
||||
if !ok {
|
||||
// 如果不存在父节点存在, 则将父节点存放到temp中
|
||||
temp[c.Pid] = parent
|
||||
}
|
||||
// 将当前节点存放到父节点的Children中
|
||||
parent.Children = append(parent.Children, category)
|
||||
}
|
||||
|
||||
return tree
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
package dp
|
||||
|
||||
import (
|
||||
"server/model/system"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ProcessMovieListInfo 处理影片列表中的信息
|
||||
func ProcessMovieListInfo(list []system.MovieInfo) []system.Movie {
|
||||
var movies []system.Movie
|
||||
for _, info := range list {
|
||||
movies = append(movies, system.Movie{
|
||||
Id: info.Id,
|
||||
Name: info.Name,
|
||||
Cid: info.Cid,
|
||||
CName: info.CName,
|
||||
EnName: info.EnName,
|
||||
Time: info.Time,
|
||||
Remarks: info.Remarks,
|
||||
PlayFrom: info.PlayFrom,
|
||||
})
|
||||
}
|
||||
return movies
|
||||
}
|
||||
|
||||
// ProcessMovieDetailList 处理影片详情列表数据
|
||||
func ProcessMovieDetailList(list []system.MovieDetailInfo) []system.MovieDetail {
|
||||
var detailList []system.MovieDetail
|
||||
for _, d := range list {
|
||||
detailList = append(detailList, ProcessMovieDetail(d))
|
||||
}
|
||||
return detailList
|
||||
}
|
||||
|
||||
// ProcessMovieDetail 处理单个影片详情信息
|
||||
func ProcessMovieDetail(detail system.MovieDetailInfo) system.MovieDetail {
|
||||
md := system.MovieDetail{
|
||||
Id: detail.Id,
|
||||
Cid: detail.Cid,
|
||||
Pid: detail.Pid,
|
||||
Name: detail.Name,
|
||||
Picture: detail.Pic,
|
||||
DownFrom: detail.DownFrom,
|
||||
MovieDescriptor: system.MovieDescriptor{
|
||||
SubTitle: detail.SubTitle,
|
||||
CName: detail.CName,
|
||||
EnName: detail.EnName,
|
||||
Initial: detail.Initial,
|
||||
ClassTag: detail.ClassTag,
|
||||
Actor: detail.Actor,
|
||||
Director: detail.Director,
|
||||
Writer: detail.Writer,
|
||||
Blurb: detail.Blurb,
|
||||
Remarks: detail.Remarks,
|
||||
ReleaseDate: detail.PubDate,
|
||||
Area: detail.Area,
|
||||
Language: detail.Language,
|
||||
Year: detail.Year,
|
||||
State: detail.State,
|
||||
UpdateTime: detail.UpdateTime,
|
||||
AddTime: detail.AddTime,
|
||||
DbId: detail.DbId,
|
||||
DbScore: detail.DbScore,
|
||||
Hits: detail.Hits,
|
||||
Content: detail.Content,
|
||||
},
|
||||
}
|
||||
// 通过分割符切分播放源信息 PlaySeparator $$$
|
||||
md.PlayFrom = strings.Split(detail.PlayFrom, detail.PlaySeparator)
|
||||
// v2 只保留m3u8播放源
|
||||
md.PlayList = ProcessPlayInfoV2(detail.PlayUrl, detail.PlaySeparator)
|
||||
md.DownloadList = ProcessPlayInfoV2(detail.DownUrl, detail.PlaySeparator)
|
||||
return md
|
||||
}
|
||||
|
||||
// ProcessPlayInfo 处理影片播放数据信息
|
||||
func ProcessPlayInfo(info, separator string) [][]system.MovieUrlInfo {
|
||||
var res [][]system.MovieUrlInfo
|
||||
// 1. 通过分隔符区分多个片源数据
|
||||
for _, l := range strings.Split(info, separator) {
|
||||
// 2.对每个片源的集数和播放地址进行分割
|
||||
var item []system.MovieUrlInfo
|
||||
for _, p := range strings.Split(l, "#") {
|
||||
// 3. 处理 Episode$Link 形式的播放信息
|
||||
if strings.Contains(p, "$") {
|
||||
item = append(item, system.MovieUrlInfo{
|
||||
Episode: strings.Split(p, "$")[0],
|
||||
Link: strings.Split(p, "$")[1],
|
||||
})
|
||||
} else {
|
||||
item = append(item, system.MovieUrlInfo{
|
||||
Episode: "O(∩_∩)O",
|
||||
Link: p,
|
||||
})
|
||||
}
|
||||
}
|
||||
// 3. 将每组播放源对应的播放列表信息存储到列表中
|
||||
res = append(res, item)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// ProcessPlayInfoV2 处理影片信息方案二 只保留m3u8播放源
|
||||
func ProcessPlayInfoV2(info, separator string) [][]system.MovieUrlInfo {
|
||||
var res [][]system.MovieUrlInfo
|
||||
if separator != "" {
|
||||
// 1. 通过分隔符切分播放源地址
|
||||
for _, l := range strings.Split(info, separator) {
|
||||
// 只对m3u8播放源 和 .mp4下载地址进行处理
|
||||
if strings.Contains(l, ".m3u8") || strings.Contains(l, ".mp4") {
|
||||
// 2.对每个片源的集数和播放地址进行分割
|
||||
var item []system.MovieUrlInfo
|
||||
for _, p := range strings.Split(l, "#") {
|
||||
// 3. 处理 Episode$Link 形式的播放信息
|
||||
if strings.Contains(p, "$") {
|
||||
item = append(item, system.MovieUrlInfo{
|
||||
Episode: strings.Split(p, "$")[0],
|
||||
Link: strings.Split(p, "$")[1],
|
||||
})
|
||||
} else {
|
||||
item = append(item, system.MovieUrlInfo{
|
||||
Episode: "O(∩_∩)O",
|
||||
Link: p,
|
||||
})
|
||||
}
|
||||
}
|
||||
// 3. 将每组播放源对应的播放列表信息存储到列表中
|
||||
res = append(res, item)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 只对m3u8播放源 和 .mp4下载地址进行处理
|
||||
if strings.Contains(info, ".m3u8") || strings.Contains(info, ".mp4") {
|
||||
// 2.对每个片源的集数和播放地址进行分割
|
||||
var item []system.MovieUrlInfo
|
||||
for _, p := range strings.Split(info, "#") {
|
||||
// 3. 处理 Episode$Link 形式的播放信息
|
||||
if strings.Contains(p, "$") {
|
||||
item = append(item, system.MovieUrlInfo{
|
||||
Episode: strings.Split(p, "$")[0],
|
||||
Link: strings.Split(p, "$")[1],
|
||||
})
|
||||
} else {
|
||||
item = append(item, system.MovieUrlInfo{
|
||||
Episode: "O(∩_∩)O",
|
||||
Link: p,
|
||||
})
|
||||
}
|
||||
}
|
||||
// 3. 将每组播放源对应的播放列表信息存储到列表中
|
||||
res = append(res, item)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -25,7 +25,7 @@ func InitMysql() (err error) {
|
||||
SingularTable: true, //是否使用 结构体名称作为表名 (关闭自动变复数)
|
||||
//NameReplacer: strings.NewReplacer("spider_", ""), // 替表名和字段中的 Me 为 空
|
||||
},
|
||||
Logger: logger.Default.LogMode(logger.Info), //设置日志级别为Info
|
||||
// Logger: logger.Default.LogMode(logger.Info), //设置日志级别为Info
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package spider
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"server/config"
|
||||
"server/model/collect"
|
||||
"server/model/system"
|
||||
"server/plugin/common/conver"
|
||||
"server/plugin/common/util"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -18,7 +22,7 @@ import (
|
||||
|
||||
var spiderCore = &JsonCollect{}
|
||||
|
||||
// =========================通用采集方法==============================
|
||||
// ======================================================= 通用采集方法 =======================================================
|
||||
|
||||
// HandleCollect 影视采集 id-采集站ID h-时长/h
|
||||
func HandleCollect(id string, h int) error {
|
||||
@@ -64,7 +68,15 @@ func HandleCollect(id string, h int) error {
|
||||
switch s.CollectType {
|
||||
case system.CollectVideo:
|
||||
// 采集视频资源
|
||||
if pageCount <= config.MAXGoroutine*2 {
|
||||
// 如果采集源参数中采集间隔参数大于500ms,则使用单线程采集
|
||||
if s.Interval > 500 {
|
||||
// 少量数据不开启协程
|
||||
for i := 1; i <= pageCount; i++ {
|
||||
collectFilm(s, h, i)
|
||||
// 执行一次采集后休眠指定时长
|
||||
time.Sleep(time.Duration(s.Interval) * time.Millisecond)
|
||||
}
|
||||
} else if pageCount <= config.MAXGoroutine*2 {
|
||||
// 少量数据不开启协程
|
||||
for i := 1; i <= pageCount; i++ {
|
||||
collectFilm(s, h, i)
|
||||
@@ -75,8 +87,6 @@ func HandleCollect(id string, h int) error {
|
||||
}
|
||||
// 视频数据采集完成后同步相关信息到mysql
|
||||
if s.Grade == system.MasterCollect {
|
||||
// 每次成功执行完都清理redis中的相关API接口数据缓存
|
||||
clearCache()
|
||||
// 执行影片信息更新操作
|
||||
if h > 0 {
|
||||
// 执行数据更新操作
|
||||
@@ -89,6 +99,8 @@ func HandleCollect(id string, h int) error {
|
||||
if s.SyncPictures {
|
||||
system.SyncFilmPicture()
|
||||
}
|
||||
// 每次成功执行完都清理redis中的相关API接口数据缓存
|
||||
clearCache()
|
||||
}
|
||||
|
||||
case system.CollectArticle, system.CollectActor, system.CollectRole, system.CollectWebSite:
|
||||
@@ -145,7 +157,7 @@ func collectFilm(s *system.FilmSource, h, pg int) {
|
||||
}
|
||||
case system.SlaveCollect:
|
||||
// 附属站点 仅保存影片播放信息到redis
|
||||
if err = system.SaveSitePlayList(s.Name, list); err != nil {
|
||||
if err = system.SaveSitePlayList(s.Id, list); err != nil {
|
||||
log.Println("SaveDetails Error: ", err)
|
||||
}
|
||||
}
|
||||
@@ -217,3 +229,34 @@ func StarZero(h int) {
|
||||
// 开启自动采集
|
||||
AutoCollect(h)
|
||||
}
|
||||
|
||||
// ======================================================= 公共方法 =======================================================
|
||||
|
||||
// CollectApiTest 测试采集接口是否可用
|
||||
func CollectApiTest(s system.FilmSource) error {
|
||||
// 使用当前采集站接口采集一页数据
|
||||
r := util.RequestInfo{Uri: s.Uri, Params: url.Values{}}
|
||||
r.Params.Set("ac", s.CollectType.GetActionType())
|
||||
r.Params.Set("pg", "3")
|
||||
err := util.ApiTest(&r)
|
||||
// 首先核对接口返回值类型
|
||||
if err == nil {
|
||||
// 如果返回值类型为Json则执行Json序列化
|
||||
if s.ResultModel == system.JsonResult {
|
||||
var dp = collect.FilmDetailLPage{}
|
||||
if err = json.Unmarshal(r.Resp, &dp); err != nil {
|
||||
return errors.New(fmt.Sprint("测试失败, 返回数据异常, JSON序列化失败: ", err))
|
||||
}
|
||||
return nil
|
||||
} else if s.ResultModel == system.XmlResult {
|
||||
// 如果返回值类型为XML则执行XML序列化
|
||||
var rd = collect.RssD{}
|
||||
if err = xml.Unmarshal(r.Resp, &rd); err != nil {
|
||||
return errors.New(fmt.Sprint("测试失败, 返回数据异常, XML序列化失败", err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errors.New("测试失败, 接口返回值类型不符合规范")
|
||||
}
|
||||
return errors.New(fmt.Sprint("测试失败, 请求响应异常 : ", err.Error()))
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package spider
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"server/model/collect"
|
||||
"server/model/system"
|
||||
@@ -20,8 +19,8 @@ type FilmCollect interface {
|
||||
GetCategoryTree(r util.RequestInfo) (*system.CategoryTree, error)
|
||||
// GetPageCount 获取API接口的分页页数
|
||||
GetPageCount(r util.RequestInfo) (count int, err error)
|
||||
// GetDetail 获取指定pageNumber的具体数据
|
||||
GetDetail(pageNumber int, r util.RequestInfo) (list []system.MovieDetail, err error)
|
||||
// GetFilmDetail 获取影片详情信息,返回影片详情列表
|
||||
GetFilmDetail(r util.RequestInfo) (list []system.MovieDetail, err error)
|
||||
}
|
||||
|
||||
// ------------------------------------------------- JSON Collect -------------------------------------------------
|
||||
@@ -55,7 +54,7 @@ func (jc *JsonCollect) GetCategoryTree(r util.RequestInfo) (*system.CategoryTree
|
||||
return tree, err
|
||||
}
|
||||
|
||||
// GetPageCount 获取总页数
|
||||
// GetPageCount 获取分页总页数
|
||||
func (jc *JsonCollect) GetPageCount(r util.RequestInfo) (count int, err error) {
|
||||
// 发送请求获取pageCount, 默认为获取 ac = detail
|
||||
if len(r.Params.Get("ac")) <= 0 {
|
||||
@@ -78,43 +77,6 @@ func (jc *JsonCollect) GetPageCount(r util.RequestInfo) (count int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// GetDetail 处理详情接口请求返回的数据
|
||||
func (jc *JsonCollect) GetDetail(pageNumber int, r util.RequestInfo) (list []system.MovieDetail, err error) {
|
||||
// 防止json解析异常引发panic
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
log.Println("GetMovieDetail Failed : ", e)
|
||||
}
|
||||
}()
|
||||
// 设置分页请求参数
|
||||
r.Params.Set(`ac`, `detail`)
|
||||
r.Params.Set(`pg`, fmt.Sprint(pageNumber))
|
||||
util.ApiGet(&r)
|
||||
// 影视详情信息
|
||||
detailPage := collect.FilmDetailLPage{}
|
||||
//details := system.DetailListInfo{}
|
||||
// 如果返回数据为空则直接结束本次循环
|
||||
if len(r.Resp) <= 0 {
|
||||
err = errors.New("response is empty")
|
||||
return
|
||||
}
|
||||
// 序列化详情数据
|
||||
if err = json.Unmarshal(r.Resp, &detailPage); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 将影视原始详情信息保存到redis中
|
||||
// 获取主站点uri
|
||||
mc := system.GetCollectSourceListByGrade(system.MasterCollect)[0]
|
||||
if mc.Uri == r.Uri {
|
||||
collect.BatchSaveOriginalDetail(detailPage.List)
|
||||
}
|
||||
|
||||
// 处理details信息
|
||||
list = conver.ConvertFilmDetails(detailPage.List)
|
||||
return
|
||||
}
|
||||
|
||||
// GetFilmDetail 通过 RequestInfo 获取并解析出对应的 MovieDetail list
|
||||
func (jc *JsonCollect) GetFilmDetail(r util.RequestInfo) (list []system.MovieDetail, err error) {
|
||||
// 防止json解析异常引发panic
|
||||
@@ -141,10 +103,10 @@ func (jc *JsonCollect) GetFilmDetail(r util.RequestInfo) (list []system.MovieDet
|
||||
|
||||
// 将影视原始详情信息保存到redis中
|
||||
// 获取主站点uri
|
||||
mc := system.GetCollectSourceListByGrade(system.MasterCollect)[0]
|
||||
if mc.Uri == r.Uri {
|
||||
collect.BatchSaveOriginalDetail(detailPage.List)
|
||||
}
|
||||
//mc := system.GetCollectSourceListByGrade(system.MasterCollect)[0]
|
||||
//if mc.Uri == r.Uri {
|
||||
// collect.BatchSaveOriginalDetail(detailPage.List)
|
||||
//}
|
||||
|
||||
// 处理details信息
|
||||
list = conver.ConvertFilmDetails(detailPage.List)
|
||||
|
||||
@@ -14,42 +14,7 @@ var (
|
||||
CronCollect *cron.Cron = CreateCron()
|
||||
)
|
||||
|
||||
// RegularUpdateMovie 定时更新, 每半小时获取一次站点的最近x小时数据
|
||||
func RegularUpdateMovie() {
|
||||
//创建一个定时任务对象
|
||||
c := cron.New(cron.WithSeconds())
|
||||
// 添加定时任务每x 分钟更新一次最近x小时的影片数据
|
||||
taskId, err := c.AddFunc(config.CornMovieUpdate, func() {
|
||||
// 执行更新最近x小时影片的Spider
|
||||
log.Println("执行一次影片更新任务...")
|
||||
UpdateMovieDetail()
|
||||
// 执行更新任务后清理redis中的相关API接口数据缓存
|
||||
clearCache()
|
||||
})
|
||||
|
||||
// 开启定时任务每月最后一天凌晨两点, 执行一次清库重取数据
|
||||
taskId2, err := c.AddFunc(config.CornUpdateAll, func() {
|
||||
StartSpiderRe()
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Println("Corn Start Error: ", err)
|
||||
}
|
||||
|
||||
log.Println(taskId, "------", taskId2)
|
||||
log.Printf("%v", c.Entries())
|
||||
|
||||
//c.Start()
|
||||
}
|
||||
|
||||
// StartCrontab 启动定时任务
|
||||
func StartCrontab() {
|
||||
// 从redis中读取待启动的定时任务列表
|
||||
|
||||
// 影片更新定时任务列表
|
||||
CronCollect.Start()
|
||||
}
|
||||
|
||||
// CreateCron 创建定时任务
|
||||
func CreateCron() *cron.Cron {
|
||||
return cron.New(cron.WithSeconds())
|
||||
}
|
||||
|
||||
@@ -1,275 +0,0 @@
|
||||
package spider
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"server/config"
|
||||
"server/model/collect"
|
||||
"server/model/system"
|
||||
"server/plugin/common/util"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
舍弃第一版的数据处理思路, v2版本
|
||||
直接分页获取采集站点的影片详情信息
|
||||
*/
|
||||
|
||||
/*
|
||||
1. 选择一个采集主站点, mysql检索表中只存储主站点检索的信息
|
||||
2. 采集多个站点数据
|
||||
2.1 主站点的采集数据完整地保存相关信息, basicInfo movieDetail search 等信息
|
||||
2.2 其余站点数据只存储 name(影片名称), playUrl(播放url), 存储形式 Key<hash(name)>:value([]MovieUrlInfo)
|
||||
3. api数据格式不变, 获取影片详情时通过subTitle 去redis匹配其他站点的对应播放源并整合到主站详情信息的playUrl中
|
||||
4. 影片搜索时不再使用name进行匹配, 改为使用 subTitle 进行匹配
|
||||
*/
|
||||
|
||||
// StartSpider 执行多源spider
|
||||
func StartSpider() {
|
||||
// 保存分类树
|
||||
CategoryList()
|
||||
log.Println("CategoryList 影片分类信息保存完毕")
|
||||
// 爬取主站点数据
|
||||
MainSiteSpider()
|
||||
log.Println("MainSiteSpider 主站点影片信息保存完毕")
|
||||
// 查找并创建search数据库, 保存search信息, 添加索引
|
||||
time.Sleep(time.Second * 10)
|
||||
system.SyncSearchInfo(0)
|
||||
system.AddSearchIndex()
|
||||
log.Println("SearchInfoToMdb 影片检索信息保存完毕")
|
||||
//获取其他站点数据
|
||||
scl := system.GetCollectSourceListByGrade(system.SlaveCollect)
|
||||
go MtSiteSpider(scl...)
|
||||
log.Println("Spider End , 数据保存执行完成")
|
||||
time.Sleep(time.Second * 10)
|
||||
}
|
||||
|
||||
// CategoryList 获取分类数据
|
||||
func CategoryList() {
|
||||
// 获取主站点uri
|
||||
mc := system.GetCollectSourceListByGrade(system.MasterCollect)[0]
|
||||
// 获取分类树形数据
|
||||
categoryTree, err := spiderCore.GetCategoryTree(util.RequestInfo{Uri: mc.Uri, Params: url.Values{}})
|
||||
if err != nil {
|
||||
log.Println("GetCategoryTree Error: ", err)
|
||||
return
|
||||
}
|
||||
// 保存 tree 到redis
|
||||
err = system.SaveCategoryTree(categoryTree)
|
||||
if err != nil {
|
||||
log.Println("SaveCategoryTree Error: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
// MainSiteSpider 主站点数据处理
|
||||
func MainSiteSpider() {
|
||||
// 获取主站点uri
|
||||
mc := system.GetCollectSourceListByGrade(system.MasterCollect)[0]
|
||||
// 获取分页页数
|
||||
pageCount, err := spiderCore.GetPageCount(util.RequestInfo{Uri: mc.Uri, Params: url.Values{}})
|
||||
// 主站点分页出错直接终止程序
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// 开启协程加快分页请求速度
|
||||
ch := make(chan int, pageCount)
|
||||
waitCh := make(chan int)
|
||||
for i := 1; i <= pageCount; i++ {
|
||||
ch <- i
|
||||
}
|
||||
close(ch)
|
||||
for i := 0; i < config.MAXGoroutine; i++ {
|
||||
go func() {
|
||||
defer func() { waitCh <- 0 }()
|
||||
for {
|
||||
pg, ok := <-ch
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
list, e := spiderCore.GetDetail(pg, util.RequestInfo{Uri: mc.Uri, Params: url.Values{}})
|
||||
if e != nil {
|
||||
log.Println("GetMovieDetail Error: ", err)
|
||||
continue
|
||||
}
|
||||
// 保存影片详情信息到redis
|
||||
if err = system.SaveDetails(list); err != nil {
|
||||
log.Println("SaveDetails Error: ", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
for i := 0; i < config.MAXGoroutine; i++ {
|
||||
<-waitCh
|
||||
}
|
||||
}
|
||||
|
||||
// MtSiteSpider 附属站点数据源处理
|
||||
func MtSiteSpider(scl ...system.FilmSource) {
|
||||
for _, s := range scl {
|
||||
// 执行每个站点的播放url缓存
|
||||
PlayDetailSpider(s)
|
||||
log.Println(s.Name, "playUrl 爬取完毕!!!")
|
||||
}
|
||||
}
|
||||
|
||||
// PlayDetailSpider SpiderSimpleInfo 获取单个站点的播放源
|
||||
func PlayDetailSpider(s system.FilmSource) {
|
||||
// 获取分页页数
|
||||
pageCount, err := spiderCore.GetPageCount(util.RequestInfo{Uri: s.Uri, Params: url.Values{}})
|
||||
// 出错直接终止当前站点数据获取
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 开启协程加快分页请求速度
|
||||
ch := make(chan int, pageCount)
|
||||
waitCh := make(chan int)
|
||||
for i := 1; i <= pageCount; i++ {
|
||||
ch <- i
|
||||
}
|
||||
close(ch)
|
||||
for i := 0; i < config.MAXGoroutine; i++ {
|
||||
go func() {
|
||||
defer func() { waitCh <- 0 }()
|
||||
for {
|
||||
pg, ok := <-ch
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
list, e := spiderCore.GetDetail(pg, util.RequestInfo{Uri: s.Uri, Params: url.Values{}})
|
||||
if e != nil || len(list) <= 0 {
|
||||
log.Println("GetMovieDetail Error: ", err)
|
||||
continue
|
||||
}
|
||||
// 保存影片播放信息到redis
|
||||
if err = system.SaveSitePlayList(s.Name, list); err != nil {
|
||||
log.Println("SaveDetails Error: ", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
for i := 0; i < config.MAXGoroutine; i++ {
|
||||
<-waitCh
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateMovieDetail 定时更新主站点和其余播放源信息
|
||||
func UpdateMovieDetail() {
|
||||
// 更新主站系列信息
|
||||
UpdateMainDetail()
|
||||
// 更新附属播放源数据信息
|
||||
scl := system.GetCollectSourceListByGrade(system.SlaveCollect)
|
||||
UpdatePlayDetail(scl...)
|
||||
}
|
||||
|
||||
// UpdateMainDetail 更新主站点的最新影片
|
||||
func UpdateMainDetail() {
|
||||
// 获取主站点uri
|
||||
l := system.GetCollectSourceListByGrade(system.MasterCollect)
|
||||
mc := system.FilmSource{}
|
||||
for _, v := range l {
|
||||
if len(v.Uri) > 0 {
|
||||
mc = v
|
||||
break
|
||||
}
|
||||
}
|
||||
// 获取分页页数
|
||||
r := util.RequestInfo{Uri: mc.Uri, Params: url.Values{}}
|
||||
r.Params.Set("h", config.UpdateInterval)
|
||||
pageCount, err := spiderCore.GetPageCount(r)
|
||||
if err != nil {
|
||||
log.Printf("Update MianStieDetail failed\n")
|
||||
}
|
||||
// 保存本次更新的所有详情信息
|
||||
var ds []system.MovieDetail
|
||||
// 获取分页数据
|
||||
for i := 1; i <= pageCount; i++ {
|
||||
list, err := spiderCore.GetDetail(i, r)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// 保存更新的影片信息, 同类型直接覆盖
|
||||
if err = system.SaveDetails(list); err != nil {
|
||||
log.Println("Update MainSiteDetail failed, SaveDetails Error ")
|
||||
}
|
||||
ds = append(ds, list...)
|
||||
}
|
||||
|
||||
// 整合详情信息切片
|
||||
var sl []system.SearchInfo
|
||||
for _, d := range ds {
|
||||
// 通过id 获取对应的详情信息
|
||||
sl = append(sl, system.ConvertSearchInfo(d))
|
||||
}
|
||||
// 调用批量保存或更新方法, 如果对应mid数据存在则更新, 否则执行插入
|
||||
system.BatchSaveOrUpdate(sl)
|
||||
}
|
||||
|
||||
// UpdatePlayDetail 更新最x小时的影片播放源数据
|
||||
func UpdatePlayDetail(scl ...system.FilmSource) {
|
||||
for _, s := range scl {
|
||||
// 获取单个站点的分页数
|
||||
r := util.RequestInfo{Uri: s.Uri, Params: url.Values{}}
|
||||
r.Params.Set("h", config.UpdateInterval)
|
||||
pageCount, err := spiderCore.GetPageCount(r)
|
||||
if err != nil {
|
||||
log.Printf("Update %s playDetail failed\n", s.Name)
|
||||
}
|
||||
for i := 1; i <= pageCount; i++ {
|
||||
// 获取详情信息, 保存到对应hashKey中
|
||||
list, e := spiderCore.GetDetail(i, r)
|
||||
if e != nil || len(list) <= 0 {
|
||||
log.Println("GetMovieDetail Error: ", err)
|
||||
continue
|
||||
}
|
||||
// 保存影片播放信息到redis
|
||||
if err = system.SaveSitePlayList(s.Name, list); err != nil {
|
||||
log.Println("SaveDetails Error: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StartSpiderRe 清空存储数据,从零开始获取
|
||||
func StartSpiderRe() {
|
||||
// 删除已有的存储数据, redis 和 mysql中的存储数据全部清空
|
||||
system.FilmZero()
|
||||
// 执行完整数据获取
|
||||
StartSpider()
|
||||
}
|
||||
|
||||
// =========================公共方法==============================
|
||||
|
||||
// CollectApiTest 测试采集接口是否可用
|
||||
func CollectApiTest(s system.FilmSource) error {
|
||||
// 使用当前采集站接口采集一页数据
|
||||
r := util.RequestInfo{Uri: s.Uri, Params: url.Values{}}
|
||||
r.Params.Set("ac", s.CollectType.GetActionType())
|
||||
r.Params.Set("pg", "3")
|
||||
err := util.ApiTest(&r)
|
||||
// 首先核对接口返回值类型
|
||||
if err == nil {
|
||||
// 如果返回值类型为Json则执行Json序列化
|
||||
if s.ResultModel == system.JsonResult {
|
||||
var dp = collect.FilmDetailLPage{}
|
||||
if err = json.Unmarshal(r.Resp, &dp); err != nil {
|
||||
return errors.New(fmt.Sprint("测试失败, 返回数据异常, JSON序列化失败: ", err))
|
||||
}
|
||||
return nil
|
||||
} else if s.ResultModel == system.XmlResult {
|
||||
// 如果返回值类型为XML则执行XML序列化
|
||||
var rd = collect.RssD{}
|
||||
if err = xml.Unmarshal(r.Resp, &rd); err != nil {
|
||||
return errors.New(fmt.Sprint("测试失败, 返回数据异常, XML序列化失败", err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errors.New("测试失败, 接口返回值类型不符合规范")
|
||||
}
|
||||
return errors.New(fmt.Sprint("测试失败, 请求响应异常 : ", err.Error()))
|
||||
}
|
||||
@@ -17,6 +17,7 @@ func SetupRouter() *gin.Engine {
|
||||
r.Static(config.FilmPictureUrlPath, config.FilmPictureUploadDir)
|
||||
|
||||
r.GET(`/index`, controller.Index)
|
||||
r.GET(`/config/basic`, controller.SiteBasicConfig)
|
||||
r.GET(`/navCategory`, controller.CategoriesInfo)
|
||||
r.GET(`/filmDetail`, controller.FilmDetail)
|
||||
r.GET(`/filmPlayInfo`, controller.FilmPlayInfo)
|
||||
|
||||
Reference in New Issue
Block a user